Display a banner instead of the composer when user cannot post message to a room (#602)

This commit is contained in:
Benoit Marty 2023-06-19 11:16:55 +02:00
parent 21f59442bd
commit dfcbb33c0a
11 changed files with 199 additions and 10 deletions

View file

@ -19,6 +19,7 @@ package io.element.android.features.messages.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -55,6 +56,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.handleSnackbarMessage
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
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.textcomposer.MessageComposerMode
@ -86,6 +88,7 @@ class MessagesPresenter @Inject constructor(
val retryState = retrySendMenuPresenter.present()
val syncUpdateFlow = room.syncUpdateFlow().collectAsState(0L)
val userHasPermissionToSendMessage by getCanSendEvent(MessageEventType.ROOM_MESSAGE)
val roomName: MutableState<String?> = rememberSaveable {
mutableStateOf(null)
}
@ -125,6 +128,7 @@ class MessagesPresenter @Inject constructor(
roomId = room.roomId,
roomName = roomName.value,
roomAvatar = roomAvatar.value,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
composerState = composerState,
timelineState = timelineState,
actionListState = actionListState,
@ -218,4 +222,13 @@ class MessagesPresenter @Inject constructor(
MessageComposerEvents.SetMode(composerMode)
)
}
@Composable
private fun getCanSendEvent(type: MessageEventType): State<Boolean> {
val canSendEvent = remember(type) { mutableStateOf(false) }
LaunchedEffect(type) {
canSendEvent.value = room.canSendEvent(type).getOrElse { false }
}
return canSendEvent
}
}

View file

@ -31,6 +31,7 @@ data class MessagesState(
val roomId: RoomId,
val roomName: String?,
val roomAvatar: AvatarData?,
val userHasPermissionToSendMessage: Boolean,
val composerState: MessageComposerState,
val timelineState: TimelineState,
val actionListState: ActionListState,

View file

@ -35,6 +35,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
aMessagesState(),
aMessagesState().copy(hasNetworkConnection = false),
aMessagesState().copy(composerState = aMessageComposerState().copy(showAttachmentSourcePicker = true)),
aMessagesState().copy(userHasPermissionToSendMessage = false),
)
}
@ -42,6 +43,7 @@ fun aMessagesState() = MessagesState(
roomId = RoomId("!id:domain"),
roomName = "Room name",
roomAvatar = AvatarData("!id:domain", "Room name"),
userHasPermissionToSendMessage = true,
composerState = aMessageComposerState().copy(
text = StableCharSequence("Hello"),
isFullScreen = false,

View file

@ -16,7 +16,9 @@
package io.element.android.features.messages.impl
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
@ -34,6 +36,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -41,7 +44,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -231,12 +236,16 @@ fun MessagesViewContent(
onTimestampClicked = onTimestampClicked,
)
}
MessageComposerView(
state = state.composerState,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(Alignment.Bottom)
)
if (state.userHasPermissionToSendMessage) {
MessageComposerView(
state = state.composerState,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(Alignment.Bottom)
)
} else {
CantSendMessageBanner()
}
}
}
@ -281,6 +290,28 @@ fun MessagesViewTopBar(
)
}
@Composable
fun CantSendMessageBanner(
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.secondary)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = R.string.screen_room_no_permission_to_post),
color = MaterialTheme.colorScheme.onSecondary,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontStyle = FontStyle.Italic,
)
}
}
@Preview
@Composable
internal fun MessagesViewLightPreview(@PreviewParameter(MessagesStateProvider::class) state: MessagesState) =

View file

@ -10,8 +10,11 @@
<string name="screen_room_attachment_source_files">"Attachment"</string>
<string name="screen_room_attachment_source_gallery">"Photo &amp; Video Library"</string>
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
<string name="screen_room_invite_again_alert_message">"Would you like to invite them back?"</string>
<string name="screen_room_invite_again_alert_title">"You are alone in this chat"</string>
<string name="screen_room_no_permission_to_post">"You do not have permission to post to this room"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Send again"</string>
<string name="screen_room_retry_send_menu_title">"Your message failed to send"</string>
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
<string name="screen_room_retry_send_menu_remove_action">"Remove"</string>
</resources>
</resources>

View file

@ -46,6 +46,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
@ -166,7 +167,7 @@ class MessagesPresenterTest {
content = TimelineItemImageContent(
body = "image.jpg",
mediaSource = MediaSource(AN_AVATAR_URL),
thumbnailSource = null,
thumbnailSource = null,
mimeType = MimeTypes.Jpeg,
blurhash = null,
width = 20,
@ -318,6 +319,32 @@ class MessagesPresenterTest {
}
}
@Test
fun `present - permission to post`() = runTest {
val matrixRoom = FakeMatrixRoom()
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true))
val presenter = createMessagePresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
}
}
@Test
fun `present - no permission to post`() = runTest {
val matrixRoom = FakeMatrixRoom()
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false))
val presenter = createMessagePresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
}
}
private fun TestScope.createMessagePresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
matrixRoom: MatrixRoom = FakeMatrixRoom()