mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 21:32:34 +00:00
Merge pull request #7372 from hayaksi1/fix/4893-copy-blocked-user-id
Copy a blocked user's Matrix ID by long pressing them
This commit is contained in:
+1
@@ -12,6 +12,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
sealed interface BlockedUsersEvents {
|
||||
data class Unblock(val userId: UserId) : BlockedUsersEvents
|
||||
data class CopyToClipboard(val userId: UserId) : BlockedUsersEvents
|
||||
data object ConfirmUnblock : BlockedUsersEvents
|
||||
data object Cancel : BlockedUsersEvents
|
||||
}
|
||||
|
||||
+14
@@ -18,14 +18,19 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -34,6 +39,8 @@ import kotlinx.coroutines.launch
|
||||
class BlockedUsersPresenter(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
) : Presenter<BlockedUsersState> {
|
||||
@Composable
|
||||
override fun present(): BlockedUsersState {
|
||||
@@ -65,12 +72,18 @@ class BlockedUsersPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
|
||||
fun handleEvent(event: BlockedUsersEvents) {
|
||||
when (event) {
|
||||
is BlockedUsersEvents.Unblock -> {
|
||||
pendingUserToUnblock = event.userId
|
||||
unblockUserAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
is BlockedUsersEvents.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.userId.value)
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
}
|
||||
BlockedUsersEvents.ConfirmUnblock -> {
|
||||
pendingUserToUnblock?.let {
|
||||
coroutineScope.unblockUser(it, unblockUserAction)
|
||||
@@ -86,6 +99,7 @@ class BlockedUsersPresenter(
|
||||
return BlockedUsersState(
|
||||
blockedUsers = ignoredMatrixUser.toImmutableList(),
|
||||
unblockUserAction = unblockUserAction.value,
|
||||
snackbarMessage = snackbarMessage,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
+2
@@ -9,11 +9,13 @@
|
||||
package io.element.android.features.preferences.impl.blockedusers
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class BlockedUsersState(
|
||||
val blockedUsers: ImmutableList<MatrixUser>,
|
||||
val unblockUserAction: AsyncAction<Unit>,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val eventSink: (BlockedUsersEvents) -> Unit,
|
||||
)
|
||||
|
||||
+3
@@ -10,6 +10,7 @@ package io.element.android.features.preferences.impl.blockedusers
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -30,11 +31,13 @@ class BlockedUsersStateProvider : PreviewParameterProvider<BlockedUsersState> {
|
||||
internal fun aBlockedUsersState(
|
||||
blockedUsers: List<MatrixUser> = aMatrixUserList(),
|
||||
unblockUserAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
eventSink: (BlockedUsersEvents) -> Unit = {},
|
||||
): BlockedUsersState {
|
||||
return BlockedUsersState(
|
||||
blockedUsers = blockedUsers.toImmutableList(),
|
||||
unblockUserAction = unblockUserAction,
|
||||
snackbarMessage = snackbarMessage,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
}
|
||||
|
||||
+14
-4
@@ -8,7 +8,7 @@
|
||||
|
||||
package io.element.android.features.preferences.impl.blockedusers
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
@@ -31,6 +31,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||
@@ -43,6 +45,7 @@ fun BlockedUsersView(
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
Box(modifier = modifier) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -52,7 +55,8 @@ fun BlockedUsersView(
|
||||
BackButton(onClick = onBackClick)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(padding)
|
||||
@@ -60,7 +64,8 @@ fun BlockedUsersView(
|
||||
items(state.blockedUsers) { matrixUser ->
|
||||
BlockedUserItem(
|
||||
matrixUser = matrixUser,
|
||||
onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) }
|
||||
onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) },
|
||||
onLongClick = { state.eventSink(BlockedUsersEvents.CopyToClipboard(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -107,9 +112,14 @@ fun BlockedUsersView(
|
||||
private fun BlockedUserItem(
|
||||
matrixUser: MatrixUser,
|
||||
onClick: (UserId) -> Unit,
|
||||
onLongClick: (UserId) -> Unit,
|
||||
) {
|
||||
MatrixUserRow(
|
||||
modifier = Modifier.clickable { onClick(matrixUser.userId) },
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = { onClick(matrixUser.userId) },
|
||||
onLongClick = { onLongClick(matrixUser.userId) },
|
||||
onLongClickLabel = stringResource(CommonStrings.action_copy),
|
||||
),
|
||||
matrixUser = matrixUser,
|
||||
)
|
||||
}
|
||||
|
||||
+16
@@ -13,8 +13,10 @@ package io.element.android.features.preferences.impl.blockedusers
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.longClick
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -57,6 +59,20 @@ class BlockedUserViewTest : RobolectricTest() {
|
||||
eventsRecorder.assertSingle(BlockedUsersEvents.Unblock(userList.first().userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `long clicking on a user emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
||||
val userList = aMatrixUserList()
|
||||
setBlockedUsersView(
|
||||
aBlockedUsersState(
|
||||
blockedUsers = userList,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
onNodeWithText(userList.first().displayName.orEmpty()).performTouchInput { longClick() }
|
||||
eventsRecorder.assertSingle(BlockedUsersEvents.CopyToClipboard(userList.first().userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on cancel sends a BlockedUsersEvents`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
||||
|
||||
+30
@@ -12,7 +12,9 @@ import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
@@ -21,6 +23,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -169,6 +172,29 @@ class BlockedUsersPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy user id to clipboard`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(
|
||||
matrixClient = matrixClient,
|
||||
clipboardHelper = clipboardHelper,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.snackbarMessage).isNull()
|
||||
initialState.eventSink(BlockedUsersEvents.CopyToClipboard(A_USER_ID))
|
||||
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo(A_USER_ID.value)
|
||||
assertThat(awaitItem().snackbarMessage?.messageResId).isEqualTo(CommonStrings.common_copied_to_clipboard)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - confirm unblock without a pending blocked user does nothing`() = runTest {
|
||||
val presenter = aBlockedUsersPresenter()
|
||||
@@ -183,8 +209,12 @@ class BlockedUsersPresenterTest {
|
||||
private fun aBlockedUsersPresenter(
|
||||
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||
) = BlockedUsersPresenter(
|
||||
matrixClient = matrixClient,
|
||||
featureFlagService = featureFlagService,
|
||||
clipboardHelper = clipboardHelper,
|
||||
snackbarDispatcher = snackbarDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user