Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## Fixed
- Linked devices: recovery words you type are kept in memory only, so anything typed is cleared if Android closes the app in the background
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -74,7 +75,7 @@ internal fun LinkedRecoveryWords(
.sorted(),
)
}
var answers by rememberSaveable { mutableStateOf(List(CHECKED_WORDS) { "" }) }
var answers by remember { mutableStateOf(List(CHECKED_WORDS) { "" }) }
val matches = positions.zip(answers).all { (i, answer) -> answer.trim().equals(list[i], ignoreCase = true) }
val typed = answers.all { it.isNotBlank() }
SyncStepHeader(icon = Icons.Outlined.Spellcheck, title = stringResource(R.string.linked_check_title))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ internal fun LinkedSetup(

Setup.RESTORE_SERVER -> {
var url by rememberSaveable { mutableStateOf("") }
var words by rememberSaveable { mutableStateOf("") }
var words by remember { mutableStateOf("") }
SyncStepHeader(icon = Icons.Outlined.Key, title = stringResource(R.string.linked_restore_title))
ServerField(url) { url = it }
WordsField(words) { words = it }
Expand All @@ -247,7 +247,7 @@ internal fun LinkedSetup(
}

Setup.RESTORE_WEBDAV -> {
var words by rememberSaveable { mutableStateOf("") }
var words by remember { mutableStateOf("") }
WebDavForm(
busy,
stringResource(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.aedev.flow.ui.screens.sync.linked

import android.app.Application
import android.content.Context
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.aedev.flow.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config

/** Asking for some of the recovery words back. */
@RunWith(AndroidJUnit4::class)
@Config(sdk = [34], application = Application::class, qualifiers = "w360dp-h1600dp")
class LinkedRecoveryWordsTest {
@get:Rule
val rule = createComposeRule()

private val context: Context get() = ApplicationProvider.getApplicationContext()

private fun text(id: Int) = context.getString(id)

@Test
fun the_words_typed_back_are_not_kept_in_saved_state() {
val restoration = StateRestorationTester(rule)
restoration.setContent { LinkedRecoveryWords(List(24) { "word" }.joinToString(" "), busy = false, onSaved = {}) }
rule.onNodeWithText(text(R.string.linked_words_saved)).performClick()
val fields = rule.onAllNodes(hasSetTextAction())
fields.fetchSemanticsNodes().indices.forEach { fields[it].performTextInput("word") }
rule.onNodeWithText(text(R.string.linked_confirm)).assertIsEnabled()

restoration.emulateSavedInstanceStateRestore()

rule.onNodeWithText("word").assertDoesNotExist()
rule.onNodeWithText(text(R.string.linked_confirm)).assertIsNotEnabled()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package io.github.aedev.flow.ui.screens.sync.linked

import android.app.Application
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
Expand Down Expand Up @@ -36,24 +38,26 @@ class LinkedSetupTest {
private val created = ArrayList<List<String>>()
private val reset = ArrayList<List<String>>()

private fun show() =
rule.setContent {
var error by remember { mutableStateOf<LinkedError?>(null) }
LinkedSetup(
hostOnly = false,
busy = false,
error = error,
onCreate = { _, _ -> error = LinkedError.SERVER_HAS_GROUP },
onResetAndCreate = { url, token -> reset += listOf(url, token) },
onCreateOnWebDav = { url, user, password -> created += listOf(url, user, password) },
onCreateOnNetwork = {},
onEnterCode = { _, _ -> },
onJoinWithAdmin = { _, _ -> },
onShowCode = {},
onRestore = { _, _ -> },
onRestoreFromWebDav = { _, _, _, _ -> },
)
}
private fun show() = rule.setContent { Content() }

@Composable
private fun Content() {
var error by remember { mutableStateOf<LinkedError?>(null) }
LinkedSetup(
hostOnly = false,
busy = false,
error = error,
onCreate = { _, _ -> error = LinkedError.SERVER_HAS_GROUP },
onResetAndCreate = { url, token -> reset += listOf(url, token) },
onCreateOnWebDav = { url, user, password -> created += listOf(url, user, password) },
onCreateOnNetwork = {},
onEnterCode = { _, _ -> },
onJoinWithAdmin = { _, _ -> },
onShowCode = {},
onRestore = { _, _ -> },
onRestoreFromWebDav = { _, _, _, _ -> },
)
}

@Test
fun a_new_group_can_live_on_a_server_in_a_webdav_folder_or_on_this_network() {
Expand Down Expand Up @@ -104,4 +108,41 @@ class LinkedSetupTest {
rule.onNodeWithText(text(R.string.linked_reset_server_confirm)).performClick()
assertEquals(listOf(listOf("https://sync.example.org", "admin")), reset)
}

@Test
fun recovery_words_for_a_server_are_not_kept_in_saved_state_but_the_address_is() {
val restoration = StateRestorationTester(rule)
restoration.setContent { Content() }
rule.onNodeWithText(text(R.string.linked_restore_title)).performClick()
rule.onNodeWithText(text(R.string.linked_where_server)).performClick()
rule.onNodeWithText(text(R.string.linked_server_url)).performTextInput("https://sync.example.org")
rule.onNodeWithText(text(R.string.linked_recovery_words_field)).performTextInput("typed words")
rule.onNodeWithText(text(R.string.linked_restore)).assertIsEnabled()

restoration.emulateSavedInstanceStateRestore()

rule.onNodeWithText("https://sync.example.org").assertExists()
rule.onNodeWithText("typed words").assertDoesNotExist()
rule.onNodeWithText(text(R.string.linked_restore)).assertIsNotEnabled()
}

@Test
fun recovery_words_for_a_webdav_folder_are_not_kept_in_saved_state_but_the_address_is() {
val restoration = StateRestorationTester(rule)
restoration.setContent { Content() }
rule.onNodeWithText(text(R.string.linked_restore_title)).performClick()
rule.onNodeWithText(text(R.string.linked_where_webdav)).performClick()
rule.onNodeWithText(text(R.string.linked_webdav_url)).performTextInput("https://cloud.example.org/dav/flow/")
rule.onNodeWithText(text(R.string.linked_webdav_user)).performTextInput("me")
rule.onNodeWithText(text(R.string.linked_webdav_password)).performTextInput("secret")
rule.onNodeWithText(text(R.string.linked_recovery_words_field)).performTextInput("typed words")
rule.onNodeWithText(text(R.string.linked_restore)).assertIsEnabled()

restoration.emulateSavedInstanceStateRestore()

rule.onNodeWithText("https://cloud.example.org/dav/flow/").assertExists()
rule.onNodeWithText("typed words").assertDoesNotExist()
rule.onNodeWithText(text(R.string.linked_webdav_password)).performTextInput("secret")
rule.onNodeWithText(text(R.string.linked_restore)).assertIsNotEnabled()
}
}
Loading