misc(power level) : use new api
This commit is contained in:
parent
2dc8018c37
commit
ee38fb5f63
29 changed files with 312 additions and 334 deletions
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.knockrequests.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||||
|
|
||||||
|
data class KnockRequestPermissions(
|
||||||
|
val canAccept: Boolean,
|
||||||
|
val canDecline: Boolean,
|
||||||
|
val canBan: Boolean,
|
||||||
|
) {
|
||||||
|
val hasAny = canAccept || canDecline || canBan
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = KnockRequestPermissions(
|
||||||
|
canAccept = false,
|
||||||
|
canDecline = false,
|
||||||
|
canBan = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoomPermissions.knockRequestPermissions(): KnockRequestPermissions {
|
||||||
|
return KnockRequestPermissions(
|
||||||
|
canAccept = canOwnUserInvite(),
|
||||||
|
canDecline = canOwnUserKick(),
|
||||||
|
canBan = canOwnUserBan(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ class KnockRequestsBannerPresenter(
|
||||||
|
|
||||||
val shouldShowBanner by remember {
|
val shouldShowBanner by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
permissions.canHandle && knockRequests.isNotEmpty()
|
permissions.hasAny && knockRequests.isNotEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.data
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
data class KnockRequestPermissions(
|
|
||||||
val canAccept: Boolean,
|
|
||||||
val canDecline: Boolean,
|
|
||||||
val canBan: Boolean,
|
|
||||||
) {
|
|
||||||
val canHandle = canAccept || canDecline || canBan
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JoinedRoom.knockRequestPermissionsFlow(): Flow<KnockRequestPermissions> {
|
|
||||||
return syncUpdateFlow.map {
|
|
||||||
val canAccept = canInvite().getOrDefault(false)
|
|
||||||
val canDecline = canKick().getOrDefault(false)
|
|
||||||
val canBan = canBan().getOrDefault(false)
|
|
||||||
KnockRequestPermissions(canAccept, canDecline, canBan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,10 +12,13 @@ import dev.zacsweers.metro.BindingContainer
|
||||||
import dev.zacsweers.metro.ContributesTo
|
import dev.zacsweers.metro.ContributesTo
|
||||||
import dev.zacsweers.metro.Provides
|
import dev.zacsweers.metro.Provides
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
|
import io.element.android.features.knockrequests.api.knockRequestPermissions
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
|
||||||
|
|
||||||
@BindingContainer
|
@BindingContainer
|
||||||
@ContributesTo(RoomScope::class)
|
@ContributesTo(RoomScope::class)
|
||||||
|
|
@ -25,7 +28,9 @@ object KnockRequestsModule {
|
||||||
fun knockRequestsService(room: JoinedRoom, featureFlagService: FeatureFlagService): KnockRequestsService {
|
fun knockRequestsService(room: JoinedRoom, featureFlagService: FeatureFlagService): KnockRequestsService {
|
||||||
return KnockRequestsService(
|
return KnockRequestsService(
|
||||||
knockRequestsFlow = room.knockRequestsFlow,
|
knockRequestsFlow = room.knockRequestsFlow,
|
||||||
permissionsFlow = room.knockRequestPermissionsFlow(),
|
permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms ->
|
||||||
|
perms.knockRequestPermissions()
|
||||||
|
},
|
||||||
isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock),
|
isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock),
|
||||||
coroutineScope = room.roomCoroutineScope
|
coroutineScope = room.roomCoroutineScope
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.data
|
package io.element.android.features.knockrequests.impl.data
|
||||||
|
|
||||||
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
package io.element.android.features.knockrequests.impl.list
|
package io.element.android.features.knockrequests.impl.list
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
package io.element.android.features.knockrequests.impl.list
|
package io.element.android.features.knockrequests.impl.list
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
package io.element.android.features.knockrequests.impl.banner
|
package io.element.android.features.knockrequests.impl.banner
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
package io.element.android.features.knockrequests.impl.list
|
package io.element.android.features.knockrequests.impl.list
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
import io.element.android.features.knockrequests.api.KnockRequestPermissions
|
||||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ import android.os.Build
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
|
@ -76,14 +74,10 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
|
@ -170,7 +164,9 @@ class MessagesPresenter(
|
||||||
val roomCallState = roomCallStatePresenter.present()
|
val roomCallState = roomCallStatePresenter.present()
|
||||||
val roomMemberModerationState = roomMemberModerationPresenter.present()
|
val roomMemberModerationState = roomMemberModerationPresenter.present()
|
||||||
|
|
||||||
val userEventPermissions by userEventPermissions(roomInfo)
|
val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms ->
|
||||||
|
perms.userEventPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
val roomAvatar by remember {
|
val roomAvatar by remember {
|
||||||
derivedStateOf { roomInfo.avatarData() }
|
derivedStateOf { roomInfo.avatarData() }
|
||||||
|
|
@ -301,24 +297,6 @@ class MessagesPresenter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun userEventPermissions(roomInfo: RoomInfo): State<UserEventPermissions> {
|
|
||||||
val key = if (roomInfo.privilegedCreatorRole && roomInfo.creators.contains(room.sessionId)) {
|
|
||||||
Long.MAX_VALUE
|
|
||||||
} else {
|
|
||||||
roomInfo.roomPowerLevels?.hashCode() ?: 0L
|
|
||||||
}
|
|
||||||
return produceState(UserEventPermissions.DEFAULT, key1 = key) {
|
|
||||||
value = UserEventPermissions(
|
|
||||||
canSendMessage = room.canSendMessage(type = MessageEventType.RoomMessage).getOrElse { true },
|
|
||||||
canSendReaction = room.canSendMessage(type = MessageEventType.Reaction).getOrElse { true },
|
|
||||||
canRedactOwn = room.canRedactOwn().getOrElse { false },
|
|
||||||
canRedactOther = room.canRedactOther().getOrElse { false },
|
|
||||||
canPinUnpin = room.canPinUnpin().getOrElse { false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun RoomInfo.avatarData(): AvatarData {
|
private fun RoomInfo.avatarData(): AvatarData {
|
||||||
return AvatarData(
|
return AvatarData(
|
||||||
id = id.value,
|
id = id.value,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the permissions a user has in a room.
|
* Represents the permissions a user has in a room.
|
||||||
* It's dependent of the user's power level in the room.
|
* It's dependent of the user's power level in the room.
|
||||||
|
|
@ -29,3 +32,13 @@ data class UserEventPermissions(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RoomPermissions.userEventPermissions(): UserEventPermissions {
|
||||||
|
return UserEventPermissions(
|
||||||
|
canRedactOwn = canOwnUserRedactOwn(),
|
||||||
|
canRedactOther = canOwnUserRedactOther(),
|
||||||
|
canSendMessage = canOwnUserSendMessage(MessageEventType.RoomMessage),
|
||||||
|
canSendReaction = canOwnUserSendMessage(MessageEventType.Reaction),
|
||||||
|
canPinUnpin = canOwnUserPinUnpin()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||||
import io.element.android.libraries.matrix.api.room.getDirectRoomMember
|
import io.element.android.libraries.matrix.api.room.getDirectRoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.use
|
||||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||||
|
|
@ -396,7 +397,9 @@ class MessageComposerPresenter(
|
||||||
val currentUserId = room.sessionId
|
val currentUserId = room.sessionId
|
||||||
|
|
||||||
suspend fun canSendRoomMention(): Boolean {
|
suspend fun canSendRoomMention(): Boolean {
|
||||||
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
|
val userCanSendAtRoom = room.roomPermissions().use(false){ perms ->
|
||||||
|
perms.canOwnUserTriggerRoomNotification()
|
||||||
|
}
|
||||||
return !room.isDm() && userCanSendAtRoom
|
return !room.isDm() && userCanSendAtRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,10 @@ package io.element.android.features.messages.impl.pinned.list
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -35,6 +34,7 @@ import io.element.android.features.messages.impl.timeline.factories.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
|
import io.element.android.features.messages.impl.userEventPermissions
|
||||||
import io.element.android.features.roomcall.api.aStandByCallState
|
import io.element.android.features.roomcall.api.aStandByCallState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -44,11 +44,9 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import io.element.android.libraries.matrix.ui.room.isDmAsState
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||||
|
|
@ -97,31 +95,33 @@ class PinnedMessagesListPresenter(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PinnedMessagesListState {
|
override fun present(): PinnedMessagesListState {
|
||||||
htmlConverterProvider.Update()
|
htmlConverterProvider.Update()
|
||||||
val isDm by room.isDmAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
|
val timelineRoomInfo by remember {
|
||||||
val timelineRoomInfo = remember(isDm) {
|
derivedStateOf {
|
||||||
TimelineRoomInfo(
|
TimelineRoomInfo(
|
||||||
isDm = isDm,
|
isDm = roomInfo.isDm,
|
||||||
name = room.info().name,
|
name = roomInfo.name,
|
||||||
// We don't need to compute those values
|
// We don't need to compute those values
|
||||||
userHasPermissionToSendMessage = false,
|
userHasPermissionToSendMessage = false,
|
||||||
userHasPermissionToSendReaction = false,
|
userHasPermissionToSendReaction = false,
|
||||||
// We do not care about the call state here.
|
// We do not care about the call state here.
|
||||||
roomCallState = aStandByCallState(),
|
roomCallState = aStandByCallState(),
|
||||||
// don't compute this value or the pin icon will be shown
|
// don't compute this value or the pin icon will be shown
|
||||||
pinnedEventIds = persistentListOf(),
|
pinnedEventIds = persistentListOf(),
|
||||||
typingNotificationState = TypingNotificationState(
|
typingNotificationState = TypingNotificationState(
|
||||||
renderTypingNotifications = false,
|
renderTypingNotifications = false,
|
||||||
typingMembers = persistentListOf(),
|
typingMembers = persistentListOf(),
|
||||||
reserveSpace = false,
|
reserveSpace = false,
|
||||||
),
|
),
|
||||||
predecessorRoom = room.predecessorRoom(),
|
predecessorRoom = room.predecessorRoom(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val timelineProtectionState = timelineProtectionPresenter.present()
|
val timelineProtectionState = timelineProtectionPresenter.present()
|
||||||
val linkState = linkPresenter.present()
|
val linkState = linkPresenter.present()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms ->
|
||||||
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
|
perms.userEventPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
|
val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
|
||||||
|
|
||||||
|
|
@ -192,19 +192,6 @@ class PinnedMessagesListPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun userEventPermissions(updateKey: Long): State<UserEventPermissions> {
|
|
||||||
return produceState(UserEventPermissions.DEFAULT, key1 = updateKey) {
|
|
||||||
value = UserEventPermissions(
|
|
||||||
canSendMessage = false,
|
|
||||||
canSendReaction = false,
|
|
||||||
canRedactOwn = room.canRedactOwn().getOrElse { false },
|
|
||||||
canRedactOther = room.canRedactOther().getOrElse { false },
|
|
||||||
canPinUnpin = room.canPinUnpin().getOrElse { false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PinnedMessagesListEffect(onItemsChange: (AsyncData<ImmutableList<TimelineItem>>) -> Unit) {
|
private fun PinnedMessagesListEffect(onItemsChange: (AsyncData<ImmutableList<TimelineItem>>) -> Unit) {
|
||||||
val updatedOnItemsChange by rememberUpdatedState(onItemsChange)
|
val updatedOnItemsChange by rememberUpdatedState(onItemsChange)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.features.messages.impl.MessagesNavigator
|
import io.element.android.features.messages.impl.MessagesNavigator
|
||||||
|
import io.element.android.features.messages.impl.UserEventPermissions
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||||
|
|
@ -32,6 +33,7 @@ import io.element.android.features.messages.impl.timeline.model.NewEventState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
|
import io.element.android.features.messages.impl.userEventPermissions
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||||
import io.element.android.features.poll.api.actions.EndPollAction
|
import io.element.android.features.poll.api.actions.EndPollAction
|
||||||
import io.element.android.features.poll.api.actions.SendPollResponseAction
|
import io.element.android.features.poll.api.actions.SendPollResponseAction
|
||||||
|
|
@ -46,14 +48,13 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
import io.element.android.libraries.matrix.api.core.asEventId
|
import io.element.android.libraries.matrix.api.core.asEventId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems
|
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems
|
||||||
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.NotificationTapOpensTimeline
|
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.NotificationTapOpensTimeline
|
||||||
|
|
@ -128,11 +129,6 @@ class TimelinePresenter(
|
||||||
|
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
|
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
|
||||||
|
|
||||||
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.RoomMessage, updateKey = syncUpdateFlow.value)
|
|
||||||
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.Reaction, updateKey = syncUpdateFlow.value)
|
|
||||||
|
|
||||||
val prevMostRecentItemId = rememberSaveable { mutableStateOf<UniqueId?>(null) }
|
val prevMostRecentItemId = rememberSaveable { mutableStateOf<UniqueId?>(null) }
|
||||||
|
|
||||||
val newEventState = remember { mutableStateOf(NewEventState.None) }
|
val newEventState = remember { mutableStateOf(NewEventState.None) }
|
||||||
|
|
@ -285,13 +281,16 @@ class TimelinePresenter(
|
||||||
|
|
||||||
val typingNotificationState = typingNotificationPresenter.present()
|
val typingNotificationState = typingNotificationPresenter.present()
|
||||||
val roomCallState = roomCallStatePresenter.present()
|
val roomCallState = roomCallStatePresenter.present()
|
||||||
|
val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms ->
|
||||||
|
perms.userEventPermissions()
|
||||||
|
}
|
||||||
val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) {
|
val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
TimelineRoomInfo(
|
TimelineRoomInfo(
|
||||||
name = roomInfo.name,
|
name = roomInfo.name,
|
||||||
isDm = roomInfo.isDm.orFalse(),
|
isDm = roomInfo.isDm,
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userHasPermissionToSendMessage = userEventPermissions.canSendMessage,
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
userHasPermissionToSendReaction = userEventPermissions.canSendReaction,
|
||||||
roomCallState = roomCallState,
|
roomCallState = roomCallState,
|
||||||
pinnedEventIds = roomInfo.pinnedEventIds,
|
pinnedEventIds = roomInfo.pinnedEventIds,
|
||||||
typingNotificationState = typingNotificationState,
|
typingNotificationState = typingNotificationState,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||||
import io.element.android.features.roomcall.api.RoomCallState
|
import io.element.android.features.roomcall.api.RoomCallState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.ui.room.canCall
|
import io.element.android.libraries.matrix.api.room.powerlevels.canCall
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class RoomCallStatePresenter(
|
class RoomCallStatePresenter(
|
||||||
|
|
@ -35,8 +36,7 @@ class RoomCallStatePresenter(
|
||||||
value = sessionEnterpriseService.isElementCallAvailable()
|
value = sessionEnterpriseService.isElementCallAvailable()
|
||||||
}
|
}
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val canJoinCall by room.permissionsAsState(false) { perms -> perms.canCall() }
|
||||||
val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value)
|
|
||||||
val isUserInTheCall by remember {
|
val isUserInTheCall by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
room.sessionId in roomInfo.activeRoomCallParticipants
|
room.sessionId in roomInfo.activeRoomCallParticipants
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -18,11 +19,14 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
import io.element.android.features.knockrequests.api.knockRequestPermissions
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.features.roomcall.api.RoomCallState
|
import io.element.android.features.roomcall.api.RoomCallState
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||||
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissionsAsState
|
import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions
|
||||||
|
import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions
|
||||||
|
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
||||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
|
@ -36,17 +40,13 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
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.canHandleKnockRequestsAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember
|
import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember
|
||||||
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.isDmAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
|
|
||||||
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -77,8 +77,6 @@ class RoomDetailsPresenter(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val leaveRoomState = leaveRoomPresenter.present()
|
val leaveRoomState = leaveRoomPresenter.present()
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
val isUserAdmin = room.isOwnUserAdmin()
|
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
|
||||||
val roomAvatar by remember { derivedStateOf { roomInfo.avatarUrl } }
|
val roomAvatar by remember { derivedStateOf { roomInfo.avatarUrl } }
|
||||||
|
|
||||||
val roomName by remember { derivedStateOf { roomInfo.name?.trim().orEmpty() } }
|
val roomName by remember { derivedStateOf { roomInfo.name?.trim().orEmpty() } }
|
||||||
|
|
@ -93,15 +91,11 @@ class RoomDetailsPresenter(
|
||||||
observeNotificationSettings()
|
observeNotificationSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isDm = roomInfo.isDm
|
||||||
val membersState by room.membersStateFlow.collectAsState()
|
val membersState by room.membersStateFlow.collectAsState()
|
||||||
val canInvite by getCanInvite(membersState)
|
val permissions by getPermissions()
|
||||||
|
|
||||||
val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } }
|
val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } }
|
||||||
val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } }
|
val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } }
|
||||||
val isDm by room.isDmAsState()
|
|
||||||
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 dmMember by room.getDirectRoomMember(membersState)
|
val dmMember by room.getDirectRoomMember(membersState)
|
||||||
val currentMember by room.getCurrentRoomMember(membersState)
|
val currentMember by room.getCurrentRoomMember(membersState)
|
||||||
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
|
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
|
||||||
|
|
@ -109,16 +103,15 @@ class RoomDetailsPresenter(
|
||||||
val roomCallState = roomCallStatePresenter.present()
|
val roomCallState = roomCallStatePresenter.present()
|
||||||
val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } }
|
val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } }
|
||||||
|
|
||||||
val topicState = remember(canEditTopic, roomTopic, roomType) {
|
val topicState = remember(permissions.editDetailsPermissions.canEditTopic, roomTopic, roomType) {
|
||||||
val topic = roomTopic
|
val topic = roomTopic
|
||||||
when {
|
when {
|
||||||
!topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic)
|
!topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic)
|
||||||
canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic
|
permissions.editDetailsPermissions.canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic
|
||||||
else -> RoomTopicState.Hidden
|
else -> RoomTopicState.Hidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value)
|
|
||||||
val isKnockRequestsEnabled by remember {
|
val isKnockRequestsEnabled by remember {
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
||||||
}.collectAsState(false)
|
}.collectAsState(false)
|
||||||
|
|
@ -126,7 +119,7 @@ class RoomDetailsPresenter(
|
||||||
room.knockRequestsFlow.collect { value = it.size }
|
room.knockRequestsFlow.collect { value = it.size }
|
||||||
}
|
}
|
||||||
val canShowKnockRequests by remember {
|
val canShowKnockRequests by remember {
|
||||||
derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests && joinRule == JoinRule.Knock }
|
derivedStateOf { isKnockRequestsEnabled && permissions.canManageKnockRequests && joinRule == JoinRule.Knock }
|
||||||
}
|
}
|
||||||
val isDeveloperModeEnabled by remember {
|
val isDeveloperModeEnabled by remember {
|
||||||
appPreferencesStore.isDeveloperModeEnabledFlow()
|
appPreferencesStore.isDeveloperModeEnabledFlow()
|
||||||
|
|
@ -162,13 +155,6 @@ class RoomDetailsPresenter(
|
||||||
|
|
||||||
val roomMemberDetailsState = roomMemberDetailsPresenter?.present()
|
val roomMemberDetailsState = roomMemberDetailsPresenter?.present()
|
||||||
|
|
||||||
val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
|
|
||||||
val canShowSecurityAndPrivacy by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasMemberVerificationViolations by produceState(false) {
|
val hasMemberVerificationViolations by produceState(false) {
|
||||||
room.roomMemberIdentityStateChange(waitForEncryption = true)
|
room.roomMemberIdentityStateChange(waitForEncryption = true)
|
||||||
.onEach { identities -> value = identities.any { it.identityState == IdentityState.VerificationViolation } }
|
.onEach { identities -> value = identities.any { it.identityState == IdentityState.VerificationViolation } }
|
||||||
|
|
@ -185,22 +171,22 @@ class RoomDetailsPresenter(
|
||||||
roomTopic = topicState,
|
roomTopic = topicState,
|
||||||
memberCount = joinedMemberCount,
|
memberCount = joinedMemberCount,
|
||||||
isEncrypted = isEncrypted,
|
isEncrypted = isEncrypted,
|
||||||
canInvite = canInvite,
|
canInvite = permissions.canInvite,
|
||||||
canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
|
canEdit = roomType == RoomDetailsType.Room && permissions.editDetailsPermissions.hasAny,
|
||||||
roomCallState = roomCallState,
|
roomCallState = roomCallState,
|
||||||
roomType = roomType,
|
roomType = roomType,
|
||||||
roomMemberDetailsState = roomMemberDetailsState,
|
roomMemberDetailsState = roomMemberDetailsState,
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
|
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
displayRolesAndPermissionsSettings = !isDm && isUserAdmin,
|
displayRolesAndPermissionsSettings = !isDm && permissions.canEditRolesAndPermissions,
|
||||||
isPublic = joinRule == JoinRule.Public,
|
isPublic = joinRule == JoinRule.Public,
|
||||||
heroes = roomInfo.heroes.toImmutableList(),
|
heroes = roomInfo.heroes.toImmutableList(),
|
||||||
pinnedMessagesCount = pinnedMessagesCount,
|
pinnedMessagesCount = pinnedMessagesCount,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
canShowKnockRequests = canShowKnockRequests,
|
canShowKnockRequests = canShowKnockRequests,
|
||||||
knockRequestsCount = knockRequestsCount,
|
knockRequestsCount = knockRequestsCount,
|
||||||
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
|
canShowSecurityAndPrivacy = !isDm && permissions.canEditSecurityAndPrivacy,
|
||||||
hasMemberVerificationViolations = hasMemberVerificationViolations,
|
hasMemberVerificationViolations = hasMemberVerificationViolations,
|
||||||
canReportRoom = canReportRoom,
|
canReportRoom = canReportRoom,
|
||||||
isTombstoned = roomInfo.successorRoom != null,
|
isTombstoned = roomInfo.successorRoom != null,
|
||||||
|
|
@ -232,14 +218,25 @@ class RoomDetailsPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
private data class Permissions(
|
||||||
private fun getCanInvite(membersState: RoomMembersState) = produceState(false, membersState) {
|
val canInvite: Boolean = false,
|
||||||
value = room.canInvite().getOrElse { false }
|
val editDetailsPermissions: RoomDetailsEditPermissions = RoomDetailsEditPermissions.DEFAULT,
|
||||||
}
|
val canManageKnockRequests: Boolean = false,
|
||||||
|
val canEditRolesAndPermissions: Boolean = false,
|
||||||
|
val canEditSecurityAndPrivacy: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getCanSendState(membersState: RoomMembersState, type: StateEventType) = produceState(false, membersState) {
|
private fun getPermissions(): State<Permissions> {
|
||||||
value = room.canSendState(type).getOrElse { false }
|
return room.permissionsAsState(Permissions()) { perms ->
|
||||||
|
Permissions(
|
||||||
|
canInvite = perms.canOwnUserInvite(),
|
||||||
|
editDetailsPermissions = perms.roomDetailsEditPermissions(),
|
||||||
|
canManageKnockRequests = perms.knockRequestPermissions().hasAny,
|
||||||
|
canEditRolesAndPermissions = perms.canEditRolesAndPermissions(),
|
||||||
|
canEditSecurityAndPrivacy = perms.securityAndPrivacyPermissions().hasAny,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.observeNotificationSettings() {
|
private fun CoroutineScope.observeNotificationSettings() {
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator
|
import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator
|
||||||
import io.element.android.libraries.matrix.ui.room.canInviteAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
|
@ -58,8 +58,7 @@ class RoomMemberListPresenter(
|
||||||
override fun present(): RoomMemberListState {
|
override fun present(): RoomMemberListState {
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
val membersState by room.membersStateFlow.collectAsState()
|
val membersState by room.membersStateFlow.collectAsState()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val canInvite by room.permissionsAsState(false) { perms -> perms.canOwnUserInvite() }
|
||||||
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
|
||||||
val roomModerationState = roomMembersModerationPresenter.present()
|
val roomModerationState = roomMembersModerationPresenter.present()
|
||||||
|
|
||||||
val roomMemberIdentityStates by produceState(persistentMapOf()) {
|
val roomMemberIdentityStates by produceState(persistentMapOf()) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomdetailsedit.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||||
|
|
||||||
|
data class RoomDetailsEditPermissions(
|
||||||
|
val canEditName: Boolean,
|
||||||
|
val canEditTopic: Boolean,
|
||||||
|
val canEditAvatar: Boolean,
|
||||||
|
){
|
||||||
|
val hasAny = canEditName ||
|
||||||
|
canEditTopic ||
|
||||||
|
canEditAvatar
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = RoomDetailsEditPermissions(
|
||||||
|
canEditName = false,
|
||||||
|
canEditTopic = false,
|
||||||
|
canEditAvatar = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoomPermissions.roomDetailsEditPermissions(): RoomDetailsEditPermissions {
|
||||||
|
return RoomDetailsEditPermissions(
|
||||||
|
canEditName = canOwnUserSendState(StateEventType.ROOM_NAME),
|
||||||
|
canEditTopic = canOwnUserSendState(StateEventType.ROOM_TOPIC),
|
||||||
|
canEditAvatar = canOwnUserSendState(StateEventType.ROOM_AVATAR),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions
|
||||||
|
import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions
|
||||||
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -30,8 +32,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
|
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
|
||||||
|
|
@ -93,14 +94,8 @@ class RoomDetailsEditPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var canChangeName by remember { mutableStateOf(false) }
|
val permissions by room.permissionsAsState(RoomDetailsEditPermissions.DEFAULT){perms ->
|
||||||
var canChangeTopic by remember { mutableStateOf(false) }
|
perms.roomDetailsEditPermissions()
|
||||||
var canChangeAvatar by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
LaunchedEffect(roomSyncUpdateFlow.value) {
|
|
||||||
canChangeName = room.canSendState(StateEventType.ROOM_NAME).getOrElse { false }
|
|
||||||
canChangeTopic = room.canSendState(StateEventType.ROOM_TOPIC).getOrElse { false }
|
|
||||||
canChangeAvatar = room.canSendState(StateEventType.ROOM_AVATAR).getOrElse { false }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
||||||
|
|
@ -181,11 +176,11 @@ class RoomDetailsEditPresenter(
|
||||||
return RoomDetailsEditState(
|
return RoomDetailsEditState(
|
||||||
roomId = room.roomId,
|
roomId = room.roomId,
|
||||||
roomRawName = roomRawNameEdited,
|
roomRawName = roomRawNameEdited,
|
||||||
canChangeName = canChangeName,
|
canChangeName = permissions.canEditName,
|
||||||
roomTopic = roomTopicEdited,
|
roomTopic = roomTopicEdited,
|
||||||
canChangeTopic = canChangeTopic,
|
canChangeTopic = permissions.canEditTopic,
|
||||||
roomAvatarUrl = roomAvatarUriEdited,
|
roomAvatarUrl = roomAvatarUriEdited,
|
||||||
canChangeAvatar = canChangeAvatar,
|
canChangeAvatar = permissions.canEditAvatar,
|
||||||
avatarActions = avatarActions,
|
avatarActions = avatarActions,
|
||||||
saveButtonEnabled = saveButtonEnabled,
|
saveButtonEnabled = saveButtonEnabled,
|
||||||
saveAction = saveAction.value,
|
saveAction = saveAction.value,
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,9 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.room.canBanAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.canKickAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState
|
import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -56,8 +55,12 @@ class RoomMemberModerationPresenter(
|
||||||
override fun present(): RoomMemberModerationState {
|
override fun present(): RoomMemberModerationState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val canBan = room.canBanAsState(syncUpdateFlow.value)
|
val permissions by room.permissionsAsState(Permissions()) { perms ->
|
||||||
val canKick = room.canKickAsState(syncUpdateFlow.value)
|
Permissions(
|
||||||
|
canKick = perms.canOwnUserKick(),
|
||||||
|
canBan = perms.canOwnUserBan(),
|
||||||
|
)
|
||||||
|
}
|
||||||
val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value)
|
val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value)
|
||||||
|
|
||||||
val kickUserAsyncAction =
|
val kickUserAsyncAction =
|
||||||
|
|
@ -80,8 +83,7 @@ class RoomMemberModerationPresenter(
|
||||||
}
|
}
|
||||||
moderationActions.value = computeModerationActions(
|
moderationActions.value = computeModerationActions(
|
||||||
member = member,
|
member = member,
|
||||||
canKick = canKick.value,
|
permissions = permissions,
|
||||||
canBan = canBan.value,
|
|
||||||
currentUserMemberPowerLevel = currentUserMemberPowerLevel.value,
|
currentUserMemberPowerLevel = currentUserMemberPowerLevel.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +136,8 @@ class RoomMemberModerationPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
return InternalRoomMemberModerationState(
|
return InternalRoomMemberModerationState(
|
||||||
canKick = canKick.value,
|
canKick = permissions.canKick,
|
||||||
canBan = canBan.value,
|
canBan = permissions.canBan,
|
||||||
selectedUser = selectedUser,
|
selectedUser = selectedUser,
|
||||||
actions = moderationActions.value,
|
actions = moderationActions.value,
|
||||||
kickUserAsyncAction = kickUserAsyncAction.value,
|
kickUserAsyncAction = kickUserAsyncAction.value,
|
||||||
|
|
@ -147,8 +149,7 @@ class RoomMemberModerationPresenter(
|
||||||
|
|
||||||
private fun computeModerationActions(
|
private fun computeModerationActions(
|
||||||
member: RoomMember?,
|
member: RoomMember?,
|
||||||
canKick: Boolean,
|
permissions: Permissions,
|
||||||
canBan: Boolean,
|
|
||||||
currentUserMemberPowerLevel: Long,
|
currentUserMemberPowerLevel: Long,
|
||||||
): ImmutableList<ModerationActionState> {
|
): ImmutableList<ModerationActionState> {
|
||||||
return buildList {
|
return buildList {
|
||||||
|
|
@ -158,11 +159,11 @@ class RoomMemberModerationPresenter(
|
||||||
val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel
|
val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel
|
||||||
// Assume the member is joined when it's unknown
|
// Assume the member is joined when it's unknown
|
||||||
val membership = member?.membership ?: RoomMembershipState.JOIN
|
val membership = member?.membership ?: RoomMembershipState.JOIN
|
||||||
if (canKick) {
|
if (permissions.canKick) {
|
||||||
val isKickEnabled = canModerateThisUser && membership.isActive()
|
val isKickEnabled = canModerateThisUser && membership.isActive()
|
||||||
add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled))
|
add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled))
|
||||||
}
|
}
|
||||||
if (canBan) {
|
if (permissions.canBan) {
|
||||||
if (membership == RoomMembershipState.BAN) {
|
if (membership == RoomMembershipState.BAN) {
|
||||||
add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser))
|
add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -208,6 +209,11 @@ class RoomMemberModerationPresenter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class Permissions(
|
||||||
|
val canKick: Boolean = false,
|
||||||
|
val canBan: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(
|
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(
|
||||||
action: MutableState<AsyncAction<T>>,
|
action: MutableState<AsyncAction<T>>,
|
||||||
block: suspend () -> Result<T>
|
block: suspend () -> Result<T>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,8 @@
|
||||||
|
|
||||||
package io.element.android.features.securityandprivacy.api
|
package io.element.android.features.securityandprivacy.api
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions.Companion.DEFAULT
|
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||||
|
|
||||||
data class SecurityAndPrivacyPermissions(
|
data class SecurityAndPrivacyPermissions(
|
||||||
val canChangeRoomAccess: Boolean,
|
val canChangeRoomAccess: Boolean,
|
||||||
|
|
@ -37,14 +32,11 @@ data class SecurityAndPrivacyPermissions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
fun RoomPermissions.securityAndPrivacyPermissions(): SecurityAndPrivacyPermissions {
|
||||||
fun BaseRoom.securityAndPrivacyPermissionsAsState(updateKey: Long): State<SecurityAndPrivacyPermissions> {
|
return SecurityAndPrivacyPermissions(
|
||||||
return produceState(DEFAULT, key1 = updateKey) {
|
canChangeRoomAccess = canOwnUserSendState(StateEventType.ROOM_JOIN_RULES),
|
||||||
value = SecurityAndPrivacyPermissions(
|
canChangeHistoryVisibility = canOwnUserSendState(StateEventType.ROOM_HISTORY_VISIBILITY),
|
||||||
canChangeRoomAccess = canSendState(type = StateEventType.ROOM_JOIN_RULES).getOrElse { false },
|
canChangeEncryption = canOwnUserSendState(StateEventType.ROOM_ENCRYPTION),
|
||||||
canChangeHistoryVisibility = canSendState(type = StateEventType.ROOM_HISTORY_VISIBILITY).getOrElse { false },
|
canChangeRoomVisibility = canOwnUserSendState(StateEventType.ROOM_CANONICAL_ALIAS),
|
||||||
canChangeEncryption = canSendState(type = StateEventType.ROOM_ENCRYPTION).getOrElse { false },
|
)
|
||||||
canChangeRoomVisibility = canSendState(type = StateEventType.ROOM_CANONICAL_ALIAS).getOrElse { false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissionsAsState
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||||
|
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
||||||
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
||||||
import io.element.android.features.securityandprivacy.impl.editroomaddress.matchesServer
|
import io.element.android.features.securityandprivacy.impl.editroomaddress.matchesServer
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -37,6 +38,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
|
@ -106,7 +108,9 @@ class SecurityAndPrivacyPresenter(
|
||||||
)
|
)
|
||||||
|
|
||||||
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
||||||
val permissions by room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
|
val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms ->
|
||||||
|
perms.securityAndPrivacyPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvent(event: SecurityAndPrivacyEvent) {
|
fun handleEvent(event: SecurityAndPrivacyEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
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.room.powerlevels.canCall
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.use
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
@ -56,7 +58,7 @@ class UserProfilePresenter(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDmRoomId(): State<RoomId?> {
|
private fun getDmRoomId(): State<RoomId?> {
|
||||||
return produceState<RoomId?>(initialValue = null) {
|
return produceState(initialValue = null) {
|
||||||
value = client.findDM(userId).getOrNull()
|
value = client.findDM(userId).getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +68,6 @@ class UserProfilePresenter(
|
||||||
val isElementCallAvailable by produceState(initialValue = false, roomId) {
|
val isElementCallAvailable by produceState(initialValue = false, roomId) {
|
||||||
value = sessionEnterpriseService.isElementCallAvailable()
|
value = sessionEnterpriseService.isElementCallAvailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
return produceState(initialValue = false, isElementCallAvailable, roomId) {
|
return produceState(initialValue = false, isElementCallAvailable, roomId) {
|
||||||
value = when {
|
value = when {
|
||||||
isElementCallAvailable.not() -> false
|
isElementCallAvailable.not() -> false
|
||||||
|
|
@ -75,7 +76,7 @@ class UserProfilePresenter(
|
||||||
roomId
|
roomId
|
||||||
?.let { client.getRoom(it) }
|
?.let { client.getRoom(it) }
|
||||||
?.use { room ->
|
?.use { room ->
|
||||||
room.canUserJoinCall(client.sessionId).getOrNull()
|
room.roomPermissions().use(false){ perms -> perms.canCall()}
|
||||||
}
|
}
|
||||||
.orFalse()
|
.orFalse()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,57 +130,6 @@ interface BaseRoom : Closeable {
|
||||||
*/
|
*/
|
||||||
suspend fun forget(): Result<Unit>
|
suspend fun forget(): Result<Unit>
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can invite other users to the room.
|
|
||||||
*/
|
|
||||||
suspend fun canUserInvite(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can kick other users from the room.
|
|
||||||
*/
|
|
||||||
suspend fun canUserKick(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can ban other users from the room.
|
|
||||||
*/
|
|
||||||
suspend fun canUserBan(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can redact their own messages.
|
|
||||||
*/
|
|
||||||
suspend fun canUserRedactOwn(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can redact messages from other users.
|
|
||||||
*/
|
|
||||||
suspend fun canUserRedactOther(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can send state events.
|
|
||||||
*/
|
|
||||||
suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can send messages.
|
|
||||||
*/
|
|
||||||
suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can trigger an `@room` notification.
|
|
||||||
*/
|
|
||||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can pin or unpin messages.
|
|
||||||
*/
|
|
||||||
suspend fun canUserPinUnpin(userId: UserId): Result<Boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` if the user with the provided [userId] can join or starts calls.
|
|
||||||
*/
|
|
||||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
|
||||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the room as favorite or not, based on the [isFavorite] parameter.
|
* Sets the room as favorite or not, based on the [isFavorite] parameter.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,18 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room.powerlevels
|
package io.element.android.libraries.matrix.api.room.powerlevels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
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.room.BaseRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides information about the permissions of users in a room.
|
* Provides information about the permissions of users in a room.
|
||||||
|
|
@ -120,23 +129,38 @@ interface RoomPermissions : AutoCloseable {
|
||||||
fun canUserTriggerRoomNotification(userId: UserId): Boolean
|
fun canUserTriggerRoomNotification(userId: UserId): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RoomPermissions.canEditRoomDetails(): Boolean {
|
|
||||||
return canOwnUserSendState(StateEventType.ROOM_NAME) ||
|
|
||||||
canOwnUserSendState(StateEventType.ROOM_TOPIC) ||
|
|
||||||
canOwnUserSendState(StateEventType.ROOM_AVATAR)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RoomPermissions.canManageKnockRequests(): Boolean {
|
|
||||||
return canOwnUserInvite() || canOwnUserBan() || canOwnUserKick()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RoomPermissions.canEditSecurityAndPrivacy(): Boolean {
|
|
||||||
return canOwnUserSendState(StateEventType.ROOM_JOIN_RULES) ||
|
|
||||||
canOwnUserSendState(StateEventType.ROOM_HISTORY_VISIBILITY) ||
|
|
||||||
canOwnUserSendState(StateEventType.ROOM_CANONICAL_ALIAS) ||
|
|
||||||
canOwnUserSendState(StateEventType.ROOM_ENCRYPTION)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RoomPermissions.canEditRolesAndPermissions(): Boolean {
|
fun RoomPermissions.canEditRolesAndPermissions(): Boolean {
|
||||||
return canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS)
|
return canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RoomPermissions.canCall(): Boolean {
|
||||||
|
return canOwnUserSendState(StateEventType.CALL_MEMBER)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Result<RoomPermissions>.use(default: T, block: (RoomPermissions) -> T): T {
|
||||||
|
return fold(
|
||||||
|
onSuccess = { perms ->
|
||||||
|
perms.use(block)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> BaseRoom.permissionsFlow(default: T, block: (RoomPermissions) -> T): Flow<T> {
|
||||||
|
return roomInfoFlow
|
||||||
|
.map { info -> info.roomPowerLevels }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.map {
|
||||||
|
roomPermissions().use(default, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> BaseRoom.permissionsAsState(default: T, block: (RoomPermissions) -> T): State<T> {
|
||||||
|
return remember(this, default, block) {
|
||||||
|
Timber.d("Computing permissionsAsState for room $roomId with default=$default")
|
||||||
|
permissionsFlow(default, block)
|
||||||
|
}.collectAsState(default)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room.powerlevels
|
package io.element.android.libraries.matrix.api.room.powerlevels
|
||||||
|
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
|
||||||
|
|
||||||
data class RoomPowerLevelsValues(
|
data class RoomPowerLevelsValues(
|
||||||
val ban: Long,
|
val ban: Long,
|
||||||
val invite: Long,
|
val invite: Long,
|
||||||
|
|
@ -24,50 +19,3 @@ data class RoomPowerLevelsValues(
|
||||||
val roomTopic: Long,
|
val roomTopic: Long,
|
||||||
val spaceChild: Long,
|
val spaceChild: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserInvite] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canInvite(): Result<Boolean> = canUserInvite(sessionId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserKick] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canKick(): Result<Boolean> = canUserKick(sessionId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserBan] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canBan(): Result<Boolean> = canUserBan(sessionId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserSendState] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canSendState(type: StateEventType): Result<Boolean> = canUserSendState(sessionId, type)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserSendMessage] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canSendMessage(type: MessageEventType): Result<Boolean> = canUserSendMessage(sessionId, type)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserRedactOwn] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canRedactOwn(): Result<Boolean> = canUserRedactOwn(sessionId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canRedactOther] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for checking if current user can handle knock requests.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canHandleKnockRequests(): Result<Boolean> = runCatchingExceptions {
|
|
||||||
canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for calling [BaseRoom.canUserPinUnpin] with our own user.
|
|
||||||
*/
|
|
||||||
suspend fun BaseRoom.canPinUnpin(): Result<Boolean> = canUserPinUnpin(sessionId)
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarM
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
||||||
import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryDataSource
|
import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryDataSource
|
||||||
|
|
@ -39,8 +38,10 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta
|
||||||
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions
|
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions
|
||||||
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
||||||
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
|
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.model.MediaPermissions
|
||||||
import io.element.android.libraries.mediaviewer.impl.model.eventId
|
import io.element.android.libraries.mediaviewer.impl.model.eventId
|
||||||
import io.element.android.libraries.mediaviewer.impl.model.mediaInfo
|
import io.element.android.libraries.mediaviewer.impl.model.mediaInfo
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.model.mediaPermissions
|
||||||
import io.element.android.libraries.mediaviewer.impl.model.mediaSource
|
import io.element.android.libraries.mediaviewer.impl.model.mediaSource
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -80,6 +81,10 @@ class MediaGalleryPresenter(
|
||||||
mediaGalleryDataSource.start()
|
mediaGalleryDataSource.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val permissions by room.permissionsAsState(MediaPermissions.DEFAULT) { perms ->
|
||||||
|
perms.mediaPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||||
localMediaActions.Configure()
|
localMediaActions.Configure()
|
||||||
|
|
||||||
|
|
@ -119,8 +124,8 @@ class MediaGalleryPresenter(
|
||||||
eventId = event.mediaItem.eventId(),
|
eventId = event.mediaItem.eventId(),
|
||||||
canDelete = when (event.mediaItem.mediaInfo().senderId) {
|
canDelete = when (event.mediaItem.mediaInfo().senderId) {
|
||||||
null -> false
|
null -> false
|
||||||
room.sessionId -> room.canRedactOwn().getOrElse { false } && event.mediaItem.eventId() != null
|
room.sessionId -> permissions.canRedactOwn && event.mediaItem.eventId() != null
|
||||||
else -> room.canRedactOther().getOrElse { false } && event.mediaItem.eventId() != null
|
else -> permissions.canRedactOther && event.mediaItem.eventId() != null
|
||||||
},
|
},
|
||||||
mediaInfo = event.mediaItem.mediaInfo(),
|
mediaInfo = event.mediaItem.mediaInfo(),
|
||||||
thumbnailSource = when (event.mediaItem) {
|
thumbnailSource = when (event.mediaItem) {
|
||||||
|
|
@ -202,6 +207,7 @@ class MediaGalleryPresenter(
|
||||||
CommonStrings.error_unknown
|
CommonStrings.error_unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun GroupedMediaItems?.find(eventId: EventId?): MediaItem.Event? {
|
private fun GroupedMediaItems?.find(eventId: EventId?): MediaItem.Event? {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.mediaviewer.impl.model
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||||
|
|
||||||
|
data class MediaPermissions(
|
||||||
|
val canRedactOwn: Boolean,
|
||||||
|
val canRedactOther: Boolean,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = MediaPermissions(
|
||||||
|
canRedactOwn = false,
|
||||||
|
canRedactOther = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoomPermissions.mediaPermissions(): MediaPermissions {
|
||||||
|
return MediaPermissions(
|
||||||
|
canRedactOwn = canOwnUserRedactOwn(),
|
||||||
|
canRedactOther = canOwnUserRedactOther(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -32,8 +32,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
||||||
|
|
@ -41,6 +40,8 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
||||||
import io.element.android.libraries.mediaviewer.impl.R
|
import io.element.android.libraries.mediaviewer.impl.R
|
||||||
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
|
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
|
||||||
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions
|
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.model.MediaPermissions
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.model.mediaPermissions
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -81,6 +82,9 @@ class MediaViewerPresenter(
|
||||||
NoMoreItemsBackwardSnackBarDisplayer(currentIndex, data)
|
NoMoreItemsBackwardSnackBarDisplayer(currentIndex, data)
|
||||||
NoMoreItemsForwardSnackBarDisplayer(currentIndex, data)
|
NoMoreItemsForwardSnackBarDisplayer(currentIndex, data)
|
||||||
|
|
||||||
|
val permissions by room.permissionsAsState(MediaPermissions.DEFAULT) { perms ->
|
||||||
|
perms.mediaPermissions()
|
||||||
|
}
|
||||||
var mediaBottomSheetState by remember { mutableStateOf<MediaBottomSheetState>(MediaBottomSheetState.Hidden) }
|
var mediaBottomSheetState by remember { mutableStateOf<MediaBottomSheetState>(MediaBottomSheetState.Hidden) }
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
|
|
@ -131,8 +135,8 @@ class MediaViewerPresenter(
|
||||||
eventId = event.data.eventId,
|
eventId = event.data.eventId,
|
||||||
canDelete = when (event.data.mediaInfo.senderId) {
|
canDelete = when (event.data.mediaInfo.senderId) {
|
||||||
null -> false
|
null -> false
|
||||||
room.sessionId -> room.canRedactOwn().getOrElse { false } && event.data.eventId != null
|
room.sessionId -> permissions.canRedactOwn && event.data.eventId != null
|
||||||
else -> room.canRedactOther().getOrElse { false } && event.data.eventId != null
|
else -> permissions.canRedactOther && event.data.eventId != null
|
||||||
},
|
},
|
||||||
mediaInfo = event.data.mediaInfo,
|
mediaInfo = event.data.mediaInfo,
|
||||||
thumbnailSource = event.data.thumbnailSource,
|
thumbnailSource = event.data.thumbnailSource,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue