Ktlint 1.5.0
This commit is contained in:
parent
31ac7b8c6d
commit
37ed3841e4
|
@ -1,6 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
enum class BuildType(val value: String) {
|
enum class BuildType(
|
||||||
|
val value: String
|
||||||
|
) {
|
||||||
DEBUG("debug"),
|
DEBUG("debug"),
|
||||||
RELEASE("release"),
|
RELEASE("release"),
|
||||||
BENCHMARK("benchmark")
|
BENCHMARK("benchmark")
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
enum class NetworkDimension(val value: String) {
|
enum class NetworkDimension(
|
||||||
|
val value: String
|
||||||
|
) {
|
||||||
MAINNET("zcashmainnet"),
|
MAINNET("zcashmainnet"),
|
||||||
TESTNET("zcashtestnet");
|
TESTNET("zcashtestnet");
|
||||||
|
|
||||||
|
@ -9,7 +11,9 @@ enum class NetworkDimension(val value: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DistributionDimension(val value: String) {
|
enum class DistributionDimension(
|
||||||
|
val value: String
|
||||||
|
) {
|
||||||
STORE("store"),
|
STORE("store"),
|
||||||
FOSS("foss");
|
FOSS("foss");
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,8 @@ data class ChangelogEntry(
|
||||||
GsonBuilder()
|
GsonBuilder()
|
||||||
.serializeNulls()
|
.serializeNulls()
|
||||||
.create()
|
.create()
|
||||||
.toJson(this).replace("\"", "\\\"")
|
.toJson(this)
|
||||||
|
.replace("\"", "\\\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ChangelogEntrySection(
|
data class ChangelogEntrySection(
|
||||||
|
|
|
@ -65,7 +65,8 @@ object ChangelogParser {
|
||||||
log("Parser: index from: $fromIndex")
|
log("Parser: index from: $fromIndex")
|
||||||
|
|
||||||
val toIndex =
|
val toIndex =
|
||||||
nodes.subList(fromIndex + 1, nodes.size)
|
nodes
|
||||||
|
.subList(fromIndex + 1, nodes.size)
|
||||||
.indexOfFirst { findNodeByPrefix(it) }
|
.indexOfFirst { findNodeByPrefix(it) }
|
||||||
.let {
|
.let {
|
||||||
// Applies to the last or the only one entry
|
// Applies to the last or the only one entry
|
||||||
|
@ -117,24 +118,22 @@ object ChangelogParser {
|
||||||
subNode.startsWith("### ${titleByLanguage(TitleType.FIXED, languageTag)}") ||
|
subNode.startsWith("### ${titleByLanguage(TitleType.FIXED, languageTag)}") ||
|
||||||
subNode.startsWith("### ${titleByLanguage(TitleType.REMOVED, languageTag)}")
|
subNode.startsWith("### ${titleByLanguage(TitleType.REMOVED, languageTag)}")
|
||||||
|
|
||||||
private fun List<String>.getVersionPart(versionNameFallback: String): String {
|
private fun List<String>.getVersionPart(versionNameFallback: String): String =
|
||||||
return if (this.contains("## [Unreleased]")) {
|
if (this.contains("## [Unreleased]")) {
|
||||||
versionNameFallback
|
versionNameFallback
|
||||||
} else {
|
} else {
|
||||||
// Parse just version name omitting version code as we currently don't need it in the UI
|
// Parse just version name omitting version code as we currently don't need it in the UI
|
||||||
this[0].split("[")[1].split(" ")[0].trim()
|
this[0].split("[")[1].split(" ")[0].trim()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val dateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
private val dateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
|
||||||
private fun List<String>.getDatePart(): String {
|
private fun List<String>.getDatePart(): String =
|
||||||
return if (this.contains("## [Unreleased]")) {
|
if (this.contains("## [Unreleased]")) {
|
||||||
dateFormatter.format(Date())
|
dateFormatter.format(Date())
|
||||||
} else {
|
} else {
|
||||||
this[0].split("- ")[1].trim()
|
this[0].split("- ")[1].trim()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<String>.getNodePart(title: String): ChangelogEntrySection? {
|
private fun List<String>.getNodePart(title: String): ChangelogEntrySection? {
|
||||||
val fromContent = "### $title"
|
val fromContent = "### $title"
|
||||||
|
@ -160,7 +159,8 @@ object ChangelogParser {
|
||||||
// To remove hard line wrap from AS
|
// To remove hard line wrap from AS
|
||||||
.map { it.replace("\n ", "") }
|
.map { it.replace("\n ", "") }
|
||||||
.joinToString(prefix = "\n", separator = "\n")
|
.joinToString(prefix = "\n", separator = "\n")
|
||||||
.takeIf { it.isNotBlank() }?.let {
|
.takeIf { it.isNotBlank() }
|
||||||
|
?.let {
|
||||||
ChangelogEntrySection(title = title, content = it)
|
ChangelogEntrySection(title = title, content = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,8 +168,8 @@ object ChangelogParser {
|
||||||
private fun titleByLanguage(
|
private fun titleByLanguage(
|
||||||
type: TitleType,
|
type: TitleType,
|
||||||
languageTag: LanguageTag
|
languageTag: LanguageTag
|
||||||
): String {
|
): String =
|
||||||
return when (type) {
|
when (type) {
|
||||||
TitleType.ADDED ->
|
TitleType.ADDED ->
|
||||||
when (languageTag) {
|
when (languageTag) {
|
||||||
is LanguageTag.English -> ADDED_PART_EN
|
is LanguageTag.English -> ADDED_PART_EN
|
||||||
|
@ -191,13 +191,18 @@ object ChangelogParser {
|
||||||
is LanguageTag.Spanish -> REMOVED_PART_ES
|
is LanguageTag.Spanish -> REMOVED_PART_ES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class LanguageTag(open val tag: String) {
|
sealed class LanguageTag(
|
||||||
data class English(override val tag: String = ENGLISH_TAG) : LanguageTag(tag)
|
open val tag: String
|
||||||
|
) {
|
||||||
|
data class English(
|
||||||
|
override val tag: String = ENGLISH_TAG
|
||||||
|
) : LanguageTag(tag)
|
||||||
|
|
||||||
data class Spanish(override val tag: String = SPANISH_TAG) : LanguageTag(tag)
|
data class Spanish(
|
||||||
|
override val tag: String = SPANISH_TAG
|
||||||
|
) : LanguageTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class TitleType {
|
private enum class TitleType {
|
||||||
|
|
|
@ -23,4 +23,7 @@ object Git {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GitInfo(val sha: String, val commitCount: Int)
|
data class GitInfo(
|
||||||
|
val sha: String,
|
||||||
|
val commitCount: Int
|
||||||
|
)
|
||||||
|
|
|
@ -13,32 +13,35 @@ import kotlinx.datetime.Instant
|
||||||
class MergingConfigurationProvider(
|
class MergingConfigurationProvider(
|
||||||
private val configurationProviders: PersistentList<ConfigurationProvider>
|
private val configurationProviders: PersistentList<ConfigurationProvider>
|
||||||
) : ConfigurationProvider {
|
) : ConfigurationProvider {
|
||||||
override fun peekConfiguration(): Configuration {
|
override fun peekConfiguration(): Configuration =
|
||||||
return MergingConfiguration(configurationProviders.map { it.peekConfiguration() }.toPersistentList())
|
MergingConfiguration(
|
||||||
}
|
configurationProviders
|
||||||
|
.map {
|
||||||
|
it.peekConfiguration()
|
||||||
|
}.toPersistentList()
|
||||||
|
)
|
||||||
|
|
||||||
override fun getConfigurationFlow(): Flow<Configuration> {
|
override fun getConfigurationFlow(): Flow<Configuration> =
|
||||||
return if (configurationProviders.isEmpty()) {
|
if (configurationProviders.isEmpty()) {
|
||||||
flowOf(MergingConfiguration(persistentListOf<Configuration>()))
|
flowOf(MergingConfiguration(persistentListOf<Configuration>()))
|
||||||
} else {
|
} else {
|
||||||
combine(configurationProviders.map { it.getConfigurationFlow() }) { configurations ->
|
combine(configurationProviders.map { it.getConfigurationFlow() }) { configurations ->
|
||||||
MergingConfiguration(configurations.toList().toPersistentList())
|
MergingConfiguration(configurations.toList().toPersistentList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun hintToRefresh() {
|
override fun hintToRefresh() {
|
||||||
configurationProviders.forEach { it.hintToRefresh() }
|
configurationProviders.forEach { it.hintToRefresh() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class MergingConfiguration(private val configurations: PersistentList<Configuration>) : Configuration {
|
private data class MergingConfiguration(
|
||||||
|
private val configurations: PersistentList<Configuration>
|
||||||
|
) : Configuration {
|
||||||
override val updatedAt: Instant?
|
override val updatedAt: Instant?
|
||||||
get() = configurations.mapNotNull { it.updatedAt }.maxOrNull()
|
get() = configurations.mapNotNull { it.updatedAt }.maxOrNull()
|
||||||
|
|
||||||
override fun hasKey(key: ConfigKey): Boolean {
|
override fun hasKey(key: ConfigKey): Boolean = null != configurations.firstWithKey(key)
|
||||||
return null != configurations.firstWithKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO [#1373]: Catch and log Configuration Key Coercion Failures
|
// TODO [#1373]: Catch and log Configuration Key Coercion Failures
|
||||||
// TODO [#1373]: https://github.com/Electric-Coin-Company/zashi-android/issues/1373
|
// TODO [#1373]: https://github.com/Electric-Coin-Company/zashi-android/issues/1373
|
||||||
|
|
|
@ -7,7 +7,9 @@ package co.electriccoin.zcash.configuration.model.entry
|
||||||
* least common denominator with some reasonable limits on what the keys can contain.
|
* least common denominator with some reasonable limits on what the keys can contain.
|
||||||
*/
|
*/
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class ConfigKey(val key: String) {
|
value class ConfigKey(
|
||||||
|
val key: String
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
requireKeyConstraints(key)
|
requireKeyConstraints(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,7 @@ package co.electriccoin.zcash.configuration.model.exception
|
||||||
* Exception that may occur when parsing a value from the remote configuration. This could mean that someone made an
|
* Exception that may occur when parsing a value from the remote configuration. This could mean that someone made an
|
||||||
* error in the remote config console.
|
* error in the remote config console.
|
||||||
*/
|
*/
|
||||||
class ConfigurationParseException(message: String, cause: Throwable?) : IllegalArgumentException(message, cause)
|
class ConfigurationParseException(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable?
|
||||||
|
) : IllegalArgumentException(message, cause)
|
||||||
|
|
|
@ -109,14 +109,12 @@ class MergingConfigurationProviderTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockConfigurationProvider(private val configuration: Configuration) : ConfigurationProvider {
|
private class MockConfigurationProvider(
|
||||||
override fun peekConfiguration(): Configuration {
|
private val configuration: Configuration
|
||||||
return configuration
|
) : ConfigurationProvider {
|
||||||
}
|
override fun peekConfiguration(): Configuration = configuration
|
||||||
|
|
||||||
override fun getConfigurationFlow(): Flow<Configuration> {
|
override fun getConfigurationFlow(): Flow<Configuration> = flowOf(configuration)
|
||||||
return flowOf(configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hintToRefresh() {
|
override fun hintToRefresh() {
|
||||||
// no-op
|
// no-op
|
||||||
|
|
|
@ -11,7 +11,9 @@ import kotlinx.datetime.Instant
|
||||||
* mutate the configuration by mutating the original map. The mapping is stored in a val field
|
* mutate the configuration by mutating the original map. The mapping is stored in a val field
|
||||||
* though, making the initial mapping thread-safe.
|
* though, making the initial mapping thread-safe.
|
||||||
*/
|
*/
|
||||||
class MockConfiguration(private val configurationMapping: Map<String, String> = emptyMap()) : Configuration {
|
class MockConfiguration(
|
||||||
|
private val configurationMapping: Map<String, String> = emptyMap()
|
||||||
|
) : Configuration {
|
||||||
override val updatedAt: Instant? = null
|
override val updatedAt: Instant? = null
|
||||||
|
|
||||||
@Throws(ConfigurationParseException::class)
|
@Throws(ConfigurationParseException::class)
|
||||||
|
|
|
@ -34,8 +34,8 @@ class IntentConfigurationReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://issuetracker.google.com/issues/36927401
|
// https://issuetracker.google.com/issues/36927401
|
||||||
private fun Intent.defuse(): Intent? {
|
private fun Intent.defuse(): Intent? =
|
||||||
return try {
|
try {
|
||||||
extras?.containsKey(null)
|
extras?.containsKey(null)
|
||||||
this
|
this
|
||||||
} catch (
|
} catch (
|
||||||
|
@ -43,4 +43,3 @@ private fun Intent.defuse(): Intent? {
|
||||||
) {
|
) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ import android.content.Context
|
||||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||||
|
|
||||||
class ListCrashReportersImpl : ListCrashReporters {
|
class ListCrashReportersImpl : ListCrashReporters {
|
||||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
override fun provideReporters(context: Context): List<CrashReporter> =
|
||||||
return listOfNotNull(
|
listOfNotNull(
|
||||||
LocalCrashReporter.getInstance(context),
|
LocalCrashReporter.getInstance(context),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import java.io.File
|
||||||
@Suppress("ReturnCount")
|
@Suppress("ReturnCount")
|
||||||
suspend fun ExceptionPath.getExceptionDirectory(context: Context): File? {
|
suspend fun ExceptionPath.getExceptionDirectory(context: Context): File? {
|
||||||
val exceptionDirectory =
|
val exceptionDirectory =
|
||||||
context.getExternalFilesDirSuspend(null)
|
context
|
||||||
|
.getExternalFilesDirSuspend(null)
|
||||||
?.let { File(File(it, ExceptionPath.LOG_DIRECTORY_NAME), ExceptionPath.EXCEPTION_DIRECTORY_NAME) }
|
?.let { File(File(it, ExceptionPath.LOG_DIRECTORY_NAME), ExceptionPath.EXCEPTION_DIRECTORY_NAME) }
|
||||||
|
|
||||||
if (null == exceptionDirectory) {
|
if (null == exceptionDirectory) {
|
||||||
|
|
|
@ -64,5 +64,6 @@ object GlobalCrashReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isCrashProcess(context: Context) =
|
private fun isCrashProcess(context: Context) =
|
||||||
ProcessNameCompat.getProcessName(context)
|
ProcessNameCompat
|
||||||
|
.getProcessName(context)
|
||||||
.endsWith(GlobalCrashReporter.CRASH_PROCESS_NAME_SUFFIX)
|
.endsWith(GlobalCrashReporter.CRASH_PROCESS_NAME_SUFFIX)
|
||||||
|
|
|
@ -13,7 +13,9 @@ import kotlinx.coroutines.launch
|
||||||
/**
|
/**
|
||||||
* Registers an exception handler to write exceptions to disk.
|
* Registers an exception handler to write exceptions to disk.
|
||||||
*/
|
*/
|
||||||
internal class LocalCrashReporter(private val applicationContext: Context) : CrashReporter {
|
internal class LocalCrashReporter(
|
||||||
|
private val applicationContext: Context
|
||||||
|
) : CrashReporter {
|
||||||
private val crashReportingScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
private val crashReportingScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
|
@ -41,8 +43,6 @@ internal class LocalCrashReporter(private val applicationContext: Context) : Cra
|
||||||
LocalCrashReporter(it.applicationContext)
|
LocalCrashReporter(it.applicationContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstance(context: Context): CrashReporter {
|
fun getInstance(context: Context): CrashReporter = lazyWithArgument.getInstance(context)
|
||||||
return lazyWithArgument.getInstance(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,6 @@ private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContai
|
||||||
FirebaseAppContainer(firebaseApp)
|
FirebaseAppContainer(firebaseApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
private class FirebaseAppContainer(
|
||||||
|
val firebaseApp: FirebaseApp?
|
||||||
|
)
|
||||||
|
|
|
@ -121,9 +121,7 @@ private class FirebaseCrashReporterImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getInstance(context: Context): CrashReporter? {
|
suspend fun getInstance(context: Context): CrashReporter? = lazyWithArgument.getInstance(context)
|
||||||
return lazyWithArgument.getInstance(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,6 @@ private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContai
|
||||||
FirebaseAppContainer(firebaseApp)
|
FirebaseAppContainer(firebaseApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
private class FirebaseAppContainer(
|
||||||
|
val firebaseApp: FirebaseApp?
|
||||||
|
)
|
||||||
|
|
|
@ -121,9 +121,7 @@ private class FirebaseCrashReporterImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getInstance(context: Context): CrashReporter? {
|
suspend fun getInstance(context: Context): CrashReporter? = lazyWithArgument.getInstance(context)
|
||||||
return lazyWithArgument.getInstance(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,6 @@ private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContai
|
||||||
FirebaseAppContainer(firebaseApp)
|
FirebaseAppContainer(firebaseApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
private class FirebaseAppContainer(
|
||||||
|
val firebaseApp: FirebaseApp?
|
||||||
|
)
|
||||||
|
|
|
@ -121,9 +121,7 @@ private class FirebaseCrashReporterImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getInstance(context: Context): CrashReporter? {
|
suspend fun getInstance(context: Context): CrashReporter? = lazyWithArgument.getInstance(context)
|
||||||
return lazyWithArgument.getInstance(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,6 @@ private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContai
|
||||||
FirebaseAppContainer(firebaseApp)
|
FirebaseAppContainer(firebaseApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
private class FirebaseAppContainer(
|
||||||
|
val firebaseApp: FirebaseApp?
|
||||||
|
)
|
||||||
|
|
|
@ -121,9 +121,7 @@ private class FirebaseCrashReporterImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getInstance(context: Context): CrashReporter? {
|
suspend fun getInstance(context: Context): CrashReporter? = lazyWithArgument.getInstance(context)
|
||||||
return lazyWithArgument.getInstance(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ FULLADLE_VERSION=0.17.5
|
||||||
GOOGLE_PLAY_SERVICES_GRADLE_PLUGIN_VERSION=4.4.2
|
GOOGLE_PLAY_SERVICES_GRADLE_PLUGIN_VERSION=4.4.2
|
||||||
GRADLE_VERSIONS_PLUGIN_VERSION=0.52.0
|
GRADLE_VERSIONS_PLUGIN_VERSION=0.52.0
|
||||||
JGIT_VERSION=6.4.0.202211300538-r
|
JGIT_VERSION=6.4.0.202211300538-r
|
||||||
KTLINT_VERSION=1.2.1
|
KTLINT_VERSION=1.5.0
|
||||||
KOIN_VERSION=4.0.2
|
KOIN_VERSION=4.0.2
|
||||||
|
|
||||||
ACCOMPANIST_PERMISSIONS_VERSION=0.37.2
|
ACCOMPANIST_PERMISSIONS_VERSION=0.37.2
|
||||||
|
|
|
@ -45,7 +45,8 @@ interface PreferenceDefault<T> {
|
||||||
* 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> =
|
fun observe(preferenceProvider: PreferenceProvider): Flow<T> =
|
||||||
preferenceProvider.observe(key)
|
preferenceProvider
|
||||||
|
.observe(key)
|
||||||
.map { getValue(preferenceProvider) }
|
.map { getValue(preferenceProvider) }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import kotlin.jvm.JvmInline
|
||||||
* find a least common denominator with some reasonable limits on what the keys can contain.
|
* find a least common denominator with some reasonable limits on what the keys can contain.
|
||||||
*/
|
*/
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class PreferenceKey(val key: String) {
|
value class PreferenceKey(
|
||||||
|
val key: String
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
requireKeyConstraints(key)
|
requireKeyConstraints(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,9 +172,11 @@ class AndroidPreferenceProvider private constructor(
|
||||||
val sharedPreferences =
|
val sharedPreferences =
|
||||||
withContext(singleThreadedDispatcher) {
|
withContext(singleThreadedDispatcher) {
|
||||||
val mainKey =
|
val mainKey =
|
||||||
MasterKey.Builder(context).apply {
|
MasterKey
|
||||||
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
.Builder(context)
|
||||||
}.build()
|
.apply {
|
||||||
|
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
}.build()
|
||||||
|
|
||||||
EncryptedSharedPreferences.create(
|
EncryptedSharedPreferences.create(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -3,8 +3,9 @@ package co.electriccoin.zcash.preference
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
||||||
|
|
||||||
class EncryptedPreferenceProvider(private val context: Context) : PreferenceHolder() {
|
class EncryptedPreferenceProvider(
|
||||||
override suspend fun create(): PreferenceProvider {
|
private val context: Context
|
||||||
return AndroidPreferenceProvider.newEncrypted(context, "co.electriccoin.zcash.encrypted")
|
) : PreferenceHolder() {
|
||||||
}
|
override suspend fun create(): PreferenceProvider =
|
||||||
|
AndroidPreferenceProvider.newEncrypted(context, "co.electriccoin.zcash.encrypted")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ package co.electriccoin.zcash.preference
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
||||||
|
|
||||||
class StandardPreferenceProvider(private val context: Context) : PreferenceHolder() {
|
class StandardPreferenceProvider(
|
||||||
override suspend fun create(): PreferenceProvider {
|
private val context: Context
|
||||||
return AndroidPreferenceProvider.newStandard(context, "co.electriccoin.zcash")
|
) : PreferenceHolder() {
|
||||||
}
|
override suspend fun create(): PreferenceProvider =
|
||||||
|
AndroidPreferenceProvider.newStandard(context, "co.electriccoin.zcash")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,8 @@ import java.text.DecimalFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
fun PercentDecimal.toPercentageWithDecimal(decimalFormat: DecimalFormat = preparePercentDecimalFormat()): String {
|
fun PercentDecimal.toPercentageWithDecimal(decimalFormat: DecimalFormat = preparePercentDecimalFormat()): String =
|
||||||
return decimalFormat.format(decimal * 100)
|
decimalFormat.format(decimal * 100)
|
||||||
}
|
|
||||||
|
|
||||||
private fun preparePercentDecimalFormat(): DecimalFormat =
|
private fun preparePercentDecimalFormat(): DecimalFormat =
|
||||||
DecimalFormat().apply {
|
DecimalFormat().apply {
|
||||||
|
|
|
@ -15,7 +15,9 @@ sealed class SeedPhraseValidation {
|
||||||
|
|
||||||
object FailedChecksum : SeedPhraseValidation()
|
object FailedChecksum : SeedPhraseValidation()
|
||||||
|
|
||||||
class Valid(val seedPhrase: SeedPhrase) : SeedPhraseValidation()
|
class Valid(
|
||||||
|
val seedPhrase: SeedPhrase
|
||||||
|
) : SeedPhraseValidation()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun new(list: List<String>): SeedPhraseValidation {
|
suspend fun new(list: List<String>): SeedPhraseValidation {
|
||||||
|
|
|
@ -3,7 +3,11 @@ package cash.z.ecc.sdk.model
|
||||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
data class ZecRequest(val address: WalletAddress.Unified, val amount: Zatoshi, val message: ZecRequestMessage) {
|
data class ZecRequest(
|
||||||
|
val address: WalletAddress.Unified,
|
||||||
|
val amount: Zatoshi,
|
||||||
|
val message: ZecRequestMessage
|
||||||
|
) {
|
||||||
// TODO [#397]: Waiting for an implementation of Uri parser in SDK project
|
// TODO [#397]: Waiting for an implementation of Uri parser in SDK project
|
||||||
// TODO [#397]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/397
|
// TODO [#397]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/397
|
||||||
suspend fun toUri(): String {
|
suspend fun toUri(): String {
|
||||||
|
@ -21,7 +25,9 @@ data class ZecRequest(val address: WalletAddress.Unified, val amount: Zatoshi, v
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class ZecRequestMessage(val value: String) {
|
value class ZecRequestMessage(
|
||||||
|
val value: String
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(value.length <= MAX_MESSAGE_LENGTH)
|
require(value.length <= MAX_MESSAGE_LENGTH)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,7 @@ object AndroidApiVersion {
|
||||||
@ChecksSdkIntAtLeast(parameter = 0)
|
@ChecksSdkIntAtLeast(parameter = 0)
|
||||||
private fun isAtLeast(
|
private fun isAtLeast(
|
||||||
@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int
|
@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int
|
||||||
): Boolean {
|
): Boolean = Build.VERSION.SDK_INT >= sdk
|
||||||
return Build.VERSION.SDK_INT >= sdk
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param sdk SDK version number to test against the current environment.
|
* @param sdk SDK version number to test against the current environment.
|
||||||
|
@ -23,9 +21,7 @@ object AndroidApiVersion {
|
||||||
*/
|
*/
|
||||||
private fun isExactly(
|
private fun isExactly(
|
||||||
@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int
|
@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int
|
||||||
): Boolean {
|
): Boolean = Build.VERSION.SDK_INT == sdk
|
||||||
return Build.VERSION.SDK_INT == sdk
|
|
||||||
}
|
|
||||||
|
|
||||||
val isExactlyO = isExactly(Build.VERSION_CODES.O_MR1)
|
val isExactlyO = isExactly(Build.VERSION_CODES.O_MR1)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ import kotlinx.coroutines.launch
|
||||||
* @param broadcastReceiverScope Scope for performing asynchronous work in the broadcast receiver.
|
* @param broadcastReceiverScope Scope for performing asynchronous work in the broadcast receiver.
|
||||||
* It is not recommended to cancel this scope.
|
* It is not recommended to cancel this scope.
|
||||||
*/
|
*/
|
||||||
abstract class CoroutineBroadcastReceiver(private val broadcastReceiverScope: CoroutineScope) : BroadcastReceiver() {
|
abstract class CoroutineBroadcastReceiver(
|
||||||
|
private val broadcastReceiverScope: CoroutineScope
|
||||||
|
) : BroadcastReceiver() {
|
||||||
final override fun onReceive(
|
final override fun onReceive(
|
||||||
context: Context,
|
context: Context,
|
||||||
intent: Intent
|
intent: Intent
|
||||||
|
|
|
@ -13,40 +13,44 @@ object StrictModeCompat {
|
||||||
StrictMode.enableDefaults()
|
StrictMode.enableDefaults()
|
||||||
|
|
||||||
StrictMode.setThreadPolicy(
|
StrictMode.setThreadPolicy(
|
||||||
StrictMode.ThreadPolicy.Builder().apply {
|
StrictMode.ThreadPolicy
|
||||||
detectAll()
|
.Builder()
|
||||||
if (isCrashOnViolation) {
|
.apply {
|
||||||
penaltyDeath()
|
detectAll()
|
||||||
} else {
|
if (isCrashOnViolation) {
|
||||||
penaltyLog()
|
penaltyDeath()
|
||||||
}
|
} else {
|
||||||
}.build()
|
penaltyLog()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Don't enable missing network tags, because those are noisy.
|
// Don't enable missing network tags, because those are noisy.
|
||||||
StrictMode.setVmPolicy(
|
StrictMode.setVmPolicy(
|
||||||
StrictMode.VmPolicy.Builder().apply {
|
StrictMode.VmPolicy
|
||||||
if (AndroidApiVersion.isAtLeastS) {
|
.Builder()
|
||||||
detectUnsafeIntentLaunch()
|
.apply {
|
||||||
}
|
if (AndroidApiVersion.isAtLeastS) {
|
||||||
detectActivityLeaks()
|
detectUnsafeIntentLaunch()
|
||||||
detectCleartextNetwork()
|
}
|
||||||
detectContentUriWithoutPermission()
|
detectActivityLeaks()
|
||||||
detectFileUriExposure()
|
detectCleartextNetwork()
|
||||||
detectLeakedClosableObjects()
|
detectContentUriWithoutPermission()
|
||||||
detectLeakedRegistrationObjects()
|
detectFileUriExposure()
|
||||||
detectLeakedSqlLiteObjects()
|
detectLeakedClosableObjects()
|
||||||
if (AndroidApiVersion.isAtLeastP) {
|
detectLeakedRegistrationObjects()
|
||||||
// Disable because this is mostly flagging Android X and Play Services
|
detectLeakedSqlLiteObjects()
|
||||||
// builder.detectNonSdkApiUsage();
|
if (AndroidApiVersion.isAtLeastP) {
|
||||||
}
|
// Disable because this is mostly flagging Android X and Play Services
|
||||||
|
// builder.detectNonSdkApiUsage();
|
||||||
|
}
|
||||||
|
|
||||||
if (isCrashOnViolation) {
|
if (isCrashOnViolation) {
|
||||||
penaltyDeath()
|
penaltyDeath()
|
||||||
} else {
|
} else {
|
||||||
penaltyLog()
|
penaltyLog()
|
||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,37 +50,27 @@ open class AbstractProcessNameContentProvider : ContentProvider() {
|
||||||
selection: String?,
|
selection: String?,
|
||||||
selectionArgs: Array<out String>?,
|
selectionArgs: Array<out String>?,
|
||||||
sortOrder: String?
|
sortOrder: String?
|
||||||
): Cursor? {
|
): Cursor? = throw UnsupportedOperationException()
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(uri: Uri): String? {
|
override fun getType(uri: Uri): String? = throw UnsupportedOperationException()
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun insert(
|
override fun insert(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
values: ContentValues?
|
values: ContentValues?
|
||||||
): Uri? {
|
): Uri? = throw UnsupportedOperationException()
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun delete(
|
override fun delete(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
selection: String?,
|
selection: String?,
|
||||||
selectionArgs: Array<out String>?
|
selectionArgs: Array<out String>?
|
||||||
): Int {
|
): Int = throw UnsupportedOperationException()
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(
|
override fun update(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
values: ContentValues?,
|
values: ContentValues?,
|
||||||
selection: String?,
|
selection: String?,
|
||||||
selectionArgs: Array<out String>?
|
selectionArgs: Array<out String>?
|
||||||
): Int {
|
): Int = throw UnsupportedOperationException()
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun getProcessNameLegacy(
|
internal fun getProcessNameLegacy(
|
||||||
|
|
|
@ -54,15 +54,14 @@ object ProcessNameCompat {
|
||||||
* @return Name of the current process. May return null if a failure occurs, which is possible
|
* @return Name of the current process. May return null if a failure occurs, which is possible
|
||||||
* due to some race conditions in Android.
|
* due to some race conditions in Android.
|
||||||
*/
|
*/
|
||||||
private fun searchForProcessName(context: Context): String? {
|
private fun searchForProcessName(context: Context): String? =
|
||||||
return if (AndroidApiVersion.isAtLeastTiramisu) {
|
if (AndroidApiVersion.isAtLeastTiramisu) {
|
||||||
getProcessNameTPlus()
|
getProcessNameTPlus()
|
||||||
} else if (AndroidApiVersion.isAtLeastP) {
|
} else if (AndroidApiVersion.isAtLeastP) {
|
||||||
getProcessNamePPlus()
|
getProcessNamePPlus()
|
||||||
} else {
|
} else {
|
||||||
searchForProcessNameLegacy(context)
|
searchForProcessNameLegacy(context)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||||
private fun getProcessNameTPlus() = Process.myProcessName()
|
private fun getProcessNameTPlus() = Process.myProcessName()
|
||||||
|
|
|
@ -5,7 +5,9 @@ package co.electriccoin.zcash.spackle
|
||||||
*
|
*
|
||||||
* This class is thread-safe.
|
* This class is thread-safe.
|
||||||
*/
|
*/
|
||||||
class LazyWithArgument<in Input, out Output>(private val deferredCreator: ((Input) -> Output)) {
|
class LazyWithArgument<in Input, out Output>(
|
||||||
|
private val deferredCreator: ((Input) -> Output)
|
||||||
|
) {
|
||||||
@Volatile
|
@Volatile
|
||||||
private var singletonInstance: Output? = null
|
private var singletonInstance: Output? = null
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ import kotlinx.coroutines.sync.withLock
|
||||||
*
|
*
|
||||||
* This class is thread-safe.
|
* This class is thread-safe.
|
||||||
*/
|
*/
|
||||||
class SuspendingLazy<in Input, out Output>(private val deferredCreator: suspend ((Input) -> Output)) {
|
class SuspendingLazy<in Input, out Output>(
|
||||||
|
private val deferredCreator: suspend ((Input) -> Output)
|
||||||
|
) {
|
||||||
private var singletonInstance: Output? = null
|
private var singletonInstance: Output? = null
|
||||||
|
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
|
@ -6,7 +6,9 @@ package co.electriccoin.zcash.spackle.model
|
||||||
* @param value A 0-based index. Must be >= 0
|
* @param value A 0-based index. Must be >= 0
|
||||||
*/
|
*/
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class Index(val value: Int) {
|
value class Index(
|
||||||
|
val value: Int
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(value >= 0) { "Index must be >= 0 but actually is $value" }
|
require(value >= 0) { "Index must be >= 0 but actually is $value" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package co.electriccoin.zcash.spackle.model
|
package co.electriccoin.zcash.spackle.model
|
||||||
|
|
||||||
data class Progress(val current: Index, val last: Index) {
|
data class Progress(
|
||||||
|
val current: Index,
|
||||||
|
val last: Index
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(last.value > 0) { "last must be > 0 but was $last" }
|
require(last.value > 0) { "last must be > 0 but was $last" }
|
||||||
require(last.value >= current.value) { "last ($last) must be >= current ($current)" }
|
require(last.value >= current.value) { "last ($last) must be >= current ($current)" }
|
||||||
|
|
|
@ -28,7 +28,8 @@ open class UiTestPrerequisites {
|
||||||
|
|
||||||
private fun isScreenOn(): Boolean {
|
private fun isScreenOn(): Boolean {
|
||||||
val powerService =
|
val powerService =
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider
|
||||||
|
.getApplicationContext<Context>()
|
||||||
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
return powerService.isInteractive
|
return powerService.isInteractive
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,8 @@ open class UiTestPrerequisites {
|
||||||
|
|
||||||
private fun isKeyguardLocked(): Boolean {
|
private fun isKeyguardLocked(): Boolean {
|
||||||
val keyguardService = (
|
val keyguardService = (
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider
|
||||||
|
.getApplicationContext<Context>()
|
||||||
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ open class ZcashUiTestRunner : AndroidJUnitRunner() {
|
||||||
super.onCreate(arguments)
|
super.onCreate(arguments)
|
||||||
|
|
||||||
val powerManager =
|
val powerManager =
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider
|
||||||
|
.getApplicationContext<Context>()
|
||||||
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
|
||||||
// There is no alternative to this deprecated API. The suggestion of a view to keep the screen
|
// There is no alternative to this deprecated API. The suggestion of a view to keep the screen
|
||||||
|
|
|
@ -4,4 +4,5 @@ root = true
|
||||||
ktlint_standard_trailing-comma-on-call-site = disabled
|
ktlint_standard_trailing-comma-on-call-site = disabled
|
||||||
ktlint_standard_trailing-comma-on-declaration-site = disabled
|
ktlint_standard_trailing-comma-on-declaration-site = disabled
|
||||||
# When using Compose, suppress the `function-naming` rule in favor of PascalCase naming convention
|
# When using Compose, suppress the `function-naming` rule in favor of PascalCase naming convention
|
||||||
ktlint_function_naming_ignore_when_annotated_with=Composable
|
ktlint_function_naming_ignore_when_annotated_with=Composable
|
||||||
|
ktlint_standard_function-signature=disabled
|
|
@ -119,17 +119,18 @@ private fun splitBalance(balanceStringParts: ZecAmountTriple): Pair<String, Stri
|
||||||
Twig.debug { "Balance parts before calculation: $balanceStringParts" }
|
Twig.debug { "Balance parts before calculation: $balanceStringParts" }
|
||||||
|
|
||||||
val cutPosition =
|
val cutPosition =
|
||||||
balanceStringParts.main.indexOf(
|
balanceStringParts.main
|
||||||
startIndex = 0,
|
.indexOf(
|
||||||
char = MonetarySeparators.current(Locale.getDefault()).decimal,
|
startIndex = 0,
|
||||||
ignoreCase = true
|
char = MonetarySeparators.current(Locale.getDefault()).decimal,
|
||||||
).let { separatorPosition ->
|
ignoreCase = true
|
||||||
if (separatorPosition + CUT_POSITION_OFFSET < balanceStringParts.main.length) {
|
).let { separatorPosition ->
|
||||||
separatorPosition + CUT_POSITION_OFFSET
|
if (separatorPosition + CUT_POSITION_OFFSET < balanceStringParts.main.length) {
|
||||||
} else {
|
separatorPosition + CUT_POSITION_OFFSET
|
||||||
balanceStringParts.main.length
|
} else {
|
||||||
|
balanceStringParts.main.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val firstPart =
|
val firstPart =
|
||||||
buildString {
|
buildString {
|
||||||
|
@ -160,7 +161,10 @@ data class ZecAmountTriple(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class BalanceTextStyle(val mostSignificantPart: TextStyle, val leastSignificantPart: TextStyle)
|
data class BalanceTextStyle(
|
||||||
|
val mostSignificantPart: TextStyle,
|
||||||
|
val leastSignificantPart: TextStyle
|
||||||
|
)
|
||||||
|
|
||||||
object StyledBalanceDefaults {
|
object StyledBalanceDefaults {
|
||||||
@Stable
|
@Stable
|
||||||
|
|
|
@ -103,8 +103,7 @@ fun LabeledCheckBox(
|
||||||
top = ZcashTheme.dimens.spacingTiny,
|
top = ZcashTheme.dimens.spacingTiny,
|
||||||
bottom = ZcashTheme.dimens.spacingTiny,
|
bottom = ZcashTheme.dimens.spacingTiny,
|
||||||
end = ZcashTheme.dimens.spacingTiny
|
end = ZcashTheme.dimens.spacingTiny
|
||||||
)
|
).then(
|
||||||
.then(
|
|
||||||
if (checkBoxTestTag != null) {
|
if (checkBoxTestTag != null) {
|
||||||
Modifier.testTag(checkBoxTestTag)
|
Modifier.testTag(checkBoxTestTag)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -120,8 +120,7 @@ fun ChipOnSurface(
|
||||||
color = ZcashTheme.colors.layoutStrokeSecondary
|
color = ZcashTheme.colors.layoutStrokeSecondary
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(size = ZcashTheme.dimens.regularRippleEffectCorner),
|
shape = RoundedCornerShape(size = ZcashTheme.dimens.regularRippleEffectCorner),
|
||||||
)
|
).clickable { onClick() },
|
||||||
.clickable { onClick() },
|
|
||||||
color = ZcashTheme.colors.primaryColor,
|
color = ZcashTheme.colors.primaryColor,
|
||||||
shadowElevation = ZcashTheme.dimens.chipShadowElevation,
|
shadowElevation = ZcashTheme.dimens.chipShadowElevation,
|
||||||
) {
|
) {
|
||||||
|
@ -134,8 +133,7 @@ fun ChipOnSurface(
|
||||||
.padding(
|
.padding(
|
||||||
vertical = ZcashTheme.dimens.spacingMid,
|
vertical = ZcashTheme.dimens.spacingMid,
|
||||||
horizontal = ZcashTheme.dimens.spacingDefault
|
horizontal = ZcashTheme.dimens.spacingDefault
|
||||||
)
|
).testTag(CommonTag.CHIP)
|
||||||
.testTag(CommonTag.CHIP)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,10 @@ fun Override(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ConfigurationOverride(val uiMode: UiMode?, val locale: LocaleList?) {
|
data class ConfigurationOverride(
|
||||||
|
val uiMode: UiMode?,
|
||||||
|
val locale: LocaleList?
|
||||||
|
) {
|
||||||
fun newConfiguration(fromConfiguration: Configuration) =
|
fun newConfiguration(fromConfiguration: Configuration) =
|
||||||
Configuration(fromConfiguration).apply {
|
Configuration(fromConfiguration).apply {
|
||||||
this@ConfigurationOverride.uiMode?.let {
|
this@ConfigurationOverride.uiMode?.let {
|
||||||
|
|
|
@ -104,8 +104,7 @@ private fun PagerTab(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(
|
.background(
|
||||||
if (selected) Color.Transparent else ZcashTheme.colors.layoutStroke
|
if (selected) Color.Transparent else ZcashTheme.colors.layoutStroke
|
||||||
)
|
).padding(vertical = ZcashTheme.dimens.spacingMid, horizontal = ZcashTheme.dimens.spacingXtiny),
|
||||||
.padding(vertical = ZcashTheme.dimens.spacingMid, horizontal = ZcashTheme.dimens.spacingXtiny),
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -8,12 +8,11 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
sealed class ScreenBrightnessState {
|
sealed class ScreenBrightnessState {
|
||||||
fun getChange(): ScreenBrightnessState {
|
fun getChange(): ScreenBrightnessState =
|
||||||
return when (this) {
|
when (this) {
|
||||||
NORMAL -> FULL
|
NORMAL -> FULL
|
||||||
FULL -> NORMAL
|
FULL -> NORMAL
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data object FULL : ScreenBrightnessState()
|
data object FULL : ScreenBrightnessState()
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,7 @@ fun SwitchWithLabel(
|
||||||
indication = null,
|
indication = null,
|
||||||
role = Role.Switch,
|
role = Role.Switch,
|
||||||
onClick = { onStateChange(!state) }
|
onClick = { onStateChange(!state) }
|
||||||
)
|
).fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
val (text, spacer, switchButton) = createRefs()
|
val (text, spacer, switchButton) = createRefs()
|
||||||
Body(
|
Body(
|
||||||
|
|
|
@ -90,8 +90,7 @@ fun FormTextField(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.then(
|
||||||
.then(
|
|
||||||
if (withBorder) {
|
if (withBorder) {
|
||||||
Modifier.border(
|
Modifier.border(
|
||||||
width = 1.dp,
|
width = 1.dp,
|
||||||
|
@ -105,8 +104,7 @@ fun FormTextField(
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
)
|
).then(
|
||||||
.then(
|
|
||||||
if (testTag.isNullOrEmpty()) {
|
if (testTag.isNullOrEmpty()) {
|
||||||
Modifier
|
Modifier
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -32,6 +32,5 @@ internal val TextFieldColors.selectionColors: TextSelectionColors
|
||||||
@Composable get() = textSelectionColors
|
@Composable get() = textSelectionColors
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TextFieldColors.cursorColor(isError: Boolean): State<Color> {
|
internal fun TextFieldColors.cursorColor(isError: Boolean): State<Color> =
|
||||||
return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
|
rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
|
||||||
}
|
|
||||||
|
|
|
@ -83,8 +83,7 @@ private fun AccountSwitch(state: AccountSwitchState) {
|
||||||
onClick =
|
onClick =
|
||||||
state
|
state
|
||||||
.onAccountTypeClick
|
.onAccountTypeClick
|
||||||
)
|
).padding(start = 4.dp),
|
||||||
.padding(start = 4.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
|
|
|
@ -163,8 +163,7 @@ private fun FullscreenDialogContent(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = onBack
|
onClick = onBack
|
||||||
)
|
).padding(start = 16.dp, end = 16.dp, bottom = 64.dp)
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 64.dp)
|
|
||||||
) {
|
) {
|
||||||
ZashiQrInternal(
|
ZashiQrInternal(
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
@ -60,8 +60,7 @@ fun RadioButton(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = state.onClick,
|
onClick = state.onClick,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
)
|
).padding(horizontal = 20.dp)
|
||||||
.padding(horizontal = 20.dp)
|
|
||||||
.then(
|
.then(
|
||||||
if (testTag != null) {
|
if (testTag != null) {
|
||||||
Modifier.testTag(testTag)
|
Modifier.testTag(testTag)
|
||||||
|
|
|
@ -260,17 +260,13 @@ object ZashiListItemDefaults {
|
||||||
fun primaryColors(
|
fun primaryColors(
|
||||||
borderColor: Color = Color.Unspecified,
|
borderColor: Color = Color.Unspecified,
|
||||||
backgroundColor: Color = Color.Transparent
|
backgroundColor: Color = Color.Transparent
|
||||||
): ZashiListItemColors {
|
): ZashiListItemColors = ZashiListItemColors(borderColor = borderColor, backgroundColor = backgroundColor)
|
||||||
return ZashiListItemColors(borderColor = borderColor, backgroundColor = backgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun secondaryColors(
|
fun secondaryColors(
|
||||||
borderColor: Color = ZashiColors.Surfaces.strokeSecondary,
|
borderColor: Color = ZashiColors.Surfaces.strokeSecondary,
|
||||||
backgroundColor: Color = Color.Transparent
|
backgroundColor: Color = Color.Transparent
|
||||||
): ZashiListItemColors {
|
): ZashiListItemColors = ZashiListItemColors(borderColor = borderColor, backgroundColor = backgroundColor)
|
||||||
return ZashiListItemColors(borderColor = borderColor, backgroundColor = backgroundColor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewScreens
|
@PreviewScreens
|
||||||
|
|
|
@ -14,7 +14,8 @@ object AndroidQrCodeImageGenerator : QrCodeImageGenerator {
|
||||||
): ImageBitmap {
|
): ImageBitmap {
|
||||||
val colorArray = bitArray.toThemeColorArray(colors)
|
val colorArray = bitArray.toThemeColorArray(colors)
|
||||||
|
|
||||||
return Bitmap.createBitmap(colorArray, sizePixels, sizePixels, Bitmap.Config.ARGB_8888)
|
return Bitmap
|
||||||
|
.createBitmap(colorArray, sizePixels, sizePixels, Bitmap.Config.ARGB_8888)
|
||||||
.asImageBitmap()
|
.asImageBitmap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,9 @@ sealed interface ImageResource {
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
@Immutable
|
@Immutable
|
||||||
value class DisplayString(val value: String) : ImageResource
|
value class DisplayString(
|
||||||
|
val value: String
|
||||||
|
) : ImageResource
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
|
@ -62,15 +62,13 @@ data class ScreenHeight(
|
||||||
val systemStatusBarHeight: Dp,
|
val systemStatusBarHeight: Dp,
|
||||||
val systemNavigationBarHeight: Dp
|
val systemNavigationBarHeight: Dp
|
||||||
) {
|
) {
|
||||||
fun overallScreenHeight(): Dp {
|
fun overallScreenHeight(): Dp =
|
||||||
return (contentHeight + systemBarsHeight()).also {
|
(contentHeight + systemBarsHeight()).also {
|
||||||
Twig.debug { "Screen height: Overall height: $it" }
|
Twig.debug { "Screen height: Overall height: $it" }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun systemBarsHeight(): Dp {
|
fun systemBarsHeight(): Dp =
|
||||||
return (systemStatusBarHeight + systemNavigationBarHeight).also {
|
(systemStatusBarHeight + systemNavigationBarHeight).also {
|
||||||
Twig.debug { "Screen height: System bars height: $it" }
|
Twig.debug { "Screen height: System bars height: $it" }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,22 +26,37 @@ sealed interface StringResource {
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
@Immutable
|
@Immutable
|
||||||
value class ByString(val value: String) : StringResource
|
value class ByString(
|
||||||
|
val value: String
|
||||||
|
) : StringResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ByZatoshi(val zatoshi: Zatoshi) : StringResource
|
data class ByZatoshi(
|
||||||
|
val zatoshi: Zatoshi
|
||||||
|
) : StringResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ByDateTime(val zonedDateTime: ZonedDateTime, val useFullFormat: Boolean) : StringResource
|
data class ByDateTime(
|
||||||
|
val zonedDateTime: ZonedDateTime,
|
||||||
|
val useFullFormat: Boolean
|
||||||
|
) : StringResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ByYearMonth(val yearMonth: YearMonth) : StringResource
|
data class ByYearMonth(
|
||||||
|
val yearMonth: YearMonth
|
||||||
|
) : StringResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ByTransactionId(val transactionId: String, val abbreviated: Boolean) : StringResource
|
data class ByTransactionId(
|
||||||
|
val transactionId: String,
|
||||||
|
val abbreviated: Boolean
|
||||||
|
) : StringResource
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ByAddress(val address: String, val abbreviated: Boolean) : StringResource
|
data class ByAddress(
|
||||||
|
val address: String,
|
||||||
|
val abbreviated: Boolean
|
||||||
|
) : StringResource
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
@ -142,15 +157,28 @@ object StringResourceDefaults {
|
||||||
.getDateTimeInstance(
|
.getDateTimeInstance(
|
||||||
DateFormat.MEDIUM,
|
DateFormat.MEDIUM,
|
||||||
DateFormat.SHORT,
|
DateFormat.SHORT,
|
||||||
|
).format(
|
||||||
|
Date.from(
|
||||||
|
res.zonedDateTime
|
||||||
|
.toInstant()
|
||||||
|
.toKotlinInstant()
|
||||||
|
.toJavaInstant()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.format(Date.from(res.zonedDateTime.toInstant().toKotlinInstant().toJavaInstant()))
|
|
||||||
} else {
|
} else {
|
||||||
val pattern = DateTimeFormatter.ofPattern("MMM dd")
|
val pattern = DateTimeFormatter.ofPattern("MMM dd")
|
||||||
val start = res.zonedDateTime.format(pattern).orEmpty()
|
val start = res.zonedDateTime.format(pattern).orEmpty()
|
||||||
val end =
|
val end =
|
||||||
DateFormat
|
DateFormat
|
||||||
.getTimeInstance(DateFormat.SHORT)
|
.getTimeInstance(DateFormat.SHORT)
|
||||||
.format(Date.from(res.zonedDateTime.toInstant().toKotlinInstant().toJavaInstant()))
|
.format(
|
||||||
|
Date.from(
|
||||||
|
res.zonedDateTime
|
||||||
|
.toInstant()
|
||||||
|
.toKotlinInstant()
|
||||||
|
.toJavaInstant()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return "$start $end"
|
return "$start $end"
|
||||||
}
|
}
|
||||||
|
@ -161,23 +189,21 @@ object StringResourceDefaults {
|
||||||
return yearMonth.format(pattern).orEmpty()
|
return yearMonth.format(pattern).orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertAddress(res: StringResource.ByAddress): String {
|
fun convertAddress(res: StringResource.ByAddress): String =
|
||||||
return if (res.abbreviated && res.address.isNotBlank()) {
|
if (res.abbreviated && res.address.isNotBlank()) {
|
||||||
"${res.address.take(ADDRESS_MAX_LENGTH_ABBREVIATED)}..."
|
"${res.address.take(ADDRESS_MAX_LENGTH_ABBREVIATED)}..."
|
||||||
} else {
|
} else {
|
||||||
res.address
|
res.address
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun convertTransactionId(res: StringResource.ByTransactionId): String {
|
fun convertTransactionId(res: StringResource.ByTransactionId): String =
|
||||||
return if (res.abbreviated) {
|
if (res.abbreviated) {
|
||||||
"${res.transactionId.take(TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT)}...${res.transactionId.takeLast(
|
"${res.transactionId.take(TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT)}...${res.transactionId.takeLast(
|
||||||
TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT
|
TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT
|
||||||
)}"
|
)}"
|
||||||
} else {
|
} else {
|
||||||
res.transactionId
|
res.transactionId
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT = 5
|
private const val TRANSACTION_MAX_PREFIX_SUFFIX_LENGHT = 5
|
||||||
|
|
|
@ -101,14 +101,15 @@ class ScanViewTest : UiTestPrerequisites() {
|
||||||
it.assertDoesNotExist()
|
it.assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
getStringResourceWithArgs(
|
.onNodeWithText(
|
||||||
resId = R.string.scan_state_permission,
|
getStringResourceWithArgs(
|
||||||
getStringResource(R.string.app_name)
|
resId = R.string.scan_state_permission,
|
||||||
)
|
getStringResource(R.string.app_name)
|
||||||
).also {
|
)
|
||||||
it.assertIsDisplayed()
|
).also {
|
||||||
}
|
it.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.scan_settings_button), ignoreCase = true).also {
|
composeTestRule.onNodeWithText(getStringResource(R.string.scan_settings_button), ignoreCase = true).also {
|
||||||
it.assertIsDisplayed()
|
it.assertIsDisplayed()
|
||||||
|
|
|
@ -37,7 +37,9 @@ class ScreenBrightnessTest : UiTestPrerequisites() {
|
||||||
assertEquals(ScreenBrightnessState.NORMAL, testSetup.getSecureBrightnessCount())
|
assertEquals(ScreenBrightnessState.NORMAL, testSetup.getSecureBrightnessCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
private class TestSetup(
|
||||||
|
composeTestRule: ComposeContentTestRule
|
||||||
|
) {
|
||||||
val mutableScreenBrightnessFlag = MutableStateFlow(true)
|
val mutableScreenBrightnessFlag = MutableStateFlow(true)
|
||||||
|
|
||||||
private val screenBrightness = ScreenBrightness
|
private val screenBrightness = ScreenBrightness
|
||||||
|
|
|
@ -58,7 +58,9 @@ class ScreenSecurityTest : UiTestPrerequisites() {
|
||||||
assertEquals(0, testSetup.getSecureScreenCount())
|
assertEquals(0, testSetup.getSecureScreenCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
private class TestSetup(
|
||||||
|
composeTestRule: ComposeContentTestRule
|
||||||
|
) {
|
||||||
val mutableSecureScreenFlag = MutableStateFlow(true)
|
val mutableSecureScreenFlag = MutableStateFlow(true)
|
||||||
|
|
||||||
private val screenSecurity = ScreenSecurity()
|
private val screenSecurity = ScreenSecurity()
|
||||||
|
|
|
@ -38,7 +38,9 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
|
||||||
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()
|
||||||
|
|
|
@ -13,7 +13,8 @@ class ConfigurationEntriesTest {
|
||||||
fun keys_unique() {
|
fun keys_unique() {
|
||||||
val fieldValueSet = mutableSetOf<String>()
|
val fieldValueSet = mutableSetOf<String>()
|
||||||
|
|
||||||
ConfigurationEntries::class.memberProperties
|
ConfigurationEntries::class
|
||||||
|
.memberProperties
|
||||||
.map { it.getter.call(ConfigurationEntries) }
|
.map { it.getter.call(ConfigurationEntries) }
|
||||||
.map { it as DefaultEntry<*> }
|
.map { it as DefaultEntry<*> }
|
||||||
.map { it.key }
|
.map { it.key }
|
||||||
|
|
|
@ -13,7 +13,8 @@ class StandardPreferenceKeysTest {
|
||||||
fun unique_keys() {
|
fun unique_keys() {
|
||||||
val fieldValueSet = mutableSetOf<String>()
|
val fieldValueSet = mutableSetOf<String>()
|
||||||
|
|
||||||
StandardPreferenceKeys::class.memberProperties
|
StandardPreferenceKeys::class
|
||||||
|
.memberProperties
|
||||||
.map { it.getter.call(StandardPreferenceKeys) }
|
.map { it.getter.call(StandardPreferenceKeys) }
|
||||||
.map { it as PreferenceDefault<*> }
|
.map { it as PreferenceDefault<*> }
|
||||||
.map { it.key }
|
.map { it.key }
|
||||||
|
|
|
@ -30,8 +30,7 @@ class AboutViewTest {
|
||||||
.onNodeWithContentDescription(
|
.onNodeWithContentDescription(
|
||||||
getStringResource(R.string.back_navigation_content_description),
|
getStringResource(R.string.back_navigation_content_description),
|
||||||
ignoreCase = true
|
ignoreCase = true
|
||||||
)
|
).also {
|
||||||
.also {
|
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,11 +59,12 @@ class AboutViewTest {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule
|
||||||
getStringResource(R.string.back_navigation_content_description)
|
.onNodeWithContentDescription(
|
||||||
).also {
|
getStringResource(R.string.back_navigation_content_description)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnBackCount())
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,8 @@ class AccountViewTest : UiTestPrerequisites() {
|
||||||
fun check_all_elementary_ui_elements_displayed() {
|
fun check_all_elementary_ui_elements_displayed() {
|
||||||
newTestSetup()
|
newTestSetup()
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(CommonTag.TOP_APP_BAR)
|
composeTestRule
|
||||||
|
.onNodeWithTag(CommonTag.TOP_APP_BAR)
|
||||||
.also {
|
.also {
|
||||||
it.assertIsDisplayed()
|
it.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ class BalancesViewTest : UiTestPrerequisites() {
|
||||||
fun check_all_elementary_ui_elements_displayed() {
|
fun check_all_elementary_ui_elements_displayed() {
|
||||||
newTestSetup()
|
newTestSetup()
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(CommonTag.TOP_APP_BAR)
|
composeTestRule
|
||||||
|
.onNodeWithTag(CommonTag.TOP_APP_BAR)
|
||||||
.also {
|
.also {
|
||||||
it.assertIsDisplayed()
|
it.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class ExportPrivateDataViewTestSetup(private val composeTestRule: ComposeContentTestRule) {
|
class ExportPrivateDataViewTestSetup(
|
||||||
|
private val composeTestRule: ComposeContentTestRule
|
||||||
|
) {
|
||||||
private val onBackCount = AtomicInteger(0)
|
private val onBackCount = AtomicInteger(0)
|
||||||
|
|
||||||
private val onAgree = AtomicBoolean(false)
|
private val onAgree = AtomicBoolean(false)
|
||||||
|
|
|
@ -19,34 +19,35 @@ class OnboardingViewTest : UiTestPrerequisites() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val composeTestRule = createComposeRule()
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
private fun newTestSetup(): OnboardingTestSetup {
|
private fun newTestSetup(): OnboardingTestSetup =
|
||||||
return OnboardingTestSetup(composeTestRule).apply {
|
OnboardingTestSetup(composeTestRule).apply {
|
||||||
setDefaultContent()
|
setDefaultContent()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun layout() {
|
fun layout() {
|
||||||
newTestSetup()
|
newTestSetup()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.onboarding_create_new_wallet),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.onboarding_create_new_wallet),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertExists()
|
).also {
|
||||||
it.assertIsEnabled()
|
it.assertExists()
|
||||||
it.assertHasClickAction()
|
it.assertIsEnabled()
|
||||||
}
|
it.assertHasClickAction()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.onboarding_import_existing_wallet),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.onboarding_import_existing_wallet),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertExists()
|
).also {
|
||||||
it.assertIsEnabled()
|
it.assertExists()
|
||||||
it.assertHasClickAction()
|
it.assertIsEnabled()
|
||||||
}
|
it.assertHasClickAction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -32,11 +32,12 @@ class ReceiveViewTest {
|
||||||
newTestSetup()
|
newTestSetup()
|
||||||
|
|
||||||
// Enable substring for ellipsizing
|
// Enable substring for ellipsizing
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = "${WalletAddressFixture.UNIFIED_ADDRESS_STRING.take(20)}...",
|
.onNodeWithText(
|
||||||
substring = true,
|
text = "${WalletAddressFixture.UNIFIED_ADDRESS_STRING.take(20)}...",
|
||||||
useUnmergedTree = true
|
substring = true,
|
||||||
).assertExists()
|
useUnmergedTree = true
|
||||||
|
).assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -47,11 +48,12 @@ class ReceiveViewTest {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnSettingsCount())
|
assertEquals(0, testSetup.getOnSettingsCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule
|
||||||
getStringResource(R.string.settings_menu_content_description)
|
.onNodeWithContentDescription(
|
||||||
).also {
|
getStringResource(R.string.settings_menu_content_description)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnSettingsCount())
|
assertEquals(1, testSetup.getOnSettingsCount())
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,9 @@ class RestoreViewSecuredScreenTest : UiTestPrerequisites() {
|
||||||
assertEquals(1, testSetup.getSecureScreenCount())
|
assertEquals(1, testSetup.getSecureScreenCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
private class TestSetup(
|
||||||
|
composeTestRule: ComposeContentTestRule
|
||||||
|
) {
|
||||||
private val screenSecurity = ScreenSecurity()
|
private val screenSecurity = ScreenSecurity()
|
||||||
|
|
||||||
fun getSecureScreenCount() = screenSecurity.referenceCount.value
|
fun getSecureScreenCount() = screenSecurity.referenceCount.value
|
||||||
|
|
|
@ -61,17 +61,19 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
it.assertTextContains("ab")
|
it.assertTextContains("ab")
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(
|
composeTestRule
|
||||||
matcher = hasText("abandon", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
.onNode(
|
||||||
).also {
|
matcher = hasText("abandon", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
||||||
it.assertExists()
|
).also {
|
||||||
}
|
it.assertExists()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(
|
composeTestRule
|
||||||
matcher = hasText("able", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
.onNode(
|
||||||
).also {
|
matcher = hasText("able", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
||||||
it.assertExists()
|
).also {
|
||||||
}
|
it.assertExists()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,11 +85,12 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
it.performTextInput("ab")
|
it.performTextInput("ab")
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(
|
composeTestRule
|
||||||
matcher = hasText("abandon", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
.onNode(
|
||||||
).also {
|
matcher = hasText("abandon", substring = true) and hasTestTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(RestoreTag.AUTOCOMPLETE_LAYOUT).also {
|
composeTestRule.onNodeWithTag(RestoreTag.AUTOCOMPLETE_LAYOUT).also {
|
||||||
it.assertDoesNotExist()
|
it.assertDoesNotExist()
|
||||||
|
@ -119,7 +122,8 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
it.assertDoesNotExist()
|
it.assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(matcher = hasText(text = "abandon", substring = true))
|
composeTestRule
|
||||||
|
.onNode(matcher = hasText(text = "abandon", substring = true))
|
||||||
.also {
|
.also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
|
@ -130,12 +134,13 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
fun seed_invalid_phrase_does_not_progress() {
|
fun seed_invalid_phrase_does_not_progress() {
|
||||||
newTestSetup(initialWordsList = generateSequence { "abandon" }.take(SeedPhrase.SEED_PHRASE_SIZE).toList())
|
newTestSetup(initialWordsList = generateSequence { "abandon" }.take(SeedPhrase.SEED_PHRASE_SIZE).toList())
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_seed_button_next),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_seed_button_next),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertIsNotEnabled()
|
).also {
|
||||||
}
|
it.assertIsNotEnabled()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -146,12 +151,13 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
newTestSetup(initialWordsList = SeedPhraseFixture.new().split)
|
newTestSetup(initialWordsList = SeedPhraseFixture.new().split)
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_seed_button_next),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_seed_button_next),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertExists()
|
).also {
|
||||||
}
|
it.assertExists()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -159,23 +165,25 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
fun seed_clear() {
|
fun seed_clear() {
|
||||||
newTestSetup(initialWordsList = listOf("abandon"))
|
newTestSetup(initialWordsList = listOf("abandon"))
|
||||||
|
|
||||||
composeTestRule.onNode(
|
composeTestRule
|
||||||
matcher = hasText(text = "abandon", substring = true),
|
.onNode(
|
||||||
useUnmergedTree = true
|
matcher = hasText(text = "abandon", substring = true),
|
||||||
).also {
|
useUnmergedTree = true
|
||||||
it.assertExists()
|
).also {
|
||||||
}
|
it.assertExists()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_button_clear)).also {
|
composeTestRule.onNodeWithText(getStringResource(R.string.restore_button_clear)).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(
|
composeTestRule
|
||||||
matcher = hasText("abandon", substring = true) and hasTestTag(CommonTag.CHIP),
|
.onNode(
|
||||||
useUnmergedTree = true
|
matcher = hasText("abandon", substring = true) and hasTestTag(CommonTag.CHIP),
|
||||||
).also {
|
useUnmergedTree = true
|
||||||
it.assertDoesNotExist()
|
).also {
|
||||||
}
|
it.assertDoesNotExist()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -187,14 +195,15 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
initialWordsList = SeedPhraseFixture.new().split
|
initialWordsList = SeedPhraseFixture.new().split
|
||||||
)
|
)
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.performScrollTo()
|
).also {
|
||||||
it.assertIsEnabled()
|
it.performScrollTo()
|
||||||
it.performClick()
|
it.assertIsEnabled()
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(testSetup.getRestoreHeight(), null)
|
assertEquals(testSetup.getRestoreHeight(), null)
|
||||||
assertEquals(1, testSetup.getOnFinishedCount())
|
assertEquals(1, testSetup.getOnFinishedCount())
|
||||||
|
@ -210,16 +219,20 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
)
|
)
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
||||||
it.performTextInput(ZcashNetwork.Mainnet.saplingActivationHeight.value.toString())
|
it.performTextInput(
|
||||||
|
ZcashNetwork.Mainnet.saplingActivationHeight.value
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertIsEnabled()
|
).also {
|
||||||
it.performClick()
|
it.assertIsEnabled()
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(testSetup.getRestoreHeight(), ZcashNetwork.Mainnet.saplingActivationHeight)
|
assertEquals(testSetup.getRestoreHeight(), ZcashNetwork.Mainnet.saplingActivationHeight)
|
||||||
assertEquals(1, testSetup.getOnFinishedCount())
|
assertEquals(1, testSetup.getOnFinishedCount())
|
||||||
|
@ -234,24 +247,26 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
initialWordsList = SeedPhraseFixture.new().split
|
initialWordsList = SeedPhraseFixture.new().split
|
||||||
)
|
)
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertIsEnabled()
|
).also {
|
||||||
}
|
it.assertIsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
||||||
it.performTextInput((ZcashNetwork.Mainnet.saplingActivationHeight.value - 1L).toString())
|
it.performTextInput((ZcashNetwork.Mainnet.saplingActivationHeight.value - 1L).toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertIsNotEnabled()
|
).also {
|
||||||
it.performClick()
|
it.assertIsNotEnabled()
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertNull(testSetup.getRestoreHeight())
|
assertNull(testSetup.getRestoreHeight())
|
||||||
assertEquals(0, testSetup.getOnFinishedCount())
|
assertEquals(0, testSetup.getOnFinishedCount())
|
||||||
|
@ -270,13 +285,14 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
it.performTextInput("1.2")
|
it.performTextInput("1.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertIsNotEnabled()
|
).also {
|
||||||
it.performClick()
|
it.assertIsNotEnabled()
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertNull(testSetup.getRestoreHeight())
|
assertNull(testSetup.getRestoreHeight())
|
||||||
assertEquals(0, testSetup.getOnFinishedCount())
|
assertEquals(0, testSetup.getOnFinishedCount())
|
||||||
|
@ -293,12 +309,13 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnFinishedCount())
|
assertEquals(0, testSetup.getOnFinishedCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnFinishedCount())
|
assertEquals(1, testSetup.getOnFinishedCount())
|
||||||
}
|
}
|
||||||
|
@ -310,11 +327,12 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule
|
||||||
getStringResource(R.string.back_navigation_content_description)
|
.onNodeWithContentDescription(
|
||||||
).also {
|
getStringResource(R.string.back_navigation_content_description)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnBackCount())
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
}
|
}
|
||||||
|
@ -330,11 +348,12 @@ class RestoreViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule
|
||||||
getStringResource(R.string.back_navigation_content_description)
|
.onNodeWithContentDescription(
|
||||||
).also {
|
getStringResource(R.string.back_navigation_content_description)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
// There appears to be a bug introduced in Compose 1.4.0 which makes this necessary
|
// There appears to be a bug introduced in Compose 1.4.0 which makes this necessary
|
||||||
composeTestRule.mainClock.autoAdvance = false
|
composeTestRule.mainClock.autoAdvance = false
|
||||||
|
|
|
@ -67,12 +67,13 @@ class ScanViewBasicTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
// Permission denied ui items (not visible):
|
// Permission denied ui items (not visible):
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.scan_settings_button),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.scan_settings_button),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.assertDoesNotExist()
|
).also {
|
||||||
}
|
it.assertDoesNotExist()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -7,7 +7,9 @@ import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class SecurityWarningViewTestSetup(private val composeTestRule: ComposeContentTestRule) {
|
class SecurityWarningViewTestSetup(
|
||||||
|
private val composeTestRule: ComposeContentTestRule
|
||||||
|
) {
|
||||||
private val onBackCount = AtomicInteger(0)
|
private val onBackCount = AtomicInteger(0)
|
||||||
|
|
||||||
private val onAcknowledged = AtomicBoolean(false)
|
private val onAcknowledged = AtomicBoolean(false)
|
||||||
|
|
|
@ -29,11 +29,12 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getBackCount())
|
assertEquals(0, testSetup.getBackCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule
|
||||||
getStringResource(R.string.back_navigation_content_description)
|
.onNodeWithContentDescription(
|
||||||
).also {
|
getStringResource(R.string.back_navigation_content_description)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getBackCount())
|
assertEquals(1, testSetup.getBackCount())
|
||||||
}
|
}
|
||||||
|
@ -45,12 +46,13 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getFeedbackCount())
|
assertEquals(0, testSetup.getFeedbackCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.settings_feedback),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.settings_feedback),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getFeedbackCount())
|
assertEquals(1, testSetup.getFeedbackCount())
|
||||||
}
|
}
|
||||||
|
@ -62,12 +64,13 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getAdvancedSettingsCount())
|
assertEquals(0, testSetup.getAdvancedSettingsCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.settings_advanced_settings),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.settings_advanced_settings),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getAdvancedSettingsCount())
|
assertEquals(1, testSetup.getAdvancedSettingsCount())
|
||||||
}
|
}
|
||||||
|
@ -79,13 +82,14 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(0, testSetup.getAboutCount())
|
assertEquals(0, testSetup.getAboutCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
text = getStringResource(R.string.settings_about_us),
|
.onNodeWithText(
|
||||||
ignoreCase = true
|
text = getStringResource(R.string.settings_about_us),
|
||||||
).also {
|
ignoreCase = true
|
||||||
it.performScrollTo()
|
).also {
|
||||||
it.performClick()
|
it.performScrollTo()
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getAboutCount())
|
assertEquals(1, testSetup.getAboutCount())
|
||||||
}
|
}
|
||||||
|
@ -145,11 +149,12 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
composeTestRule.openTroubleshootingMenu()
|
composeTestRule.openTroubleshootingMenu()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
getStringResource(R.string.settings_troubleshooting_enable_background_sync)
|
.onNodeWithText(
|
||||||
).also {
|
getStringResource(R.string.settings_troubleshooting_enable_background_sync)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getBackgroundSyncCount())
|
assertEquals(1, testSetup.getBackgroundSyncCount())
|
||||||
}
|
}
|
||||||
|
@ -168,11 +173,12 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
composeTestRule.openTroubleshootingMenu()
|
composeTestRule.openTroubleshootingMenu()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
getStringResource(R.string.settings_troubleshooting_enable_keep_screen_on)
|
.onNodeWithText(
|
||||||
).also {
|
getStringResource(R.string.settings_troubleshooting_enable_keep_screen_on)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getKeepScreenOnSyncCount())
|
assertEquals(1, testSetup.getKeepScreenOnSyncCount())
|
||||||
}
|
}
|
||||||
|
@ -191,11 +197,12 @@ class SettingsViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
composeTestRule.openTroubleshootingMenu()
|
composeTestRule.openTroubleshootingMenu()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(
|
composeTestRule
|
||||||
getStringResource(R.string.settings_troubleshooting_enable_analytics)
|
.onNodeWithText(
|
||||||
).also {
|
getStringResource(R.string.settings_troubleshooting_enable_analytics)
|
||||||
it.performClick()
|
).also {
|
||||||
}
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getAnalyticsCount())
|
assertEquals(1, testSetup.getAnalyticsCount())
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,11 @@ class QrCodeAnalyzerImpl(
|
||||||
override fun analyze(image: ImageProxy) {
|
override fun analyze(image: ImageProxy) {
|
||||||
image.use {
|
image.use {
|
||||||
if (image.format in supportedImageFormats) {
|
if (image.format in supportedImageFormats) {
|
||||||
val bytes = image.planes.first().buffer.toByteArray()
|
val bytes =
|
||||||
|
image.planes
|
||||||
|
.first()
|
||||||
|
.buffer
|
||||||
|
.toByteArray()
|
||||||
|
|
||||||
Twig.verbose {
|
Twig.verbose {
|
||||||
"Scan result: " +
|
"Scan result: " +
|
||||||
|
@ -89,14 +93,15 @@ class QrCodeAnalyzerImpl(
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val result =
|
val result =
|
||||||
MultiFormatReader().apply {
|
MultiFormatReader()
|
||||||
setHints(
|
.apply {
|
||||||
mapOf(
|
setHints(
|
||||||
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
|
mapOf(
|
||||||
DecodeHintType.ALSO_INVERTED to true
|
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
|
||||||
|
DecodeHintType.ALSO_INVERTED to true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}.decodeWithState(binaryBmp)
|
||||||
}.decodeWithState(binaryBmp)
|
|
||||||
|
|
||||||
onQrCodeScanned(result.text)
|
onQrCodeScanned(result.text)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|
|
@ -12,8 +12,8 @@ fun WalletCoordinator.Companion.newInstance(
|
||||||
context: Context,
|
context: Context,
|
||||||
encryptedPreferenceProvider: EncryptedPreferenceProvider,
|
encryptedPreferenceProvider: EncryptedPreferenceProvider,
|
||||||
persistableWalletPreference: PersistableWalletPreferenceDefault
|
persistableWalletPreference: PersistableWalletPreferenceDefault
|
||||||
): WalletCoordinator {
|
): WalletCoordinator =
|
||||||
return WalletCoordinator(
|
WalletCoordinator(
|
||||||
context = context,
|
context = context,
|
||||||
persistableWallet =
|
persistableWallet =
|
||||||
flow {
|
flow {
|
||||||
|
@ -22,6 +22,5 @@ fun WalletCoordinator.Companion.newInstance(
|
||||||
accountName = context.getString(R.string.zashi_wallet_name),
|
accountName = context.getString(R.string.zashi_wallet_name),
|
||||||
keySource = ZASHI_KEYSOURCE
|
keySource = ZASHI_KEYSOURCE
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private const val ZASHI_KEYSOURCE = "zashi"
|
private const val ZASHI_KEYSOURCE = "zashi"
|
||||||
|
|
|
@ -42,11 +42,11 @@ class BiometricActivity : FragmentActivity() {
|
||||||
)
|
)
|
||||||
|
|
||||||
val promptInfo =
|
val promptInfo =
|
||||||
BiometricPrompt.PromptInfo.Builder()
|
BiometricPrompt.PromptInfo
|
||||||
|
.Builder()
|
||||||
.setTitle(
|
.setTitle(
|
||||||
getString(R.string.authentication_system_ui_title, getString(R.string.app_name))
|
getString(R.string.authentication_system_ui_title, getString(R.string.app_name))
|
||||||
)
|
).setSubtitle(subtitle)
|
||||||
.setSubtitle(subtitle)
|
|
||||||
.setAllowedAuthenticators(biometricRepository.allowedAuthenticators)
|
.setAllowedAuthenticators(biometricRepository.allowedAuthenticators)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
|
@ -472,7 +472,11 @@ private fun MainActivity.NavigationHome(
|
||||||
|
|
||||||
val isEnoughSpace by storageCheckViewModel.isEnoughSpace.collectAsStateWithLifecycle()
|
val isEnoughSpace by storageCheckViewModel.isEnoughSpace.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val sdkStatus = walletViewModel.currentWalletSnapshot.collectAsStateWithLifecycle().value?.status
|
val sdkStatus =
|
||||||
|
walletViewModel.currentWalletSnapshot
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value
|
||||||
|
?.status
|
||||||
|
|
||||||
val currentAppState = applicationStateProvider.state.collectAsStateWithLifecycle().value
|
val currentAppState = applicationStateProvider.state.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
|
|
|
@ -69,13 +69,21 @@ class NavigationRouterImpl : NavigationRouter {
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface NavigationCommand {
|
sealed interface NavigationCommand {
|
||||||
data class Forward(val route: Any) : NavigationCommand
|
data class Forward(
|
||||||
|
val route: Any
|
||||||
|
) : NavigationCommand
|
||||||
|
|
||||||
data class Replace(val route: Any) : NavigationCommand
|
data class Replace(
|
||||||
|
val route: Any
|
||||||
|
) : NavigationCommand
|
||||||
|
|
||||||
data class ReplaceAll(val route: Any) : NavigationCommand
|
data class ReplaceAll(
|
||||||
|
val route: Any
|
||||||
|
) : NavigationCommand
|
||||||
|
|
||||||
data class NewRoot(val route: Any) : NavigationCommand
|
data class NewRoot(
|
||||||
|
val route: Any
|
||||||
|
) : NavigationCommand
|
||||||
|
|
||||||
data object Back : NavigationCommand
|
data object Back : NavigationCommand
|
||||||
|
|
||||||
|
|
|
@ -106,8 +106,7 @@ class AccountDataSourceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}?.flatMapLatest { accountsWithAddresses ->
|
||||||
?.flatMapLatest { accountsWithAddresses ->
|
|
||||||
if (accountsWithAddresses == null) {
|
if (accountsWithAddresses == null) {
|
||||||
flowOf(null)
|
flowOf(null)
|
||||||
} else {
|
} else {
|
||||||
|
@ -132,8 +131,7 @@ class AccountDataSourceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}?.retryWhen { _, attempt ->
|
||||||
?.retryWhen { _, attempt ->
|
|
||||||
emit(null)
|
emit(null)
|
||||||
delay(attempt.coerceAtMost(RETRY_DELAY).seconds)
|
delay(attempt.coerceAtMost(RETRY_DELAY).seconds)
|
||||||
true
|
true
|
||||||
|
@ -189,8 +187,7 @@ class AccountDataSourceImpl(
|
||||||
accounts.size == 1,
|
accounts.size == 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}?.sortedDescending()
|
||||||
?.sortedDescending()
|
|
||||||
}.flowOn(Dispatchers.IO)
|
}.flowOn(Dispatchers.IO)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
|
@ -202,15 +199,13 @@ class AccountDataSourceImpl(
|
||||||
allAccounts
|
allAccounts
|
||||||
.map { account ->
|
.map { account ->
|
||||||
account?.firstOrNull { it.isSelected }
|
account?.firstOrNull { it.isSelected }
|
||||||
}
|
}.distinctUntilChanged()
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
override val zashiAccount: Flow<ZashiAccount?> =
|
override val zashiAccount: Flow<ZashiAccount?> =
|
||||||
allAccounts
|
allAccounts
|
||||||
.map { account ->
|
.map { account ->
|
||||||
account?.filterIsInstance<ZashiAccount>()?.firstOrNull()
|
account?.filterIsInstance<ZashiAccount>()?.firstOrNull()
|
||||||
}
|
}.distinctUntilChanged()
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
override suspend fun getAllAccounts() =
|
override suspend fun getAllAccounts() =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -250,7 +245,8 @@ class AccountDataSourceImpl(
|
||||||
index: Long
|
index: Long
|
||||||
): Account =
|
): Account =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
synchronizerProvider.getSynchronizer()
|
synchronizerProvider
|
||||||
|
.getSynchronizer()
|
||||||
.importAccountByUfvk(
|
.importAccountByUfvk(
|
||||||
AccountImportSetup(
|
AccountImportSetup(
|
||||||
accountName = context.getString(R.string.keystone_wallet_name),
|
accountName = context.getString(R.string.keystone_wallet_name),
|
||||||
|
|
|
@ -108,7 +108,10 @@ class LocalAddressBookDataSourceImpl(
|
||||||
lastUpdated = lastUpdated,
|
lastUpdated = lastUpdated,
|
||||||
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
||||||
contacts =
|
contacts =
|
||||||
addressBook?.contacts.orEmpty().toMutableList()
|
addressBook
|
||||||
|
?.contacts
|
||||||
|
.orEmpty()
|
||||||
|
.toMutableList()
|
||||||
.apply {
|
.apply {
|
||||||
set(
|
set(
|
||||||
indexOf(contact),
|
indexOf(contact),
|
||||||
|
@ -118,8 +121,7 @@ class LocalAddressBookDataSourceImpl(
|
||||||
lastUpdated = Clock.System.now()
|
lastUpdated = Clock.System.now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}.toList(),
|
||||||
.toList(),
|
|
||||||
).also {
|
).also {
|
||||||
addressBook = it
|
addressBook = it
|
||||||
}
|
}
|
||||||
|
@ -138,11 +140,13 @@ class LocalAddressBookDataSourceImpl(
|
||||||
lastUpdated = lastUpdated,
|
lastUpdated = lastUpdated,
|
||||||
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
||||||
contacts =
|
contacts =
|
||||||
addressBook?.contacts.orEmpty().toMutableList()
|
addressBook
|
||||||
|
?.contacts
|
||||||
|
.orEmpty()
|
||||||
|
.toMutableList()
|
||||||
.apply {
|
.apply {
|
||||||
remove(addressBookContact)
|
remove(addressBookContact)
|
||||||
}
|
}.toList(),
|
||||||
.toList(),
|
|
||||||
).also {
|
).also {
|
||||||
addressBook = it
|
addressBook = it
|
||||||
}
|
}
|
||||||
|
@ -176,7 +180,8 @@ class LocalAddressBookDataSourceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (unencryptedFile != null) {
|
return if (unencryptedFile != null) {
|
||||||
addressBookProvider.readLegacyUnencryptedAddressBookFromFile(unencryptedFile)
|
addressBookProvider
|
||||||
|
.readLegacyUnencryptedAddressBookFromFile(unencryptedFile)
|
||||||
.also { unencryptedAddressBook ->
|
.also { unencryptedAddressBook ->
|
||||||
writeAddressBookToLocalStorage(unencryptedAddressBook, addressBookKey)
|
writeAddressBookToLocalStorage(unencryptedAddressBook, addressBookKey)
|
||||||
unencryptedFile.deleteSuspend()
|
unencryptedFile.deleteSuspend()
|
||||||
|
|
|
@ -167,8 +167,8 @@ class MetadataDataSourceImpl(
|
||||||
txId: String,
|
txId: String,
|
||||||
key: MetadataKey,
|
key: MetadataKey,
|
||||||
transform: (AnnotationMetadata) -> AnnotationMetadata
|
transform: (AnnotationMetadata) -> AnnotationMetadata
|
||||||
): Metadata {
|
): Metadata =
|
||||||
return updateMetadata(
|
updateMetadata(
|
||||||
key = key,
|
key = key,
|
||||||
transform = { metadata ->
|
transform = { metadata ->
|
||||||
metadata.copy(
|
metadata.copy(
|
||||||
|
@ -184,14 +184,13 @@ class MetadataDataSourceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateMetadataBookmark(
|
private suspend fun updateMetadataBookmark(
|
||||||
txId: String,
|
txId: String,
|
||||||
key: MetadataKey,
|
key: MetadataKey,
|
||||||
transform: (BookmarkMetadata) -> BookmarkMetadata
|
transform: (BookmarkMetadata) -> BookmarkMetadata
|
||||||
): Metadata {
|
): Metadata =
|
||||||
return updateMetadata(
|
updateMetadata(
|
||||||
key = key,
|
key = key,
|
||||||
transform = { metadata ->
|
transform = { metadata ->
|
||||||
metadata.copy(
|
metadata.copy(
|
||||||
|
@ -207,13 +206,12 @@ class MetadataDataSourceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateMetadata(
|
private suspend fun updateMetadata(
|
||||||
key: MetadataKey,
|
key: MetadataKey,
|
||||||
transform: (AccountMetadata) -> AccountMetadata
|
transform: (AccountMetadata) -> AccountMetadata
|
||||||
): Metadata {
|
): Metadata =
|
||||||
return withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val metadata = getMetadataInternal(key)
|
val metadata = getMetadataInternal(key)
|
||||||
|
|
||||||
val accountMetadata = metadata.accountMetadata
|
val accountMetadata = metadata.accountMetadata
|
||||||
|
@ -228,7 +226,6 @@ class MetadataDataSourceImpl(
|
||||||
|
|
||||||
updatedMetadata
|
updatedMetadata
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultAccountMetadata() =
|
private fun defaultAccountMetadata() =
|
||||||
|
@ -258,11 +255,11 @@ private fun <T : Any> List<T>.replaceOrAdd(
|
||||||
): List<T> {
|
): List<T> {
|
||||||
val index = this.indexOfFirst(predicate)
|
val index = this.indexOfFirst(predicate)
|
||||||
return if (index != -1) {
|
return if (index != -1) {
|
||||||
this.toMutableList()
|
this
|
||||||
|
.toMutableList()
|
||||||
.apply {
|
.apply {
|
||||||
set(index, transform(this[index]))
|
set(index, transform(this[index]))
|
||||||
}
|
}.toList()
|
||||||
.toList()
|
|
||||||
} else {
|
} else {
|
||||||
this + transform(null)
|
this + transform(null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,9 @@ interface ProposalDataSource {
|
||||||
suspend fun redactPcztForSigner(pczt: Pczt): Pczt
|
suspend fun redactPcztForSigner(pczt: Pczt): Pczt
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionProposalNotCreatedException(reason: Exception) : Exception(reason)
|
class TransactionProposalNotCreatedException(
|
||||||
|
reason: Exception
|
||||||
|
) : Exception(reason)
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class ProposalDataSourceImpl(
|
class ProposalDataSourceImpl(
|
||||||
|
@ -150,7 +152,8 @@ class ProposalDataSourceImpl(
|
||||||
|
|
||||||
override suspend fun addProofsToPczt(pczt: Pczt): Pczt =
|
override suspend fun addProofsToPczt(pczt: Pczt): Pczt =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
synchronizerProvider.getSynchronizer()
|
synchronizerProvider
|
||||||
|
.getSynchronizer()
|
||||||
.addProofsToPczt(pczt)
|
.addProofsToPczt(pczt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +181,8 @@ class ProposalDataSourceImpl(
|
||||||
|
|
||||||
override suspend fun redactPcztForSigner(pczt: Pczt): Pczt =
|
override suspend fun redactPcztForSigner(pczt: Pczt): Pczt =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
synchronizerProvider.getSynchronizer()
|
synchronizerProvider
|
||||||
|
.getSynchronizer()
|
||||||
.redactPcztForSigner(pczt)
|
.redactPcztForSigner(pczt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +236,12 @@ class ProposalDataSourceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
private inline fun <T : Any> getOrThrow(block: () -> T): T {
|
private inline fun <T : Any> getOrThrow(block: () -> T): T =
|
||||||
return try {
|
try {
|
||||||
block()
|
block()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw TransactionProposalNotCreatedException(e)
|
throw TransactionProposalNotCreatedException(e)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun AddressType.toWalletAddress(value: String) =
|
private suspend fun AddressType.toWalletAddress(value: String) =
|
||||||
when (this) {
|
when (this) {
|
||||||
|
|
|
@ -23,8 +23,8 @@ class TransactionHistoryMapper {
|
||||||
data: ListTransactionData,
|
data: ListTransactionData,
|
||||||
restoreTimestamp: Instant,
|
restoreTimestamp: Instant,
|
||||||
onTransactionClick: (Transaction) -> Unit
|
onTransactionClick: (Transaction) -> Unit
|
||||||
): TransactionState {
|
): TransactionState =
|
||||||
return TransactionState(
|
TransactionState(
|
||||||
key = data.transaction.id.txIdString(),
|
key = data.transaction.id.txIdString(),
|
||||||
icon = getIcon(data),
|
icon = getIcon(data),
|
||||||
title = getTitle(data),
|
title = getTitle(data),
|
||||||
|
@ -34,7 +34,6 @@ class TransactionHistoryMapper {
|
||||||
onClick = { onTransactionClick(data.transaction) },
|
onClick = { onTransactionClick(data.transaction) },
|
||||||
isUnread = isUnread(data, restoreTimestamp)
|
isUnread = isUnread(data, restoreTimestamp)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private fun isUnread(
|
private fun isUnread(
|
||||||
data: ListTransactionData,
|
data: ListTransactionData,
|
||||||
|
|
|
@ -6,4 +6,7 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
|
||||||
* @property servers an ascended sorted list of fastest servers, or null if none loaded
|
* @property servers an ascended sorted list of fastest servers, or null if none loaded
|
||||||
* @property isLoading indicates whether newer data is being loaded
|
* @property isLoading indicates whether newer data is being loaded
|
||||||
*/
|
*/
|
||||||
data class FastestServersState(val servers: List<LightWalletEndpoint>?, val isLoading: Boolean)
|
data class FastestServersState(
|
||||||
|
val servers: List<LightWalletEndpoint>?,
|
||||||
|
val isLoading: Boolean
|
||||||
|
)
|
||||||
|
|
|
@ -3,23 +3,31 @@ package co.electriccoin.zcash.ui.common.model
|
||||||
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
|
|
||||||
sealed interface SubmitResult {
|
sealed interface SubmitResult {
|
||||||
data class Success(val txIds: List<String>) : SubmitResult
|
data class Success(
|
||||||
|
val txIds: List<String>
|
||||||
|
) : SubmitResult
|
||||||
|
|
||||||
data class MultipleTrxFailure(val results: List<TransactionSubmitResult>) : SubmitResult
|
data class MultipleTrxFailure(
|
||||||
|
val results: List<TransactionSubmitResult>
|
||||||
|
) : SubmitResult
|
||||||
|
|
||||||
sealed interface SimpleTrxFailure : SubmitResult {
|
sealed interface SimpleTrxFailure : SubmitResult {
|
||||||
fun toErrorMessage(): String
|
fun toErrorMessage(): String
|
||||||
|
|
||||||
fun toErrorStacktrace(): String
|
fun toErrorStacktrace(): String
|
||||||
|
|
||||||
data class SimpleTrxFailureGrpc(val result: TransactionSubmitResult.Failure) : SimpleTrxFailure {
|
data class SimpleTrxFailureGrpc(
|
||||||
|
val result: TransactionSubmitResult.Failure
|
||||||
|
) : SimpleTrxFailure {
|
||||||
// Currently, we intentionally do not include any error related details
|
// Currently, we intentionally do not include any error related details
|
||||||
override fun toErrorMessage() = ""
|
override fun toErrorMessage() = ""
|
||||||
|
|
||||||
override fun toErrorStacktrace() = ""
|
override fun toErrorStacktrace() = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SimpleTrxFailureSubmit(val result: TransactionSubmitResult.Failure) : SimpleTrxFailure {
|
data class SimpleTrxFailureSubmit(
|
||||||
|
val result: TransactionSubmitResult.Failure
|
||||||
|
) : SimpleTrxFailure {
|
||||||
override fun toErrorMessage() =
|
override fun toErrorMessage() =
|
||||||
buildString {
|
buildString {
|
||||||
appendLine("Error code: ${result.code}")
|
appendLine("Error code: ${result.code}")
|
||||||
|
@ -29,7 +37,9 @@ sealed interface SubmitResult {
|
||||||
override fun toErrorStacktrace(): String = toErrorMessage()
|
override fun toErrorStacktrace(): String = toErrorMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SimpleTrxFailureOther(val error: Throwable) : SimpleTrxFailure {
|
data class SimpleTrxFailureOther(
|
||||||
|
val error: Throwable
|
||||||
|
) : SimpleTrxFailure {
|
||||||
override fun toErrorMessage() = error.message ?: "Unknown error"
|
override fun toErrorMessage() = error.message ?: "Unknown error"
|
||||||
|
|
||||||
override fun toErrorStacktrace(): String = error.stackTraceToString()
|
override fun toErrorStacktrace(): String = error.stackTraceToString()
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
package co.electriccoin.zcash.ui.common.model
|
package co.electriccoin.zcash.ui.common.model
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class ValidContactName(val value: String)
|
value class ValidContactName(
|
||||||
|
val value: String
|
||||||
|
)
|
||||||
|
|
|
@ -49,17 +49,18 @@ data class VersionInfo(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveBestReleaseNotes(): String {
|
private fun resolveBestReleaseNotes(): String =
|
||||||
return if (Locale.getDefault().language.contains("es", ignoreCase = true)) {
|
if (Locale.getDefault().language.contains("es", ignoreCase = true)) {
|
||||||
releaseNotesEs
|
releaseNotesEs
|
||||||
} else {
|
} else {
|
||||||
releaseNotesEn
|
releaseNotesEn
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DistributionDimension(val value: String) {
|
enum class DistributionDimension(
|
||||||
|
val value: String
|
||||||
|
) {
|
||||||
STORE("store"),
|
STORE("store"),
|
||||||
FOSS("foss")
|
FOSS("foss")
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,7 @@ class AddressBookKeyStorageProviderImpl(
|
||||||
) : AddressBookKeyStorageProvider {
|
) : AddressBookKeyStorageProvider {
|
||||||
private val default = AddressBookKeyPreferenceDefault()
|
private val default = AddressBookKeyPreferenceDefault()
|
||||||
|
|
||||||
override suspend fun getAddressBookKey(): AddressBookKey? {
|
override suspend fun getAddressBookKey(): AddressBookKey? = default.getValue(encryptedPreferenceProvider())
|
||||||
return default.getValue(encryptedPreferenceProvider())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun storeAddressBookKey(addressBookKey: AddressBookKey) {
|
override suspend fun storeAddressBookKey(addressBookKey: AddressBookKey) {
|
||||||
default.putValue(encryptedPreferenceProvider(), addressBookKey)
|
default.putValue(encryptedPreferenceProvider(), addressBookKey)
|
||||||
|
|
|
@ -43,18 +43,16 @@ class AddressBookProviderImpl(
|
||||||
override fun readAddressBookFromFile(
|
override fun readAddressBookFromFile(
|
||||||
file: File,
|
file: File,
|
||||||
addressBookKey: AddressBookKey
|
addressBookKey: AddressBookKey
|
||||||
): AddressBook {
|
): AddressBook =
|
||||||
return file.inputStream().use { stream ->
|
file.inputStream().use { stream ->
|
||||||
addressBookEncryptor.decrypt(
|
addressBookEncryptor.decrypt(
|
||||||
key = addressBookKey,
|
key = addressBookKey,
|
||||||
inputStream = stream
|
inputStream = stream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun readLegacyUnencryptedAddressBookFromFile(file: File): AddressBook {
|
override fun readLegacyUnencryptedAddressBookFromFile(file: File): AddressBook =
|
||||||
return file.inputStream().use { stream ->
|
file.inputStream().use { stream ->
|
||||||
addressBookSerializer.deserializeAddressBook(stream)
|
addressBookSerializer.deserializeAddressBook(stream)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,13 @@ interface AddressBookStorageProvider {
|
||||||
class AddressBookStorageProviderImpl(
|
class AddressBookStorageProviderImpl(
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : AddressBookStorageProvider {
|
) : AddressBookStorageProvider {
|
||||||
override fun getStorageFile(addressBookKey: AddressBookKey): File? {
|
override fun getStorageFile(addressBookKey: AddressBookKey): File? =
|
||||||
return File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
|
File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
|
||||||
.takeIf { it.exists() && it.isFile }
|
.takeIf { it.exists() && it.isFile }
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLegacyUnencryptedStorageFile(): File? {
|
override fun getLegacyUnencryptedStorageFile(): File? =
|
||||||
return File(context.noBackupFilesDir, LEGACY_UNENCRYPTED_ADDRESS_BOOK_FILE_NAME)
|
File(context.noBackupFilesDir, LEGACY_UNENCRYPTED_ADDRESS_BOOK_FILE_NAME)
|
||||||
.takeIf { it.exists() && it.isFile }
|
.takeIf { it.exists() && it.isFile }
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File {
|
override fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File {
|
||||||
val file = File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
|
val file = File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
|
||||||
|
|
|
@ -20,9 +20,7 @@ class ApplicationStateProviderImpl : ApplicationStateProvider {
|
||||||
|
|
||||||
override val state = _state.asStateFlow()
|
override val state = _state.asStateFlow()
|
||||||
|
|
||||||
override suspend fun getApplicationState(): Lifecycle.Event? {
|
override suspend fun getApplicationState(): Lifecycle.Event? = _state.last()
|
||||||
return _state.last()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setApplicationState(newState: Lifecycle.Event) {
|
override fun setApplicationState(newState: Lifecycle.Event) {
|
||||||
_state.update { newState }
|
_state.update { newState }
|
||||||
|
|
|
@ -8,7 +8,9 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
|
||||||
|
|
||||||
// TODO [#1273]: Add ChooseServer Tests #1273
|
// TODO [#1273]: Add ChooseServer Tests #1273
|
||||||
// TODO [#1273]: https://github.com/Electric-Coin-Company/zashi-android/issues/1273
|
// TODO [#1273]: https://github.com/Electric-Coin-Company/zashi-android/issues/1273
|
||||||
class GetDefaultServersProvider(private val application: Application) {
|
class GetDefaultServersProvider(
|
||||||
|
private val application: Application
|
||||||
|
) {
|
||||||
private val lightWalletEndpoints by lazy {
|
private val lightWalletEndpoints by lazy {
|
||||||
if (ZcashNetwork.fromResources(application) == ZcashNetwork.Mainnet) {
|
if (ZcashNetwork.fromResources(application) == ZcashNetwork.Mainnet) {
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
@ -3,6 +3,8 @@ package co.electriccoin.zcash.ui.common.provider
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||||
|
|
||||||
class GetVersionInfoProvider(private val application: Application) {
|
class GetVersionInfoProvider(
|
||||||
|
private val application: Application
|
||||||
|
) {
|
||||||
operator fun invoke() = VersionInfo.new(application)
|
operator fun invoke() = VersionInfo.new(application)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package co.electriccoin.zcash.ui.common.provider
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import cash.z.ecc.sdk.type.ZcashCurrency
|
import cash.z.ecc.sdk.type.ZcashCurrency
|
||||||
|
|
||||||
class GetZcashCurrencyProvider(private val application: Application) {
|
class GetZcashCurrencyProvider(
|
||||||
|
private val application: Application
|
||||||
|
) {
|
||||||
operator fun invoke() = ZcashCurrency.fromResources(application)
|
operator fun invoke() = ZcashCurrency.fromResources(application)
|
||||||
|
|
||||||
fun getLocalizedName() = ZcashCurrency.getLocalizedName(application)
|
fun getLocalizedName() = ZcashCurrency.getLocalizedName(application)
|
||||||
|
|
|
@ -25,12 +25,11 @@ class MetadataKeyStorageProviderImpl(
|
||||||
) : MetadataKeyStorageProvider {
|
) : MetadataKeyStorageProvider {
|
||||||
private val default = MetadataKeyPreferenceDefault()
|
private val default = MetadataKeyPreferenceDefault()
|
||||||
|
|
||||||
override suspend fun get(account: WalletAccount): MetadataKey? {
|
override suspend fun get(account: WalletAccount): MetadataKey? =
|
||||||
return default.getValue(
|
default.getValue(
|
||||||
walletAccount = account,
|
walletAccount = account,
|
||||||
preferenceProvider = encryptedPreferenceProvider(),
|
preferenceProvider = encryptedPreferenceProvider(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun store(
|
override suspend fun store(
|
||||||
key: MetadataKey,
|
key: MetadataKey,
|
||||||
|
@ -51,11 +50,11 @@ private class MetadataKeyPreferenceDefault {
|
||||||
suspend fun getValue(
|
suspend fun getValue(
|
||||||
walletAccount: WalletAccount,
|
walletAccount: WalletAccount,
|
||||||
preferenceProvider: PreferenceProvider,
|
preferenceProvider: PreferenceProvider,
|
||||||
): MetadataKey? {
|
): MetadataKey? =
|
||||||
return preferenceProvider.getStringSet(
|
preferenceProvider
|
||||||
key = getKey(walletAccount)
|
.getStringSet(
|
||||||
)?.decode()
|
key = getKey(walletAccount)
|
||||||
}
|
)?.decode()
|
||||||
|
|
||||||
suspend fun putValue(
|
suspend fun putValue(
|
||||||
newValue: MetadataKey?,
|
newValue: MetadataKey?,
|
||||||
|
@ -73,20 +72,19 @@ private class MetadataKeyPreferenceDefault {
|
||||||
PreferenceKey("metadata_key_${walletAccount.sdkAccount.accountUuid.value.toHexString()}")
|
PreferenceKey("metadata_key_${walletAccount.sdkAccount.accountUuid.value.toHexString()}")
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
private fun MetadataKey?.encode(): Set<String>? {
|
private fun MetadataKey?.encode(): Set<String>? =
|
||||||
return this
|
this
|
||||||
?.bytes
|
?.bytes
|
||||||
?.map {
|
?.map {
|
||||||
Base64.encode(it.toByteArray(secretKeyAccess))
|
Base64.encode(it.toByteArray(secretKeyAccess))
|
||||||
}
|
}?.toSet()
|
||||||
?.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
private fun Set<String>?.decode() =
|
private fun Set<String>?.decode() =
|
||||||
if (this != null) {
|
if (this != null) {
|
||||||
MetadataKey(
|
MetadataKey(
|
||||||
this.toList()
|
this
|
||||||
|
.toList()
|
||||||
.map {
|
.map {
|
||||||
SecretBytes.copyFrom(Base64.decode(it), secretKeyAccess)
|
SecretBytes.copyFrom(Base64.decode(it), secretKeyAccess)
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue