Skip to content

Commit

Permalink
UX cleanup: room details (#2816)
Browse files Browse the repository at this point in the history
* UX cleanup: room details screen

Add new CTA buttons for Invite and Call actions

* Update screenshots

* Fix maestro

---------

Co-authored-by: ElementBot <[email protected]>
  • Loading branch information
jmartinesp and ElementBot authored May 8, 2024
1 parent d86b7d2 commit 46b22d7
Show file tree
Hide file tree
Showing 34 changed files with 149 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .maestro/tests/roomList/createAndDeleteRoom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ appId: ${MAESTRO_APP_ID}
- tapOn: "Create"
- takeScreenshot: build/maestro/320-createAndDeleteRoom
- tapOn: "aRoomName"
- tapOn: "Invite people"
- tapOn: "Invite"
# assert there's 1 member and 1 invitee
- tapOn: "Search for someone"
- inputText: ${MAESTRO_INVITEE2_MXID}
Expand Down
1 change: 1 addition & 0 deletions changelog.d/2814.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UX cleanup: room details screen, add new CTA buttons for Invite and Call actions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
import io.element.android.libraries.matrix.ui.room.canCall
import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
Expand Down Expand Up @@ -158,9 +159,7 @@ class MessagesPresenter @AssistedInject constructor(
mutableStateOf(false)
}

var canJoinCall by rememberSaveable {
mutableStateOf(false)
}
val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value)

LaunchedEffect(Unit) {
// Remove the unread flag on entering but don't send read receipts
Expand All @@ -170,12 +169,6 @@ class MessagesPresenter @AssistedInject constructor(
}
}

LaunchedEffect(syncUpdateFlow.value) {
withContext(dispatchers.io) {
canJoinCall = room.canUserJoinCall(room.sessionId).getOrDefault(false)
}
}

val inviteProgress = remember { mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized) }
var showReinvitePrompt by remember { mutableStateOf(false) }
LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow.value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ class MessagesPresenterTest {
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
val finalState = awaitItem()
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand All @@ -298,10 +298,9 @@ class MessagesPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(2)
val initialState = awaitItem()
val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(initialState.actionListState.target).isEqualTo(ActionListState.Target.None)
// Otherwise we would have some extra items here
ensureAllEventsConsumed()
}
Expand Down Expand Up @@ -335,7 +334,7 @@ class MessagesPresenterTest {
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand Down Expand Up @@ -368,7 +367,7 @@ class MessagesPresenterTest {
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand All @@ -394,7 +393,7 @@ class MessagesPresenterTest {
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand All @@ -408,7 +407,7 @@ class MessagesPresenterTest {
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
val finalState = awaitItem()
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand Down Expand Up @@ -732,7 +731,7 @@ class MessagesPresenterTest {
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
assertThat(replyMode.attachmentThumbnailInfo?.textContent)
.isEqualTo("What type of food should we have at the party?")
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

Expand Down
3 changes: 2 additions & 1 deletion features/roomdetails/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ dependencies {
api(projects.libraries.usersearch.api)
api(projects.services.apperror.api)
implementation(libs.coil.compose)
implementation(projects.features.leaveroom.api)
implementation(projects.features.call)
implementation(projects.features.createroom.api)
implementation(projects.features.leaveroom.api)
implementation(projects.features.userprofile.shared)
implementation(projects.services.analytics.api)
implementation(projects.features.poll.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.element.android.features.roomdetails.impl

import android.content.Context
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand All @@ -28,6 +29,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.call.CallType
import io.element.android.features.call.ui.ElementCallActivity
import io.element.android.features.poll.api.history.PollHistoryEntryPoint
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
Expand All @@ -42,10 +45,12 @@ import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.mediaviewer.api.local.MediaInfo
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
import kotlinx.parcelize.Parcelize
Expand All @@ -54,7 +59,9 @@ import kotlinx.parcelize.Parcelize
class RoomDetailsFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
@ApplicationContext private val context: Context,
private val pollHistoryEntryPoint: PollHistoryEntryPoint,
private val room: MatrixRoom,
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
backstack = BackStack(
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
Expand Down Expand Up @@ -129,6 +136,14 @@ class RoomDetailsFlowNode @AssistedInject constructor(
override fun openAdminSettings() {
backstack.push(NavTarget.AdminSettings)
}

override fun onJoinCall() {
val inputs = CallType.RoomCall(
sessionId = room.sessionId,
roomId = room.roomId,
)
ElementCallActivity.start(context, inputs)
}
}
createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class RoomDetailsNode @AssistedInject constructor(
fun openAvatarPreview(name: String, url: String)
fun openPollHistory()
fun openAdminSettings()
fun onJoinCall()
}

private val callbacks = plugins<Callback>()
Expand Down Expand Up @@ -86,6 +87,10 @@ class RoomDetailsNode @AssistedInject constructor(
callbacks.forEach { it.openPollHistory() }
}

private fun onJoinCall() {
callbacks.forEach { it.onJoinCall() }
}

private fun CoroutineScope.onShareRoom(context: Context) = launch {
room.getPermalink()
.onSuccess { permalink ->
Expand Down Expand Up @@ -162,6 +167,7 @@ class RoomDetailsNode @AssistedInject constructor(
openAvatarPreview = ::openAvatarPreview,
openPollHistory = ::openPollHistory,
openAdminSettings = this::openAdminSettings,
onJoinCallClicked = ::onJoinCall,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.ui.room.canCall
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
import io.element.android.services.analytics.api.AnalyticsService
Expand Down Expand Up @@ -86,11 +87,14 @@ class RoomDetailsPresenter @Inject constructor(
}
}

val syncUpdateTimestamp by room.syncUpdateFlow.collectAsState()

val membersState by room.membersStateFlow.collectAsState()
val canInvite by getCanInvite(membersState)
val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME)
val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR)
val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC)
val canJoinCall by room.canCall(updateKey = syncUpdateTimestamp)
val dmMember by room.getDirectRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType by getRoomType(dmMember)
Expand Down Expand Up @@ -138,6 +142,7 @@ class RoomDetailsPresenter @Inject constructor(
canInvite = canInvite,
canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
canShowNotificationSettings = canShowNotificationSettings.value,
canCall = canJoinCall,
roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState,
leaveRoomState = leaveRoomState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ data class RoomDetailsState(
val canEdit: Boolean,
val canInvite: Boolean,
val canShowNotificationSettings: Boolean,
val canCall: Boolean,
val leaveRoomState: LeaveRoomState,
val roomNotificationSettings: RoomNotificationSettings?,
val isFavorite: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
// Also test the roomNotificationSettings ALL_MESSAGES in the same screenshot. Icon 'Mute' should be displayed
roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.ALL_MESSAGES, isDefault = true)
),
aRoomDetailsState(canCall = false, canInvite = false),
// Add other state here
)
}
Expand Down Expand Up @@ -89,6 +90,7 @@ fun aRoomDetailsState(
canInvite: Boolean = false,
canEdit: Boolean = false,
canShowNotificationSettings: Boolean = true,
canCall: Boolean = true,
roomType: RoomDetailsType = RoomDetailsType.Room,
roomMemberDetailsState: UserProfileState? = null,
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
Expand All @@ -107,6 +109,7 @@ fun aRoomDetailsState(
canInvite = canInvite,
canEdit = canEdit,
canShowNotificationSettings = canShowNotificationSettings,
canCall = canCall,
roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState,
leaveRoomState = leaveRoomState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -100,6 +99,7 @@ fun RoomDetailsView(
openAvatarPreview: (name: String, url: String) -> Unit,
openPollHistory: () -> Unit,
openAdminSettings: () -> Unit,
onJoinCallClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onShareMember() {
Expand Down Expand Up @@ -137,7 +137,9 @@ fun RoomDetailsView(
)
MainActionsSection(
state = state,
onShareRoom = onShareRoom
onShareRoom = onShareRoom,
onInvitePeople = invitePeople,
onCall = onJoinCallClicked,
)
}

Expand Down Expand Up @@ -188,20 +190,12 @@ fun RoomDetailsView(
}

val displayMemberListItem = state.roomType is RoomDetailsType.Room
val displayInviteMembersItem = state.canInvite
if (displayMemberListItem || displayInviteMembersItem) {
if (displayMemberListItem) {
PreferenceCategory {
if (displayMemberListItem) {
MembersItem(
memberCount = state.memberCount,
openRoomMemberList = openRoomMemberList,
)
}
if (displayInviteMembersItem) {
InviteItem(
invitePeople = invitePeople
)
}
MembersItem(
memberCount = state.memberCount,
openRoomMemberList = openRoomMemberList,
)
}
}

Expand Down Expand Up @@ -267,10 +261,12 @@ private fun RoomDetailsTopBar(
private fun MainActionsSection(
state: RoomDetailsState,
onShareRoom: () -> Unit,
onInvitePeople: () -> Unit,
onCall: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
val roomNotificationSettings = state.roomNotificationSettings
if (state.canShowNotificationSettings && roomNotificationSettings != null) {
Expand All @@ -292,9 +288,22 @@ private fun MainActionsSection(
)
}
}
Spacer(modifier = Modifier.width(20.dp))
if (state.canCall) {
MainActionButton(
title = stringResource(CommonStrings.action_call),
imageVector = CompoundIcons.VideoCall(),
onClick = onCall,
)
}
if (state.roomType is RoomDetailsType.Room && state.canInvite) {
MainActionButton(
title = stringResource(CommonStrings.action_invite),
imageVector = CompoundIcons.UserAdd(),
onClick = onInvitePeople,
)
}
MainActionButton(
title = stringResource(R.string.screen_room_details_share_room_title),
title = stringResource(CommonStrings.action_share),
imageVector = CompoundIcons.ShareAndroid(),
onClick = onShareRoom
)
Expand Down Expand Up @@ -410,17 +419,6 @@ private fun MembersItem(
)
}

@Composable
private fun InviteItem(
invitePeople: () -> Unit,
) {
ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_details_invite_people_title)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserAdd())),
onClick = invitePeople,
)
}

@Composable
private fun PollsSection(
openPollHistory: () -> Unit,
Expand Down Expand Up @@ -491,5 +489,6 @@ private fun ContentToPreview(state: RoomDetailsState) {
openAvatarPreview = { _, _ -> },
openPollHistory = {},
openAdminSettings = {},
onJoinCallClicked = {},
)
}
Loading

0 comments on commit 46b22d7

Please sign in to comment.