[#584 ]Adopt Twitter's Compose Detekt rules
Twitter had a set of Compose rules, which have been forked into Detekt, Ktlint, and Android Lint implementations. I see ktlint as mostly being formatting, Detekt being more for multiplatform static analysis, and Android Lint for Android-specific issues. Android lint is slow, so I didn’t want to use it as the first choice. I went with the Detekt implementation which can be useful if we leverage multiplatform for our Composables in the future.
This commit is contained in:
parent
6d01f210fe
commit
4acd5d3593
|
@ -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
|
||||
|
|
|
@ -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<Configuration> {
|
||||
return if (configurationProviders.isEmpty()) {
|
||||
flowOf(MergingConfiguration(emptyList<Configuration>().toPersistentList()))
|
||||
flowOf(MergingConfiguration(persistentListOf<Configuration>()))
|
||||
} else {
|
||||
combine(configurationProviders.map { it.getConfigurationFlow() }) { configurations ->
|
||||
MergingConfiguration(configurations.toList().toPersistentList())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,3 +29,7 @@ style:
|
|||
excludes: [ '**/*.kts' ]
|
||||
WildcardImport:
|
||||
active: false
|
||||
|
||||
Compose:
|
||||
ModifierMissing:
|
||||
active: false
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<String>) {
|
||||
fun ChipGrid(wordList: ImmutableList<String>) {
|
||||
Column(Modifier.testTag(CommonTag.CHIP_LAYOUT)) {
|
||||
wordList.chunked(CHIP_GRID_ROW_SIZE).forEachIndexed { chunkIndex, chunk ->
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
|
|
|
@ -46,8 +46,8 @@ fun Body(
|
|||
@Composable
|
||||
fun Small(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textAlign: TextAlign
|
||||
textAlign: TextAlign,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
|
|
|
@ -173,6 +173,7 @@ internal val LightExtendedColorPalette = ExtendedColors(
|
|||
reference = Light.reference
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
internal val LocalExtendedColors = staticCompositionLocalOf {
|
||||
ExtendedColors(
|
||||
surfaceEnd = Color.Unspecified,
|
||||
|
|
|
@ -70,6 +70,7 @@ data class ExtendedTypography(
|
|||
val zecBalance: TextStyle
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
val LocalExtendedTypography = staticCompositionLocalOf {
|
||||
ExtendedTypography(
|
||||
chipIndex = Typography.bodyLarge.copy(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class HomeViewIntegrationTest : UiTestPrerequisites() {
|
|||
val testSetup = newTestSetup(walletSnapshot)
|
||||
|
||||
restorationTester.setContent {
|
||||
testSetup.getDefaultContent()
|
||||
testSetup.DefaultContent()
|
||||
}
|
||||
|
||||
assertNotEquals(WalletSnapshotFixture.STATUS, testSetup.getWalletSnapshot().status)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class OnboardingIntegrationTest : UiTestPrerequisites() {
|
|||
val testSetup = newTestSetup(OnboardingStage.UnifiedAddresses)
|
||||
|
||||
restorationTester.setContent {
|
||||
testSetup.getDefaultContent()
|
||||
testSetup.DefaultContent()
|
||||
}
|
||||
|
||||
assertEquals(OnboardingStage.UnifiedAddresses, testSetup.getOnboardingStage())
|
||||
|
|
|
@ -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 = { "" },
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class UpdateViewIntegrationTest {
|
|||
)
|
||||
|
||||
restorationTester.setContent {
|
||||
testSetup.getDefaultContent()
|
||||
testSetup.DefaultContent()
|
||||
}
|
||||
|
||||
assertEquals(testSetup.getUpdateInfo().priority, AppUpdateChecker.Priority.HIGH)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ class ScreenBrightness {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
val LocalScreenBrightness = compositionLocalOf { ScreenBrightness() }
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -26,6 +26,7 @@ class ScreenSecurity {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
val LocalScreenSecurity = compositionLocalOf { ScreenSecurity() }
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -26,6 +26,7 @@ class ScreenTimeout {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -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<Configuration> { StringConfiguration(persistentMapOf(), null) }
|
||||
|
|
|
@ -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<Index, String?> = emptyMap()) {
|
||||
private val mutableState = MutableStateFlow<Map<Index, String?>>(HashMap(initial))
|
||||
private val mutableState = MutableStateFlow<ImmutableMap<Index, String?>>(initial.toPersistentMap())
|
||||
|
||||
val current: StateFlow<Map<Index, String?>> = mutableState
|
||||
val current: StateFlow<ImmutableMap<Index, String?>> = mutableState
|
||||
|
||||
fun set(map: Map<Index, String?>) {
|
||||
mutableState.value = HashMap(map)
|
||||
mutableState.value = map.toPersistentMap()
|
||||
}
|
||||
|
||||
companion object
|
||||
|
|
|
@ -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<String>,
|
||||
splitSeedPhrase: ImmutableList<String>,
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
onChoiceSelected: (Index) -> Unit
|
||||
choices: ImmutableList<String>,
|
||||
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,
|
||||
|
|
|
@ -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<CommonTransaction>,
|
||||
transactionHistory: ImmutableList<CommonTransaction>,
|
||||
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<CommonTransaction>,
|
||||
transactionHistory: ImmutableList<CommonTransaction>,
|
||||
isUpdateAvailable: Boolean,
|
||||
isKeepScreenOnDuringSync: Boolean?,
|
||||
goReceive: () -> Unit,
|
||||
|
@ -406,7 +410,7 @@ private fun Status(
|
|||
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
private fun History(transactionHistory: List<CommonTransaction>) {
|
||||
private fun History(transactionHistory: ImmutableList<CommonTransaction>) {
|
||||
if (transactionHistory.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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<List<CommonTransaction>> = synchronizer
|
||||
val transactionSnapshot: StateFlow<ImmutableList<CommonTransaction>> = 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<WalletAddresses?> = synchronizer
|
||||
|
@ -360,7 +363,7 @@ private fun Synchronizer.toWalletSnapshot() =
|
|||
)
|
||||
}
|
||||
|
||||
private fun Synchronizer.toTransactions() =
|
||||
private fun Synchronizer.toTransactions(): Flow<ImmutableList<CommonTransaction>> =
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> = emptyList()) {
|
||||
private val mutableState = MutableStateFlow(initial)
|
||||
private val mutableState: MutableStateFlow<ImmutableList<String>> = MutableStateFlow(initial.toPersistentList())
|
||||
|
||||
val current: StateFlow<List<String>> = mutableState
|
||||
val current: StateFlow<ImmutableList<String>> = mutableState
|
||||
|
||||
fun set(list: List<String>) {
|
||||
mutableState.value = ArrayList(list)
|
||||
mutableState.value = list.toPersistentList()
|
||||
}
|
||||
|
||||
fun append(words: List<String>) {
|
||||
mutableState.value = ArrayList(current.value) + words
|
||||
mutableState.value = (current.value + words).toPersistentList()
|
||||
}
|
||||
|
||||
// Custom toString to prevent leaking word list
|
||||
|
|
|
@ -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<String>,
|
||||
completeWordList: ImmutableSet<String>,
|
||||
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<String>
|
||||
userWordList: ImmutableList<String>
|
||||
) {
|
||||
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(
|
||||
|
|
|
@ -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<String>) : CompleteWordSetState()
|
||||
data class Loaded(val list: ImmutableSet<String>) : CompleteWordSetState()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue