diff --git a/.idea/runConfigurations/configuration_impl_android_lib_connectedCheck.xml b/.idea/runConfigurations/configuration_impl_android_lib_connectedCheck.xml
new file mode 100644
index 00000000..a0d7871b
--- /dev/null
+++ b/.idea/runConfigurations/configuration_impl_android_lib_connectedCheck.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configuration-api-lib/src/commonMain/kotlin/co/electriccoin/zcash/configuration/api/MergingConfigurationProvider.kt b/configuration-api-lib/src/commonMain/kotlin/co/electriccoin/zcash/configuration/api/MergingConfigurationProvider.kt
index 6f11329b..f2a8fa8c 100644
--- a/configuration-api-lib/src/commonMain/kotlin/co/electriccoin/zcash/configuration/api/MergingConfigurationProvider.kt
+++ b/configuration-api-lib/src/commonMain/kotlin/co/electriccoin/zcash/configuration/api/MergingConfigurationProvider.kt
@@ -3,6 +3,7 @@ package co.electriccoin.zcash.configuration.api
import co.electriccoin.zcash.configuration.model.entry.ConfigKey
import co.electriccoin.zcash.configuration.model.map.Configuration
import kotlinx.collections.immutable.PersistentList
+import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.datetime.Instant
@@ -14,7 +15,7 @@ class MergingConfigurationProvider(private val configurationProviders: Persisten
override fun getConfigurationFlow(): Flow {
return combine(configurationProviders.map { it.getConfigurationFlow() }) { configurations ->
- MergingConfiguration(configurations.toList())
+ MergingConfiguration(configurations.toList().toPersistentList())
}
}
@@ -23,7 +24,7 @@ class MergingConfigurationProvider(private val configurationProviders: Persisten
}
}
-private data class MergingConfiguration(private val configurations: List) : Configuration {
+private data class MergingConfiguration(private val configurations: PersistentList) : Configuration {
override val updatedAt: Instant?
get() = configurations.mapNotNull { it.updatedAt }.maxOrNull()
diff --git a/configuration-impl-android-lib/build.gradle.kts b/configuration-impl-android-lib/build.gradle.kts
new file mode 100644
index 00000000..13d7671d
--- /dev/null
+++ b/configuration-impl-android-lib/build.gradle.kts
@@ -0,0 +1,39 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ id("secant.android-build-conventions")
+ id("wtf.emulator.gradle")
+ id("secant.emulator-wtf-conventions")
+ id("secant.jacoco-conventions")
+}
+
+android {
+ namespace = "co.electriccoin.zcash.configuration"
+}
+
+dependencies {
+ implementation(libs.kotlin.stdlib)
+ implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.datetime)
+ implementation(libs.kotlinx.immutable)
+ api(projects.configurationApiLib)
+ implementation(projects.spackleLib)
+
+ androidTestImplementation(libs.bundles.androidx.test)
+ androidTestImplementation(libs.kotlinx.coroutines.test)
+
+ androidTestUtil(libs.androidx.test.services) {
+ artifact {
+ type = "apk"
+ }
+ }
+
+ if (project.property("IS_USE_TEST_ORCHESTRATOR").toString().toBoolean()) {
+ androidTestUtil(libs.androidx.test.orchestrator) {
+ artifact {
+ type = "apk"
+ }
+ }
+ }
+}
diff --git a/configuration-impl-android-lib/proguard-consumer.txt b/configuration-impl-android-lib/proguard-consumer.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/configuration-impl-android-lib/src/androidTest/AndroidManifest.xml b/configuration-impl-android-lib/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000..756affdb
--- /dev/null
+++ b/configuration-impl-android-lib/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/configuration-impl-android-lib/src/androidTest/java/co/electriccoin/zcash/configuration/internal/intent/IntentProviderTest.kt b/configuration-impl-android-lib/src/androidTest/java/co/electriccoin/zcash/configuration/internal/intent/IntentProviderTest.kt
new file mode 100644
index 00000000..1ac922d3
--- /dev/null
+++ b/configuration-impl-android-lib/src/androidTest/java/co/electriccoin/zcash/configuration/internal/intent/IntentProviderTest.kt
@@ -0,0 +1,27 @@
+package co.electriccoin.zcash.configuration.internal.intent
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import co.electriccoin.zcash.configuration.model.entry.ConfigKey
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class IntentProviderTest {
+ @Test
+ @SmallTest
+ fun testInsertValue() {
+ val key = ConfigKey("test")
+ assertFalse(IntentConfigurationProvider.peekConfiguration().hasKey(key))
+
+ IntentConfigurationReceiver().onReceive(
+ null,
+ Intent().apply {
+ putExtra(ConfigurationIntent.EXTRA_STRING_KEY, key.key)
+ putExtra(ConfigurationIntent.EXTRA_STRING_VALUE, "test")
+ }
+ )
+
+ assertTrue(IntentConfigurationProvider.peekConfiguration().hasKey(key))
+ }
+}
diff --git a/configuration-impl-android-lib/src/debug/AndroidManifest.xml b/configuration-impl-android-lib/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..785b235f
--- /dev/null
+++ b/configuration-impl-android-lib/src/debug/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/configuration-impl-android-lib/src/main/AndroidManifest.xml b/configuration-impl-android-lib/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..b03c811c
--- /dev/null
+++ b/configuration-impl-android-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt
new file mode 100644
index 00000000..14f1c458
--- /dev/null
+++ b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt
@@ -0,0 +1,33 @@
+package co.electriccoin.zcash.configuration
+
+import android.content.Context
+import co.electriccoin.zcash.configuration.api.ConfigurationProvider
+import co.electriccoin.zcash.configuration.api.MergingConfigurationProvider
+import co.electriccoin.zcash.configuration.internal.intent.IntentConfigurationProvider
+import co.electriccoin.zcash.spackle.LazyWithArgument
+import kotlinx.collections.immutable.toPersistentList
+
+object AndroidConfigurationFactory {
+
+ private val instance = LazyWithArgument { context ->
+ new(context)
+ }
+
+ fun getInstance(context: Context): ConfigurationProvider = instance.getInstance(context)
+
+ // Context will be needed for most cloud providers, e.g. to integrate with Firebase or other
+ // remote configuration providers.
+ private fun new(@Suppress("UNUSED_PARAMETER") context: Context): ConfigurationProvider {
+ val configurationProviders = buildList {
+ // For ordering, ensure the IntentConfigurationProvider is first so that it can
+ // override any other configuration providers.
+ if (BuildConfig.DEBUG) {
+ add(IntentConfigurationProvider)
+ }
+
+ // In the future, add a third party cloud-based configuration provider
+ }
+
+ return MergingConfigurationProvider(configurationProviders.toPersistentList())
+ }
+}
diff --git a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/ConfigurationIntent.kt b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/ConfigurationIntent.kt
new file mode 100644
index 00000000..c4dfffab
--- /dev/null
+++ b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/ConfigurationIntent.kt
@@ -0,0 +1,7 @@
+package co.electriccoin.zcash.configuration.internal.intent
+
+internal object ConfigurationIntent {
+ const val EXTRA_STRING_KEY = "key" // $NON-NLS
+
+ const val EXTRA_STRING_VALUE = "value" // $NON-NLS
+}
diff --git a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationProvider.kt b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationProvider.kt
new file mode 100644
index 00000000..1ffa9f5b
--- /dev/null
+++ b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationProvider.kt
@@ -0,0 +1,30 @@
+package co.electriccoin.zcash.configuration.internal.intent
+
+import co.electriccoin.zcash.configuration.api.ConfigurationProvider
+import co.electriccoin.zcash.configuration.model.map.Configuration
+import co.electriccoin.zcash.configuration.model.map.StringConfiguration
+import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+internal object IntentConfigurationProvider : ConfigurationProvider {
+
+ private val configurationStateFlow = MutableStateFlow(StringConfiguration(persistentMapOf(), null))
+
+ override fun peekConfiguration() = configurationStateFlow.value
+
+ override fun getConfigurationFlow(): Flow = configurationStateFlow
+
+ override fun hintToRefresh() {
+ // Do nothing
+ }
+
+ /**
+ * Sets the configuration to the provided value.
+ *
+ * @see IntentConfigurationProvider
+ */
+ internal fun setConfiguration(configuration: StringConfiguration) {
+ configurationStateFlow.value = configuration
+ }
+}
diff --git a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationReceiver.kt b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationReceiver.kt
new file mode 100644
index 00000000..346a5c4e
--- /dev/null
+++ b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/internal/intent/IntentConfigurationReceiver.kt
@@ -0,0 +1,38 @@
+package co.electriccoin.zcash.configuration.internal.intent
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import co.electriccoin.zcash.configuration.model.map.StringConfiguration
+import kotlinx.collections.immutable.toPersistentMap
+import kotlinx.datetime.Clock
+
+class IntentConfigurationReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ intent?.defuse()?.let {
+ val key = it.getStringExtra(ConfigurationIntent.EXTRA_STRING_KEY)
+ val value = it.getStringExtra(ConfigurationIntent.EXTRA_STRING_VALUE)
+
+ if (null != key) {
+ val existingConfiguration = IntentConfigurationProvider.peekConfiguration().configurationMapping
+ val newConfiguration = if (null == value) {
+ existingConfiguration.remove(key)
+ } else {
+ existingConfiguration + (key to value)
+ }
+
+ IntentConfigurationProvider.setConfiguration(StringConfiguration(newConfiguration.toPersistentMap(), Clock.System.now()))
+ }
+ }
+ }
+}
+
+// https://issuetracker.google.com/issues/36927401
+private fun Intent.defuse(): Intent? {
+ return try {
+ extras?.containsKey(null)
+ this
+ } catch (@Suppress("SwallowedException", "TooGenericExceptionCaught") e: Exception) {
+ null
+ }
+}
diff --git a/docs/Architecture.md b/docs/Architecture.md
index 21aada9a..49fd3a3a 100644
--- a/docs/Architecture.md
+++ b/docs/Architecture.md
@@ -3,10 +3,10 @@ _Note: This document will continue to be updated as the app is implemented._
# Gradle
* Versions are declared in [gradle.properties](../gradle.properties). There's still enough inconsistency in how versions are handled in Gradle, that this is as close as we can get to a universal system. A version catalog is used for dependencies and is configured in [settings.gradle.kts](../settings.gradle.kts), but other versions like Gradle Plug-ins, the NDK version, Java version, and Android SDK versions don't fit into the version catalog model and are read directly from the properties
- * Much of the Gradle configuration lives in [build-convention](../build-convention/) to prevent repetitive configuration as additional modules are added to the project
+ * Much of the Gradle configuration lives in [build-conventions-secant](../build-conventions-secant/) to prevent repetitive configuration as additional modules are added to the project
* Build scripts are written in Kotlin, so that a single language is used across build and the app code bases
* Only Gradle, Google, and JetBrains plug-ins are included in the critical path. Third party plug-ins can be used, but they're outside the critical path. For example, the Gradle Versions Plugin could be removed and wouldn't negatively impact local building, testing, or releasing the app
- * Repository restrictions are enabled in [build-convention](../build-convention/settings.gradle.kts), [settings.gradle.kts](../settings.gradle.kts), and [build.gradle.kts](../build.gradle.kts) to reduce likelihood of pulling in an incorrect dependency. If adding a new dependency, these restrictions may need to be changed otherwise an error that the dependency cannot be found will be displayed
+ * Repository restrictions are enabled in [build-conventions-secant](../build-conventions-secant/settings.gradle.kts), [settings.gradle.kts](../settings.gradle.kts), and [build.gradle.kts](../build.gradle.kts) to reduce likelihood of pulling in an incorrect dependency. If adding a new dependency, these restrictions may need to be changed otherwise an error that the dependency cannot be found will be displayed
# Multiplatform
While this repository is for an Android application, efforts are made to give multiplatform flexibility in the future. Specific adaptions that are being made:
@@ -26,7 +26,9 @@ The logical components of the app are implemented as a number of Gradle modules.
* `app` — Compiles all the modules together into the final application. This module contains minimal actual code. Note that the Java package structure for this module is under `co.electriccoin.zcash.app` while the Android package name is `co.electriccoin.zcash`.
* `build-info-lib` — Collects information from the build environment (e.g. Git SHA, Git commit count) and compiles them into the application. Can also be used for injection of API keys or other secrets.
- * `configuration-api-lib` — Multiplatform interfaces for remote configuration.
+ * configuration
+ * `configuration-api-lib` — Multiplatform interfaces for remote configuration.
+ * `configuration-impl-android-lib` — Android-specific implementation for remote configuration storage.
* crash — For collecting and reporting exceptions and crashes
* `crash-lib` — Common crash collection logic for Kotlin and JVM. This is not fully-featured by itself, but the long-term plan is multiplatform support.
* `crash-android-lib` — Android-specific crash collection logic, built on top of the common and JVM implementation in `crash-lib`
@@ -57,6 +59,11 @@ The following diagram shows a rough depiction of dependencies between the module
sdkExtLib[[sdk-ext-lib]];
end
sdkLib[[sdk-lib]] --> sdkExtLib[[sdk-ext-lib]];
+ subgraph configuration
+ configurationApiLib[[configuration-api-lib]];
+ configurationImplAndroidLib[[configuration-impl-android-lib]];
+ end
+ configurationApiLib[[configuration-api-lib]] --> configurationImplAndroidLib[[configuration-impl-android-lib]];
subgraph preference
preferenceApiLib[[preference-api-lib]];
preferenceImplAndroidLib[[preference-impl-android-lib]];
@@ -82,6 +89,7 @@ The following diagram shows a rough depiction of dependencies between the module
spackleAndroidLib[[spackle-android-lib]];
end
spackleLib[[spackle-lib]] --> spackleAndroidLib[[spackle-android-lib]];
+ configuration --> ui[[ui]];
preference --> ui[[ui]];
sdk --> ui[[ui]];
spackle[[spackle]] --> ui[[ui]];
@@ -93,6 +101,15 @@ The following diagram shows a rough depiction of dependencies between the module
# Test Fixtures
Until the Kotlin adopts support for fixtures, fixtures live within the main source modules. These fixtures make it easy to write automated tests, as well as create Compose previews. Although these fixtures are compiled into the main application, they should be removed by R8 in release builds.
+# Debugging
+The application has support for remote configuration (aka feature toggles), which allows decoupling of releases from features being enabled.
+
+Debug builds allow for manual override of feature toggle entries, which can be set by command line invocations. These overrides last for the lifetime of the process, so they will reset if the process dies. Pressing the home button on Android does not necessarily stop the process, so the best way to ensure process death is to choose Force Stop in the Android settings.
+
+To set a configuration value manually, run the following shell command replacing `$SOME_KEY` and `$SOME_VALUE` with the key-value pair you'd like to set. The change will take effect immediately.
+
+`adb shell am broadcast -n co.electriccoin.zcash/co.electriccoin.zcash.configuration.internal.intent.IntentConfigurationReceiver --es key "$SOME_KEY" --es value "$NEW_VALUE"`
+
# Shared Resources
There are some app-wide resources that share a common namespace, and these should be documented here to make it easy to ensure there are no collisions.
diff --git a/docs/testing/manual_testing/Release build.md b/docs/testing/manual_testing/Release build.md
new file mode 100644
index 00000000..f7da324f
--- /dev/null
+++ b/docs/testing/manual_testing/Release build.md
@@ -0,0 +1,11 @@
+The app has both debug and release builds, as well as testnet and mainnet flavors. We deploy release mainnet builds to users, but often use debug mainnet or testnet builds for testing.
+
+# Ensure remote config debugging is disabled
+1. Download the release APK from CI server or from Google Play (you can also build it locally with `./gradlew assembleRelease` but fetching the version from CI or Google Play will be closer to the version users receive)
+1. In Android Studio, go to the Build menu and choose Analyze APK
+1. Navigate to the APK file
+1. Inspect the AndroidManifest and ensure that no `` entry for `IntentConfigurationReceiver` exists
+
+# Ensure logging is stripped
+
+# Ensure application is minified - The application is minified through R8 without obfuscation. This is especially important to improve runtime performance of the app that we release.
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1dd0e29b..961d1c4a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -310,6 +310,7 @@ includeBuild("build-conventions-secant")
include("app")
include("build-info-lib")
include("configuration-api-lib")
+include("configuration-impl-android-lib")
include("crash-lib")
include("crash-android-lib")
include("preference-api-lib")
diff --git a/ui-lib/build.gradle.kts b/ui-lib/build.gradle.kts
index acf8cfb8..b062fd03 100644
--- a/ui-lib/build.gradle.kts
+++ b/ui-lib/build.gradle.kts
@@ -73,6 +73,8 @@ dependencies {
implementation(libs.zxing)
implementation(projects.buildInfoLib)
+ implementation(projects.configurationApiLib)
+ implementation(projects.configurationImplAndroidLib)
implementation(projects.crashAndroidLib)
implementation(projects.preferenceApiLib)
implementation(projects.preferenceImplAndroidLib)
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntriesTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntriesTest.kt
new file mode 100644
index 00000000..e33a0612
--- /dev/null
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntriesTest.kt
@@ -0,0 +1,26 @@
+package co.electriccoin.zcash.ui.configuration
+
+import androidx.test.filters.SmallTest
+import co.electriccoin.zcash.configuration.model.entry.DefaultEntry
+import org.junit.Test
+import kotlin.reflect.full.memberProperties
+import kotlin.test.assertFalse
+
+class ConfigurationEntriesTest {
+ // This test is primary to prevent copy-paste errors in configuration keys
+ @SmallTest
+ @Test
+ fun keys_unique() {
+ val fieldValueSet = mutableSetOf()
+
+ ConfigurationEntries::class.memberProperties
+ .map { it.getter.call(ConfigurationEntries) }
+ .map { it as DefaultEntry<*> }
+ .map { it.key }
+ .forEach {
+ assertFalse(fieldValueSet.contains(it.key), "Duplicate key $it")
+
+ fieldValueSet.add(it.key)
+ }
+ }
+}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt
index 91433120..be7c5403 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt
@@ -1,24 +1,24 @@
package co.electriccoin.zcash.ui.preference
import androidx.test.filters.SmallTest
-import org.hamcrest.CoreMatchers.equalTo
-import org.hamcrest.MatcherAssert.assertThat
+import co.electriccoin.zcash.preference.model.entry.PreferenceDefault
import org.junit.Test
import kotlin.reflect.full.memberProperties
+import kotlin.test.assertFalse
class EncryptedPreferenceKeysTest {
// This test is primary to prevent copy-paste errors in preference keys
@SmallTest
@Test
- fun key_values_unique() {
+ fun unique_keys() {
val fieldValueSet = mutableSetOf()
EncryptedPreferenceKeys::class.memberProperties
.map { it.getter.call(EncryptedPreferenceKeys) }
- .map { it as PersistableWalletPreferenceDefault }
+ .map { it as PreferenceDefault<*> }
.map { it.key }
.forEach {
- assertThat("Duplicate key $it", fieldValueSet.contains(it.key), equalTo(false))
+ assertFalse(fieldValueSet.contains(it.key), "Duplicate key $it")
fieldValueSet.add(it.key)
}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/StandardPreferenceKeysTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/StandardPreferenceKeysTest.kt
new file mode 100644
index 00000000..b1467c3c
--- /dev/null
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/StandardPreferenceKeysTest.kt
@@ -0,0 +1,26 @@
+package co.electriccoin.zcash.ui.preference
+
+import androidx.test.filters.SmallTest
+import co.electriccoin.zcash.preference.model.entry.PreferenceDefault
+import org.junit.Test
+import kotlin.reflect.full.memberProperties
+import kotlin.test.assertFalse
+
+class StandardPreferenceKeysTest {
+ // This test is primary to prevent copy-paste errors in preference keys
+ @SmallTest
+ @Test
+ fun unique_keys() {
+ val fieldValueSet = mutableSetOf()
+
+ StandardPreferenceKeys::class.memberProperties
+ .map { it.getter.call(StandardPreferenceKeys) }
+ .map { it as PreferenceDefault<*> }
+ .map { it.key }
+ .forEach {
+ assertFalse(fieldValueSet.contains(it.key), "Duplicate key $it")
+
+ fieldValueSet.add(it.key)
+ }
+ }
+}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/about/AboutViewTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/about/AboutViewTest.kt
index 3ce7a7b0..31fa134b 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/about/AboutViewTest.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/about/AboutViewTest.kt
@@ -9,9 +9,11 @@ import androidx.test.filters.MediumTest
import co.electriccoin.zcash.build.gitSha
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
+import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo
import co.electriccoin.zcash.ui.screen.about.view.About
+import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals
import org.junit.Rule
@@ -50,9 +52,17 @@ class AboutViewTest {
assertEquals(1, testSetup.getOnBackCount())
}
- private fun newTestSetup() = TestSetup(composeTestRule, VersionInfoFixture.new())
+ private fun newTestSetup() = TestSetup(
+ composeTestRule,
+ VersionInfoFixture.new(),
+ ConfigInfoFixture.new()
+ )
- private class TestSetup(private val composeTestRule: ComposeContentTestRule, versionInfo: VersionInfo) {
+ private class TestSetup(
+ private val composeTestRule: ComposeContentTestRule,
+ versionInfo: VersionInfo,
+ configInfo: ConfigInfo
+ ) {
private val onBackCount = AtomicInteger(0)
@@ -64,7 +74,7 @@ class AboutViewTest {
init {
composeTestRule.setContent {
ZcashTheme {
- About(versionInfo = versionInfo) {
+ About(versionInfo = versionInfo, configInfo = configInfo) {
onBackCount.incrementAndGet()
}
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt
index f6d2ba16..8b35a96e 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt
@@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavHostController
import co.electriccoin.zcash.ui.common.BindCompLocalProvider
+import co.electriccoin.zcash.ui.configuration.RemoteConfig
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Override
@@ -41,6 +43,8 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
+ val homeViewModel by viewModels()
+
val walletViewModel by viewModels()
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -74,7 +78,8 @@ class MainActivity : ComponentActivity() {
}
}
- SecretState.Loading == walletViewModel.secretState.value
+ // Note this condition needs to be kept in sync with the condition in MainContent()
+ homeViewModel.configurationFlow.value == null || SecretState.Loading == walletViewModel.secretState.value
}
}
@@ -107,22 +112,35 @@ class MainActivity : ComponentActivity() {
@Composable
private fun MainContent() {
- when (val secretState = walletViewModel.secretState.collectAsStateWithLifecycle().value) {
- SecretState.Loading -> {
- // For now, keep displaying splash screen using condition above.
- // In the future, we might consider displaying something different here.
- }
- SecretState.None -> {
- WrapOnboarding()
- }
- is SecretState.NeedsBackup -> {
- WrapBackup(
- secretState.persistableWallet,
- onBackupComplete = { walletViewModel.persistBackupComplete() }
- )
- }
- is SecretState.Ready -> {
- Navigation()
+ val configuration = homeViewModel.configurationFlow.collectAsStateWithLifecycle().value
+ val secretState = walletViewModel.secretState.collectAsStateWithLifecycle().value
+
+ // Note this condition needs to be kept in sync with the condition in setupSplashScreen()
+ if (null == configuration || secretState == SecretState.Loading) {
+ // For now, keep displaying splash screen using condition above.
+ // In the future, we might consider displaying something different here.
+ } else {
+ // Note that the deeply nested child views will probably receive arguments derived from
+ // the configuration. The CompositionLocalProvider is helpful for passing the configuration
+ // to the "platform" layer, which is where the arguments will be derived from.
+ CompositionLocalProvider(RemoteConfig provides configuration) {
+ when (secretState) {
+ SecretState.None -> {
+ WrapOnboarding()
+ }
+ is SecretState.NeedsBackup -> {
+ WrapBackup(
+ secretState.persistableWallet,
+ onBackupComplete = { walletViewModel.persistBackupComplete() }
+ )
+ }
+ is SecretState.Ready -> {
+ Navigation()
+ }
+ else -> {
+ error("Unhandled secret state: $secretState")
+ }
+ }
}
}
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt
index 41f9144c..6b697502 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt
@@ -17,6 +17,8 @@ import co.electriccoin.zcash.ui.NavigationTargets.SEND
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
+import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
+import co.electriccoin.zcash.ui.configuration.RemoteConfig
import co.electriccoin.zcash.ui.screen.about.WrapAbout
import co.electriccoin.zcash.ui.screen.address.WrapWalletAddresses
import co.electriccoin.zcash.ui.screen.home.WrapHome
@@ -47,7 +49,9 @@ internal fun MainActivity.Navigation() {
goRequest = { navController.navigateJustOnce(REQUEST) }
)
- WrapCheckForUpdate()
+ if (ConfigurationEntries.IS_APP_UPDATE_CHECK_ENABLED.getValue(RemoteConfig.current)) {
+ WrapCheckForUpdate()
+ }
}
composable(PROFILE) {
WrapProfile(
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntries.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntries.kt
new file mode 100644
index 00000000..6161364a
--- /dev/null
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationEntries.kt
@@ -0,0 +1,14 @@
+package co.electriccoin.zcash.ui.configuration
+
+import co.electriccoin.zcash.configuration.model.entry.BooleanConfigurationEntry
+import co.electriccoin.zcash.configuration.model.entry.ConfigKey
+
+object ConfigurationEntries {
+ val IS_APP_UPDATE_CHECK_ENABLED = BooleanConfigurationEntry(ConfigKey("is_update_check_enabled"), true)
+
+ /*
+ * Disabled because we don't have the URI parser support in the SDK yet.
+ *
+ */
+ val IS_REQUEST_ZEC_ENABLED = BooleanConfigurationEntry(ConfigKey("is_update_check_enabled"), false)
+}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationLocal.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationLocal.kt
new file mode 100644
index 00000000..83aca34a
--- /dev/null
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/configuration/ConfigurationLocal.kt
@@ -0,0 +1,8 @@
+package co.electriccoin.zcash.ui.configuration
+
+import androidx.compose.runtime.compositionLocalOf
+import co.electriccoin.zcash.configuration.model.map.Configuration
+import co.electriccoin.zcash.configuration.model.map.StringConfiguration
+import kotlinx.collections.immutable.persistentMapOf
+
+val RemoteConfig = compositionLocalOf { StringConfiguration(persistentMapOf(), null) }
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/fixture/ConfigInfoFixture.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/fixture/ConfigInfoFixture.kt
new file mode 100644
index 00000000..90c52e3e
--- /dev/null
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/fixture/ConfigInfoFixture.kt
@@ -0,0 +1,15 @@
+package co.electriccoin.zcash.ui.fixture
+
+import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
+import kotlinx.datetime.Instant
+import kotlinx.datetime.toInstant
+
+// Magic Number doesn't matter here for hard-coded fixture values
+@Suppress("MagicNumber")
+object ConfigInfoFixture {
+ val UPDATED_AT = "2023-01-15T08:38:45.415Z".toInstant()
+
+ fun new(
+ updatedAt: Instant? = UPDATED_AT,
+ ) = ConfigInfo(updatedAt)
+}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt
index d0e6dc5c..e370687b 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt
@@ -4,10 +4,13 @@ package co.electriccoin.zcash.ui.screen.about
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.spackle.getPackageInfoCompat
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo
import co.electriccoin.zcash.ui.screen.about.view.About
+import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
@Composable
internal fun MainActivity.WrapAbout(
@@ -22,6 +25,12 @@ internal fun WrapAbout(
goBack: () -> Unit
) {
val packageInfo = activity.packageManager.getPackageInfoCompat(activity.packageName, 0L)
+ val configurationProvider = AndroidConfigurationFactory.getInstance(activity.applicationContext)
- About(VersionInfo.new(packageInfo), goBack)
+ About(VersionInfo.new(packageInfo), ConfigInfo.new(configurationProvider), goBack)
+
+ // Allows an implicit way to force configuration refresh by simply visiting the About screen
+ LaunchedEffect(key1 = true) {
+ AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh()
+ }
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/view/AboutView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/view/AboutView.kt
index dd76a234..da41b4b1 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/view/AboutView.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/view/AboutView.kt
@@ -27,15 +27,21 @@ import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
+import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo
+import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
@Preview
@Composable
fun AboutPreview() {
ZcashTheme(darkTheme = true) {
GradientSurface {
- About(versionInfo = VersionInfoFixture.new(), goBack = {})
+ About(
+ versionInfo = VersionInfoFixture.new(),
+ configInfo = ConfigInfoFixture.new(),
+ goBack = {}
+ )
}
}
}
@@ -44,6 +50,7 @@ fun AboutPreview() {
@Composable
fun About(
versionInfo: VersionInfo,
+ configInfo: ConfigInfo,
goBack: () -> Unit
) {
Scaffold(topBar = {
@@ -51,7 +58,8 @@ fun About(
}) { paddingValues ->
AboutMainContent(
paddingValues,
- versionInfo
+ versionInfo,
+ configInfo
)
}
}
@@ -75,7 +83,7 @@ private fun AboutTopAppBar(onBack: () -> Unit) {
}
@Composable
-fun AboutMainContent(paddingValues: PaddingValues, versionInfo: VersionInfo) {
+fun AboutMainContent(paddingValues: PaddingValues, versionInfo: VersionInfo, configInfo: ConfigInfo) {
Column(
Modifier
.verticalScroll(rememberScrollState())
@@ -96,6 +104,13 @@ fun AboutMainContent(paddingValues: PaddingValues, versionInfo: VersionInfo) {
Spacer(modifier = Modifier.height(24.dp))
+ configInfo.configurationUpdatedAt?.let { updatedAt ->
+ Header(stringResource(id = R.string.about_build_configuration))
+ Body(updatedAt.toString())
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
Header(stringResource(id = R.string.about_legal_header))
Body(stringResource(id = R.string.about_legal_info))
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/HomeViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/HomeViewModel.kt
index 8539abf3..3cec1c2a 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/HomeViewModel.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/HomeViewModel.kt
@@ -3,6 +3,8 @@ package co.electriccoin.zcash.ui.screen.home.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
+import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
+import co.electriccoin.zcash.configuration.model.map.Configuration
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
@@ -21,4 +23,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
val preferenceProvider = StandardPreferenceSingleton.getInstance(application)
emitAll(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED.observe(preferenceProvider))
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT.inWholeMilliseconds), null)
+
+ val configurationFlow: StateFlow = AndroidConfigurationFactory.getInstance(application).getConfigurationFlow()
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT.inWholeMilliseconds), null)
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/AppInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/AppInfo.kt
index 6acbae54..cdb6c367 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/AppInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/AppInfo.kt
@@ -4,7 +4,7 @@ import android.content.pm.PackageInfo
import co.electriccoin.zcash.build.gitSha
import co.electriccoin.zcash.spackle.versionCodeCompat
-class AppInfo(val versionName: String, val versionCode: Long, val gitSha: String) {
+data class AppInfo(val versionName: String, val versionCode: Long, val gitSha: String) {
fun toSupportString() = buildString {
appendLine("App version: $versionName ($versionCode) $gitSha")
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/ConfigInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/ConfigInfo.kt
new file mode 100644
index 00000000..f78de3ab
--- /dev/null
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/ConfigInfo.kt
@@ -0,0 +1,17 @@
+package co.electriccoin.zcash.ui.screen.support.model
+
+import co.electriccoin.zcash.configuration.api.ConfigurationProvider
+import kotlinx.datetime.Instant
+
+data class ConfigInfo(val configurationUpdatedAt: Instant?) {
+
+ fun toSupportString() = buildString {
+ appendLine("Configuration: $configurationUpdatedAt")
+ }
+
+ companion object {
+ fun new(configurationProvider: ConfigurationProvider) = ConfigInfo(
+ configurationProvider.peekConfiguration().updatedAt
+ )
+ }
+}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/CrashInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/CrashInfo.kt
index 52205172..87e35a9a 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/CrashInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/CrashInfo.kt
@@ -9,7 +9,7 @@ import co.electriccoin.zcash.spackle.io.listFilesSuspend
import kotlinx.datetime.Instant
import java.io.File
-class CrashInfo(val exceptionClassName: String, val isUncaught: Boolean, val timestamp: Instant) {
+data class CrashInfo(val exceptionClassName: String, val isUncaught: Boolean, val timestamp: Instant) {
fun toSupportString() = buildString {
appendLine("Exception")
appendLine(" Class name: $exceptionClassName")
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/DeviceInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/DeviceInfo.kt
index 8d33e3b6..dc9fa3e2 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/DeviceInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/DeviceInfo.kt
@@ -2,7 +2,7 @@ package co.electriccoin.zcash.ui.screen.support.model
import android.os.Build
-class DeviceInfo(val manufacturer: String, val device: String, val model: String) {
+data class DeviceInfo(val manufacturer: String, val device: String, val model: String) {
fun toSupportString() = buildString {
appendLine("Device: $manufacturer $device $model")
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/EnvironmentInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/EnvironmentInfo.kt
index 6aab7160..d94eeda6 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/EnvironmentInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/EnvironmentInfo.kt
@@ -5,7 +5,7 @@ import cash.z.ecc.android.sdk.model.MonetarySeparators
import co.electriccoin.zcash.global.StorageChecker
import java.util.Locale
-class EnvironmentInfo(val locale: Locale, val monetarySeparators: MonetarySeparators, val usableStorageMegabytes: Int) {
+data class EnvironmentInfo(val locale: Locale, val monetarySeparators: MonetarySeparators, val usableStorageMegabytes: Int) {
fun toSupportString() = buildString {
appendLine("Locale: ${locale.androidResName()}")
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/OperatingSystemInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/OperatingSystemInfo.kt
index f9ecacd3..70f14635 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/OperatingSystemInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/OperatingSystemInfo.kt
@@ -3,7 +3,7 @@ package co.electriccoin.zcash.ui.screen.support.model
import android.os.Build
import co.electriccoin.zcash.spackle.AndroidApiVersion
-class OperatingSystemInfo(val sdkInt: Int, val isPreview: Boolean) {
+data class OperatingSystemInfo(val sdkInt: Int, val isPreview: Boolean) {
fun toSupportString() = buildString {
if (isPreview) {
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/PermissionInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/PermissionInfo.kt
index 6ede30ae..b044df22 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/PermissionInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/PermissionInfo.kt
@@ -6,7 +6,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import co.electriccoin.zcash.spackle.getPackageInfoCompatSuspend
-class PermissionInfo(val permissionName: String, val permissionStatus: PermissionStatus) {
+data class PermissionInfo(val permissionName: String, val permissionStatus: PermissionStatus) {
fun toSupportString() = buildString {
appendLine("$permissionName $permissionStatus")
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt
index 236a2d1e..ac92e543 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt
@@ -1,7 +1,10 @@
package co.electriccoin.zcash.ui.screen.support.model
import android.content.Context
+import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.spackle.getPackageInfoCompatSuspend
+import kotlinx.collections.immutable.PersistentList
+import kotlinx.collections.immutable.toPersistentList
enum class SupportInfoType {
Time,
@@ -16,11 +19,12 @@ enum class SupportInfoType {
data class SupportInfo(
val timeInfo: TimeInfo,
val appInfo: AppInfo,
+ val configInfo: ConfigInfo,
val operatingSystemInfo: OperatingSystemInfo,
val deviceInfo: DeviceInfo,
val environmentInfo: EnvironmentInfo,
- val permissionInfo: List,
- val crashInfo: List
+ val permissionInfo: PersistentList,
+ val crashInfo: PersistentList
) {
// The set of enum values is to allow optional filtering of different types of information
@@ -62,15 +66,17 @@ data class SupportInfo(
suspend fun new(context: Context): SupportInfo {
val applicationContext = context.applicationContext
val packageInfo = applicationContext.packageManager.getPackageInfoCompatSuspend(context.packageName, 0L)
+ val configurationProvider = AndroidConfigurationFactory.getInstance(applicationContext)
return SupportInfo(
TimeInfo.new(packageInfo),
AppInfo.new(packageInfo),
+ ConfigInfo.new(configurationProvider),
OperatingSystemInfo.new(),
DeviceInfo.new(),
EnvironmentInfo.new(applicationContext),
- PermissionInfo.all(applicationContext),
- CrashInfo.all(context)
+ PermissionInfo.all(applicationContext).toPersistentList(),
+ CrashInfo.all(context).toPersistentList()
)
}
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/TimeInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/TimeInfo.kt
index 170db04f..cdf7b853 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/TimeInfo.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/TimeInfo.kt
@@ -9,7 +9,7 @@ import java.util.Date
import java.util.Locale
import kotlin.time.Duration.Companion.milliseconds
-class TimeInfo(
+data class TimeInfo(
val currentTime: Instant,
val rebootTime: Instant,
val installTime: Instant,
diff --git a/ui-lib/src/main/res/ui/about/values/strings.xml b/ui-lib/src/main/res/ui/about/values/strings.xml
index 338e292c..294ab4e3 100644
--- a/ui-lib/src/main/res/ui/about/values/strings.xml
+++ b/ui-lib/src/main/res/ui/about/values/strings.xml
@@ -5,6 +5,7 @@
Version%1$s (%2$d)Build
+ ConfigurationLegal