UX cleanup: room details (#2816)
* UX cleanup: room details screen Add new CTA buttons for Invite and Call actions * Update screenshots * Fix maestro --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
committed by
GitHub
parent
0f60299de0
commit
b524645b89
@@ -16,7 +16,7 @@ appId: ${MAESTRO_APP_ID}
|
|||||||
- tapOn: "Create"
|
- tapOn: "Create"
|
||||||
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
||||||
- tapOn: "aRoomName"
|
- tapOn: "aRoomName"
|
||||||
- tapOn: "Invite people"
|
- tapOn: "Invite"
|
||||||
# assert there's 1 member and 1 invitee
|
# assert there's 1 member and 1 invitee
|
||||||
- tapOn: "Search for someone"
|
- tapOn: "Search for someone"
|
||||||
- inputText: ${MAESTRO_INVITEE2_MXID}
|
- inputText: ${MAESTRO_INVITEE2_MXID}
|
||||||
|
|||||||
1
changelog.d/2814.misc
Normal file
1
changelog.d/2814.misc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UX cleanup: room details screen, add new CTA buttons for Invite and Call actions.
|
||||||
@@ -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.api.room.MessageEventType
|
||||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
|
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.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.canRedactOtherAsState
|
||||||
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
|
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
|
||||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||||
@@ -158,9 +159,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var canJoinCall by rememberSaveable {
|
val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value)
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Remove the unread flag on entering but don't send read receipts
|
// Remove the unread flag on entering but don't send read receipts
|
||||||
@@ -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) }
|
val inviteProgress = remember { mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized) }
|
||||||
var showReinvitePrompt by remember { mutableStateOf(false) }
|
var showReinvitePrompt by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow.value) {
|
LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow.value) {
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class MessagesPresenterTest {
|
|||||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,10 +298,9 @@ class MessagesPresenterTest {
|
|||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(2)
|
val initialState = awaitFirstItem()
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
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
|
// Otherwise we would have some extra items here
|
||||||
ensureAllEventsConsumed()
|
ensureAllEventsConsumed()
|
||||||
}
|
}
|
||||||
@@ -335,7 +334,7 @@ class MessagesPresenterTest {
|
|||||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||||
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
||||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +367,7 @@ class MessagesPresenterTest {
|
|||||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||||
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
||||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +393,7 @@ class MessagesPresenterTest {
|
|||||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||||
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
||||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +407,7 @@ class MessagesPresenterTest {
|
|||||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,7 +731,7 @@ class MessagesPresenterTest {
|
|||||||
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
|
||||||
assertThat(replyMode.attachmentThumbnailInfo?.textContent)
|
assertThat(replyMode.attachmentThumbnailInfo?.textContent)
|
||||||
.isEqualTo("What type of food should we have at the party?")
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ dependencies {
|
|||||||
api(projects.libraries.usersearch.api)
|
api(projects.libraries.usersearch.api)
|
||||||
api(projects.services.apperror.api)
|
api(projects.services.apperror.api)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(projects.features.leaveroom.api)
|
implementation(projects.features.call)
|
||||||
implementation(projects.features.createroom.api)
|
implementation(projects.features.createroom.api)
|
||||||
|
implementation(projects.features.leaveroom.api)
|
||||||
implementation(projects.features.userprofile.shared)
|
implementation(projects.features.userprofile.shared)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(projects.features.poll.api)
|
implementation(projects.features.poll.api)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package io.element.android.features.roomdetails.impl
|
package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -28,6 +29,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push
|
|||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
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.poll.api.history.PollHistoryEntryPoint
|
||||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||||
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
||||||
@@ -42,10 +45,12 @@ import io.element.android.libraries.architecture.BackstackView
|
|||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
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.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
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.local.MediaInfo
|
||||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -54,7 +59,9 @@ import kotlinx.parcelize.Parcelize
|
|||||||
class RoomDetailsFlowNode @AssistedInject constructor(
|
class RoomDetailsFlowNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
private val pollHistoryEntryPoint: PollHistoryEntryPoint,
|
private val pollHistoryEntryPoint: PollHistoryEntryPoint,
|
||||||
|
private val room: MatrixRoom,
|
||||||
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
|
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
|
||||||
@@ -129,6 +136,14 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||||||
override fun openAdminSettings() {
|
override fun openAdminSettings() {
|
||||||
backstack.push(NavTarget.AdminSettings)
|
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))
|
createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||||||
fun openAvatarPreview(name: String, url: String)
|
fun openAvatarPreview(name: String, url: String)
|
||||||
fun openPollHistory()
|
fun openPollHistory()
|
||||||
fun openAdminSettings()
|
fun openAdminSettings()
|
||||||
|
fun onJoinCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callbacks = plugins<Callback>()
|
private val callbacks = plugins<Callback>()
|
||||||
@@ -86,6 +87,10 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||||||
callbacks.forEach { it.openPollHistory() }
|
callbacks.forEach { it.openPollHistory() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onJoinCall() {
|
||||||
|
callbacks.forEach { it.onJoinCall() }
|
||||||
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.onShareRoom(context: Context) = launch {
|
private fun CoroutineScope.onShareRoom(context: Context) = launch {
|
||||||
room.getPermalink()
|
room.getPermalink()
|
||||||
.onSuccess { permalink ->
|
.onSuccess { permalink ->
|
||||||
@@ -162,6 +167,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||||||
openAvatarPreview = ::openAvatarPreview,
|
openAvatarPreview = ::openAvatarPreview,
|
||||||
openPollHistory = ::openPollHistory,
|
openPollHistory = ::openPollHistory,
|
||||||
openAdminSettings = this::openAdminSettings,
|
openAdminSettings = this::openAdminSettings,
|
||||||
|
onJoinCallClicked = ::onJoinCall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.canInvite
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
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.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.getDirectRoomMember
|
||||||
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
|
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
@@ -86,11 +87,14 @@ class RoomDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syncUpdateTimestamp by room.syncUpdateFlow.collectAsState()
|
||||||
|
|
||||||
val membersState by room.membersStateFlow.collectAsState()
|
val membersState by room.membersStateFlow.collectAsState()
|
||||||
val canInvite by getCanInvite(membersState)
|
val canInvite by getCanInvite(membersState)
|
||||||
val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME)
|
val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME)
|
||||||
val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR)
|
val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR)
|
||||||
val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC)
|
val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC)
|
||||||
|
val canJoinCall by room.canCall(updateKey = syncUpdateTimestamp)
|
||||||
val dmMember by room.getDirectRoomMember(membersState)
|
val dmMember by room.getDirectRoomMember(membersState)
|
||||||
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
|
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
|
||||||
val roomType by getRoomType(dmMember)
|
val roomType by getRoomType(dmMember)
|
||||||
@@ -138,6 +142,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
|
canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
|
||||||
canShowNotificationSettings = canShowNotificationSettings.value,
|
canShowNotificationSettings = canShowNotificationSettings.value,
|
||||||
|
canCall = canJoinCall,
|
||||||
roomType = roomType,
|
roomType = roomType,
|
||||||
roomMemberDetailsState = roomMemberDetailsState,
|
roomMemberDetailsState = roomMemberDetailsState,
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ data class RoomDetailsState(
|
|||||||
val canEdit: Boolean,
|
val canEdit: Boolean,
|
||||||
val canInvite: Boolean,
|
val canInvite: Boolean,
|
||||||
val canShowNotificationSettings: Boolean,
|
val canShowNotificationSettings: Boolean,
|
||||||
|
val canCall: Boolean,
|
||||||
val leaveRoomState: LeaveRoomState,
|
val leaveRoomState: LeaveRoomState,
|
||||||
val roomNotificationSettings: RoomNotificationSettings?,
|
val roomNotificationSettings: RoomNotificationSettings?,
|
||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
|
|||||||
// Also test the roomNotificationSettings ALL_MESSAGES in the same screenshot. Icon 'Mute' should be displayed
|
// Also test the roomNotificationSettings ALL_MESSAGES in the same screenshot. Icon 'Mute' should be displayed
|
||||||
roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.ALL_MESSAGES, isDefault = true)
|
roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.ALL_MESSAGES, isDefault = true)
|
||||||
),
|
),
|
||||||
|
aRoomDetailsState(canCall = false, canInvite = false),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ fun aRoomDetailsState(
|
|||||||
canInvite: Boolean = false,
|
canInvite: Boolean = false,
|
||||||
canEdit: Boolean = false,
|
canEdit: Boolean = false,
|
||||||
canShowNotificationSettings: Boolean = true,
|
canShowNotificationSettings: Boolean = true,
|
||||||
|
canCall: Boolean = true,
|
||||||
roomType: RoomDetailsType = RoomDetailsType.Room,
|
roomType: RoomDetailsType = RoomDetailsType.Room,
|
||||||
roomMemberDetailsState: UserProfileState? = null,
|
roomMemberDetailsState: UserProfileState? = null,
|
||||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||||
@@ -107,6 +109,7 @@ fun aRoomDetailsState(
|
|||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
canEdit = canEdit,
|
canEdit = canEdit,
|
||||||
canShowNotificationSettings = canShowNotificationSettings,
|
canShowNotificationSettings = canShowNotificationSettings,
|
||||||
|
canCall = canCall,
|
||||||
roomType = roomType,
|
roomType = roomType,
|
||||||
roomMemberDetailsState = roomMemberDetailsState,
|
roomMemberDetailsState = roomMemberDetailsState,
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -100,6 +99,7 @@ fun RoomDetailsView(
|
|||||||
openAvatarPreview: (name: String, url: String) -> Unit,
|
openAvatarPreview: (name: String, url: String) -> Unit,
|
||||||
openPollHistory: () -> Unit,
|
openPollHistory: () -> Unit,
|
||||||
openAdminSettings: () -> Unit,
|
openAdminSettings: () -> Unit,
|
||||||
|
onJoinCallClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun onShareMember() {
|
fun onShareMember() {
|
||||||
@@ -137,7 +137,9 @@ fun RoomDetailsView(
|
|||||||
)
|
)
|
||||||
MainActionsSection(
|
MainActionsSection(
|
||||||
state = state,
|
state = state,
|
||||||
onShareRoom = onShareRoom
|
onShareRoom = onShareRoom,
|
||||||
|
onInvitePeople = invitePeople,
|
||||||
|
onCall = onJoinCallClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,21 +190,13 @@ fun RoomDetailsView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val displayMemberListItem = state.roomType is RoomDetailsType.Room
|
val displayMemberListItem = state.roomType is RoomDetailsType.Room
|
||||||
val displayInviteMembersItem = state.canInvite
|
|
||||||
if (displayMemberListItem || displayInviteMembersItem) {
|
|
||||||
PreferenceCategory {
|
|
||||||
if (displayMemberListItem) {
|
if (displayMemberListItem) {
|
||||||
|
PreferenceCategory {
|
||||||
MembersItem(
|
MembersItem(
|
||||||
memberCount = state.memberCount,
|
memberCount = state.memberCount,
|
||||||
openRoomMemberList = openRoomMemberList,
|
openRoomMemberList = openRoomMemberList,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (displayInviteMembersItem) {
|
|
||||||
InviteItem(
|
|
||||||
invitePeople = invitePeople
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PollsSection(
|
PollsSection(
|
||||||
@@ -267,10 +261,12 @@ private fun RoomDetailsTopBar(
|
|||||||
private fun MainActionsSection(
|
private fun MainActionsSection(
|
||||||
state: RoomDetailsState,
|
state: RoomDetailsState,
|
||||||
onShareRoom: () -> Unit,
|
onShareRoom: () -> Unit,
|
||||||
|
onInvitePeople: () -> Unit,
|
||||||
|
onCall: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
) {
|
) {
|
||||||
val roomNotificationSettings = state.roomNotificationSettings
|
val roomNotificationSettings = state.roomNotificationSettings
|
||||||
if (state.canShowNotificationSettings && roomNotificationSettings != null) {
|
if (state.canShowNotificationSettings && roomNotificationSettings != null) {
|
||||||
@@ -292,9 +288,22 @@ private fun MainActionsSection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
if (state.canCall) {
|
||||||
MainActionButton(
|
MainActionButton(
|
||||||
title = stringResource(R.string.screen_room_details_share_room_title),
|
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(CommonStrings.action_share),
|
||||||
imageVector = CompoundIcons.ShareAndroid(),
|
imageVector = CompoundIcons.ShareAndroid(),
|
||||||
onClick = onShareRoom
|
onClick = onShareRoom
|
||||||
)
|
)
|
||||||
@@ -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
|
@Composable
|
||||||
private fun PollsSection(
|
private fun PollsSection(
|
||||||
openPollHistory: () -> Unit,
|
openPollHistory: () -> Unit,
|
||||||
@@ -491,5 +489,6 @@ private fun ContentToPreview(state: RoomDetailsState) {
|
|||||||
openAvatarPreview = { _, _ -> },
|
openAvatarPreview = { _, _ -> },
|
||||||
openPollHistory = {},
|
openPollHistory = {},
|
||||||
openAdminSettings = {},
|
openAdminSettings = {},
|
||||||
|
onJoinCallClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class RoomDetailsViewTest {
|
|||||||
rule.setRoomDetailView(
|
rule.setRoomDetailView(
|
||||||
onShareRoom = callback,
|
onShareRoom = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_details_share_room_title)
|
rule.clickOn(CommonStrings.action_share)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +112,8 @@ class RoomDetailsViewTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on invite people invokes expected callback`() {
|
fun `click on invite invokes expected callback`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setRoomDetailView(
|
rule.setRoomDetailView(
|
||||||
state = aRoomDetailsState(
|
state = aRoomDetailsState(
|
||||||
@@ -123,7 +122,21 @@ class RoomDetailsViewTest {
|
|||||||
),
|
),
|
||||||
invitePeople = callback,
|
invitePeople = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_details_invite_people_title)
|
rule.clickOn(CommonStrings.action_invite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `click on call invokes expected callback`() {
|
||||||
|
ensureCalledOnce { callback ->
|
||||||
|
rule.setRoomDetailView(
|
||||||
|
state = aRoomDetailsState(
|
||||||
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
|
canInvite = true,
|
||||||
|
),
|
||||||
|
onJoinCallClicked = callback,
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +271,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
|
|||||||
openAvatarPreview: (name: String, url: String) -> Unit = EnsureNeverCalledWithTwoParams(),
|
openAvatarPreview: (name: String, url: String) -> Unit = EnsureNeverCalledWithTwoParams(),
|
||||||
openPollHistory: () -> Unit = EnsureNeverCalled(),
|
openPollHistory: () -> Unit = EnsureNeverCalled(),
|
||||||
openAdminSettings: () -> Unit = EnsureNeverCalled(),
|
openAdminSettings: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onJoinCallClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
RoomDetailsView(
|
RoomDetailsView(
|
||||||
@@ -272,6 +286,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
|
|||||||
openAvatarPreview = openAvatarPreview,
|
openAvatarPreview = openAvatarPreview,
|
||||||
openPollHistory = openPollHistory,
|
openPollHistory = openPollHistory,
|
||||||
openAdminSettings = openAdminSettings,
|
openAdminSettings = openAdminSettings,
|
||||||
|
onJoinCallClicked = onJoinCallClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -53,12 +54,14 @@ fun MainActionButton(
|
|||||||
val ripple = rememberRipple(bounded = false)
|
val ripple = rememberRipple(bounded = false)
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
Column(
|
Column(
|
||||||
modifier.clickable(
|
modifier
|
||||||
|
.clickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
indication = ripple
|
indication = ripple
|
||||||
),
|
)
|
||||||
|
.widthIn(min = 76.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
val tintColor = if (enabled) LocalContentColor.current else MaterialTheme.colorScheme.secondary
|
val tintColor = if (enabled) LocalContentColor.current else MaterialTheme.colorScheme.secondary
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ fun MatrixRoom.canRedactOtherAsState(updateKey: Long): State<Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MatrixRoom.canCall(updateKey: Long): State<Boolean> {
|
||||||
|
return produceState(initialValue = false, key1 = updateKey) {
|
||||||
|
value = canUserJoinCall(sessionId).getOrElse { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MatrixRoom.isOwnUserAdmin(): Boolean {
|
fun MatrixRoom.isOwnUserAdmin(): Boolean {
|
||||||
val roomInfo by roomInfoFlow.collectAsState(initial = null)
|
val roomInfo by roomInfoFlow.collectAsState(initial = null)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<string name="action_accept">"Accept"</string>
|
<string name="action_accept">"Accept"</string>
|
||||||
<string name="action_add_to_timeline">"Add to timeline"</string>
|
<string name="action_add_to_timeline">"Add to timeline"</string>
|
||||||
<string name="action_back">"Back"</string>
|
<string name="action_back">"Back"</string>
|
||||||
|
<string name="action_call">"Call"</string>
|
||||||
<string name="action_cancel">"Cancel"</string>
|
<string name="action_cancel">"Cancel"</string>
|
||||||
<string name="action_choose_photo">"Choose photo"</string>
|
<string name="action_choose_photo">"Choose photo"</string>
|
||||||
<string name="action_clear">"Clear"</string>
|
<string name="action_clear">"Clear"</string>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user