Add room badges (#2822)

* Add room badges

* Remove no longer used `onShareRoomMember` callback

* Update screenshots

* Add changelog

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-05-09 08:38:40 +02:00 committed by GitHub
parent 4c1bd2d45b
commit 48bb0f4122
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
188 changed files with 355 additions and 80 deletions

View file

@ -32,9 +32,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -48,7 +46,6 @@ class RoomDetailsNode @AssistedInject constructor(
private val presenter: RoomDetailsPresenter,
private val room: MatrixRoom,
private val analyticsService: AnalyticsService,
private val permalinkBuilder: PermalinkBuilder,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun openRoomMemberList()
@ -106,22 +103,6 @@ class RoomDetailsNode @AssistedInject constructor(
}
}
private fun onShareMember(context: Context, member: RoomMember) {
val permalinkResult = permalinkBuilder.permalinkForUser(member.userId)
permalinkResult
.onSuccess { permalink ->
context.startSharePlainTextIntent(
activityResultLauncher = null,
chooserTitle = context.getString(R.string.screen_room_details_share_room_title),
text = permalink,
noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found)
)
}
.onFailure {
Timber.e(it)
}
}
private fun onEditRoomDetails() {
callbacks.forEach { it.editRoomDetails() }
}
@ -143,10 +124,6 @@ class RoomDetailsNode @AssistedInject constructor(
lifecycleScope.onShareRoom(context)
}
fun onShareMember(roomMember: RoomMember) {
this.onShareMember(context, roomMember)
}
fun onActionClicked(action: RoomDetailsAction) {
when (action) {
RoomDetailsAction.Edit -> onEditRoomDetails()
@ -160,7 +137,6 @@ class RoomDetailsNode @AssistedInject constructor(
goBack = this::navigateUp,
onActionClicked = ::onActionClicked,
onShareRoom = ::onShareRoom,
onShareMember = ::onShareMember,
openRoomMemberList = ::openRoomMemberList,
openRoomNotificationSettings = ::openRoomNotificationSettings,
invitePeople = ::invitePeople,

View file

@ -78,6 +78,7 @@ class RoomDetailsPresenter @Inject constructor(
val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.name ?: room.displayName).trim() } }
val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } }
val isFavorite by remember { derivedStateOf { roomInfo?.isFavorite.orFalse() } }
val isPublic by remember { derivedStateOf { roomInfo?.isPublic.orFalse() } }
LaunchedEffect(Unit) {
canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
@ -149,6 +150,7 @@ class RoomDetailsPresenter @Inject constructor(
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
isFavorite = isFavorite,
displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin,
isPublic = isPublic,
eventSink = ::handleEvents,
)
}

View file

@ -41,6 +41,7 @@ data class RoomDetailsState(
val roomNotificationSettings: RoomNotificationSettings?,
val isFavorite: Boolean,
val displayRolesAndPermissionsSettings: Boolean,
val isPublic: Boolean,
val eventSink: (RoomDetailsEvent) -> Unit
)

View file

@ -47,6 +47,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
roomNotificationSettings = aRoomNotificationSettings(mode = RoomNotificationMode.ALL_MESSAGES, isDefault = true)
),
aRoomDetailsState(canCall = false, canInvite = false),
aRoomDetailsState(isPublic = false),
// Add other state here
)
}
@ -97,6 +98,7 @@ fun aRoomDetailsState(
roomNotificationSettings: RoomNotificationSettings = aRoomNotificationSettings(),
isFavorite: Boolean = false,
displayAdminSettings: Boolean = false,
isPublic: Boolean = true,
eventSink: (RoomDetailsEvent) -> Unit = {},
) = RoomDetailsState(
roomId = roomId,
@ -116,6 +118,7 @@ fun aRoomDetailsState(
roomNotificationSettings = roomNotificationSettings,
isFavorite = isFavorite,
displayRolesAndPermissionsSettings = displayAdminSettings,
isPublic = isPublic,
eventSink = eventSink
)

View file

@ -48,6 +48,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.leaveroom.api.LeaveRoomView
import io.element.android.features.roomdetails.impl.components.RoomBadge
import io.element.android.features.userprofile.shared.UserProfileHeaderSection
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
@ -78,7 +79,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.testtags.TestTags
@ -91,7 +91,6 @@ fun RoomDetailsView(
goBack: () -> Unit,
onActionClicked: (RoomDetailsAction) -> Unit,
onShareRoom: () -> Unit,
onShareMember: (RoomMember) -> Unit,
openRoomMemberList: () -> Unit,
openRoomNotificationSettings: () -> Unit,
invitePeople: () -> Unit,
@ -101,10 +100,6 @@ fun RoomDetailsView(
onJoinCallClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onShareMember() {
onShareMember((state.roomType as RoomDetailsType.Dm).roomMember)
}
Scaffold(
modifier = modifier,
topBar = {
@ -130,6 +125,8 @@ fun RoomDetailsView(
roomId = state.roomId,
roomName = state.roomName,
roomAlias = state.roomAlias,
isEncrypted = state.isEncrypted,
isPublic = state.isPublic,
openAvatarPreview = { avatarUrl ->
openAvatarPreview(state.roomName, avatarUrl)
},
@ -160,7 +157,7 @@ fun RoomDetailsView(
)
}
}
Spacer(Modifier.height(18.dp))
Spacer(Modifier.height(12.dp))
if (state.roomTopic !is RoomTopicState.Hidden) {
TopicSection(
@ -269,7 +266,9 @@ private fun MainActionsSection(
onCall: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
val roomNotificationSettings = state.roomNotificationSettings
@ -323,6 +322,8 @@ private fun RoomHeaderSection(
roomId: RoomId,
roomName: String,
roomAlias: RoomAlias?,
isEncrypted: Boolean,
isPublic: Boolean,
openAvatarPreview: (url: String) -> Unit,
) {
Column(
@ -353,10 +354,46 @@ private fun RoomHeaderSection(
textAlign = TextAlign.Center,
)
}
BadgeList(isEncrypted = isEncrypted, isPublic = isPublic)
Spacer(Modifier.height(32.dp))
}
}
@Composable
private fun BadgeList(
isEncrypted: Boolean,
isPublic: Boolean,
) {
if (isEncrypted || isPublic) {
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (isEncrypted) {
RoomBadge.View(
text = stringResource(R.string.screen_room_details_badge_encrypted),
icon = CompoundIcons.LockSolid(),
type = RoomBadge.Type.Positive,
)
} else {
RoomBadge.View(
text = stringResource(R.string.screen_room_details_badge_not_encrypted),
icon = CompoundIcons.LockOff(),
type = RoomBadge.Type.Neutral,
)
}
if (isPublic) {
RoomBadge.View(
text = stringResource(R.string.screen_room_details_badge_public),
icon = CompoundIcons.Public(),
type = RoomBadge.Type.Neutral,
)
}
}
}
}
@Composable
private fun TopicSection(
roomTopic: RoomTopicState,
@ -489,7 +526,6 @@ private fun ContentToPreview(state: RoomDetailsState) {
goBack = {},
onActionClicked = {},
onShareRoom = {},
onShareMember = {},
openRoomMemberList = {},
openRoomNotificationSettings = {},
invitePeople = {},

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomdetails.impl.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.components.Badge
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.badgeNegativeBackgroundColor
import io.element.android.libraries.designsystem.theme.badgeNegativeContentColor
import io.element.android.libraries.designsystem.theme.badgeNeutralBackgroundColor
import io.element.android.libraries.designsystem.theme.badgeNeutralContentColor
import io.element.android.libraries.designsystem.theme.badgePositiveBackgroundColor
import io.element.android.libraries.designsystem.theme.badgePositiveContentColor
object RoomBadge {
enum class Type {
Positive,
Neutral,
Negative
}
@Composable fun View(
text: String,
icon: ImageVector,
type: Type,
) {
val backgroundColor = when (type) {
Type.Positive -> ElementTheme.colors.badgePositiveBackgroundColor
Type.Neutral -> ElementTheme.colors.badgeNeutralBackgroundColor
Type.Negative -> ElementTheme.colors.badgeNegativeBackgroundColor
}
val textColor = when (type) {
Type.Positive -> ElementTheme.colors.badgePositiveContentColor
Type.Neutral -> ElementTheme.colors.badgeNeutralContentColor
Type.Negative -> ElementTheme.colors.badgeNegativeContentColor
}
val iconColor = when (type) {
Type.Positive -> ElementTheme.colors.iconSuccessPrimary
Type.Neutral -> ElementTheme.colors.iconSecondary
Type.Negative -> ElementTheme.colors.iconCriticalPrimary
}
Badge(
text = text,
icon = icon,
backgroundColor = backgroundColor,
iconColor = iconColor,
textColor = textColor,
)
}
}
@PreviewsDayNight
@Composable
internal fun RoomBadgePositivePreview() {
ElementPreview {
RoomBadge.View(
text = "Trusted",
icon = CompoundIcons.Verified(),
type = RoomBadge.Type.Positive,
)
}
}
@PreviewsDayNight
@Composable
internal fun RoomBadgeNeutralPreview() {
ElementPreview {
RoomBadge.View(
text = "Public room",
icon = CompoundIcons.Public(),
type = RoomBadge.Type.Neutral,
)
}
}
@PreviewsDayNight
@Composable
internal fun RoomBadgeNegativePreview() {
ElementPreview {
RoomBadge.View(
text = "Not trusted",
icon = CompoundIcons.Error(),
type = RoomBadge.Type.Negative,
)
}
}

View file

@ -35,6 +35,9 @@
<string name="screen_room_details_add_topic_title">"Add topic"</string>
<string name="screen_room_details_already_a_member">"Already a member"</string>
<string name="screen_room_details_already_invited">"Already invited"</string>
<string name="screen_room_details_badge_encrypted">"Encrypted"</string>
<string name="screen_room_details_badge_not_encrypted">"Not encrypted"</string>
<string name="screen_room_details_badge_public">"Public room"</string>
<string name="screen_room_details_edit_room_title">"Edit Room"</string>
<string name="screen_room_details_edition_error">"There was an unknown error and the information couldn\'t be changed."</string>
<string name="screen_room_details_edition_error_title">"Unable to update room"</string>

View file

@ -23,7 +23,6 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings
@ -251,7 +250,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
goBack: () -> Unit = EnsureNeverCalled(),
onActionClicked: (RoomDetailsAction) -> Unit = EnsureNeverCalledWithParam(),
onShareRoom: () -> Unit = EnsureNeverCalled(),
onShareMember: (RoomMember) -> Unit = EnsureNeverCalledWithParam(),
openRoomMemberList: () -> Unit = EnsureNeverCalled(),
openRoomNotificationSettings: () -> Unit = EnsureNeverCalled(),
invitePeople: () -> Unit = EnsureNeverCalled(),
@ -266,7 +264,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
goBack = goBack,
onActionClicked = onActionClicked,
onShareRoom = onShareRoom,
onShareMember = onShareMember,
openRoomMemberList = openRoomMemberList,
openRoomNotificationSettings = openRoomNotificationSettings,
invitePeople = invitePeople,