Replace 'leave room' text with 'leave conversation' for DMs (#2231)

* Replace 'leave room' text with 'leave conversation' for DMs

* Add `isDm` property to both `RoomSummary` and `RoomListRoomSummary`

* Remove redundant `leave_conversation_alert_subtitle_*` texts

* Fix maestro flow

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-01-16 15:49:44 +01:00 committed by GitHub
parent a9776f9fcb
commit 64a18afc1b
77 changed files with 115 additions and 19 deletions

View file

@ -9,5 +9,5 @@ appId: ${APP_ID}
index: 1 index: 1
- takeScreenshot: build/maestro/330-createAndDeleteDM - takeScreenshot: build/maestro/330-createAndDeleteDM
- tapOn: "maestroelement2" - tapOn: "maestroelement2"
- tapOn: "Leave room" - tapOn: "Leave conversation"
- tapOn: "Leave" - tapOn: "Leave"

1
changelog.d/2218.misc Normal file
View file

@ -0,0 +1 @@
Replace 'leave room' text with 'leave conversation' for DMs.

View file

@ -26,6 +26,7 @@ data class LeaveRoomState(
) { ) {
sealed interface Confirmation { sealed interface Confirmation {
data object Hidden : Confirmation data object Hidden : Confirmation
data class Dm(val roomId: RoomId) : Confirmation
data class Generic(val roomId: RoomId) : Confirmation data class Generic(val roomId: RoomId) : Confirmation
data class PrivateRoom(val roomId: RoomId) : Confirmation data class PrivateRoom(val roomId: RoomId) : Confirmation
data class LastUserInRoom(val roomId: RoomId) : Confirmation data class LastUserInRoom(val roomId: RoomId) : Confirmation

View file

@ -28,17 +28,17 @@ class LeaveRoomStateProvider : PreviewParameterProvider<LeaveRoomState> {
error = LeaveRoomState.Error.Hidden, error = LeaveRoomState.Error.Hidden,
), ),
aLeaveRoomState( aLeaveRoomState(
confirmation = LeaveRoomState.Confirmation.Generic(A_ROOM_ID), confirmation = LeaveRoomState.Confirmation.Generic(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden, progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden, error = LeaveRoomState.Error.Hidden,
), ),
aLeaveRoomState( aLeaveRoomState(
confirmation = LeaveRoomState.Confirmation.PrivateRoom(A_ROOM_ID), confirmation = LeaveRoomState.Confirmation.PrivateRoom(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden, progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden, error = LeaveRoomState.Error.Hidden,
), ),
aLeaveRoomState( aLeaveRoomState(
confirmation = LeaveRoomState.Confirmation.LastUserInRoom(A_ROOM_ID), confirmation = LeaveRoomState.Confirmation.LastUserInRoom(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden, progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden, error = LeaveRoomState.Error.Hidden,
), ),
@ -52,6 +52,11 @@ class LeaveRoomStateProvider : PreviewParameterProvider<LeaveRoomState> {
progress = LeaveRoomState.Progress.Hidden, progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Shown, error = LeaveRoomState.Error.Shown,
), ),
aLeaveRoomState(
confirmation = LeaveRoomState.Confirmation.Dm(roomId = A_ROOM_ID),
progress = LeaveRoomState.Progress.Hidden,
error = LeaveRoomState.Error.Hidden,
),
) )
} }

View file

@ -47,21 +47,32 @@ private fun LeaveRoomConfirmationDialog(
) { ) {
when (state.confirmation) { when (state.confirmation) {
is LeaveRoomState.Confirmation.Hidden -> {} is LeaveRoomState.Confirmation.Hidden -> {}
is LeaveRoomState.Confirmation.Dm -> LeaveRoomConfirmationDialog(
text = R.string.leave_conversation_alert_subtitle,
roomId = state.confirmation.roomId,
isDm = true,
eventSink = state.eventSink,
)
is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_private_subtitle, text = R.string.leave_room_alert_private_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
isDm = false,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_empty_subtitle, text = R.string.leave_room_alert_empty_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
isDm = false,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog(
text = R.string.leave_room_alert_subtitle, text = R.string.leave_room_alert_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
isDm = false,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
} }
@ -71,10 +82,11 @@ private fun LeaveRoomConfirmationDialog(
private fun LeaveRoomConfirmationDialog( private fun LeaveRoomConfirmationDialog(
@StringRes text: Int, @StringRes text: Int,
roomId: RoomId, roomId: RoomId,
isDm: Boolean,
eventSink: (LeaveRoomEvent) -> Unit, eventSink: (LeaveRoomEvent) -> Unit,
) { ) {
ConfirmationDialog( ConfirmationDialog(
title = stringResource(CommonStrings.action_leave_room), title = stringResource(if (isDm) CommonStrings.action_leave_conversation else CommonStrings.action_leave_room),
content = stringResource(text), content = stringResource(text),
submitText = stringResource(CommonStrings.action_leave), submitText = stringResource(CommonStrings.action_leave),
onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) }, onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) },

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Are you sure that you want to leave this conversation? This conversation is not public and you won\'t be able to rejoin without an invite."</string>
<string name="leave_room_alert_empty_subtitle">"Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you."</string> <string name="leave_room_alert_empty_subtitle">"Are you sure that you want to leave this room? You\'re the only person here. If you leave, no one will be able to join in the future, including you."</string>
<string name="leave_room_alert_private_subtitle">"Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite."</string> <string name="leave_room_alert_private_subtitle">"Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite."</string>
<string name="leave_room_alert_subtitle">"Are you sure that you want to leave the room?"</string> <string name="leave_room_alert_subtitle">"Are you sure that you want to leave the room?"</string>

View file

@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom
@ -85,6 +86,7 @@ private suspend fun showLeaveRoomAlert(
) { ) {
matrixClient.getRoom(roomId)?.use { room -> matrixClient.getRoom(roomId)?.use { room ->
confirmation.value = when { confirmation.value = when {
room.isDm -> Dm(roomId)
!room.isPublic -> PrivateRoom(roomId) !room.isPublic -> PrivateRoom(roomId)
room.joinedMemberCount == 1L -> LastUserInRoom(roomId) room.joinedMemberCount == 1L -> LastUserInRoom(roomId)
else -> Generic(roomId) else -> Generic(roomId)

View file

@ -114,6 +114,26 @@ class LeaveRoomPresenterImplTest {
} }
} }
@Test
fun `present - show DM confirmation`() = runTest {
val presenter = createLeaveRoomPresenter(
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true, isOneToOne = true),
)
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID))
val confirmationState = awaitItem()
assertThat(confirmationState.confirmation).isEqualTo(LeaveRoomState.Confirmation.Dm(A_ROOM_ID))
}
}
@Test @Test
fun `present - leaving a room leaves the room`() = runTest { fun `present - leaving a room leaves the room`() = runTest {
val roomMembershipObserver = RoomMembershipObserver() val roomMembershipObserver = RoomMembershipObserver()

View file

@ -183,9 +183,8 @@ class MessageComposerPresenter @Inject constructor(
val currentUserId = currentSessionIdHolder.current val currentUserId = currentSessionIdHolder.current
suspend fun canSendRoomMention(): Boolean { suspend fun canSendRoomMention(): Boolean {
val roomIsDm = room.isDirect && room.isOneToOne
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false) val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
return !roomIsDm && userCanSendAtRoom return !room.isDm && userCanSendAtRoom
} }
// This will trigger a search immediately when `@` is typed // This will trigger a search immediately when `@` is typed

View file

@ -190,9 +190,10 @@ fun RoomDetailsView(
BlockUserDialogs(roomMemberState) BlockUserDialogs(roomMemberState)
} }
OtherActionsSection(onLeaveRoom = { OtherActionsSection(
state.eventSink(RoomDetailsEvent.LeaveRoom) isDm = state.roomType is RoomDetailsType.Dm,
}) onLeaveRoom = { state.eventSink(RoomDetailsEvent.LeaveRoom) }
)
} }
} }
} }
@ -410,10 +411,17 @@ private fun SecuritySection(modifier: Modifier = Modifier) {
} }
@Composable @Composable
private fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) { private fun OtherActionsSection(isDm: Boolean, onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(showDivider = false, modifier = modifier) { PreferenceCategory(showDivider = false, modifier = modifier) {
ListItem( ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_details_leave_room_title)) }, headlineContent = {
val leaveText = stringResource(id = if (isDm) {
R.string.screen_room_details_leave_conversation_title
} else {
R.string.screen_room_details_leave_room_title
})
Text(leaveText)
},
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave)), leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave)),
style = ListItemStyle.Destructive, style = ListItemStyle.Destructive,
onClick = onLeaveRoom, onClick = onLeaveRoom,

View file

@ -47,6 +47,7 @@
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string> <string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
<string name="screen_dm_details_unblock_alert_description">"You\'ll be able to see all messages from them again."</string> <string name="screen_dm_details_unblock_alert_description">"You\'ll be able to see all messages from them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string> <string name="screen_dm_details_unblock_user">"Unblock user"</string>
<string name="screen_room_details_leave_conversation_title">"Leave conversation"</string>
<string name="screen_room_details_leave_room_title">"Leave room"</string> <string name="screen_room_details_leave_room_title">"Leave room"</string>
<string name="screen_room_details_security_title">"Security"</string> <string name="screen_room_details_security_title">"Security"</string>
<string name="screen_room_details_topic_title">"Topic"</string> <string name="screen_room_details_topic_title">"Topic"</string>

View file

@ -95,7 +95,14 @@ private fun RoomListModalBottomSheetContent(
style = ListItemStyle.Primary, style = ListItemStyle.Primary,
) )
ListItem( ListItem(
headlineContent = { Text(text = stringResource(id = CommonStrings.action_leave_room)) }, headlineContent = {
val leaveText = stringResource(id = if (contextMenu.isDm) {
CommonStrings.action_leave_conversation
} else {
CommonStrings.action_leave_room
})
Text(text = leaveText)
},
modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) }, modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) },
leadingContent = ListItemContent.Icon( leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector( iconSource = IconSource.Vector(
@ -117,7 +124,22 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
RoomListModalBottomSheetContent( RoomListModalBottomSheetContent(
contextMenu = RoomListState.ContextMenu.Shown( contextMenu = RoomListState.ContextMenu.Shown(
roomId = RoomId(value = "!aRoom:aDomain"), roomId = RoomId(value = "!aRoom:aDomain"),
roomName = "aRoom" roomName = "aRoom",
isDm = false,
),
onRoomSettingsClicked = {},
onLeaveRoomClicked = {}
)
}
@PreviewsDayNight
@Composable
internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
RoomListModalBottomSheetContent(
contextMenu = RoomListState.ContextMenu.Shown(
roomId = RoomId(value = "!aRoom:aDomain"),
roomName = "aRoom",
isDm = true,
), ),
onRoomSettingsClicked = {}, onRoomSettingsClicked = {},
onLeaveRoomClicked = {} onLeaveRoomClicked = {}

View file

@ -119,7 +119,8 @@ class RoomListPresenter @Inject constructor(
is RoomListEvents.ShowContextMenu -> { is RoomListEvents.ShowContextMenu -> {
contextMenu = RoomListState.ContextMenu.Shown( contextMenu = RoomListState.ContextMenu.Shown(
roomId = event.roomListRoomSummary.roomId, roomId = event.roomListRoomSummary.roomId,
roomName = event.roomListRoomSummary.name roomName = event.roomListRoomSummary.name,
isDm = event.roomListRoomSummary.isDm,
) )
} }
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden

View file

@ -46,6 +46,7 @@ data class RoomListState(
data class Shown( data class Shown(
val roomId: RoomId, val roomId: RoomId,
val roomName: String, val roomName: String,
val isDm: Boolean,
) : ContextMenu ) : ContextMenu
} }
} }

View file

@ -44,7 +44,8 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
aRoomListState().copy( aRoomListState().copy(
contextMenu = RoomListState.ContextMenu.Shown( contextMenu = RoomListState.ContextMenu.Shown(
roomId = RoomId("!aRoom:aDomain"), roomId = RoomId("!aRoom:aDomain"),
roomName = "A nice room name" roomName = "A nice room name",
isDm = false,
) )
), ),
aRoomListState().copy(displayRecoveryKeyPrompt = true), aRoomListState().copy(displayRecoveryKeyPrompt = true),

View file

@ -162,6 +162,7 @@ class RoomListDataSource @Inject constructor(
avatarData = avatarData, avatarData = avatarData,
notificationMode = roomSummary.details.notificationMode, notificationMode = roomSummary.details.notificationMode,
hasOngoingCall = roomSummary.details.hasOngoingCall, hasOngoingCall = roomSummary.details.hasOngoingCall,
isDm = roomSummary.details.isDm,
) )
} }
null -> null null -> null

View file

@ -34,4 +34,5 @@ data class RoomListRoomSummary(
val isPlaceholder: Boolean = false, val isPlaceholder: Boolean = false,
val notificationMode: RoomNotificationMode? = null, val notificationMode: RoomNotificationMode? = null,
val hasOngoingCall: Boolean = false, val hasOngoingCall: Boolean = false,
val isDm: Boolean = false,
) )

View file

@ -321,7 +321,7 @@ class RoomListPresenterTests {
val shownState = awaitItem() val shownState = awaitItem()
assertThat(shownState.contextMenu) assertThat(shownState.contextMenu)
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name)) .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
scope.cancel() scope.cancel()
} }
} }
@ -341,7 +341,7 @@ class RoomListPresenterTests {
val shownState = awaitItem() val shownState = awaitItem()
assertThat(shownState.contextMenu) assertThat(shownState.contextMenu)
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name)) .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
shownState.eventSink(RoomListEvents.HideContextMenu) shownState.eventSink(RoomListEvents.HideContextMenu)
val hiddenState = awaitItem() val hiddenState = awaitItem()

View file

@ -52,6 +52,9 @@ interface MatrixRoom : Closeable {
val activeMemberCount: Long val activeMemberCount: Long
val joinedMemberCount: Long val joinedMemberCount: Long
/** Whether the room is a direct message. */
val isDm: Boolean get() = isDirect && isOneToOne
val roomInfoFlow: Flow<MatrixRoomInfo> val roomInfoFlow: Flow<MatrixRoomInfo>
/** /**

View file

@ -45,4 +45,5 @@ data class RoomSummaryDetails(
val inviter: RoomMember? = null, val inviter: RoomMember? = null,
val notificationMode: RoomNotificationMode? = null, val notificationMode: RoomNotificationMode? = null,
val hasOngoingCall: Boolean = false, val hasOngoingCall: Boolean = false,
val isDm: Boolean = false,
) )

View file

@ -41,6 +41,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
inviter = roomInfo.inviter?.let(RoomMemberMapper::map), inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode), notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
hasOngoingCall = roomInfo.hasRoomCall, hasOngoingCall = roomInfo.hasRoomCall,
isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L,
) )
} }
} }

View file

@ -57,6 +57,7 @@
<string name="action_join">"Join"</string> <string name="action_join">"Join"</string>
<string name="action_learn_more">"Learn more"</string> <string name="action_learn_more">"Learn more"</string>
<string name="action_leave">"Leave"</string> <string name="action_leave">"Leave"</string>
<string name="action_leave_conversation">"Leave conversation"</string>
<string name="action_leave_room">"Leave room"</string> <string name="action_leave_room">"Leave room"</string>
<string name="action_manage_account">"Manage account"</string> <string name="action_manage_account">"Manage account"</string>
<string name="action_manage_devices">"Manage devices"</string> <string name="action_manage_devices">"Manage devices"</string>

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:925d6129884511860505ed7ba3cec9f2ddb50105aa1f74d9ff2dc8bbed22f8d4
size 37706

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a46f95f72addd2775c6dfd4dcbfe2bfb2f68eb0eaa4fe2676c3d17b285390e27
size 32718

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1bd5f590fd30facfb007050c26aa567d568ab0d797a27986b6f88a23af76f9dc
size 14167

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e609d908d159cffdc2a17ac8944413dbb52e5cd1ebdd2bd30057dc9865749bd6
size 13369

View file

@ -97,7 +97,8 @@
{ {
"name" : ":features:leaveroom:api", "name" : ":features:leaveroom:api",
"includeRegex" : [ "includeRegex" : [
"leave_room_alert_.*" "leave_room_alert_.*",
"leave_conversation_alert_.*"
] ]
}, },
{ {