diff --git a/build-conventions-secant/src/main/kotlin/secant.detekt-conventions.gradle.kts b/build-conventions-secant/src/main/kotlin/secant.detekt-conventions.gradle.kts index 54a06488..d7d340e1 100644 --- a/build-conventions-secant/src/main/kotlin/secant.detekt-conventions.gradle.kts +++ b/build-conventions-secant/src/main/kotlin/secant.detekt-conventions.gradle.kts @@ -5,6 +5,10 @@ plugins { id("io.gitlab.arturbosch.detekt") } +dependencies { + detektPlugins("io.nlopez.compose.rules:detekt:${project.property("DETEKT_COMPOSE_RULES_VERSION")}") +} + tasks { register("detektAll", Detekt::class) { parallel = true 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 dcd8c30b..dd4e7d00 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.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -16,7 +17,7 @@ class MergingConfigurationProvider(private val configurationProviders: Persisten override fun getConfigurationFlow(): Flow { return if (configurationProviders.isEmpty()) { - flowOf(MergingConfiguration(emptyList().toPersistentList())) + flowOf(MergingConfiguration(persistentListOf())) } else { combine(configurationProviders.map { it.getConfigurationFlow() }) { configurations -> MergingConfiguration(configurations.toList().toPersistentList()) diff --git a/gradle.properties b/gradle.properties index e7a4534b..a427e74d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -102,6 +102,7 @@ ANDROID_NDK_VERSION=23.0.7599858 ANDROID_GRADLE_PLUGIN_VERSION=7.4.0 DETEKT_VERSION=1.22.0 +DETEKT_COMPOSE_RULES_VERSION=0.1.2 EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.15 FIREBASE_CRASHLYTICS_BUILD_TOOLS_VERSION=2.9.2 FLANK_VERSION=23.01.0 diff --git a/tools/detekt.yml b/tools/detekt.yml index eb06ebe4..d0fe0f6a 100644 --- a/tools/detekt.yml +++ b/tools/detekt.yml @@ -29,3 +29,7 @@ style: excludes: [ '**/*.kts' ] WildcardImport: active: false + +Compose: + ModifierMissing: + active: false diff --git a/ui-design-lib/build.gradle.kts b/ui-design-lib/build.gradle.kts index a91c3c1d..ad212fb9 100644 --- a/ui-design-lib/build.gradle.kts +++ b/ui-design-lib/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.core) + api(libs.kotlinx.immutable) implementation(projects.spackleAndroidLib) androidTestImplementation(libs.bundles.androidx.test) diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Button.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Button.kt index 0cb5d4f6..09934d77 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Button.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Button.kt @@ -162,12 +162,12 @@ fun DangerousButton( @Suppress("LongParameterList") @Composable fun TimedButton( + onClick: () -> Unit, + content: @Composable (RowScope.() -> Unit), modifier: Modifier = Modifier, duration: Duration = 5.seconds, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default, - onClick: () -> Unit, - content: @Composable RowScope.() -> Unit + coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default ) { LaunchedEffect(interactionSource) { var action: Job? = null diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/ChipGrid.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/ChipGrid.kt index 8c8746e9..3a159ed8 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/ChipGrid.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/ChipGrid.kt @@ -8,12 +8,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import co.electriccoin.zcash.spackle.model.Index +import kotlinx.collections.immutable.ImmutableList // Note: Row size should probably change for landscape layouts const val CHIP_GRID_ROW_SIZE = 3 @Composable -fun ChipGrid(wordList: List) { +fun ChipGrid(wordList: ImmutableList) { Column(Modifier.testTag(CommonTag.CHIP_LAYOUT)) { wordList.chunked(CHIP_GRID_ROW_SIZE).forEachIndexed { chunkIndex, chunk -> Row(Modifier.fillMaxWidth()) { diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Text.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Text.kt index c8f181f2..cc1526f1 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Text.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Text.kt @@ -46,8 +46,8 @@ fun Body( @Composable fun Small( text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign + textAlign: TextAlign, + modifier: Modifier = Modifier ) { Text( text = text, diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Color.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Color.kt index 7c75beac..581b2391 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Color.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Color.kt @@ -173,6 +173,7 @@ internal val LightExtendedColorPalette = ExtendedColors( reference = Light.reference ) +@Suppress("CompositionLocalAllowlist") internal val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( surfaceEnd = Color.Unspecified, diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Typography.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Typography.kt index 271036f3..45e89374 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Typography.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/internal/Typography.kt @@ -70,6 +70,7 @@ data class ExtendedTypography( val zecBalance: TextStyle ) +@Suppress("CompositionLocalAllowlist") val LocalExtendedTypography = staticCompositionLocalOf { ExtendedTypography( chipIndex = Typography.bodyLarge.copy( diff --git a/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewIntegrationTest.kt b/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewIntegrationTest.kt index eb29871d..73e29f58 100644 --- a/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewIntegrationTest.kt +++ b/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewIntegrationTest.kt @@ -34,7 +34,7 @@ class ScanViewIntegrationTest : UiTestPrerequisites() { val restorationTester = StateRestorationTester(composeTestRule) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(testSetup.getScanState(), ScanState.Permission) @@ -54,7 +54,7 @@ class ScanViewIntegrationTest : UiTestPrerequisites() { val restorationTester = StateRestorationTester(composeTestRule) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } val permissionPositiveButtonUiObject = getPermissionPositiveButtonUiObject() @@ -78,7 +78,7 @@ class ScanViewIntegrationTest : UiTestPrerequisites() { val restorationTester = StateRestorationTester(composeTestRule) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } testSetup.grantPermission() diff --git a/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewTestSetup.kt b/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewTestSetup.kt index 9e49f40b..950345ed 100644 --- a/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewTestSetup.kt +++ b/ui-integration-test/src/main/java/co/electriccoin/zcash/ui/integration/test/screen/scan/view/ScanViewTestSetup.kt @@ -46,7 +46,7 @@ class ScanViewTestSetup( } @Composable - fun getDefaultContent() { + fun DefaultContent() { Scan( snackbarHostState = SnackbarHostState(), onBack = {}, @@ -63,7 +63,7 @@ class ScanViewTestSetup( fun setDefaultContent() { composeTestRule.setContent { ZcashTheme { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/build.gradle.kts b/ui-lib/build.gradle.kts index ca8b7e8f..b1b1d331 100644 --- a/ui-lib/build.gradle.kts +++ b/ui-lib/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.guava) implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.immutable) implementation(libs.zcash.sdk) implementation(libs.zcash.sdk.incubator) implementation(libs.zcash.bip39) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/integration/BackupIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/integration/BackupIntegrationTest.kt index 46b33c6f..9cb6ef32 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/integration/BackupIntegrationTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/integration/BackupIntegrationTest.kt @@ -50,7 +50,7 @@ class BackupIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup(BackupStage.EducationOverview) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(BackupStage.EducationOverview, testSetup.getStage()) @@ -73,7 +73,7 @@ class BackupIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup(BackupStage.Test) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(BackupStage.Test, testSetup.getStage()) @@ -102,7 +102,7 @@ class BackupIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup(BackupStage.Test) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(0, testSetup.getOnChoicesCallbackCount()) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/view/BackupTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/view/BackupTestSetup.kt index a24acbd7..c94719ea 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/view/BackupTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/backup/view/BackupTestSetup.kt @@ -1,6 +1,5 @@ package co.electriccoin.zcash.ui.screen.backup.view -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.ComposeContentTestRule import cash.z.ecc.sdk.fixture.PersistableWalletFixture @@ -50,9 +49,9 @@ class BackupTestSetup( return state.current.value } - @SuppressLint("ComposableNaming") @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { ZcashTheme { BackupWallet( PersistableWalletFixture.new(), @@ -70,7 +69,7 @@ class BackupTestSetup( fun setDefaultContent() { composeTestRule.setContent { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/HomeTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/HomeTestSetup.kt index 6a19eb5b..68f97d38 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/HomeTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/HomeTestSetup.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot import co.electriccoin.zcash.ui.screen.home.view.Home +import kotlinx.collections.immutable.persistentListOf import java.util.concurrent.atomic.AtomicInteger class HomeTestSetup( @@ -54,10 +55,11 @@ class HomeTestSetup( } @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { Home( walletSnapshot, - transactionHistory = emptyList(), + transactionHistory = persistentListOf(), isKeepScreenOnDuringSync = false, isUpdateAvailable = false, goSettings = { @@ -86,7 +88,7 @@ class HomeTestSetup( fun setDefaultContent() { composeTestRule.setContent { ZcashTheme { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/integration/HomeViewIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/integration/HomeViewIntegrationTest.kt index 7c8ba485..b1926166 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/integration/HomeViewIntegrationTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/home/integration/HomeViewIntegrationTest.kt @@ -41,7 +41,7 @@ class HomeViewIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup(walletSnapshot) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertNotEquals(WalletSnapshotFixture.STATUS, testSetup.getWalletSnapshot().status) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/OnboardingTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/OnboardingTestSetup.kt index a1f7cb1a..8628a14e 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/OnboardingTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/OnboardingTestSetup.kt @@ -1,6 +1,5 @@ package co.electriccoin.zcash.ui.screen.onboarding -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.ComposeContentTestRule import co.electriccoin.zcash.ui.design.theme.ZcashTheme @@ -42,9 +41,9 @@ class OnboardingTestSetup( return onboardingState.current.value } - @SuppressLint("ComposableNaming") @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { ZcashTheme { Onboarding( isFullOnboardingEnabled, @@ -60,7 +59,7 @@ class OnboardingTestSetup( fun setDefaultContent() { composeTestRule.setContent { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/integration/OnboardingIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/integration/OnboardingIntegrationTest.kt index 0d18c331..da6a1cb9 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/integration/OnboardingIntegrationTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/onboarding/integration/OnboardingIntegrationTest.kt @@ -37,7 +37,7 @@ class OnboardingIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup(OnboardingStage.UnifiedAddresses) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(OnboardingStage.UnifiedAddresses, testSetup.getOnboardingStage()) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewSecuredScreenTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewSecuredScreenTest.kt index 4fd36a11..fe5f3995 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewSecuredScreenTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewSecuredScreenTest.kt @@ -10,6 +10,7 @@ import co.electriccoin.zcash.ui.common.LocalScreenSecurity import co.electriccoin.zcash.ui.common.ScreenSecurity import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.screen.restore.state.WordList +import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -40,7 +41,7 @@ class RestoreViewSecuredScreenTest : UiTestPrerequisites() { CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) { ZcashTheme { RestoreWallet( - Mnemonics.getCachedWords(Locale.ENGLISH.language).toSortedSet(), + Mnemonics.getCachedWords(Locale.ENGLISH.language).toPersistentSet(), WordList(emptyList()), onBack = { }, paste = { "" }, diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewTest.kt index fff994fc..89962161 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreViewTest.kt @@ -26,6 +26,7 @@ import co.electriccoin.zcash.ui.screen.restore.RestoreTag import co.electriccoin.zcash.ui.screen.restore.state.WordList import co.electriccoin.zcash.ui.test.getAppContext import co.electriccoin.zcash.ui.test.getStringResource +import kotlinx.collections.immutable.toPersistentSet import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Rule @@ -219,7 +220,7 @@ class RestoreViewTest : UiTestPrerequisites() { composeTestRule.setContent { ZcashTheme { RestoreWallet( - Mnemonics.getCachedWords(Locale.ENGLISH.language).toSortedSet(), + Mnemonics.getCachedWords(Locale.ENGLISH.language).toPersistentSet(), state, onBack = { onBackCount.incrementAndGet() diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/scan/view/ScanViewBasicTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/scan/view/ScanViewBasicTestSetup.kt index 5d70b643..1e8f3c36 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/scan/view/ScanViewBasicTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/scan/view/ScanViewBasicTestSetup.kt @@ -25,7 +25,8 @@ class ScanViewBasicTestSetup( } @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { Scan( snackbarHostState = SnackbarHostState(), onBack = { @@ -42,7 +43,7 @@ class ScanViewBasicTestSetup( fun setDefaultContent() { composeTestRule.setContent { ZcashTheme { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewIntegrationTest.kt index b52dedfb..9ac48147 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewIntegrationTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewIntegrationTest.kt @@ -26,7 +26,7 @@ class SupportViewIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup() restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } composeTestRule.onNodeWithText("I can haz cheezburger?").also { @@ -55,7 +55,7 @@ class SupportViewIntegrationTest : UiTestPrerequisites() { val testSetup = newTestSetup() restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } composeTestRule.onNodeWithText("I can haz cheezburger?").also { diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewTestSetup.kt index 5ee4fef0..26b55a31 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/view/SupportViewTestSetup.kt @@ -31,7 +31,8 @@ class SupportViewTestSetup(private val composeTestRule: ComposeContentTestRule) } @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { Support( SnackbarHostState(), onBack = { @@ -47,7 +48,7 @@ class SupportViewTestSetup(private val composeTestRule: ComposeContentTestRule) fun setDefaultContent() { composeTestRule.setContent { ZcashTheme { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/integration/UpdateViewIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/integration/UpdateViewIntegrationTest.kt index c619bec1..b622dcb8 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/integration/UpdateViewIntegrationTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/integration/UpdateViewIntegrationTest.kt @@ -36,7 +36,7 @@ class UpdateViewIntegrationTest { ) restorationTester.setContent { - testSetup.getDefaultContent() + testSetup.DefaultContent() } assertEquals(testSetup.getUpdateInfo().priority, AppUpdateChecker.Priority.HIGH) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewTestSetup.kt index 1b56bfb1..721bb0e6 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewTestSetup.kt @@ -44,7 +44,8 @@ class UpdateViewTestSetup( } @Composable - fun getDefaultContent() { + @Suppress("TestFunctionName") + fun DefaultContent() { Update( snackbarHostState = SnackbarHostState(), updateInfo = updateInfo, @@ -64,7 +65,7 @@ class UpdateViewTestSetup( fun setDefaultContent() { composeTestRule.setContent { ZcashTheme { - getDefaultContent() + DefaultContent() } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenBrightness.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenBrightness.kt index 625eb76d..e84e4b42 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenBrightness.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenBrightness.kt @@ -26,6 +26,7 @@ class ScreenBrightness { } } +@Suppress("CompositionLocalAllowlist") val LocalScreenBrightness = compositionLocalOf { ScreenBrightness() } @Composable diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenSecurity.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenSecurity.kt index e98450c6..598df750 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenSecurity.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenSecurity.kt @@ -26,6 +26,7 @@ class ScreenSecurity { } } +@Suppress("CompositionLocalAllowlist") val LocalScreenSecurity = compositionLocalOf { ScreenSecurity() } @Composable diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenTimeout.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenTimeout.kt index 08997b38..6edd337f 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenTimeout.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/ScreenTimeout.kt @@ -26,6 +26,7 @@ class ScreenTimeout { } } +@Suppress("CompositionLocalAllowlist") val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() } @Composable 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 index 83aca34a..bd4e7ac8 100644 --- 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 @@ -5,4 +5,5 @@ import co.electriccoin.zcash.configuration.model.map.Configuration import co.electriccoin.zcash.configuration.model.map.StringConfiguration import kotlinx.collections.immutable.persistentMapOf +@Suppress("CompositionLocalAllowlist") val RemoteConfig = compositionLocalOf { StringConfiguration(persistentMapOf(), null) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/state/TestChoices.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/state/TestChoices.kt index adf73563..0202ed66 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/state/TestChoices.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/state/TestChoices.kt @@ -1,16 +1,18 @@ package co.electriccoin.zcash.ui.screen.backup.state import co.electriccoin.zcash.spackle.model.Index +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toPersistentMap import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow class TestChoices(initial: Map = emptyMap()) { - private val mutableState = MutableStateFlow>(HashMap(initial)) + private val mutableState = MutableStateFlow>(initial.toPersistentMap()) - val current: StateFlow> = mutableState + val current: StateFlow> = mutableState fun set(map: Map) { - mutableState.value = HashMap(map) + mutableState.value = map.toPersistentMap() } companion object diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/BackupView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/BackupView.kt index e40e643f..60f3d656 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/BackupView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/BackupView.kt @@ -57,6 +57,8 @@ import co.electriccoin.zcash.ui.screen.backup.BackupTag import co.electriccoin.zcash.ui.screen.backup.model.BackupStage import co.electriccoin.zcash.ui.screen.backup.state.BackupState import co.electriccoin.zcash.ui.screen.backup.state.TestChoices +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList @Preview(device = Devices.PIXEL_4) @Composable @@ -145,7 +147,7 @@ fun BackupMainContent( is BackupStage.Test -> TestInProgress( selectedTestChoices = choices, onChoicesChanged = onChoicesChanged, - splitSeedPhrase = wallet.seedPhrase.split, + splitSeedPhrase = wallet.seedPhrase.split.toPersistentList(), backupState = backupState ) is BackupStage.Failure -> TestFailure() @@ -202,7 +204,7 @@ private fun SeedPhrase(persistableWallet: PersistableWallet) { .padding(vertical = ZcashTheme.paddings.padding) ) { Body(stringResource(R.string.new_wallet_3_body_1)) - ChipGrid(persistableWallet.seedPhrase.split) + ChipGrid(persistableWallet.seedPhrase.split.toPersistentList()) } } @@ -219,7 +221,7 @@ private data class TestChoice(val originalIndex: Index, val word: String) @Composable private fun TestInProgress( - splitSeedPhrase: List, + splitSeedPhrase: ImmutableList, selectedTestChoices: TestChoices, onChoicesChanged: ((choicesCount: Int) -> Unit)?, backupState: BackupState @@ -258,20 +260,21 @@ private fun TestInProgress( currentIndex, dropdownText = currentSelectedTestChoice[currentIndex] ?: "", - choices = testChoices.map { it.word }, + choices = testChoices.map { it.word }.toPersistentList(), + { + selectedTestChoices.set( + HashMap(currentSelectedTestChoice).apply { + this[currentIndex] = testChoices[it.value].word + } + ) + if (onChoicesChanged != null) { + onChoicesChanged(selectedTestChoices.current.value.size) + } + }, modifier = Modifier .weight(MINIMAL_WEIGHT) .testTag(BackupTag.DROPDOWN_CHIP) - ) { - selectedTestChoices.set( - HashMap(currentSelectedTestChoice).apply { - this[currentIndex] = testChoices[it.value].word - } - ) - if (onChoicesChanged != null) { - onChoicesChanged(selectedTestChoices.current.value.size) - } - } + ) } else { Chip( index = Index(chunkIndex * CHIP_GRID_ROW_SIZE + subIndex), @@ -388,25 +391,27 @@ private fun BackupTopAppBar( @Composable private fun CopySeedMenu(onCopyToClipboard: () -> Unit) { - var expanded by rememberSaveable { mutableStateOf(false) } - IconButton(onClick = { expanded = true }) { - Icon( - Icons.Default.MoreVert, - contentDescription = stringResource(R.string.new_wallet_toolbar_more_button_content_description) - ) - } + Column { + var expanded by rememberSaveable { mutableStateOf(false) } + IconButton(onClick = { expanded = true }) { + Icon( + Icons.Default.MoreVert, + contentDescription = stringResource(R.string.new_wallet_toolbar_more_button_content_description) + ) + } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.new_wallet_3_button_copy)) }, - onClick = { - expanded = false - onCopyToClipboard() - } - ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.new_wallet_3_button_copy)) }, + onClick = { + expanded = false + onCopyToClipboard() + } + ) + } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/ChipDropDown.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/ChipDropDown.kt index 9f0dad3b..c6addaa9 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/ChipDropDown.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/backup/view/ChipDropDown.kt @@ -29,6 +29,7 @@ import co.electriccoin.zcash.spackle.model.Index import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.screen.backup.BackupTag +import kotlinx.collections.immutable.ImmutableList /** * @param chipIndex The index of the chip, which is displayed to the user. @@ -36,17 +37,15 @@ import co.electriccoin.zcash.ui.screen.backup.BackupTag * @param choices Item choices to display in the open drop down menu. Positional index is important. * @param onChoiceSelected Callback with the positional index of the item the user selected from [choices]. */ - @Composable fun ChipDropDown( chipIndex: Index, dropdownText: String, - choices: List, - modifier: Modifier = Modifier, - onChoiceSelected: (Index) -> Unit + choices: ImmutableList, + onChoiceSelected: (Index) -> Unit, + modifier: Modifier = Modifier ) { var expanded by remember { mutableStateOf(false) } - Surface( modifier = modifier.then( Modifier @@ -71,7 +70,7 @@ fun ChipDropDown( style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSecondary ) - Spacer(modifier = modifier.fillMaxWidth(MINIMAL_WEIGHT)) + Spacer(modifier = Modifier.fillMaxWidth(MINIMAL_WEIGHT)) Icon( imageVector = Icons.Filled.ArrowDropDown, contentDescription = null, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/view/HomeView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/view/HomeView.kt index 0c6062e3..02bb1c4c 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/view/HomeView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/view/HomeView.kt @@ -73,6 +73,8 @@ import co.electriccoin.zcash.ui.screen.home.HomeTag import co.electriccoin.zcash.ui.screen.home.model.CommonTransaction import co.electriccoin.zcash.ui.screen.home.model.WalletDisplayValues import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope @Preview @@ -82,7 +84,7 @@ fun ComposablePreview() { GradientSurface { Home( walletSnapshot = WalletSnapshotFixture.new(), - transactionHistory = emptyList(), + transactionHistory = persistentListOf(), isUpdateAvailable = false, isKeepScreenOnDuringSync = false, isDebugMenuEnabled = false, @@ -103,7 +105,7 @@ fun ComposablePreview() { @Composable fun Home( walletSnapshot: WalletSnapshot, - transactionHistory: List, + transactionHistory: ImmutableList, isUpdateAvailable: Boolean, isKeepScreenOnDuringSync: Boolean?, isDebugMenuEnabled: Boolean, @@ -181,39 +183,41 @@ private fun HomeTopAppBar( private fun DebugMenu( resetSdk: () -> Unit ) { - var expanded by rememberSaveable { mutableStateOf(false) } - IconButton(onClick = { expanded = true }) { - Icon(Icons.Default.MoreVert, contentDescription = null) - } + Column { + var expanded by rememberSaveable { mutableStateOf(false) } + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = null) + } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem( - text = { Text("Throw Uncaught Exception") }, - onClick = { - // Supposed to be generic, for manual debugging only - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("Manually crashed from debug menu") - } - ) - DropdownMenuItem( - text = { Text("Report Caught Exception") }, - onClick = { - // Eventually this shouldn't rely on the Android implementation, but rather an expect/actual - // should be used at the crash API level. - GlobalCrashReporter.reportCaughtException(RuntimeException("Manually caught exception from debug menu")) - expanded = false - } - ) - DropdownMenuItem( - text = { Text("Reset SDK") }, - onClick = { - resetSdk() - expanded = false - } - ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text("Throw Uncaught Exception") }, + onClick = { + // Supposed to be generic, for manual debugging only + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Manually crashed from debug menu") + } + ) + DropdownMenuItem( + text = { Text("Report Caught Exception") }, + onClick = { + // Eventually this shouldn't rely on the Android implementation, but rather an expect/actual + // should be used at the crash API level. + GlobalCrashReporter.reportCaughtException(RuntimeException("Manually caught exception from debug menu")) + expanded = false + } + ) + DropdownMenuItem( + text = { Text("Reset SDK") }, + onClick = { + resetSdk() + expanded = false + } + ) + } } } @@ -275,7 +279,7 @@ private fun HomeDrawer( private fun HomeMainContent( paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, - transactionHistory: List, + transactionHistory: ImmutableList, isUpdateAvailable: Boolean, isKeepScreenOnDuringSync: Boolean?, goReceive: () -> Unit, @@ -406,7 +410,7 @@ private fun Status( @Composable @Suppress("MagicNumber") -private fun History(transactionHistory: List) { +private fun History(transactionHistory: ImmutableList) { if (transactionHistory.isEmpty()) { return } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/WalletViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/WalletViewModel.kt index 8991e358..686418aa 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/WalletViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/viewmodel/WalletViewModel.kt @@ -32,6 +32,9 @@ import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import co.electriccoin.zcash.ui.screen.home.model.CommonTransaction import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -151,10 +154,10 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) // This is not the right API, because the transaction list could be very long and might need UI filtering @OptIn(ExperimentalCoroutinesApi::class) - val transactionSnapshot: StateFlow> = synchronizer + val transactionSnapshot: StateFlow> = synchronizer .flatMapLatest { if (null == it) { - flowOf(emptyList()) + flowOf(persistentListOf()) } else { it.toTransactions() } @@ -162,7 +165,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) .stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - emptyList() + persistentListOf() ) val addresses: StateFlow = synchronizer @@ -360,7 +363,7 @@ private fun Synchronizer.toWalletSnapshot() = ) } -private fun Synchronizer.toTransactions() = +private fun Synchronizer.toTransactions(): Flow> = combine( clearedTransactions.distinctUntilChanged(), pendingTransactions.distinctUntilChanged() @@ -372,5 +375,5 @@ private fun Synchronizer.toTransactions() = buildList { addAll(cleared.map { CommonTransaction.Overview(it) }) addAll(pending.map { CommonTransaction.Pending(it) }) - } + }.toPersistentList() } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/view/OnboardingView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/view/OnboardingView.kt index 8ab73d11..80bff2aa 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/view/OnboardingView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/view/OnboardingView.kt @@ -137,19 +137,21 @@ private fun OnboardingTopAppBar( @Composable private fun DebugMenu(onFixtureWallet: () -> Unit) { - var expanded by rememberSaveable { mutableStateOf(false) } - IconButton(onClick = { expanded = true }) { - Icon(Icons.Default.MoreVert, contentDescription = null) - } + Column { + var expanded by rememberSaveable { mutableStateOf(false) } + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = null) + } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem( - text = { Text("Import wallet with fixture seed phrase") }, - onClick = onFixtureWallet - ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text("Import wallet with fixture seed phrase") }, + onClick = onFixtureWallet + ) + } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt index 59cefded..e016dd5a 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/view/ReceiveView.kt @@ -109,23 +109,24 @@ private fun ReceiveContents( } @Composable -private fun QrCode(data: String, size: Dp, modifier: Modifier) { - BrightenScreen() - DisableScreenTimeout() - val sizePixels = with(LocalDensity.current) { size.toPx() }.roundToInt() +private fun QrCode(data: String, size: Dp, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + BrightenScreen() + DisableScreenTimeout() + val sizePixels = with(LocalDensity.current) { size.toPx() }.roundToInt() - // In the future, use actual/expect to switch QR code generator implementations for multiplatform + // In the future, use actual/expect to switch QR code generator implementations for multiplatform - // Note that our implementation has an extra array copy to BooleanArray, which is a cross-platform - // representation. This should have minimal performance impact since the QR code is relatively - // small and we only generate QR codes infrequently. + // Note that our implementation has an extra array copy to BooleanArray, which is a cross-platform + // representation. This should have minimal performance impact since the QR code is relatively + // small and we only generate QR codes infrequently. - val qrCodePixelArray = JvmQrCodeGenerator.generate(data, sizePixels) - val qrCodeImage = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, sizePixels) + val qrCodePixelArray = JvmQrCodeGenerator.generate(data, sizePixels) + val qrCodeImage = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, sizePixels) - Image( - bitmap = qrCodeImage, - contentDescription = stringResource(R.string.receive_qr_code_content_description), - modifier = modifier - ) + Image( + bitmap = qrCodeImage, + contentDescription = stringResource(R.string.receive_qr_code_content_description) + ) + } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/state/WordList.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/state/WordList.kt index 2207a139..5b6e11c9 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/state/WordList.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/state/WordList.kt @@ -1,21 +1,23 @@ package co.electriccoin.zcash.ui.screen.restore.state import cash.z.ecc.sdk.model.SeedPhraseValidation +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map class WordList(initial: List = emptyList()) { - private val mutableState = MutableStateFlow(initial) + private val mutableState: MutableStateFlow> = MutableStateFlow(initial.toPersistentList()) - val current: StateFlow> = mutableState + val current: StateFlow> = mutableState fun set(list: List) { - mutableState.value = ArrayList(list) + mutableState.value = list.toPersistentList() } fun append(words: List) { - mutableState.value = ArrayList(current.value) + words + mutableState.value = (current.value + words).toPersistentList() } // Custom toString to prevent leaking word list diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreView.kt index 2c171a44..f05ef340 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/view/RestoreView.kt @@ -70,6 +70,9 @@ import co.electriccoin.zcash.ui.screen.restore.RestoreTag import co.electriccoin.zcash.ui.screen.restore.model.ParseResult import co.electriccoin.zcash.ui.screen.restore.state.WordList import co.electriccoin.zcash.ui.screen.restore.state.wordValidation +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentHashSetOf import kotlinx.coroutines.launch @Preview("Restore Wallet") @@ -78,7 +81,7 @@ fun PreviewRestore() { ZcashTheme(darkTheme = true) { GradientSurface { RestoreWallet( - completeWordList = setOf( + completeWordList = persistentHashSetOf( "abandon", "ability", "able", @@ -113,7 +116,7 @@ fun PreviewRestoreComplete() { @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun RestoreWallet( - completeWordList: Set, + completeWordList: ImmutableSet, userWordList: WordList, onBack: () -> Unit, paste: () -> String?, @@ -131,16 +134,16 @@ fun RestoreWallet( }, bottomBar = { Column(Modifier.verticalScroll(rememberScrollState())) { Warn(parseResult) - Autocomplete(parseResult = parseResult) { + Autocomplete(parseResult = parseResult, { textState = "" userWordList.append(listOf(it)) focusRequester.requestFocus() - } + }) NextWordTextField( - modifier = Modifier.focusRequester(focusRequester), parseResult = parseResult, text = textState, - setText = { textState = it } + setText = { textState = it }, + modifier = Modifier.focusRequester(focusRequester) ) } }) { paddingValues -> @@ -236,7 +239,7 @@ private fun RestoreMainContent( @Composable private fun ChipGridWithText( - userWordList: List + userWordList: ImmutableList ) { Column( Modifier @@ -268,10 +271,10 @@ private fun ChipGridWithText( @Composable @OptIn(ExperimentalMaterial3Api::class) private fun NextWordTextField( - modifier: Modifier = Modifier, parseResult: ParseResult, text: String, - setText: (String) -> Unit + setText: (String) -> Unit, + modifier: Modifier = Modifier ) { /* * Treat the user input as a password, but disable the transformation to obscure input. @@ -285,7 +288,7 @@ private fun NextWordTextField( shadowElevation = 8.dp ) { TextField( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(4.dp) .testTag(RestoreTag.SEED_WORD_TEXT_FIELD), @@ -312,9 +315,9 @@ private fun NextWordTextField( @Composable private fun Autocomplete( - modifier: Modifier = Modifier, parseResult: ParseResult, - onSuggestionSelected: (String) -> Unit + onSuggestionSelected: (String) -> Unit, + modifier: Modifier = Modifier ) { val (isHighlight, suggestions) = when (parseResult) { is ParseResult.Autocomplete -> { @@ -336,6 +339,7 @@ private fun Autocomplete( modifier } + @Suppress("ModifierReused") LazyRow(highlightModifier.testTag(RestoreTag.AUTOCOMPLETE_LAYOUT)) { items(it) { Chip( diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt index 37fa0b7d..96f525fc 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt @@ -8,6 +8,8 @@ import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.sdk.ext.collectWith import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.screen.restore.state.WordList +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.WhileSubscribed @@ -15,7 +17,6 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import java.util.Locale -import java.util.TreeSet class RestoreViewModel(application: Application, savedStateHandle: SavedStateHandle) : AndroidViewModel(application) { @@ -29,7 +30,7 @@ class RestoreViewModel(application: Application, savedStateHandle: SavedStateHan Mnemonics.getCachedWords(Locale.ENGLISH.language) } - emit(CompleteWordSetState.Loaded(TreeSet(completeWordList))) + emit(CompleteWordSetState.Loaded(completeWordList.toPersistentSet())) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), @@ -54,7 +55,7 @@ class RestoreViewModel(application: Application, savedStateHandle: SavedStateHan // viewModelScope is constructed with Dispatchers.Main.immediate, so this will // update the save state as soon as a change occurs. userWordList.current.collectWith(viewModelScope) { - savedStateHandle.set(KEY_WORD_LIST, it) + savedStateHandle[KEY_WORD_LIST] = ArrayList(it) } } @@ -65,5 +66,5 @@ class RestoreViewModel(application: Application, savedStateHandle: SavedStateHan sealed class CompleteWordSetState { object Loading : CompleteWordSetState() - data class Loaded(val list: Set) : CompleteWordSetState() + data class Loaded(val list: ImmutableSet) : CompleteWordSetState() } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seed/view/SeedView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seed/view/SeedView.kt index ed494c4e..07b1ff33 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seed/view/SeedView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seed/view/SeedView.kt @@ -26,6 +26,7 @@ import co.electriccoin.zcash.ui.design.component.ChipGrid import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.TertiaryButton import co.electriccoin.zcash.ui.design.theme.ZcashTheme +import kotlinx.collections.immutable.toPersistentList @Preview("Seed") @Composable @@ -95,7 +96,7 @@ private fun SeedMainContent( ) { Body(stringResource(R.string.seed_body)) - ChipGrid(persistableWallet.seedPhrase.split) + ChipGrid(persistableWallet.seedPhrase.split.toPersistentList()) TertiaryButton(onClick = onCopyToClipboard, text = stringResource(R.string.seed_copy)) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt index 2549f942..283ee69b 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt @@ -72,9 +72,9 @@ fun PreviewSend() { @Composable fun Send( mySpendableBalance: Zatoshi, - pressAndHoldInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, goBack: () -> Unit, - onCreateAndSend: (ZecSend) -> Unit + onCreateAndSend: (ZecSend) -> Unit, + pressAndHoldInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { // For now, we're avoiding sub-navigation to keep the navigation logic simple. But this might // change once deep-linking support is added. It depends on whether deep linking should do one of: @@ -272,9 +272,10 @@ private fun Confirmation( TimedButton( onClick = onConfirmation, + { + Text(text = stringResource(id = R.string.send_confirm)) + }, interactionSource = pressAndHoldInteractionSource - ) { - Text(text = stringResource(id = R.string.send_confirm)) - } + ) } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt index a10829e9..62eccede 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt @@ -122,22 +122,24 @@ private fun SettingsTopAppBar( private fun TroubleshootingMenu( onRescanWallet: () -> Unit ) { - var expanded by rememberSaveable { mutableStateOf(false) } - IconButton(onClick = { expanded = true }) { - Icon(Icons.Default.MoreVert, contentDescription = stringResource(id = R.string.settings_overflow_content_description)) - } + Column { + var expanded by rememberSaveable { mutableStateOf(false) } + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = stringResource(id = R.string.settings_overflow_content_description)) + } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(id = R.string.settings_rescan)) }, - onClick = { - onRescanWallet() - expanded = false - } - ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(id = R.string.settings_rescan)) }, + onClick = { + onRescanWallet() + expanded = false + } + ) + } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/view/NotEnoughSpaceView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/view/NotEnoughSpaceView.kt index 0286d647..faf0fae2 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/view/NotEnoughSpaceView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/view/NotEnoughSpaceView.kt @@ -48,9 +48,9 @@ fun NotEnoughSpaceView(storageSpaceRequiredGigabytes: Int, spaceRequiredToContin ) Spacer(Modifier.height(64.dp)) Small( - modifier = Modifier.fillMaxWidth(), text = stringResource(id = R.string.space_required_to_continue, spaceRequiredToContinueMegabytes), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() ) } }