[#785] Remove press-and-hold from Send

* [#785] Remove press-and-hold for send confirmation

- Timed button replaced by standard behaviour button
- Related SendView test updated

* Remove unused API

If we need this back, we can restore it from the Git history.

* Remove unused imports

---------

Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
Honza Rychnovsky 2023-03-20 10:17:22 +01:00 committed by GitHub
parent d37310a935
commit 0c0bf8cb34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 11 additions and 177 deletions

View File

@ -1,93 +0,0 @@
package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.material3.Text
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.testTimeSource
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.ExperimentalTime
class ButtonTest {
@get:Rule
val composeTestRule = createComposeRule()
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTime::class)
@Test
@MediumTest
fun timedButtonTest(): Unit = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val testSetup = newTestSetup(testDispatcher, 2.seconds)
val mark = testTimeSource.markNow()
launch(Dispatchers.Main) {
testSetup.interactionSource.emit(PressInteraction.Press(Offset.Zero))
advanceTimeBy(3.seconds.inWholeMilliseconds)
}
launch {
testSetup.mutableActionExecuted.collect {
if (!it) return@collect
assertTrue { mark.elapsedNow() >= 2.seconds }
this.cancel()
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@MediumTest
fun buttonClickTest() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val testSetup = newTestSetup(testDispatcher, 2.seconds)
composeTestRule.onNodeWithText("button").also {
it.performClick()
}
advanceTimeBy(3.seconds.inWholeMilliseconds)
assertFalse { testSetup.mutableActionExecuted.value }
}
private fun newTestSetup(testDispatcher: CoroutineDispatcher, duration: Duration) = TestSetup(testDispatcher, composeTestRule, duration)
private class TestSetup(coroutineDispatcher: CoroutineDispatcher, composeTestRule: ComposeContentTestRule, duration: Duration) {
val mutableActionExecuted = MutableStateFlow(false)
val interactionSource = MutableInteractionSource()
init {
composeTestRule.setContent {
ZcashTheme {
TimedButton(
duration = duration,
onClick = { mutableActionExecuted.update { true } },
coroutineDispatcher = coroutineDispatcher,
content = { Text(text = "button") },
interactionSource = interactionSource
)
}
}
}
}
}

View File

@ -1,9 +1,6 @@
package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
@ -12,20 +9,10 @@ import androidx.compose.material3.ButtonDefaults.buttonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@Preview
@Composable
@ -158,41 +145,3 @@ 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
) {
LaunchedEffect(interactionSource) {
var action: Job? = null
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> {
action = launch(coroutineDispatcher) {
delay(duration)
withContext(Dispatchers.Main) {
onClick()
}
}
}
is PressInteraction.Release -> {
action?.cancel()
}
}
}
}
Button(
modifier = modifier,
onClick = {},
interactionSource = interactionSource,
content = content
)
}

View File

@ -1,8 +1,5 @@
package co.electriccoin.zcash.ui.screen.send.view
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.ComposeContentTestRule
@ -26,13 +23,11 @@ import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.test.getStringResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@ -70,7 +65,7 @@ class SendViewTest : UiTestPrerequisites() {
composeTestRule.setValidAddress()
composeTestRule.clickCreateAndSend()
composeTestRule.assertOnConfirmation()
clickConfirmation(testSetup.interactionSource)
composeTestRule.clickConfirmation()
launch {
testSetup.mutableActionExecuted.collectWith(this) {
@ -106,7 +101,7 @@ class SendViewTest : UiTestPrerequisites() {
composeTestRule.clickCreateAndSend()
composeTestRule.assertOnConfirmation()
clickConfirmation(testSetup.interactionSource)
composeTestRule.clickConfirmation()
launch {
testSetup.mutableActionExecuted.collectWith(this) {
@ -164,7 +159,7 @@ class SendViewTest : UiTestPrerequisites() {
composeTestRule.clickCreateAndSend()
composeTestRule.assertOnConfirmation()
clickConfirmation(testSetup.interactionSource)
composeTestRule.clickConfirmation()
launch {
testSetup.mutableActionExecuted.collectWith(this) {
@ -242,7 +237,7 @@ class SendViewTest : UiTestPrerequisites() {
composeTestRule.clickCreateAndSend()
composeTestRule.assertOnConfirmation()
clickConfirmation(testSetup.interactionSource)
composeTestRule.clickConfirmation()
launch {
testSetup.mutableActionExecuted.collectWith(this) {
@ -296,7 +291,6 @@ class SendViewTest : UiTestPrerequisites() {
private val onBackCount = AtomicInteger(0)
private val onCreateCount = AtomicInteger(0)
val interactionSource = MutableInteractionSource()
val mutableActionExecuted = MutableStateFlow(false)
@Volatile
@ -322,7 +316,6 @@ class SendViewTest : UiTestPrerequisites() {
ZcashTheme {
Send(
mySpendableBalance = ZatoshiFixture.new(),
pressAndHoldInteractionSource = interactionSource,
goBack = {
onBackCount.incrementAndGet()
},
@ -386,10 +379,9 @@ private fun ComposeContentTestRule.clickCreateAndSend() {
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun TestScope.clickConfirmation(interactionSource: MutableInteractionSource) {
launch(Dispatchers.Main) {
interactionSource.emit(PressInteraction.Press(Offset.Zero))
private fun ComposeContentTestRule.clickConfirmation() {
onNodeWithText(getStringResource(R.string.send_confirm)).also {
it.performClick()
}
}

View File

@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.screen.send.view
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
@ -22,7 +21,6 @@ import androidx.compose.material3.TopAppBar
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
@ -47,7 +45,6 @@ import co.electriccoin.zcash.ui.design.component.FormTextField
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.TimedButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
@ -69,16 +66,12 @@ fun PreviewSend() {
}
}
/**
* @param pressAndHoldInteractionSource This is an argument that can be injected for automated testing.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Send(
mySpendableBalance: Zatoshi,
goBack: () -> Unit,
onCreateAndSend: (ZecSend) -> Unit,
pressAndHoldInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }
onCreateAndSend: (ZecSend) -> Unit
) {
// 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:
@ -98,7 +91,6 @@ fun Send(
SendMainContent(
myBalance = mySpendableBalance,
sendStage = sendStage,
pressAndHoldInteractionSource = pressAndHoldInteractionSource,
setSendStage = setSendStage,
onCreateAndSend = onCreateAndSend,
modifier = Modifier
@ -138,7 +130,6 @@ private fun SendTopAppBar(onBack: () -> Unit) {
private fun SendMainContent(
myBalance: Zatoshi,
sendStage: SendStage,
pressAndHoldInteractionSource: MutableInteractionSource,
setSendStage: (SendStage) -> Unit,
onCreateAndSend: (ZecSend) -> Unit,
modifier: Modifier = Modifier
@ -158,7 +149,6 @@ private fun SendMainContent(
} else {
Confirmation(
zecSend = zecSend,
pressAndHoldInteractionSource = pressAndHoldInteractionSource,
onConfirmation = {
onCreateAndSend(zecSend)
},
@ -293,7 +283,6 @@ private fun SendForm(
@Composable
private fun Confirmation(
zecSend: ZecSend,
pressAndHoldInteractionSource: MutableInteractionSource,
onConfirmation: () -> Unit,
modifier: Modifier = Modifier
) {
@ -306,12 +295,9 @@ private fun Confirmation(
)
)
TimedButton(
PrimaryButton(
onClick = onConfirmation,
{
Text(text = stringResource(id = R.string.send_confirm))
},
interactionSource = pressAndHoldInteractionSource
text = stringResource(id = R.string.send_confirm)
)
}
}

View File

@ -12,6 +12,6 @@
<string name="send_amount_and_address_format" formatted="true">Send <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g>?</string>
<string name="send_abbreviated_address_format" formatted="true"><xliff:g id="first_five" example="zs1g7">%1$s</xliff:g><xliff:g id="last_five" example="mvyzg">%2$s</xliff:g></string>
<string name="send_confirm">Press and hold to send ZEC</string>
<string name="send_confirm">Press to send ZEC</string>
</resources>