From 58d9b12ab304e817c74db62a6f300736bd76cdc6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 6 May 2025 22:39:18 +0200 Subject: [PATCH 01/13] change (member moderation) : extract in a separate module --- features/roomdetails/impl/build.gradle.kts | 1 + .../roomdetails/impl/di/RoomDetailsModule.kt | 23 --- .../impl/members/RoomMemberListNode.kt | 14 ++ .../impl/members/RoomMemberListPresenter.kt | 16 +- .../impl/members/RoomMemberListState.kt | 4 +- .../members/RoomMemberListStateProvider.kt | 23 ++- .../impl/members/RoomMemberListView.kt | 11 +- .../moderation/ConfirmingRoomMemberAction.kt | 15 -- .../moderation/RoomMembersModerationEvents.kt | 21 --- .../moderation/RoomMembersModerationState.kt | 30 ---- .../RoomMembersModerationStateProvider.kt | 95 ------------ .../roommembermoderation/api/build.gradle.kts | 21 +++ .../api/RoomMemberModerationEvents.kt | 15 ++ .../api/RoomMemberModerationRenderer.kt | 20 +++ .../api/RoomMemberModerationState.kt | 23 +++ .../impl/build.gradle.kts | 39 +++++ .../DefaultRoomMemberModerationRenderer.kt | 37 +++++ .../InternalRoomMemberModerationEvents.kt | 17 +++ .../impl/InternalRoomMemberModerationState.kt | 32 ++++ .../impl/RoomMemberModerationPresenter.kt} | 144 ++++++++++-------- .../impl/RoomMemberModerationStateProvider.kt | 94 ++++++++++++ .../impl/RoomMemberModerationView.kt} | 110 +++++++------ .../impl/di/RoomMemberModerationModule.kt | 24 +++ .../src/main/res/values-be/translations.xml | 18 +++ .../src/main/res/values-cs/translations.xml | 21 +++ .../src/main/res/values-cy/translations.xml | 21 +++ .../src/main/res/values-de/translations.xml | 21 +++ .../src/main/res/values-el/translations.xml | 21 +++ .../src/main/res/values-es/translations.xml | 18 +++ .../src/main/res/values-et/translations.xml | 21 +++ .../src/main/res/values-eu/translations.xml | 16 ++ .../src/main/res/values-fa/translations.xml | 21 +++ .../src/main/res/values-fi/translations.xml | 21 +++ .../src/main/res/values-fr/translations.xml | 21 +++ .../src/main/res/values-hu/translations.xml | 21 +++ .../src/main/res/values-it/translations.xml | 18 +++ .../src/main/res/values-ka/translations.xml | 18 +++ .../src/main/res/values-nb/translations.xml | 21 +++ .../src/main/res/values-nl/translations.xml | 18 +++ .../src/main/res/values-pl/translations.xml | 21 +++ .../main/res/values-pt-rBR/translations.xml | 18 +++ .../src/main/res/values-pt/translations.xml | 19 +++ .../src/main/res/values-ro/translations.xml | 18 +++ .../src/main/res/values-ru/translations.xml | 20 +++ .../src/main/res/values-sk/translations.xml | 21 +++ .../src/main/res/values-sv/translations.xml | 21 +++ .../src/main/res/values-tr/translations.xml | 18 +++ .../src/main/res/values-uk/translations.xml | 21 +++ .../main/res/values-zh-rTW/translations.xml | 21 +++ .../src/main/res/values-zh/translations.xml | 21 +++ .../impl/src/main/res/values/localazy.xml | 21 +++ .../src/main/res/values-be/translations.xml | 9 -- .../src/main/res/values-cs/translations.xml | 12 -- .../src/main/res/values-cy/translations.xml | 12 -- .../src/main/res/values-de/translations.xml | 12 -- .../src/main/res/values-el/translations.xml | 12 -- .../src/main/res/values-es/translations.xml | 9 -- .../src/main/res/values-et/translations.xml | 12 -- .../src/main/res/values-eu/translations.xml | 8 - .../src/main/res/values-fa/translations.xml | 12 -- .../src/main/res/values-fi/translations.xml | 12 -- .../src/main/res/values-fr/translations.xml | 12 -- .../src/main/res/values-hu/translations.xml | 12 -- .../src/main/res/values-in/translations.xml | 9 -- .../src/main/res/values-it/translations.xml | 9 -- .../src/main/res/values-ka/translations.xml | 9 -- .../src/main/res/values-nb/translations.xml | 12 -- .../src/main/res/values-nl/translations.xml | 9 -- .../src/main/res/values-pl/translations.xml | 12 -- .../main/res/values-pt-rBR/translations.xml | 9 -- .../src/main/res/values-pt/translations.xml | 10 -- .../src/main/res/values-ro/translations.xml | 9 -- .../src/main/res/values-ru/translations.xml | 11 -- .../src/main/res/values-sk/translations.xml | 12 -- .../src/main/res/values-sv/translations.xml | 12 -- .../src/main/res/values-tr/translations.xml | 9 -- .../src/main/res/values-uk/translations.xml | 12 -- .../main/res/values-zh-rTW/translations.xml | 12 -- .../src/main/res/values-zh/translations.xml | 12 -- tools/localazy/config.json | 8 + 80 files changed, 1062 insertions(+), 633 deletions(-) delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt create mode 100644 features/roommembermoderation/api/build.gradle.kts create mode 100644 features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt create mode 100644 features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt create mode 100644 features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt create mode 100644 features/roommembermoderation/impl/build.gradle.kts create mode 100644 features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt create mode 100644 features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt create mode 100644 features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt rename features/{roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt => roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt} (53%) create mode 100644 features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt rename features/{roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt => roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt} (72%) create mode 100644 features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt create mode 100644 features/roommembermoderation/impl/src/main/res/values-be/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-cs/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-cy/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-de/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-el/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-es/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-et/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-eu/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-fa/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-fi/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-fr/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-hu/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-it/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-ka/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-nb/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-nl/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-pl/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-pt/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-ro/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-ru/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-sk/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-sv/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-tr/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-uk/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-zh/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values/localazy.xml diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 87ca1afe4d..a23997b43d 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(projects.features.knockrequests.api) implementation(projects.features.verifysession.api) implementation(projects.features.reportroom.api) + implementation(projects.features.roommembermoderation.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt deleted file mode 100644 index 0361cfb2ca..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.di - -import com.squareup.anvil.annotations.ContributesTo -import dagger.Binds -import dagger.Module -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.di.RoomScope - -@Module -@ContributesTo(RoomScope::class) -interface RoomDetailsModule { - @Binds - fun bindRoomMembersModerationPresenter(presenter: RoomMembersModerationPresenter): Presenter -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index b1eb471391..05a35fad88 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -18,6 +18,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.services.analytics.api.AnalyticsService @@ -28,6 +31,7 @@ class RoomMemberListNode @AssistedInject constructor( @Assisted plugins: List, presenterFactory: RoomMemberListPresenter.Factory, private val analyticsService: AnalyticsService, + private val roomMemberModerationRenderer: RoomMemberModerationRenderer, ) : Node(buildContext, plugins = plugins), RoomMemberListNavigator { interface Callback : Plugin { fun openRoomMemberDetails(roomMemberId: UserId) @@ -69,6 +73,16 @@ class RoomMemberListNode @AssistedInject constructor( modifier = modifier, navigator = this, ) + roomMemberModerationRenderer.Render( + state = state.moderationState, + onSelectAction = { action -> + when (action) { + is ModerationAction.DisplayProfile -> openRoomMemberDetails(action.member.userId) + else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action)) + } + }, + modifier = Modifier, + ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index e2527444aa..07688387e3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -19,8 +19,9 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -34,6 +35,7 @@ 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.roomMembers import io.element.android.libraries.matrix.ui.room.canInviteAsState +import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf @@ -48,7 +50,7 @@ class RoomMemberListPresenter @AssistedInject constructor( private val room: JoinedRoom, private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, - private val roomMembersModerationPresenter: Presenter, + private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, @Assisted private val navigator: RoomMemberListNavigator, ) : Presenter { @@ -69,7 +71,7 @@ class RoomMemberListPresenter @AssistedInject constructor( val membersState by room.membersStateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canInvite by room.canInviteAsState(syncUpdateFlow.value) - + val isDm = room.isDmAsState() val roomModerationState = roomMembersModerationPresenter.present() val roomMemberIdentityStates by produceState(persistentMapOf()) { @@ -163,8 +165,10 @@ class RoomMemberListPresenter @AssistedInject constructor( is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> - if (roomModerationState.canDisplayModerationActions) { - roomModerationState.eventSink(RoomMembersModerationEvents.SelectRoomMember(event.roomMember)) + if (event.roomMember.membership == RoomMembershipState.BAN) { + roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser(event.roomMember))) + } else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) { + roomModerationState.eventSink(RoomMemberModerationEvents.RenderActions(event.roomMember)) } else { navigator.openRoomMemberDetails(event.roomMember.userId) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index b5fa6c37a9..8e492e5371 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -7,7 +7,7 @@ package io.element.android.features.roomdetails.impl.members -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -20,7 +20,7 @@ data class RoomMemberListState( val searchResults: SearchBarResultState>, val isSearchActive: Boolean, val canInvite: Boolean, - val moderationState: RoomMembersModerationState, + val moderationState: RoomMemberModerationState, val eventSink: (RoomMemberListEvents) -> Unit, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 6fec01c675..09a7d9c910 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -8,8 +8,8 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState -import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId @@ -87,7 +87,7 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider = AsyncData.Loading(), searchResults: SearchBarResultState> = SearchBarResultState.Initial(), - moderationState: RoomMembersModerationState = aRoomMembersModerationState(), + moderationState: RoomMemberModerationState = aRoomMemberModerationState(), ) = RoomMemberListState( roomMembers = roomMembers, searchQuery = "", @@ -130,6 +130,17 @@ internal fun aRoomMemberListState( eventSink = {} ) +fun aRoomMemberModerationState( + canBan: Boolean = false, + canKick: Boolean = false, +): RoomMemberModerationState { + return object : RoomMemberModerationState { + override val canKick: Boolean = canKick + override val canBan: Boolean = canBan + override val eventSink: (RoomMemberModerationEvents) -> Unit = {} + } +} + fun aRoomMember( userId: UserId = UserId("@alice:server.org"), displayName: String? = null, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 875606f948..fab12adb4a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -45,7 +45,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.roomdetails.impl.R -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationView +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton @@ -99,7 +99,7 @@ fun RoomMemberListView( } ) { padding -> var selectedSection by remember { mutableStateOf(SelectedSection.entries[initialSelectedSectionIndex]) } - if (!state.moderationState.canDisplayBannedUsers && selectedSection == SelectedSection.BANNED) { + if (!state.moderationState.canBan && selectedSection == SelectedSection.BANNED) { SideEffect { selectedSection = SelectedSection.MEMBERS } @@ -127,7 +127,7 @@ fun RoomMemberListView( RoomMemberList( roomMembers = state.roomMembers, showMembersCount = true, - canDisplayBannedUsersControls = state.moderationState.canDisplayBannedUsers, + canDisplayBannedUsersControls = state.moderationState.canBan, selectedSection = selectedSection, onSelectedSectionChange = { selectedSection = it }, onSelectUser = ::onSelectUser, @@ -135,11 +135,6 @@ fun RoomMemberListView( } } } - - RoomMembersModerationView( - state = state.moderationState, - onDisplayMemberProfile = navigator::openRoomMemberDetails - ) } @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt deleted file mode 100644 index b561dc9ac7..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/ConfirmingRoomMemberAction.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.room.RoomMember - -data class ConfirmingRoomMemberAction( - val roomMember: RoomMember, -) : AsyncAction.Confirming diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt deleted file mode 100644 index eaabc8e4c2..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationEvents.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.RoomMember - -sealed interface RoomMembersModerationEvents { - data class SelectRoomMember(val roomMember: RoomMember) : RoomMembersModerationEvents - data object KickUser : RoomMembersModerationEvents - data class DoKickUser(val reason: String) : RoomMembersModerationEvents - data object BanUser : RoomMembersModerationEvents - data class DoBanUser(val reason: String) : RoomMembersModerationEvents - data class UnbanUser(val userId: UserId) : RoomMembersModerationEvents - data object Reset : RoomMembersModerationEvents -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt deleted file mode 100644 index a6c9a3e704..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.RoomMember -import kotlinx.collections.immutable.ImmutableList - -data class RoomMembersModerationState( - val canDisplayModerationActions: Boolean, - val selectedRoomMember: RoomMember?, - val actions: ImmutableList, - val kickUserAsyncAction: AsyncAction, - val banUserAsyncAction: AsyncAction, - val unbanUserAsyncAction: AsyncAction, - val canDisplayBannedUsers: Boolean, - val eventSink: (RoomMembersModerationEvents) -> Unit, -) - -sealed interface ModerationAction { - data class DisplayProfile(val userId: UserId) : ModerationAction - data class KickUser(val userId: UserId) : ModerationAction - data class BanUser(val userId: UserId) : ModerationAction -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt deleted file mode 100644 index 9139100980..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStateProvider.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdetails.impl.members.anAlice -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.room.RoomMember -import kotlinx.collections.immutable.toPersistentList - -class RoomMembersModerationStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - actions = listOf( - ModerationAction.DisplayProfile(anAlice().userId), - ), - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - actions = listOf( - ModerationAction.DisplayProfile(anAlice().userId), - ModerationAction.KickUser(userId = anAlice().userId), - ), - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - actions = listOf( - ModerationAction.DisplayProfile(anAlice().userId), - ModerationAction.KickUser(userId = anAlice().userId), - ModerationAction.BanUser(userId = anAlice().userId), - ), - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - kickUserAsyncAction = AsyncAction.ConfirmingNoParams, - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - kickUserAsyncAction = AsyncAction.Loading, - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - banUserAsyncAction = AsyncAction.ConfirmingNoParams, - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - banUserAsyncAction = AsyncAction.Loading, - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - unbanUserAsyncAction = AsyncAction.Loading, - ), - aRoomMembersModerationState( - kickUserAsyncAction = AsyncAction.Failure(Exception("Failed to kick user")), - banUserAsyncAction = AsyncAction.Failure(Exception("Failed to ban user")), - unbanUserAsyncAction = AsyncAction.Failure(Exception("Failed to unban user")), - ), - aRoomMembersModerationState( - selectedRoomMember = anAlice(), - unbanUserAsyncAction = ConfirmingRoomMemberAction(anAlice()), - ), - aRoomMembersModerationState( - kickUserAsyncAction = AsyncAction.Success(Unit), - banUserAsyncAction = AsyncAction.Success(Unit), - unbanUserAsyncAction = AsyncAction.Success(Unit), - ), - ) -} - -fun aRoomMembersModerationState( - canDisplayModerationActions: Boolean = false, - selectedRoomMember: RoomMember? = null, - actions: List = emptyList(), - kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, - banUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, - unbanUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, - canDisplayBannedUsers: Boolean = false, - eventSink: (RoomMembersModerationEvents) -> Unit = {}, -) = RoomMembersModerationState( - canDisplayModerationActions = canDisplayModerationActions, - selectedRoomMember = selectedRoomMember, - actions = actions.toPersistentList(), - kickUserAsyncAction = kickUserAsyncAction, - banUserAsyncAction = banUserAsyncAction, - unbanUserAsyncAction = unbanUserAsyncAction, - canDisplayBannedUsers = canDisplayBannedUsers, - eventSink = eventSink, -) diff --git a/features/roommembermoderation/api/build.gradle.kts b/features/roommembermoderation/api/build.gradle.kts new file mode 100644 index 0000000000..aeea031757 --- /dev/null +++ b/features/roommembermoderation/api/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright 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. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.features.roommembermoderation.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.matrix.api) +} diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt new file mode 100644 index 0000000000..265a59a125 --- /dev/null +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 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.roommembermoderation.api + +import io.element.android.libraries.matrix.api.room.RoomMember + +interface RoomMemberModerationEvents { + data class RenderActions(val roomMember: RoomMember) : RoomMemberModerationEvents + data class ProcessAction(val action: ModerationAction): RoomMemberModerationEvents +} diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt new file mode 100644 index 0000000000..2911bf1b8b --- /dev/null +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt @@ -0,0 +1,20 @@ +/* + * Copyright 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.roommembermoderation.api + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +interface RoomMemberModerationRenderer { + @Composable + fun Render( + state: RoomMemberModerationState, + onSelectAction: (ModerationAction) -> Unit, + modifier: Modifier, + ) +} diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt new file mode 100644 index 0000000000..410b8d6423 --- /dev/null +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 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.roommembermoderation.api + +import io.element.android.libraries.matrix.api.room.RoomMember + +interface RoomMemberModerationState { + val canKick: Boolean + val canBan: Boolean + val eventSink: (RoomMemberModerationEvents) -> Unit +} + +sealed interface ModerationAction { + data class DisplayProfile(val member: RoomMember) : ModerationAction + data class KickUser(val member: RoomMember) : ModerationAction + data class BanUser(val member: RoomMember) : ModerationAction + data class UnbanUser(val member: RoomMember) : ModerationAction +} diff --git a/features/roommembermoderation/impl/build.gradle.kts b/features/roommembermoderation/impl/build.gradle.kts new file mode 100644 index 0000000000..3d566077eb --- /dev/null +++ b/features/roommembermoderation/impl/build.gradle.kts @@ -0,0 +1,39 @@ +import extension.setupAnvil + +/* + * Copyright 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. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.features.roommembermoderation.impl" +} + +setupAnvil() + +dependencies { + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + api(projects.features.roommembermoderation.api) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.uiStrings) + implementation(projects.services.analytics.compose) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.coroutines.core) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) + testImplementation(projects.services.analytics.test) +} diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt new file mode 100644 index 0000000000..9a9cce3eb7 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt @@ -0,0 +1,37 @@ +/* + * Copyright 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.roommembermoderation.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Modifier +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.di.RoomScope +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(RoomScope::class) +class DefaultRoomMemberModerationRenderer @Inject constructor() : RoomMemberModerationRenderer { + @Composable + override fun Render( + state: RoomMemberModerationState, + onSelectAction: (ModerationAction) -> Unit, + modifier: Modifier + ) { + if (state is InternalRoomMemberModerationState) { + RoomMemberModerationView(state, onSelectAction, modifier) + } else { + SideEffect { + Timber.d("RoomMemberModerationRenderer: Render called with unsupported state: $state") + } + } + } +} diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt new file mode 100644 index 0000000000..206a379698 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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.roommembermoderation.impl + +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents + +sealed interface InternalRoomMemberModerationEvents: RoomMemberModerationEvents { + data class DoKickUser(val reason: String) : InternalRoomMemberModerationEvents + data class DoBanUser(val reason: String) : InternalRoomMemberModerationEvents + data object DoUnbanUser : InternalRoomMemberModerationEvents + data object Reset : InternalRoomMemberModerationEvents +} diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt new file mode 100644 index 0000000000..e6258f2003 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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.roommembermoderation.impl + +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.room.RoomMember +import kotlinx.collections.immutable.ImmutableList + +data class InternalRoomMemberModerationState( + override val canKick: Boolean, + override val canBan: Boolean, + val selectedRoomMember: AsyncData, + val actions: ImmutableList, + val kickUserAsyncAction: AsyncAction, + val banUserAsyncAction: AsyncAction, + val unbanUserAsyncAction: AsyncAction, + override val eventSink: (RoomMemberModerationEvents) -> Unit, +) : RoomMemberModerationState { + + val canOnlyDisplayProfile = actions.size == 1 && actions.first() is ModerationAction.DisplayProfile + val canDisplayActions = actions.isNotEmpty() && !canOnlyDisplayProfile +} + diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt similarity index 53% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt rename to features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index af71e21dff..5f7fa80c60 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -5,31 +5,34 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.members.moderation +package io.element.android.features.roommembermoderation.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.RoomMember -import io.element.android.libraries.matrix.api.room.RoomMembershipState 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.isDmAsState import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.drop @@ -37,45 +40,20 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import javax.inject.Inject -class RoomMembersModerationPresenter @Inject constructor( +class RoomMemberModerationPresenter @Inject constructor( private val room: JoinedRoom, private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, -) : Presenter { - private var selectedMember by mutableStateOf(null) +) : Presenter { + private var selectedMember by mutableStateOf>(AsyncData.Uninitialized) @Composable - override fun present(): RoomMembersModerationState { + override fun present(): RoomMemberModerationState { val coroutineScope = rememberCoroutineScope() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canBan by room.canBanAsState(syncUpdateFlow.value) - val canKick by room.canKickAsState(syncUpdateFlow.value) - val isDm by room.isDmAsState() - val currentUserMemberPowerLevel by room.userPowerLevelAsState(syncUpdateFlow.value) - - val canDisplayModerationActions by remember { - derivedStateOf { !isDm && (canBan || canKick) } - } - val canDisplayBannedUsers by remember { - derivedStateOf { !isDm && canBan } - } - val moderationActions by remember { - derivedStateOf { - buildList { - selectedMember?.let { roomMember -> - add(ModerationAction.DisplayProfile(roomMember.userId)) - if (currentUserMemberPowerLevel > roomMember.powerLevel) { - if (canKick) { - add(ModerationAction.KickUser(roomMember.userId)) - } - if (canBan) { - add(ModerationAction.BanUser(roomMember.userId)) - } - } - } - }.toPersistentList() - } - } + val canBan = room.canBanAsState(syncUpdateFlow.value) + val canKick = room.canKickAsState(syncUpdateFlow.value) + val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value) val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } @@ -84,60 +62,92 @@ class RoomMembersModerationPresenter @Inject constructor( val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } - fun handleEvent(event: RoomMembersModerationEvents) { + val moderationActions = remember { mutableStateOf(persistentListOf()) } + + fun handleEvent(event: RoomMemberModerationEvents) { when (event) { - is RoomMembersModerationEvents.SelectRoomMember -> { - if (event.roomMember.membership == RoomMembershipState.BAN && canBan) { - // In this case the view will render a dialog to confirm the unbanning of the user - unbanUserAsyncAction.value = ConfirmingRoomMemberAction(event.roomMember) - } else { - // In this case the view will render a bottom sheet. - selectedMember = event.roomMember + is RoomMemberModerationEvents.RenderActions -> { + selectedMember = AsyncData.Success(event.roomMember) + moderationActions.value = computeModerationActions( + member = event.roomMember, + canKick = canKick.value, + canBan = canBan.value, + currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + ) + } + is RoomMemberModerationEvents.ProcessAction -> { + when(val action = event.action) { + is ModerationAction.DisplayProfile -> Unit + is ModerationAction.KickUser -> { + selectedMember = AsyncData.Success(action.member) + kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams + } + is ModerationAction.BanUser -> { + selectedMember = AsyncData.Success(action.member) + banUserAsyncAction.value = AsyncAction.ConfirmingNoParams + } + is ModerationAction.UnbanUser -> { + selectedMember = AsyncData.Success(action.member) + unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams + } } } - is RoomMembersModerationEvents.KickUser -> { - kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams - } - is RoomMembersModerationEvents.DoKickUser -> { - selectedMember?.let { + is InternalRoomMemberModerationEvents.DoKickUser -> { + selectedMember.dataOrNull()?.let { coroutineScope.kickUser(it.userId, event.reason, kickUserAsyncAction) } - selectedMember = null + selectedMember = AsyncData.Uninitialized } - is RoomMembersModerationEvents.BanUser -> { - banUserAsyncAction.value = AsyncAction.ConfirmingNoParams - } - is RoomMembersModerationEvents.DoBanUser -> { - selectedMember?.let { + is InternalRoomMemberModerationEvents.DoBanUser -> { + selectedMember.dataOrNull()?.let { coroutineScope.banUser(it.userId, event.reason, banUserAsyncAction) } - selectedMember = null + selectedMember = AsyncData.Uninitialized } - is RoomMembersModerationEvents.UnbanUser -> { - // We are already confirming when we are reaching this point - coroutineScope.unbanUser(event.userId, unbanUserAsyncAction) - } - is RoomMembersModerationEvents.Reset -> { - selectedMember = null + is InternalRoomMemberModerationEvents.Reset -> { + selectedMember = AsyncData.Uninitialized kickUserAsyncAction.value = AsyncAction.Uninitialized banUserAsyncAction.value = AsyncAction.Uninitialized unbanUserAsyncAction.value = AsyncAction.Uninitialized } + is InternalRoomMemberModerationEvents.DoUnbanUser -> { + selectedMember.dataOrNull()?.let { + coroutineScope.unbanUser(it.userId, unbanUserAsyncAction) + } + selectedMember = AsyncData.Uninitialized + } } } - return RoomMembersModerationState( - canDisplayModerationActions = canDisplayModerationActions, + return InternalRoomMemberModerationState( + canKick = canKick.value, + canBan = canBan.value, selectedRoomMember = selectedMember, - actions = moderationActions, + actions = moderationActions.value, kickUserAsyncAction = kickUserAsyncAction.value, banUserAsyncAction = banUserAsyncAction.value, unbanUserAsyncAction = unbanUserAsyncAction.value, - canDisplayBannedUsers = canDisplayBannedUsers, eventSink = { handleEvent(it) }, ) } + private fun computeModerationActions( + member: RoomMember, + canKick: Boolean, + canBan: Boolean, + currentUserMemberPowerLevel: Long, + ): PersistentList { + return buildList { + add(ModerationAction.DisplayProfile(member)) + if (canKick && member.powerLevel < currentUserMemberPowerLevel) { + add(ModerationAction.KickUser(member)) + } + if (canBan && member.powerLevel < currentUserMemberPowerLevel) { + add(ModerationAction.BanUser(member)) + } + }.toPersistentList() + } + private fun CoroutineScope.kickUser( userId: UserId, reason: String, @@ -167,7 +177,7 @@ class RoomMembersModerationPresenter @Inject constructor( unbanUserAction: MutableState>, ) = runActionAndWaitForMembershipChange(unbanUserAction) { analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember)) - room.unbanUser(userId) + room.unbanUser(userId = userId) } private fun CoroutineScope.runActionAndWaitForMembershipChange( diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt new file mode 100644 index 0000000000..b705e5217d --- /dev/null +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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.roommembermoderation.impl + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.RoomMembershipState +import kotlinx.collections.immutable.toPersistentList + +class RoomMemberModerationStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + actions = listOf( + ModerationAction.DisplayProfile(anAlice()), + ), + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + actions = listOf( + ModerationAction.DisplayProfile(anAlice()), + ModerationAction.KickUser(anAlice()), + ), + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + actions = listOf( + ModerationAction.DisplayProfile(anAlice()), + ModerationAction.KickUser(anAlice()), + ModerationAction.BanUser(anAlice()), + ), + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + kickUserAsyncAction = AsyncAction.Loading, + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + banUserAsyncAction = AsyncAction.ConfirmingNoParams, + ), + aRoomMembersModerationState( + selectedRoomMember = AsyncData.Success(anAlice()), + banUserAsyncAction = AsyncAction.Loading, + ), + ) +} + +fun anAlice() = RoomMember( + UserId(value = "@alice:server.org"), + displayName = "Alice", + avatarUrl = null, + role = RoomMember.Role.forPowerLevel(100L), + membership = RoomMembershipState.JOIN, + isNameAmbiguous = false, + powerLevel = 100L, + normalizedPowerLevel = 100L, + isIgnored = false, + membershipChangeReason = null, +) + +fun aRoomMembersModerationState( + canKick: Boolean = false, + canBan: Boolean = false, + selectedRoomMember: AsyncData = AsyncData.Uninitialized, + actions: List = emptyList(), + kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, + banUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, + unbanUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (RoomMemberModerationEvents) -> Unit = {}, +) = InternalRoomMemberModerationState( + canKick = canKick, + canBan = canBan, + selectedRoomMember = selectedRoomMember, + actions = actions.toPersistentList(), + kickUserAsyncAction = kickUserAsyncAction, + banUserAsyncAction = banUserAsyncAction, + unbanUserAsyncAction = unbanUserAsyncAction, + eventSink = eventSink, +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt similarity index 72% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt rename to features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 535a3fc5b7..ff949fde59 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.members.moderation +package io.element.android.features.roommembermoderation.impl import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -20,7 +20,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -30,7 +32,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter 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.roomdetails.impl.R +import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost @@ -47,7 +49,6 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.getBestName import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -57,32 +58,27 @@ import kotlinx.coroutines.launch import timber.log.Timber @Composable -fun RoomMembersModerationView( - state: RoomMembersModerationState, - onDisplayMemberProfile: (UserId) -> Unit, +fun RoomMemberModerationView( + state: InternalRoomMemberModerationState, + onSelectAction: (ModerationAction) -> Unit, modifier: Modifier = Modifier, ) { + val selectedRoomMember = state.selectedRoomMember.dataOrNull() Box(modifier = modifier) { - if (state.selectedRoomMember != null && state.actions.isNotEmpty()) { + if (selectedRoomMember != null && state.canDisplayActions) { RoomMemberActionsBottomSheet( - roomMember = state.selectedRoomMember, + roomMember = selectedRoomMember, actions = state.actions, - onSelectAction = { action -> - when (action) { - is ModerationAction.DisplayProfile -> { - onDisplayMemberProfile(action.userId) - } - is ModerationAction.KickUser -> { - state.eventSink(RoomMembersModerationEvents.KickUser) - } - is ModerationAction.BanUser -> { - state.eventSink(RoomMembersModerationEvents.BanUser) - } - } - }, - onDismiss = { state.eventSink(RoomMembersModerationEvents.Reset) }, + onSelectAction = onSelectAction, + onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, ) } + val onSelectAction by rememberUpdatedState(onSelectAction) + LaunchedEffect(state.canOnlyDisplayProfile) { + if (state.canOnlyDisplayProfile) { + onSelectAction(state.actions.first()) + } + } val asyncIndicatorState = rememberAsyncIndicatorState() AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), state = asyncIndicatorState) @@ -90,23 +86,23 @@ fun RoomMembersModerationView( when (val action = state.kickUserAsyncAction) { is AsyncAction.Confirming -> { TextFieldDialog( - title = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_title), - submitText = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action), + title = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_title), + submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action), onSubmit = { reason -> - state.eventSink(RoomMembersModerationEvents.DoKickUser(reason = reason)) + state.eventSink(InternalRoomMemberModerationEvents.DoKickUser(reason = reason)) }, - onDismissRequest = { state.eventSink(RoomMembersModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), label = stringResource(id = CommonStrings.common_reason), - content = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_description), + content = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_description), value = "", ) } is AsyncAction.Loading -> { LaunchedEffect(action) { - val userDisplayName = state.selectedRoomMember?.getBestName().orEmpty() + val userDisplayName = selectedRoomMember?.getBestName().orEmpty() asyncIndicatorState.enqueue { - AsyncIndicator.Loading(text = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_removing_user, userDisplayName)) + AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_removing_user, userDisplayName)) } } } @@ -129,23 +125,23 @@ fun RoomMembersModerationView( when (val action = state.banUserAsyncAction) { is AsyncAction.Confirming -> { TextFieldDialog( - title = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_title), - submitText = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action), + title = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_title), + submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action), onSubmit = { reason -> - state.eventSink(RoomMembersModerationEvents.DoBanUser(reason = reason)) + state.eventSink(InternalRoomMemberModerationEvents.DoBanUser(reason = reason)) }, - onDismissRequest = { state.eventSink(RoomMembersModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), label = stringResource(id = CommonStrings.common_reason), - content = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_description), + content = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_description), value = "", ) } is AsyncAction.Loading -> { LaunchedEffect(action) { - val userDisplayName = state.selectedRoomMember?.getBestName().orEmpty() + val userDisplayName = selectedRoomMember?.getBestName().orEmpty() asyncIndicatorState.enqueue { - AsyncIndicator.Loading(text = stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_banning_user, userDisplayName)) + AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_banning_user, userDisplayName)) } } } @@ -164,24 +160,21 @@ fun RoomMembersModerationView( } else -> Unit } - when (val action = state.unbanUserAsyncAction) { is AsyncAction.Confirming -> { - if (action is ConfirmingRoomMemberAction) { - ConfirmationDialog( - title = stringResource(R.string.screen_room_member_list_manage_member_unban_title), - content = stringResource(R.string.screen_room_member_list_manage_member_unban_message), - submitText = stringResource(R.string.screen_room_member_list_manage_member_unban_action), - onSubmitClick = { - val userDisplayName = action.roomMember.getBestName() - asyncIndicatorState.enqueue { - AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName)) - } - state.eventSink(RoomMembersModerationEvents.UnbanUser(action.roomMember.userId)) - }, - onDismiss = { state.eventSink(RoomMembersModerationEvents.Reset) }, - ) - } + ConfirmationDialog( + title = stringResource(R.string.screen_room_member_list_manage_member_unban_title), + content = stringResource(R.string.screen_room_member_list_manage_member_unban_message), + submitText = stringResource(R.string.screen_room_member_list_manage_member_unban_action), + onSubmitClick = { + val userDisplayName = selectedRoomMember?.getBestName().orEmpty() + asyncIndicatorState.enqueue { + AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName)) + } + state.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser) + }, + onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + ) } is AsyncAction.Failure -> { Timber.e(action.error, "Failed to unban user.") @@ -260,7 +253,7 @@ private fun RoomMemberActionsBottomSheet( when (action) { is ModerationAction.DisplayProfile -> { ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_member_user_info)) }, + headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_member_user_info)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), onClick = { coroutineScope.launch { @@ -272,7 +265,7 @@ private fun RoomMemberActionsBottomSheet( } is ModerationAction.KickUser -> { ListItem( - headlineContent = { Text(stringResource(CommonStrings.screen_bottom_sheet_manage_room_member_remove)) }, + headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_remove)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), onClick = { coroutineScope.launch { @@ -284,7 +277,7 @@ private fun RoomMemberActionsBottomSheet( } is ModerationAction.BanUser -> { ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_member_list_manage_member_remove_confirmation_ban)) }, + headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_ban)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), style = ListItemStyle.Destructive, onClick = { @@ -295,6 +288,7 @@ private fun RoomMemberActionsBottomSheet( } ) } + is ModerationAction.UnbanUser -> Unit } } } @@ -303,16 +297,16 @@ private fun RoomMemberActionsBottomSheet( @PreviewsDayNight @Composable -internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMembersModerationStateProvider::class) state: RoomMembersModerationState) { +internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) { ElementPreview { Box( modifier = Modifier .fillMaxWidth() .heightIn(min = 64.dp) ) { - RoomMembersModerationView( + RoomMemberModerationView( state = state, - onDisplayMemberProfile = {}, + onSelectAction = {}, ) } } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt new file mode 100644 index 0000000000..d8caadd71f --- /dev/null +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt @@ -0,0 +1,24 @@ +/* + * Copyright 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.roommembermoderation.impl.di + +import com.squareup.anvil.annotations.ContributesTo +import dagger.Binds +import dagger.Module +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.features.roommembermoderation.impl.RoomMemberModerationPresenter +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SessionScope + +@ContributesTo(RoomScope::class) +@Module +interface RoomMemberModerationModule { + @Binds + fun bindRoomMemberModerationPresenter(presenter: RoomMemberModerationPresenter): Presenter +} diff --git a/features/roommembermoderation/impl/src/main/res/values-be/translations.xml b/features/roommembermoderation/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..8a488cf10c --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,18 @@ + + + "Выдаліць і заблакіраваць удзельніка" + "Заблакіраваць" + "Яны не змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." + "Вы ўпэўнены, што хочаце заблакіраваць гэтага карыстальніка?" + "Блакіроўка %1$s" + "Прагляд профілю" + "Выдаліць удзельніка з пакоя" + "Выдаліць удзельніка і забараніць далучацца ў будучыні?" + "Выдаленне %1$s…" + "Выдаліць і заблакіраваць удзельніка" + "Толькі выдаліць удзельніка" + "Разблакіраваць" + "Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." + "Разблакіраваць удзельніка" + "Разблакіроўка %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..490671e9ff --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,21 @@ + + + "Odebrat a vykázat člena" + "Vykázat" + "Nebudou se moci znovu připojit k této místnosti, pokud budou pozváni." + "Jste si jisti, že chcete vykázat tohoto člena?" + "Vykazování %1$s" + "Odebrat" + "Budou moci znovu vstoupit do této místnosti, pokud budou pozváni." + "Opravdu chcete tohoto člena odebrat?" + "Zobrazit profil" + "Odebrat z místnosti" + "Odebrat člena a zakázat mu připojení v budoucnu?" + "Odstraňování %1$s…" + "Odebrat a vykázat člena" + "Pouze odebrat člena" + "Zrušit vykázání" + "Pokud budou pozváni, budou se moci do této místnosti znovu připojit." + "Zrušit vykázání uživatele" + "Rušení vykázání %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml b/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml new file mode 100644 index 0000000000..55fc68adf0 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml @@ -0,0 +1,21 @@ + + + "Gwahardd o ystafell" + "Atal" + "Fyddan nhw ddim yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." + "Ydych chi\'n siŵr eich bod am wahardd yr aelod hwn?" + "Yn gwahardd %1$s" + "Tynnu" + "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." + "Ydych chi\'n siŵr eich bod am ddileu\'r aelod hwn?" + "Gweld proffil" + "Tynnu o\'r ystafell" + "Dileu aelod a\'u gwahardd rhag ymuno yn y dyfodol?" + "Wrthi\'n dileu %1$s…" + "Gwahardd o ystafell" + "Dileu aelod yn unig" + "Adfer" + "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." + "Gwahardd defnyddiwr" + "Dad-wahardd %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-de/translations.xml b/features/roommembermoderation/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..c1fb73626d --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,21 @@ + + + "Mitglied entfernen und sperren" + "Sperren" + "Sie können dem Raum nicht mehr beitreten, selbst wenn sie eingeladen werden." + "Bist du sicher, dass du dieses Mitglied sperren möchtest?" + "%1$s wird gesperrt." + "Entfernen" + "Sie können diesen Raum wieder betreten, wenn sie eingeladen werden." + "Möchten Sie dieses Mitglied wirklich entfernen?" + "Benutzerinformationen anzeigen" + "Mitglied entfernen" + "Mitglied entfernen und den erneuten Beitritt sperren?" + "%1$s wird entfernt." + "Mitglied entfernen und sperren" + "Mitglied nur entfernen" + "Sperre aufheben" + "Die Nutzer können den Raum wieder beitreten, wenn sie dazu eingeladen werden." + "Benutzer entsperren" + "%1$s wird entsperrt." + diff --git a/features/roommembermoderation/impl/src/main/res/values-el/translations.xml b/features/roommembermoderation/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..85ea09cae4 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,21 @@ + + + "Αφαίρεση και αποκλεισμός μέλους" + "Αποκλεισμός" + "Δεν θα μπορεί να συμμετέχει ξανά σε αυτό το δωμάτιο εάν προσκληθεί." + "Θες σίγουρα να αποκλείσεις αυτό το μέλος;" + "Αποκλεισμός %1$s" + "Αφαίρεση" + "Θα μπορούν να συμμετάσχουν ξανά σε αυτό το δωμάτιο εάν προσκληθούν." + "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτό το μέλος;" + "Προβολή προφίλ" + "Αφαίρεση από το δωμάτιο" + "Αφαίρεση μέλους και απαγόρευση συμμετοχής στο μέλλον;" + "Αφαίρεση %1$s…" + "Αφαίρεση και αποκλεισμός μέλους" + "Μόνο αφαίρεση μέλους" + "Αναίρεση αποκλεισμού" + "Θα μπορεί να συμμετάσχει ξανά στο δωμάτιο εάν προσκληθεί." + "Άρση αποκλεισμού χρήστη" + "Άρση αποκλεισμού %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-es/translations.xml b/features/roommembermoderation/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..08a49a1425 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,18 @@ + + + "Sacar y vetar a un miembro" + "Vetar" + "No podrán volver a unirse a esta sala si son invitados." + "¿Estás seguro de que quieres vetar a este miembro?" + "Vetando a %1$s" + "Ver perfil" + "Sacar de la sala" + "¿Sacar al miembro y prohibirle unirse en el futuro?" + "Eliminando %1$s…" + "Sacar y vetar a un miembro" + "Solo eliminar miembro" + "Quitar veto" + "Podrán volver a unirse a esta sala si son invitados de nuevo." + "Quitar veto al usuario" + "Levantando veto a %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-et/translations.xml b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..98a05ea6b6 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,21 @@ + + + "Eemalda ja sea suhtluskeeld" + "Sea suhtluskeeld" + "Ta ei saa selle jututoaga liituda isegi kutse olemasolul." + "Kas sa oled kindel, et soovid sellele kasutajale seada suhtluskeelu?" + "Seame kasutajale %1$s suhtluskeelu" + "Eemalda" + "Uue kutse saamisel on tal võimalik selle jututoaga uuesti liituda." + "Kas sa oled kindel, et soovid selle osaleja eemaldada?" + "Vaata profiili" + "Eemalda kasutaja jututoast" + "Kas eemaldama kasutaja ja seame talle tulevikuks suhtluskeelu?" + "Eemaldame kasutajat %1$s…" + "Eemalda ja sea suhtluskeeld" + "Ainult eemalda kasutaja" + "Eemalda suhtluskeeld" + "Kutse olemasolul saab ta nüüd jututoaga uuesti liituda" + "Eemalda kasutaja suhtluskeeld" + "Eemaldame suhtluskeelu kasutajalt %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml b/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..c3d1f7c5a8 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,16 @@ + + + "Kendu kidea eta ezarri debekua" + "Ezarri debekua" + "Ziur kide honi debekua ezarri nahi diozula?" + "%1$s(r)i debekua ezartzen" + "Ikusi profila" + "Kendu gelatik" + "Kidea kendu eta etorkizunean sartzea debekatu?" + "%1$s kentzen…" + "Kendu kidea eta ezarri debekua" + "Kendu kidea soilik" + "Kendu debekua" + "Kendu debekua erabiltzaileari" + "%1$s(r)i debekua kentzen" + diff --git a/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..d1b986598a --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,21 @@ + + + "برداشت و تحریم عضو" + "تحریم" + "در صورت دعوت نمی‌تواند دوباره به اتاق بپیوندد." + "مطمئنید می‌خواهید این عضو را تحریم کنید؟" + "تحریم کردن %1$s" + "برداشتن" + "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." + "مطمئنید می‌خواهید این عضو را بردارید؟" + "دیدن نمایه" + "برداشتن از اتاق" + "برداشتن عضو و تحریم پیوستن در آینده؟" + "برداشتن %1$s…" + "برداشت و تحریم عضو" + "تنها برداشتن عضو" + "رفع انسداد" + "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." + "تحریم نکردن کاربر" + "رفع تحریم %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..050d20600a --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,21 @@ + + + "Poista jäsen huoneesta ja anna porttikielto" + "Anna porttikielto" + "He eivät voi enää liittyä tähän huoneeseen, jos heidät kutsutaan." + "Haluatko varmasti antaa tälle jäsenelle porttikiellon?" + "Annetaan porttikieltoa käyttäjälle %1$s" + "Poista" + "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." + "Haluatko varmasti poistaa tämän jäsenen?" + "Näytä profiili" + "Poista huoneesta" + "Poistetaanko jäsen huoneesta ja kielletäänkö heitä liittymästä tulevaisuudessa?" + "Poistetaan käyttäjää %1$s huoneesta…" + "Poista jäsen huoneesta ja anna porttikielto" + "Poista vain jäsen huoneesta" + "Poista porttikielto" + "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." + "Poista käyttäjän porttikielto" + "Poistetaan käyttäjän %1$s porttikieltoa" + diff --git a/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..88cfd7b309 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,21 @@ + + + "Retirer et bannir ce membre" + "Bannir" + "Il ne pourra pas rejoindre le salon à nouveau, même si il est invité." + "Êtes-vous certain de vouloir bannir ce membre ?" + "Bannissement de %1$s" + "Retirer" + "Cet utilisateur pourra rejoindre le salon à nouveau si il est invité." + "Voulez-vous vraiment supprimer ce membre ?" + "Voir le profil" + "Retirer le membre du salon" + "Retirer le membre et interdire l’adhésion à l’avenir ?" + "Enlever %1$s…" + "Retirer et bannir ce membre" + "Retirer le membre uniquement" + "Débannir" + "Il pourra rejoindre le salon à nouveau si il est invité." + "Débannir l’utilisateur" + "Débannissement de %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..bb2123e479 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,21 @@ + + + "Eltávolítás és a tag kitiltása" + "Kitiltás" + "Többé nem csatlakozhat ehhez a szobához, akkor sem, ha meghívják." + "Biztos, hogy kitiltja ezt a tagot?" + "%1$s kitiltása" + "Eltávolítás" + "Ehhez a szobához is csatlakozhat, ha meghívják." + "Biztos, hogy eltávolítja ezt a tagot?" + "Profil megtekintése" + "Eltávolítás a szobából" + "Eltávolítja a tagot, és megtiltja a jövőbeni csatlakozást?" + "%1$s eltávolítása…" + "Eltávolítás és a tag kitiltása" + "Csak a tag eltávolítása" + "Tiltás feloldása" + "Ehhez a szobához is csatlakozhat, ha meghívják." + "Felhasználó tiltásának feloldása" + "%1$s tiltásának feloldása" + diff --git a/features/roommembermoderation/impl/src/main/res/values-it/translations.xml b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..a1bdeed0b0 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,18 @@ + + + "Rimuovi ed escludi" + "Escludi" + "Non potrà entrare nuovamente in questa stanza se invitato." + "Vuoi davvero escludere questo membro?" + "Esclusione di %1$s" + "Visualizza profilo" + "Rimuovi dalla stanza" + "Rimuovere e vietare l\'accesso in futuro?" + "Rimozione di %1$s…" + "Rimuovi ed escludi" + "Rimuovi soltanto" + "Riammetti" + "Potrà entrare nuovamente in questa stanza se invitato." + "Riammetti utente" + "Riammissione di %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml new file mode 100644 index 0000000000..730fff2714 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml @@ -0,0 +1,18 @@ + + + "წევრის წაშლა და დაბლოკვა" + "დაბლოკვა" + "მოწვევის შემთხვევაში ამ ოთახში კვლავ გაწევრიანებას ვერ შეძლებენ." + "დარწმუნებული ხართ, რომ ამ წევრის დაბლოკვა გსურთ?" + "%1$s-ს დაბლოკვა" + "პროფილის ნახვა" + "ოთახიდან გაგდება" + "გსურთ წევრის გაგდება და მომავალში გაწევრიანების აკრძალვა?" + "%1$s-ს გაგდება…" + "წევრის წაშლა და დაბლოკვა" + "მხოლოდ წევრის წაშლა" + "განბლოკვა" + "მოწვევის შემთხვევაში განბლოკილი მომხმარებელი ისევ შეძლებს ოთახს შეუერთდეს." + "მომხმარებლის განბლოკვა" + "%1$s-ს განბლოკვა" + diff --git a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml new file mode 100644 index 0000000000..b25be530ec --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml @@ -0,0 +1,21 @@ + + + "Fjern og utesteng medlem" + "Utesteng" + "De vil ikke kunne bli med i dette rommet igjen hvis de blir invitert." + "Er du sikker på at du vil utestenge dette medlemmet?" + "Utestenger %1$s" + "Fjern" + "De vil kunne bli med i dette rommet igjen hvis de blir invitert." + "Er du sikker på at du vil fjerne dette medlemmet?" + "Vis profil" + "Fjern fra rommet" + "Fjerne medlem og utestenge fra å bli med i fremtiden?" + "Fjerner %1$s…" + "Fjern og utesteng medlem" + "Bare fjern medlem" + "Opphev utestengelse" + "De vil kunne bli med i dette rommet igjen hvis de blir invitert." + "Opphev utestengelse av bruker" + "Oppheve utestengelsen av %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..f025cbb584 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,18 @@ + + + "Lid verwijderen en verbannen" + "Verbannen" + "Ze kunnen niet meer toetreden tot deze kamer als ze worden uitgenodigd." + "Weet je zeker dat je dit lid wilt verbannen?" + "%1$s verbannen" + "Profiel bekijken" + "Verwijderen uit kamer" + "Lid verwijderen en toekomstige deelname verbieden?" + "%1$s wordt verwijderd…" + "Lid verwijderen en verbannen" + "Alleen lid verwijderen" + "Ontbannen" + "Ze kunnen opnieuw tot de kamer toetreden als ze worden uitgenodigd." + "Ontban gebruiker" + "%1$s ontbannen" + diff --git a/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..40bd309913 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,21 @@ + + + "Usuń i zbanuj członka" + "Zbanuj" + "Nie będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." + "Czy na pewno chcesz zbanować tego członka?" + "Banowanie %1$s" + "Usuń" + "Będą mogli ponownie dołączyć do pokoju, jeśli zostaną zaproszeni." + "Czy na pewno chcesz usunąć tego członka?" + "Wyświetl profil" + "Usuń z pokoju" + "Usunąć członka i zablokować możliwość dołączenia w przyszłości?" + "Usuwanie %1$s…" + "Usuń i zbanuj członka" + "Tylko usuń członka" + "Odbanuj" + "Będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." + "Odbanuj użytkownika" + "Odbanowanie %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..764c313a96 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,18 @@ + + + "Remover e banir membro" + "Banir" + "Eles não poderão entrar nesta sala novamente se forem convidados." + "Tem certeza de que quer banir este membro?" + "Banindo %1$s" + "Ver perfil" + "Remover da sala" + "Remover membro e banir de entrar novamente no futuro?" + "Removendo %1$s…" + "Remover e banir membro" + "Somente remover membro" + "Desbanir" + "Eles poderão entrar nesta sala novamente se forem convidados." + "Desbanir usuário" + "Desbanindo %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml new file mode 100644 index 0000000000..89ddd6f5ba --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml @@ -0,0 +1,19 @@ + + + "Remover e banir participante" + "Banir" + "Não poderão voltar a entrar nesta sala, mesmo se forem convidados." + "Tens a certeza que queres banir este participante?" + "A banir %1$s" + "Poderão entrar na sala novamente se convidados." + "Ver perfil" + "Remover da sala" + "Remover participante e proibir que entre no futuro?" + "A remover %1$s…" + "Remover e banir participante" + "Remover apenas" + "Anular banimento" + "Poderão juntar-se novamente a esta sala se forem convidados." + "Anular banimento do utilizador" + "A anular banimento de %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..526db044db --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,18 @@ + + + "Eliminați și interziceți membrul" + "Interzicere" + "Nu se vor putea alătura din nou acestei camere dacă sunt invitați." + "Sunteți sigur că doriți să interziceți acest membru?" + "Se interzice %1$s" + "Vizualizare profil" + "Înlăturați membrul" + "Înlăturați membrul și interziceți-i să se alăture în viitor?" + "Se elimină %1$s" + "Eliminați și interziceți membrul" + "Doar înlăturare" + "Anulare excludere" + "Se vor putea alătura din nou acestei săli dacă sunt invitați." + "Anulați interzicerea utilizatorului" + "Se anulează interzicerea lui %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..93fa22ac3f --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,20 @@ + + + "Удалить и заблокировать участника" + "Заблокировать" + "Они не смогут снова присоединиться к этой комнате, если их пригласят." + "Вы уверены, что хотите заблокировать этого участника?" + "Блокировка %1$s" + "Удалить" + "Вы действительно хотите удалить этого участника?" + "Посмотреть профиль" + "Удалить участника из комнаты" + "Удалить участника и запретить присоединяться в будущем?" + "Удаление %1$s…" + "Удалить и заблокировать участника" + "Только удалить участника" + "Разблокировать" + "Они снова смогут присоединиться в эту комнату если их пригласят." + "Разбанить пользователя?" + "Разблокировка %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..8e7c4a1399 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,21 @@ + + + "Odstrániť a zakázať člena" + "Zakázať" + "Nebudú sa môcť pripojiť k tejto miestnosti znova ani ak budú pozvaní." + "Ste si istý, že chcete zakázať tohto člena?" + "Zakazuje sa %1$s" + "Odstrániť" + "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." + "Ste si istý, že chcete odstrániť tohto člena?" + "Zobraziť profil" + "Odstrániť z miestnosti" + "Odstrániť člena a zakázať vstup v budúcnosti?" + "Odstraňuje sa %1$s…" + "Odstrániť a zakázať člena" + "Iba odstrániť člena" + "Zrušiť zákaz" + "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." + "Zrušiť zákaz používateľa" + "Zrušenie zákazu %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml b/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml new file mode 100644 index 0000000000..bb512b1142 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml @@ -0,0 +1,21 @@ + + + "Ta bort och banna medlem" + "Banna" + "Denne kommer inte att kunna gå med i det här rummet igen om denne bjuds in." + "Är du säker på att du vill banna den här medlemmen?" + "Bannar %1$s" + "Ta bort" + "Denne kommer kunna gå med i rummet igen om denne bjuds in" + "Är du säker på att du vill ta bort den här medlemmen?" + "Visa profil" + "Ta bort från rummet" + "Ta bort medlem och banna från att gå med i framtiden?" + "Tar bort %1$s …" + "Ta bort och banna medlem" + "Ta bara bort medlem" + "Avbanna" + "Denne kommer kunna gå med i rummet igen om denne bjuds in" + "Avbanna användare" + "Avbannar %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml new file mode 100644 index 0000000000..beee8fa720 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml @@ -0,0 +1,18 @@ + + + "Üyeyi çıkar ve yasakla" + "Yasakla" + "Davet edilseler bile bu odaya tekrar katılamazlar." + "Bu üyeyi yasaklamak istediğinize emin misiniz?" + "Yasaklanıyor %1$s" + "Profili görüntüle" + "Odadan çıkar" + "Üyeyi çıkarın ve gelecekte katılmasını yasaklayın?" + "Kaldırılıyor %1$s…" + "Üyeyi çıkar ve yasakla" + "Yalnızca üyeyi kaldır" + "Yasağı Kaldır" + "Davet edildikleri takdirde bu odaya tekrar katılabileceklerdir." + "Kullanıcının yasağını kaldır" + "Yasak kaldırılıyor %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml b/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..d6ca0625a8 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,21 @@ + + + "Вилучити й заблокувати учасника" + "Заблокувати" + "Він не зможе приєднатися до цієї кімнати знову, якщо його запросять." + "Ви точно хочете заблокувати цього користувача?" + "Блокування %1$s" + "Вилучити" + "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." + "Ви дійсно хочете вилучити цього учасника?" + "Переглянути профіль" + "Вилучити з кімнати" + "Вилучити учасника та заборонити приєднання в майбутньому?" + "Вилучення %1$s…" + "Вилучити й заблокувати учасника" + "Лише вилучити учасника" + "Розблокувати" + "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." + "Розблокувати користувача" + "Розблокування %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..6f1ab7d548 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,21 @@ + + + "踢出並加入黑名單" + "加入黑名單" + "即使收到邀請,他們仍然無法加入聊天室。" + "您確定要將此成員加入黑名單?" + "正在將 %1$s 加入黑名單" + "移除" + "若收到邀請,他們可以再次加入此聊天室。" + "您真的想要移除此成員嗎?" + "查看個人檔案" + "踢出聊天室" + "移除成員並禁止未來再度加入?" + "正在踢出 %1$s…" + "踢出並加入黑名單" + "僅移除成員" + "解除黑名單" + "如果收到邀請,他們能再次加入聊天室。" + "解除黑名單" + "正在解除黑名單 %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml new file mode 100644 index 0000000000..cf89b13e06 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml @@ -0,0 +1,21 @@ + + + "移除并封禁成员" + "封禁" + "即使受到邀请,他们也无法再次加入聊天室。" + "您确定要封禁该成员吗?" + "封禁 %1$s" + "移除" + "如果受到邀请,他们可以重新加入聊天室。" + "您确定要移除此成员吗?" + "查看个人资料" + "从聊天室移除" + "删除成员并禁止重新加入?" + "正在移除 %1$s……" + "移除并封禁成员" + "仅移除成员" + "取消封禁" + "如果受到邀请,他们可以重新加入聊天室。" + "解封用户" + "解除封禁 %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values/localazy.xml b/features/roommembermoderation/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..8e9b3defc1 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values/localazy.xml @@ -0,0 +1,21 @@ + + + "Ban from room" + "Ban" + "They won’t be able to join this room again if invited." + "Are you sure you want to ban this member?" + "Banning %1$s" + "Remove" + "They will be able to join this room again if invited." + "Are you sure you want to remove this member?" + "View profile" + "Remove from room" + "Remove member and ban from joining in the future?" + "Removing %1$s…" + "Ban from room" + "Only remove member" + "Unban" + "They will be able to join this room again if invited." + "Unban user" + "Unbanning %1$s" + diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index 6448bfb974..51a60d788f 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -289,15 +289,6 @@ "Гэй, пагавары са мной у %1$s: %2$s" "%1$s Android" "Паведаміць аб памылцы з дапамогай Rageshake" - "Выдаліць і заблакіраваць удзельніка" - "Заблакіраваць" - "Яны не змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." - "Вы ўпэўнены, што хочаце заблакіраваць гэтага карыстальніка?" - "Блакіроўка %1$s" - "Прагляд профілю" - "Выдаліць удзельніка з пакоя" - "Выдаліць удзельніка і забараніць далучацца ў будучыні?" - "Выдаленне %1$s…" "Не ўдалося выбраць носьбіт, паўтарыце спробу." "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз." diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 4d7409e7c1..01bb2d1519 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -343,18 +343,6 @@ Opravdu chcete pokračovat?" "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Odebrat a vykázat člena" - "Vykázat" - "Nebudou se moci znovu připojit k této místnosti, pokud budou pozváni." - "Jste si jisti, že chcete vykázat tohoto člena?" - "Vykazování %1$s" - "Odebrat" - "Budou moci znovu vstoupit do této místnosti, pokud budou pozváni." - "Opravdu chcete tohoto člena odebrat?" - "Zobrazit profil" - "Odebrat z místnosti" - "Odebrat člena a zakázat mu připojení v budoucnu?" - "Odstraňování %1$s…" "Výběr média se nezdařil, zkuste to prosím znovu." "Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace." "Nahrání média se nezdařilo, zkuste to prosím znovu." diff --git a/libraries/ui-strings/src/main/res/values-cy/translations.xml b/libraries/ui-strings/src/main/res/values-cy/translations.xml index 262e145296..6cda9da5ff 100644 --- a/libraries/ui-strings/src/main/res/values-cy/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cy/translations.xml @@ -355,18 +355,6 @@ Ydych chi\'n siŵr eich bod am barhau?" "Hei, siaradwch â mi ar %1$s: %2$s" "Android %1$s" "Rageshake i adrodd gwall" - "Gwahardd o ystafell" - "Atal" - "Fyddan nhw ddim yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Ydych chi\'n siŵr eich bod am wahardd yr aelod hwn?" - "Yn gwahardd %1$s" - "Tynnu" - "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Ydych chi\'n siŵr eich bod am ddileu\'r aelod hwn?" - "Gweld proffil" - "Tynnu o\'r ystafell" - "Dileu aelod a\'u gwahardd rhag ymuno yn y dyfodol?" - "Wrthi\'n dileu %1$s…" "Wedi methu dewis cyfrwng, ceisiwch eto." "Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn." "Wedi methu â phrosesu cyfryngau i\'w llwytho, ceisiwch eto." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 8945b8cd6b..451d4bc140 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -339,18 +339,6 @@ Möchten Sie wirklich fortfahren?" "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - "Mitglied entfernen und sperren" - "Sperren" - "Sie können dem Raum nicht mehr beitreten, selbst wenn sie eingeladen werden." - "Bist du sicher, dass du dieses Mitglied sperren möchtest?" - "%1$s wird gesperrt." - "Entfernen" - "Sie können diesen Raum wieder betreten, wenn sie eingeladen werden." - "Möchten Sie dieses Mitglied wirklich entfernen?" - "Benutzerinformationen anzeigen" - "Mitglied entfernen" - "Mitglied entfernen und den erneuten Beitritt sperren?" - "%1$s wird entfernt." "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 24a92cad85..7b7c4ea183 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -339,18 +339,6 @@ "Γεια, μίλα μου στην εφαρμογή %1$s :%2$s" "%1$s Android" "Κούνησε δυνατά τη συσκευή σου για να αναφέρεις κάποιο σφάλμα" - "Αφαίρεση και αποκλεισμός μέλους" - "Αποκλεισμός" - "Δεν θα μπορεί να συμμετέχει ξανά σε αυτό το δωμάτιο εάν προσκληθεί." - "Θες σίγουρα να αποκλείσεις αυτό το μέλος;" - "Αποκλεισμός %1$s" - "Αφαίρεση" - "Θα μπορούν να συμμετάσχουν ξανά σε αυτό το δωμάτιο εάν προσκληθούν." - "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτό το μέλος;" - "Προβολή προφίλ" - "Αφαίρεση από το δωμάτιο" - "Αφαίρεση μέλους και απαγόρευση συμμετοχής στο μέλλον;" - "Αφαίρεση %1$s…" "Αποτυχία επιλογής πολυμέσου, δοκίμασε ξανά." "Οι λεζάντες ενδέχεται να μην είναι ορατές σε άτομα που χρησιμοποιούν παλαιότερες εφαρμογές." "Αποτυχία μεταφόρτωσης μέσου, δοκίμασε ξανά." diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index 1cf59dacbf..66ffa2aee3 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -316,15 +316,6 @@ Motivo: %1$s." "Hola, puedes hablar conmigo en %1$s: %2$s" "%1$s Android" "Agitar con fuerza para informar de un error" - "Sacar y vetar a un miembro" - "Vetar" - "No podrán volver a unirse a esta sala si son invitados." - "¿Estás seguro de que quieres vetar a este miembro?" - "Vetando a %1$s" - "Ver perfil" - "Sacar de la sala" - "¿Sacar al miembro y prohibirle unirse en el futuro?" - "Eliminando %1$s…" "Error al seleccionar archivos multimedia, por favor inténtalo de nuevo." "Es posible que las leyendas no sean visibles para las personas que usan aplicaciones más antiguas." "Error al procesar el contenido multimedia, por favor inténtalo de nuevo." diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index 4d0bc5c422..e1e7f809c6 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -339,18 +339,6 @@ Kas sa oled kindel, et soovid jätkata?" "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" "Veast teatamiseks raputa nutiseadet ägedalt" - "Eemalda ja sea suhtluskeeld" - "Sea suhtluskeeld" - "Ta ei saa selle jututoaga liituda isegi kutse olemasolul." - "Kas sa oled kindel, et soovid sellele kasutajale seada suhtluskeelu?" - "Seame kasutajale %1$s suhtluskeelu" - "Eemalda" - "Uue kutse saamisel on tal võimalik selle jututoaga uuesti liituda." - "Kas sa oled kindel, et soovid selle osaleja eemaldada?" - "Vaata profiili" - "Eemalda kasutaja jututoast" - "Kas eemaldama kasutaja ja seame talle tulevikuks suhtluskeelu?" - "Eemaldame kasutajat %1$s…" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele." "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." diff --git a/libraries/ui-strings/src/main/res/values-eu/translations.xml b/libraries/ui-strings/src/main/res/values-eu/translations.xml index e43f2d24f7..73d10a27e1 100644 --- a/libraries/ui-strings/src/main/res/values-eu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-eu/translations.xml @@ -312,14 +312,6 @@ Arrazoia: %1$s." "🔐️ Zatoz nirekin %1$s(e)ra" "%1$s Android" "Astindu erroreen berri emateko" - "Kendu kidea eta ezarri debekua" - "Ezarri debekua" - "Ziur kide honi debekua ezarri nahi diozula?" - "%1$s(r)i debekua ezartzen" - "Ikusi profila" - "Kendu gelatik" - "Kidea kendu eta etorkizunean sartzea debekatu?" - "%1$s kentzen…" "Huts egin du multimedia aukeratzeak, saiatu berriro." "Huts egin du multimedia igotzeak, saiatu berriro." "Finkatutako mezuak" diff --git a/libraries/ui-strings/src/main/res/values-fa/translations.xml b/libraries/ui-strings/src/main/res/values-fa/translations.xml index 50f98c1a19..4fb5ceea2d 100644 --- a/libraries/ui-strings/src/main/res/values-fa/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fa/translations.xml @@ -310,18 +310,6 @@ "🔐️ پییوستن به من روی %1$s" "درود. با من روی %1$s صحبت کن: %2$s" "%1$s اندروید" - "برداشت و تحریم عضو" - "تحریم" - "در صورت دعوت نمی‌تواند دوباره به اتاق بپیوندد." - "مطمئنید می‌خواهید این عضو را تحریم کنید؟" - "تحریم کردن %1$s" - "برداشتن" - "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." - "مطمئنید می‌خواهید این عضو را بردارید؟" - "دیدن نمایه" - "برداشتن از اتاق" - "برداشتن عضو و تحریم پیوستن در آینده؟" - "برداشتن %1$s…" "گزینش رسانه شکست خورد. لطفاً دوباره تلاش کنید." "پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید." "بارگذاری رسانه شکست خورد. لطفاً دوباره تلاش کنید." diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index 293b3a49e2..5dd5b6b3b2 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -339,18 +339,6 @@ Haluatko varmasti jatkaa?" "Hei, keskustele kanssani %1$s -sovelluksessa: %2$s" "%1$s Android" "Raivostunut ravistaminen ilmoittaa virheestä" - "Poista jäsen huoneesta ja anna porttikielto" - "Anna porttikielto" - "He eivät voi enää liittyä tähän huoneeseen, jos heidät kutsutaan." - "Haluatko varmasti antaa tälle jäsenelle porttikiellon?" - "Annetaan porttikieltoa käyttäjälle %1$s" - "Poista" - "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." - "Haluatko varmasti poistaa tämän jäsenen?" - "Näytä profiili" - "Poista huoneesta" - "Poistetaanko jäsen huoneesta ja kielletäänkö heitä liittymästä tulevaisuudessa?" - "Poistetaan käyttäjää %1$s huoneesta…" "Median valinta epäonnistui, yritä uudelleen." "Kuvatekstit eivät välttämättä näy ihmisille, jotka käyttävät vanhempia sovelluksia." "Median käsittely epäonnistui, yritä uudelleen." diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index 27cac31aa0..1171f32df8 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -339,18 +339,6 @@ Raison : %1$s." "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" "Rageshake pour signaler un problème" - "Retirer et bannir ce membre" - "Bannir" - "Il ne pourra pas rejoindre le salon à nouveau, même si il est invité." - "Êtes-vous certain de vouloir bannir ce membre ?" - "Bannissement de %1$s" - "Retirer" - "Cet utilisateur pourra rejoindre le salon à nouveau si il est invité." - "Voulez-vous vraiment supprimer ce membre ?" - "Voir le profil" - "Retirer le membre du salon" - "Retirer le membre et interdire l’adhésion à l’avenir ?" - "Enlever %1$s…" "Échec de la sélection du média, veuillez réessayer." "Les légendes peuvent ne pas être visibles pour les utilisateurs d’anciennes applications." "Échec du traitement des médias à télécharger, veuillez réessayer." diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index d304030e00..2704f8796b 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -339,18 +339,6 @@ Biztos, hogy folytatja?" "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" - "Eltávolítás és a tag kitiltása" - "Kitiltás" - "Többé nem csatlakozhat ehhez a szobához, akkor sem, ha meghívják." - "Biztos, hogy kitiltja ezt a tagot?" - "%1$s kitiltása" - "Eltávolítás" - "Ehhez a szobához is csatlakozhat, ha meghívják." - "Biztos, hogy eltávolítja ezt a tagot?" - "Profil megtekintése" - "Eltávolítás a szobából" - "Eltávolítja a tagot, és megtiltja a jövőbeni csatlakozást?" - "%1$s eltávolítása…" "Nem sikerült kiválasztani a médiát, próbálja újra." "Előfordulhat, hogy a feliratok nem láthatók a régebbi alkalmazásokat használók számára." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index c32ce5f730..cec28f2ad4 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -304,15 +304,6 @@ Alasan: %1$s." "Hai, bicaralah dengan saya di %1$s: %2$s" "%1$s Android" "Rageshake untuk melaporkan kutu" - "Keluarkan dan cekal anggota" - "Cekal" - "Mereka tidak akan dapat bergabung ke ruangan ini lagi jika diundang." - "Apakah Anda yakin ingin mencekal anggota ini?" - "Mencekal %1$s" - "Tampilkan profil" - "Keluarkan dari ruangan" - "Keluarkan pengguna dan cekal pengguna bergabung lagi di masa mendatang?" - "Mengeluarkan %1$s…" "Gagal memilih media, silakan coba lagi." "Keterangan mungkin tidak terlihat oleh orang yang menggunakan aplikasi lama." "Gagal memproses media untuk diunggah, silakan coba lagi." diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index e9864e090d..bf11478b9e 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -326,15 +326,6 @@ Sei sicuro di voler continuare?" "Ehi, parliamo su %1$s: %2$s" "%1$s Android" "Scuoti per segnalare un problema" - "Rimuovi ed escludi" - "Escludi" - "Non potrà entrare nuovamente in questa stanza se invitato." - "Vuoi davvero escludere questo membro?" - "Esclusione di %1$s" - "Visualizza profilo" - "Rimuovi dalla stanza" - "Rimuovere e vietare l\'accesso in futuro?" - "Rimozione di %1$s…" "Selezione del file multimediale fallita, riprova." "Le didascalie potrebbero non essere visibili agli utenti di app meno recenti." "Elaborazione del file multimediale da caricare fallita, riprova." diff --git a/libraries/ui-strings/src/main/res/values-ka/translations.xml b/libraries/ui-strings/src/main/res/values-ka/translations.xml index 0405aa6235..88bd672c82 100644 --- a/libraries/ui-strings/src/main/res/values-ka/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ka/translations.xml @@ -247,15 +247,6 @@ "გაგიმარჯოს! მესაუბრე %1$s-ზე: %2$s" "%1$s Android" "შეცდომის შესატყობინებლად ტელეფონის შენჯღრევა" - "წევრის წაშლა და დაბლოკვა" - "დაბლოკვა" - "მოწვევის შემთხვევაში ამ ოთახში კვლავ გაწევრიანებას ვერ შეძლებენ." - "დარწმუნებული ხართ, რომ ამ წევრის დაბლოკვა გსურთ?" - "%1$s-ს დაბლოკვა" - "პროფილის ნახვა" - "ოთახიდან გაგდება" - "გსურთ წევრის გაგდება და მომავალში გაწევრიანების აკრძალვა?" - "%1$s-ს გაგდება…" "მედიის შერჩევა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა." "მედიის ატვირთვა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა." "მედიის ატვირთვა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა." diff --git a/libraries/ui-strings/src/main/res/values-nb/translations.xml b/libraries/ui-strings/src/main/res/values-nb/translations.xml index ce8d655b6a..df9d892f14 100644 --- a/libraries/ui-strings/src/main/res/values-nb/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nb/translations.xml @@ -339,18 +339,6 @@ Er du sikker på at du vil fortsette?" "Hei, snakk med meg på %1$s: %2$s" "%1$s Android" "Rageshake for å rapportere feil" - "Fjern og utesteng medlem" - "Utesteng" - "De vil ikke kunne bli med i dette rommet igjen hvis de blir invitert." - "Er du sikker på at du vil utestenge dette medlemmet?" - "Utestenger %1$s" - "Fjern" - "De vil kunne bli med i dette rommet igjen hvis de blir invitert." - "Er du sikker på at du vil fjerne dette medlemmet?" - "Vis profil" - "Fjern fra rommet" - "Fjerne medlem og utestenge fra å bli med i fremtiden?" - "Fjerner %1$s…" "Kunne ikke velge medium, prøv igjen." "Teksting er kanskje ikke synlig for personer som bruker eldre apper." "Kunne ikke behandle medier for opplasting, vennligst prøv igjen." diff --git a/libraries/ui-strings/src/main/res/values-nl/translations.xml b/libraries/ui-strings/src/main/res/values-nl/translations.xml index 0ad734232f..290720da86 100644 --- a/libraries/ui-strings/src/main/res/values-nl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nl/translations.xml @@ -292,15 +292,6 @@ Reden: %1$s." "Hé, praat met me op %1$s: %2$s" "%1$s Android" "Schudden om een bug te melden" - "Lid verwijderen en verbannen" - "Verbannen" - "Ze kunnen niet meer toetreden tot deze kamer als ze worden uitgenodigd." - "Weet je zeker dat je dit lid wilt verbannen?" - "%1$s verbannen" - "Profiel bekijken" - "Verwijderen uit kamer" - "Lid verwijderen en toekomstige deelname verbieden?" - "%1$s wordt verwijderd…" "Het selecteren van media is mislukt. Probeer het opnieuw." "Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw." "Het uploaden van media is mislukt. Probeer het opnieuw." diff --git a/libraries/ui-strings/src/main/res/values-pl/translations.xml b/libraries/ui-strings/src/main/res/values-pl/translations.xml index e3e742072a..4d0941fcb1 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -343,18 +343,6 @@ Czy na pewno chcesz kontynuować?" "Hej, porozmawiajmy na %1$s: %2$s" "%1$s Android" "Wstrząśnij gniewnie, aby zgłosić błąd" - "Usuń i zbanuj członka" - "Zbanuj" - "Nie będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." - "Czy na pewno chcesz zbanować tego członka?" - "Banowanie %1$s" - "Usuń" - "Będą mogli ponownie dołączyć do pokoju, jeśli zostaną zaproszeni." - "Czy na pewno chcesz usunąć tego członka?" - "Wyświetl profil" - "Usuń z pokoju" - "Usunąć członka i zablokować możliwość dołączenia w przyszłości?" - "Usuwanie %1$s…" "Nie udało się wybrać multimediów. Spróbuj ponownie." "Opis może być niedostępny dla osób korzystających ze starszej wersji aplikacji." "Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie." diff --git a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml index b654dd355b..12df92cca6 100644 --- a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml @@ -287,15 +287,6 @@ "Ei, fale comigo em %1$s: %2$s" "%1$s Android" "Rageshake para relatar um bug" - "Remover e banir membro" - "Banir" - "Eles não poderão entrar nesta sala novamente se forem convidados." - "Tem certeza de que quer banir este membro?" - "Banindo %1$s" - "Ver perfil" - "Remover da sala" - "Remover membro e banir de entrar novamente no futuro?" - "Removendo %1$s…" "Falha ao selecionar a mídia, tente novamente." "Falha ao processar mídia para upload. Tente novamente." "Falha ao enviar mídia. Tente novamente." diff --git a/libraries/ui-strings/src/main/res/values-pt/translations.xml b/libraries/ui-strings/src/main/res/values-pt/translations.xml index 24fa3d7e35..696de1e319 100644 --- a/libraries/ui-strings/src/main/res/values-pt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt/translations.xml @@ -324,16 +324,6 @@ Tens a certeza de que queres continuar?" "Alô! Fala comigo na %1$s: %2$s" "%1$s Android" "Agita o dispositivo em fúria para comunicar um problema" - "Remover e banir participante" - "Banir" - "Não poderão voltar a entrar nesta sala, mesmo se forem convidados." - "Tens a certeza que queres banir este participante?" - "A banir %1$s" - "Poderão entrar na sala novamente se convidados." - "Ver perfil" - "Remover da sala" - "Remover participante e proibir que entre no futuro?" - "A remover %1$s…" "Falha ao selecionar multimédia, por favor tente novamente." "As legendas poderão não ser visíveis em versões mais antigas da aplicação." "Falha ao processar multimédia para carregamento, por favor tente novamente." diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 13a63c3ba9..98d849ce1b 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -294,15 +294,6 @@ Motiv:%1$s." "Hei, vorbește cu mine pe %1$s: %2$s" "%1$s Android" "Rageshake pentru a raporta erori" - "Eliminați și interziceți membrul" - "Interzicere" - "Nu se vor putea alătura din nou acestei camere dacă sunt invitați." - "Sunteți sigur că doriți să interziceți acest membru?" - "Se interzice %1$s" - "Vizualizare profil" - "Înlăturați membrul" - "Înlăturați membrul și interziceți-i să se alăture în viitor?" - "Se elimină %1$s" "Selectarea fișierelor media a eșuat, încercați din nou." "Procesarea datelor media a eșuat, vă rugăm să încercați din nou." "Încărcarea fișierelor media a eșuat, încercați din nou." diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index 1b67806b09..7329755b34 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -343,17 +343,6 @@ "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" "Встряхните устройство, чтобы сообщить об ошибке" - "Удалить и заблокировать участника" - "Заблокировать" - "Они не смогут снова присоединиться к этой комнате, если их пригласят." - "Вы уверены, что хотите заблокировать этого участника?" - "Блокировка %1$s" - "Удалить" - "Вы действительно хотите удалить этого участника?" - "Посмотреть профиль" - "Удалить участника из комнаты" - "Удалить участника и запретить присоединяться в будущем?" - "Удаление %1$s…" "Не удалось выбрать носитель, попробуйте еще раз." "Подпись может быть не видна пользователям старых приложений." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 7c4302db9a..cffde3494a 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -343,18 +343,6 @@ Naozaj chcete pokračovať?" "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" "Zúrivo potriasť pre nahlásenie chyby" - "Odstrániť a zakázať člena" - "Zakázať" - "Nebudú sa môcť pripojiť k tejto miestnosti znova ani ak budú pozvaní." - "Ste si istý, že chcete zakázať tohto člena?" - "Zakazuje sa %1$s" - "Odstrániť" - "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." - "Ste si istý, že chcete odstrániť tohto člena?" - "Zobraziť profil" - "Odstrániť z miestnosti" - "Odstrániť člena a zakázať vstup v budúcnosti?" - "Odstraňuje sa %1$s…" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 0fda3671c3..098226a069 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -339,18 +339,6 @@ Anledning:%1$s." "Hallå, prata med mig på %1$s: %2$s" "%1$s Android" "Raseriskaka för att rapportera bugg" - "Ta bort och banna medlem" - "Banna" - "Denne kommer inte att kunna gå med i det här rummet igen om denne bjuds in." - "Är du säker på att du vill banna den här medlemmen?" - "Bannar %1$s" - "Ta bort" - "Denne kommer kunna gå med i rummet igen om denne bjuds in" - "Är du säker på att du vill ta bort den här medlemmen?" - "Visa profil" - "Ta bort från rummet" - "Ta bort medlem och banna från att gå med i framtiden?" - "Tar bort %1$s …" "Misslyckades att välja media, vänligen pröva igen." "Bildtexter kanske inte är synliga för personer som använder äldre appar." "Misslyckades att bearbeta media för uppladdning, vänligen pröva igen." diff --git a/libraries/ui-strings/src/main/res/values-tr/translations.xml b/libraries/ui-strings/src/main/res/values-tr/translations.xml index 16766f12af..46f87e2019 100644 --- a/libraries/ui-strings/src/main/res/values-tr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-tr/translations.xml @@ -316,15 +316,6 @@ Neden: %1$s." "Hey, benimle konuş %1$s: %2$s" "%1$s Android" "Hata bildirmek için Rageshake" - "Üyeyi çıkar ve yasakla" - "Yasakla" - "Davet edilseler bile bu odaya tekrar katılamazlar." - "Bu üyeyi yasaklamak istediğinize emin misiniz?" - "Yasaklanıyor %1$s" - "Profili görüntüle" - "Odadan çıkar" - "Üyeyi çıkarın ve gelecekte katılmasını yasaklayın?" - "Kaldırılıyor %1$s…" "Medya seçilemedi, lütfen tekrar deneyin." "Açıklamalar, eski uygulamaları kullanan kişiler tarafından görülemeyebilir." "Medya yüklenemedi, lütfen tekrar deneyin." diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index f8a233d183..141a65850c 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -343,18 +343,6 @@ "Вітаю, поспілкуйтеся зі мною в %1$s: %2$s" "%1$s Android" "Повідомити про ваду за допомогою Rageshake" - "Вилучити й заблокувати учасника" - "Заблокувати" - "Він не зможе приєднатися до цієї кімнати знову, якщо його запросять." - "Ви точно хочете заблокувати цього користувача?" - "Блокування %1$s" - "Вилучити" - "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." - "Ви дійсно хочете вилучити цього учасника?" - "Переглянути профіль" - "Вилучити з кімнати" - "Вилучити учасника та заборонити приєднання в майбутньому?" - "Вилучення %1$s…" "Не вдалося вибрати медіафайл, спробуйте ще раз." "Користувачі старих застосунків можуть не бачити підписи." "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index ec0c7be7b6..8986bf6e79 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -333,18 +333,6 @@ "嘿,來 %1$s 和我聊天:%2$s" "%1$s Android" "憤怒搖晃以回報臭蟲" - "踢出並加入黑名單" - "加入黑名單" - "即使收到邀請,他們仍然無法加入聊天室。" - "您確定要將此成員加入黑名單?" - "正在將 %1$s 加入黑名單" - "移除" - "若收到邀請,他們可以再次加入此聊天室。" - "您真的想要移除此成員嗎?" - "查看個人檔案" - "踢出聊天室" - "移除成員並禁止未來再度加入?" - "正在踢出 %1$s…" "選取媒體失敗,請再試一次。" "使用舊應用程式的使用者可能看不到標題。" "無法處理要上傳的媒體,請再試一次。" diff --git a/libraries/ui-strings/src/main/res/values-zh/translations.xml b/libraries/ui-strings/src/main/res/values-zh/translations.xml index cd7cd61f9b..50eaf02824 100644 --- a/libraries/ui-strings/src/main/res/values-zh/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh/translations.xml @@ -327,18 +327,6 @@ "嗨!请通过 %1$s 与我联系:%2$s" "%1$s Android" "摇一摇以报错" - "移除并封禁成员" - "封禁" - "即使受到邀请,他们也无法再次加入聊天室。" - "您确定要封禁该成员吗?" - "封禁 %1$s" - "移除" - "如果受到邀请,他们可以重新加入聊天室。" - "您确定要移除此成员吗?" - "查看个人资料" - "从聊天室移除" - "删除成员并禁止重新加入?" - "正在移除 %1$s……" "选择媒体失败,请重试。" "使用旧版应用程序的用户可能无法看到字幕。" "处理要上传的媒体失败,请重试。" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index e5d35def0c..51db790fd8 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -325,6 +325,14 @@ "includeRegex" : [ "screen\\.report_room\\..*" ] + }, + { + "name" : ":features:roommembermoderation:impl", + "includeRegex" : [ + "screen\\.bottom_sheet\\.manage_room_member\\..*", + "screen_room_member_list_manage_member.*", + "screen_room_member_list_unbanning_user" + ] } ] } From a09cc8de979f2dadf96ddb0aa4808cff04316bb1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 13 May 2025 11:39:19 +0200 Subject: [PATCH 02/13] change (member moderation) : branch moderation on timeline --- features/messages/impl/build.gradle.kts | 1 + .../features/messages/impl/MessagesEvents.kt | 3 + .../features/messages/impl/MessagesNode.kt | 16 ++++- .../messages/impl/MessagesPresenter.kt | 9 ++- .../features/messages/impl/MessagesState.kt | 2 + .../messages/impl/MessagesStateProvider.kt | 15 +++++ .../features/messages/impl/MessagesView.kt | 7 +- .../pinned/list/PinnedMessagesListNode.kt | 5 +- .../pinned/list/PinnedMessagesListView.kt | 7 +- .../messages/impl/timeline/TimelineView.kt | 3 +- .../components/TimelineItemEventRow.kt | 13 +++- .../TimelineItemGroupedEventsRow.kt | 5 +- .../timeline/components/TimelineItemRow.kt | 3 +- .../impl/members/RoomMemberListNode.kt | 2 +- .../impl/members/RoomMemberListPresenter.kt | 5 +- .../api/RoomMemberModerationEvents.kt | 6 +- .../api/RoomMemberModerationState.kt | 10 +-- .../impl/InternalRoomMemberModerationState.kt | 7 +- .../impl/RoomMemberModerationPresenter.kt | 67 +++++++++++-------- .../impl/RoomMemberModerationStateProvider.kt | 29 ++++---- .../impl/RoomMemberModerationView.kt | 42 ++++++------ .../item/event/ProfileTimelineDetails.kt | 7 ++ 22 files changed, 169 insertions(+), 95 deletions(-) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index cf8f73f47a..5789afab57 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(libs.telephoto.zoomableimage) implementation(libs.matrix.emojibase.bindings) implementation(projects.features.knockrequests.api) + implementation(projects.features.roommembermoderation.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt index 8d4d597502..f299deaf0e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt @@ -9,12 +9,15 @@ package io.element.android.features.messages.impl import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId +import io.element.android.libraries.matrix.api.user.MatrixUser sealed interface MessagesEvents { data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvents data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents + data class OnUserClicked(val user: MatrixUser) : MessagesEvents data object Dismiss : MessagesEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index d2d4b1c705..5f2546dd50 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -39,6 +39,9 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.androidutils.system.toast @@ -76,7 +79,8 @@ class MessagesNode @AssistedInject constructor( private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, private val permalinkParser: PermalinkParser, - private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer + private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer, + private val roomMemberModerationRenderer: RoomMemberModerationRenderer, ) : Node(buildContext, plugins = plugins), MessagesNavigator { private val presenter = presenterFactory.create( navigator = this, @@ -257,6 +261,16 @@ class MessagesNode @AssistedInject constructor( }, modifier = modifier, ) + roomMemberModerationRenderer.Render( + state = state.roomMemberModerationState, + onSelectAction = { action -> + when (action) { + is ModerationAction.DisplayProfile -> onUserDataClick(action.user.userId) + else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action)) + } + }, + modifier = Modifier, + ) var focusedEventId by rememberSaveable { mutableStateOf(inputs.focusedEventId) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 8bf5d330d7..6be5646649 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -50,6 +50,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.roomcall.api.RoomCallState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -103,6 +105,7 @@ class MessagesPresenter @AssistedInject constructor( private val readReceiptBottomSheetPresenter: Presenter, private val pinnedMessagesBannerPresenter: Presenter, private val roomCallStatePresenter: Presenter, + private val roomMemberModerationPresenter: Presenter, private val syncService: SyncService, private val snackbarDispatcher: SnackbarDispatcher, private val dispatchers: CoroutineDispatchers, @@ -143,7 +146,7 @@ class MessagesPresenter @AssistedInject constructor( val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present() val pinnedMessagesBannerState = pinnedMessagesBannerPresenter.present() val roomCallState = roomCallStatePresenter.present() - + val roomMemberModerationState = roomMemberModerationPresenter.present() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userEventPermissions by userEventPermissions(syncUpdateFlow.value) @@ -233,6 +236,9 @@ class MessagesPresenter @AssistedInject constructor( } } is MessagesEvents.Dismiss -> actionListState.eventSink(ActionListEvents.Clear) + is MessagesEvents.OnUserClicked -> { + roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user)) + } } } @@ -262,6 +268,7 @@ class MessagesPresenter @AssistedInject constructor( roomCallState = roomCallState, pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, + roomMemberModerationState = roomMemberModerationState, eventSink = { handleEvents(it) } ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 2a6889be41..8ba2cbd039 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -20,6 +20,7 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.roomcall.api.RoomCallState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage @@ -54,5 +55,6 @@ data class MessagesState( val appName: String, val pinnedMessagesBannerState: PinnedMessagesBannerState, val dmUserVerificationState: IdentityState?, + val roomMemberModerationState: RoomMemberModerationState, val eventSink: (MessagesEvents) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 12c6d607b0..debee82b3a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -37,11 +37,14 @@ import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMe import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roomcall.api.anOngoingCallState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.aTextEditorStateRich import kotlinx.collections.immutable.persistentListOf @@ -116,6 +119,7 @@ fun aMessagesState( roomCallState: RoomCallState = aStandByCallState(), pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), dmUserVerificationState: IdentityState? = null, + roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), eventSink: (MessagesEvents) -> Unit = {}, ) = MessagesState( roomId = RoomId("!id:domain"), @@ -143,9 +147,20 @@ fun aMessagesState( appName = "Element", pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, + roomMemberModerationState = roomMemberModerationState, eventSink = eventSink, ) +fun aRoomMemberModerationState( + canKick: Boolean = false, + canBan: Boolean = false, + +) = object : RoomMemberModerationState { + override val canKick: Boolean = canKick + override val canBan: Boolean = canBan + override val eventSink: (RoomMemberModerationEvents) -> Unit = {} +} + fun aUserEventPermissions( canRedactOwn: Boolean = false, canRedactOther: Boolean = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 948b8c3cb3..262698894a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -103,6 +103,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbar import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.textcomposer.model.TextEditorState import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.wysiwyg.link.Link @@ -208,7 +209,9 @@ fun MessagesView( .consumeWindowInsets(padding), onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, - onUserDataClick = { hidingKeyboard { onUserDataClick(it) } }, + onUserDataClick = { hidingKeyboard { + state.eventSink(MessagesEvents.OnUserClicked(it)) + } }, onLinkClick = { link, customTab -> if (customTab) { onLinkClick(link.url, true) @@ -293,7 +296,7 @@ private fun ReinviteDialog(state: MessagesState) { private fun MessagesViewContent( state: MessagesState, onContentClick: (TimelineItem.Event) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link, Boolean) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt index 9827698f99..2dbf76bc8d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings @ContributesNode(RoomScope::class) @@ -63,8 +64,8 @@ class PinnedMessagesListNode @AssistedInject constructor( return callbacks.forEach { it.onEventClick(event) } } - private fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } + private fun onUserDataClick(user: MatrixUser) { + callbacks.forEach { it.onUserDataClick(user.userId) } } private fun onLinkClick(context: Context, url: String) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index c87e4c0ffd..e0356f1670 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction @@ -59,7 +60,7 @@ fun PinnedMessagesListView( state: PinnedMessagesListState, onBackClick: () -> Unit, onEventClick: (event: TimelineItem.Event) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, modifier: Modifier = Modifier, @@ -115,7 +116,7 @@ private fun PinnedMessagesListTopBar( private fun PinnedMessagesListContent( state: PinnedMessagesListState, onEventClick: (event: TimelineItem.Event) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, onErrorDismiss: () -> Unit, @@ -171,7 +172,7 @@ private fun PinnedMessagesListEmpty( private fun PinnedMessagesListLoaded( state: PinnedMessagesListState.Filled, onEventClick: (event: TimelineItem.Event) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, modifier: Modifier = Modifier, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index c64eec9bee..b14ff51ce2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -71,6 +71,7 @@ import io.element.android.libraries.designsystem.utils.animateScrollToItemCenter import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings @@ -92,7 +93,7 @@ import kotlin.time.Duration.Companion.milliseconds fun TimelineView( state: TimelineState, timelineProtectionState: TimelineProtectionState, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onContentClick: (TimelineItem.Event) -> Unit, onMessageLongClick: (TimelineItem.Event) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index f3b890cb18..e38d212041 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -87,6 +87,10 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl +import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName +import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToView import io.element.android.libraries.matrix.ui.messages.reply.eventId @@ -122,7 +126,7 @@ fun TimelineItemEventRow( onLongClick: () -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, inReplyToClick: (EventId) -> Unit, onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, @@ -160,7 +164,12 @@ fun TimelineItemEventRow( } fun onUserDataClick() { - onUserDataClick(event.senderId) + val sender = MatrixUser( + userId = event.senderId, + displayName = event.senderProfile.getDisplayName(), + avatarUrl = event.senderProfile.getAvatarUrl(), + ) + onUserDataClick(sender) } fun inReplyToClick() { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 8f868370b3..4e55d6c3ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.wysiwyg.link.Link @Composable @@ -48,7 +49,7 @@ fun TimelineItemGroupedEventsRow( onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, @@ -117,7 +118,7 @@ private fun TimelineItemGroupedEventsRowContent( onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 88b19c43fe..136cdcbfed 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -43,6 +43,7 @@ import io.element.android.libraries.designsystem.theme.LocalBuildMeta import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.time.isTalkbackActive import io.element.android.wysiwyg.link.Link @@ -57,7 +58,7 @@ internal fun TimelineItemRow( isLastOutgoingMessage: Boolean, timelineProtectionState: TimelineProtectionState, focusedEventId: EventId?, - onUserDataClick: (UserId) -> Unit, + onUserDataClick: (MatrixUser) -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, onContentClick: (TimelineItem.Event) -> Unit, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index 05a35fad88..fd9dc1cd51 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -77,7 +77,7 @@ class RoomMemberListNode @AssistedInject constructor( state = state.moderationState, onSelectAction = { action -> when (action) { - is ModerationAction.DisplayProfile -> openRoomMemberDetails(action.member.userId) + is ModerationAction.DisplayProfile -> openRoomMemberDetails(action.user.userId) else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action)) } }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 07688387e3..0066b80231 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -34,6 +34,7 @@ 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.RoomMembershipState import io.element.android.libraries.matrix.api.room.roomMembers +import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.room.canInviteAsState import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange @@ -166,9 +167,9 @@ class RoomMemberListPresenter @AssistedInject constructor( is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> if (event.roomMember.membership == RoomMembershipState.BAN) { - roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser(event.roomMember))) + roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser(event.roomMember.toMatrixUser()))) } else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) { - roomModerationState.eventSink(RoomMemberModerationEvents.RenderActions(event.roomMember)) + roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) } else { navigator.openRoomMemberDetails(event.roomMember.userId) } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt index 265a59a125..277b4275d6 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt @@ -7,9 +7,9 @@ package io.element.android.features.roommembermoderation.api -import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.user.MatrixUser interface RoomMemberModerationEvents { - data class RenderActions(val roomMember: RoomMember) : RoomMemberModerationEvents - data class ProcessAction(val action: ModerationAction): RoomMemberModerationEvents + data class ShowActionsForUser(val user: MatrixUser) : RoomMemberModerationEvents + data class ProcessAction(val action: ModerationAction) : RoomMemberModerationEvents } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt index 410b8d6423..b31857b594 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -7,7 +7,7 @@ package io.element.android.features.roommembermoderation.api -import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.user.MatrixUser interface RoomMemberModerationState { val canKick: Boolean @@ -16,8 +16,8 @@ interface RoomMemberModerationState { } sealed interface ModerationAction { - data class DisplayProfile(val member: RoomMember) : ModerationAction - data class KickUser(val member: RoomMember) : ModerationAction - data class BanUser(val member: RoomMember) : ModerationAction - data class UnbanUser(val member: RoomMember) : ModerationAction + data class DisplayProfile(val user: MatrixUser) : ModerationAction + data class KickUser(val user: MatrixUser) : ModerationAction + data class BanUser(val user: MatrixUser) : ModerationAction + data class UnbanUser(val user: MatrixUser) : ModerationAction } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index e6258f2003..7e0c27a62d 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -13,20 +13,19 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList data class InternalRoomMemberModerationState( override val canKick: Boolean, override val canBan: Boolean, - val selectedRoomMember: AsyncData, + val selectedUser: MatrixUser?, val actions: ImmutableList, val kickUserAsyncAction: AsyncAction, val banUserAsyncAction: AsyncAction, val unbanUserAsyncAction: AsyncAction, override val eventSink: (RoomMemberModerationEvents) -> Unit, ) : RoomMemberModerationState { - - val canOnlyDisplayProfile = actions.size == 1 && actions.first() is ModerationAction.DisplayProfile - val canDisplayActions = actions.isNotEmpty() && !canOnlyDisplayProfile + val canDisplayActions = actions.isNotEmpty() } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 5f7fa80c60..fb9199ea54 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -20,13 +20,14 @@ import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.RoomMember +import io.element.android.libraries.matrix.api.room.toMatrixUser +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 @@ -45,7 +46,6 @@ class RoomMemberModerationPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, ) : Presenter { - private var selectedMember by mutableStateOf>(AsyncData.Uninitialized) @Composable override fun present(): RoomMemberModerationState { @@ -61,60 +61,68 @@ class RoomMemberModerationPresenter @Inject constructor( remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } - + var selectedUser by remember { + mutableStateOf(null) + } val moderationActions = remember { mutableStateOf(persistentListOf()) } fun handleEvent(event: RoomMemberModerationEvents) { when (event) { - is RoomMemberModerationEvents.RenderActions -> { - selectedMember = AsyncData.Success(event.roomMember) - moderationActions.value = computeModerationActions( - member = event.roomMember, - canKick = canKick.value, - canBan = canBan.value, - currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, - ) + is RoomMemberModerationEvents.ShowActionsForUser -> { + coroutineScope.launch { + selectedUser = event.user + moderationActions.value = persistentListOf(ModerationAction.DisplayProfile(event.user)) + room.getUpdatedMember(event.user.userId) + .onSuccess { + moderationActions.value = computeModerationActions( + member = it, + canKick = canKick.value, + canBan = canBan.value, + currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + ) + } + } } is RoomMemberModerationEvents.ProcessAction -> { - when(val action = event.action) { + when (val action = event.action) { is ModerationAction.DisplayProfile -> Unit is ModerationAction.KickUser -> { - selectedMember = AsyncData.Success(action.member) + selectedUser = action.user kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams } is ModerationAction.BanUser -> { - selectedMember = AsyncData.Success(action.member) + selectedUser = action.user banUserAsyncAction.value = AsyncAction.ConfirmingNoParams } is ModerationAction.UnbanUser -> { - selectedMember = AsyncData.Success(action.member) + selectedUser = action.user unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams } } } is InternalRoomMemberModerationEvents.DoKickUser -> { - selectedMember.dataOrNull()?.let { + selectedUser?.let { coroutineScope.kickUser(it.userId, event.reason, kickUserAsyncAction) } - selectedMember = AsyncData.Uninitialized + selectedUser = null } is InternalRoomMemberModerationEvents.DoBanUser -> { - selectedMember.dataOrNull()?.let { + selectedUser?.let { coroutineScope.banUser(it.userId, event.reason, banUserAsyncAction) } - selectedMember = AsyncData.Uninitialized + selectedUser = null } is InternalRoomMemberModerationEvents.Reset -> { - selectedMember = AsyncData.Uninitialized + selectedUser = null kickUserAsyncAction.value = AsyncAction.Uninitialized banUserAsyncAction.value = AsyncAction.Uninitialized unbanUserAsyncAction.value = AsyncAction.Uninitialized } is InternalRoomMemberModerationEvents.DoUnbanUser -> { - selectedMember.dataOrNull()?.let { + selectedUser?.let { coroutineScope.unbanUser(it.userId, unbanUserAsyncAction) } - selectedMember = AsyncData.Uninitialized + selectedUser = null } } } @@ -122,7 +130,7 @@ class RoomMemberModerationPresenter @Inject constructor( return InternalRoomMemberModerationState( canKick = canKick.value, canBan = canBan.value, - selectedRoomMember = selectedMember, + selectedUser = selectedUser, actions = moderationActions.value, kickUserAsyncAction = kickUserAsyncAction.value, banUserAsyncAction = banUserAsyncAction.value, @@ -137,13 +145,15 @@ class RoomMemberModerationPresenter @Inject constructor( canBan: Boolean, currentUserMemberPowerLevel: Long, ): PersistentList { + val memberAsUser = member.toMatrixUser() return buildList { - add(ModerationAction.DisplayProfile(member)) - if (canKick && member.powerLevel < currentUserMemberPowerLevel) { - add(ModerationAction.KickUser(member)) + add(ModerationAction.DisplayProfile(memberAsUser)) + val canModerateThisUser = member.powerLevel < currentUserMemberPowerLevel && member.membership.isActive() + if (canKick && canModerateThisUser) { + add(ModerationAction.KickUser(memberAsUser)) } - if (canBan && member.powerLevel < currentUserMemberPowerLevel) { - add(ModerationAction.BanUser(member)) + if (canBan && canModerateThisUser) { + add(ModerationAction.BanUser(memberAsUser)) } }.toPersistentList() } @@ -194,4 +204,5 @@ class RoomMemberModerationPresenter @Inject constructor( } } } + } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt index b705e5217d..9d52eec503 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt @@ -15,26 +15,28 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId 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.toMatrixUser +import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.toPersistentList class RoomMemberModerationStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomMembersModerationState( - selectedRoomMember = AsyncData.Success(anAlice()), + selectedUser = anAlice(), actions = listOf( ModerationAction.DisplayProfile(anAlice()), ), ), aRoomMembersModerationState( - selectedRoomMember = AsyncData.Success(anAlice()), + selectedUser = anAlice(), actions = listOf( ModerationAction.DisplayProfile(anAlice()), ModerationAction.KickUser(anAlice()), ), ), aRoomMembersModerationState( - selectedRoomMember = AsyncData.Success(anAlice()), + selectedUser = anAlice(), actions = listOf( ModerationAction.DisplayProfile(anAlice()), ModerationAction.KickUser(anAlice()), @@ -42,41 +44,34 @@ class RoomMemberModerationStateProvider : PreviewParameterProvider = AsyncData.Uninitialized, + selectedUser: MatrixUser? = null, actions: List = emptyList(), kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, banUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, @@ -85,7 +80,7 @@ fun aRoomMembersModerationState( ) = InternalRoomMemberModerationState( canKick = canKick, canBan = canBan, - selectedRoomMember = selectedRoomMember, + selectedUser = selectedUser, actions = actions.toPersistentList(), kickUserAsyncAction = kickUserAsyncAction, banUserAsyncAction = banUserAsyncAction, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index ff949fde59..31b474057d 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -20,9 +20,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -49,9 +47,9 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.getBestName +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.launch @@ -63,23 +61,27 @@ fun RoomMemberModerationView( onSelectAction: (ModerationAction) -> Unit, modifier: Modifier = Modifier, ) { - val selectedRoomMember = state.selectedRoomMember.dataOrNull() Box(modifier = modifier) { - if (selectedRoomMember != null && state.canDisplayActions) { + val selectedUser = state.selectedUser + if (selectedUser != null && state.canDisplayActions) { RoomMemberActionsBottomSheet( - roomMember = selectedRoomMember, + user = selectedUser, actions = state.actions, onSelectAction = onSelectAction, onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, ) } - val onSelectAction by rememberUpdatedState(onSelectAction) - LaunchedEffect(state.canOnlyDisplayProfile) { - if (state.canOnlyDisplayProfile) { - onSelectAction(state.actions.first()) - } - } + RoomMemberAsyncActions(state = state) + } +} +@Composable +private fun RoomMemberAsyncActions( + state: InternalRoomMemberModerationState, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier) { + val selectedUser = state.selectedUser val asyncIndicatorState = rememberAsyncIndicatorState() AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), state = asyncIndicatorState) @@ -100,7 +102,7 @@ fun RoomMemberModerationView( } is AsyncAction.Loading -> { LaunchedEffect(action) { - val userDisplayName = selectedRoomMember?.getBestName().orEmpty() + val userDisplayName = selectedUser?.getBestName().orEmpty() asyncIndicatorState.enqueue { AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_removing_user, userDisplayName)) } @@ -139,7 +141,7 @@ fun RoomMemberModerationView( } is AsyncAction.Loading -> { LaunchedEffect(action) { - val userDisplayName = selectedRoomMember?.getBestName().orEmpty() + val userDisplayName = selectedUser?.getBestName().orEmpty() asyncIndicatorState.enqueue { AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_banning_user, userDisplayName)) } @@ -167,7 +169,7 @@ fun RoomMemberModerationView( content = stringResource(R.string.screen_room_member_list_manage_member_unban_message), submitText = stringResource(R.string.screen_room_member_list_manage_member_unban_action), onSubmitClick = { - val userDisplayName = selectedRoomMember?.getBestName().orEmpty() + val userDisplayName = selectedUser?.getBestName().orEmpty() asyncIndicatorState.enqueue { AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName)) } @@ -198,7 +200,7 @@ fun RoomMemberModerationView( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomMemberActionsBottomSheet( - roomMember: RoomMember, + user: MatrixUser, actions: ImmutableList, onSelectAction: (ModerationAction) -> Unit, onDismiss: () -> Unit, @@ -219,12 +221,12 @@ private fun RoomMemberActionsBottomSheet( modifier = Modifier.padding(vertical = 16.dp) ) { Avatar( - avatarData = roomMember.getAvatarData(size = AvatarSize.RoomListManageUser), + avatarData = user.getAvatarData(size = AvatarSize.RoomListManageUser), modifier = Modifier .padding(bottom = 28.dp) .align(Alignment.CenterHorizontally) ) - roomMember.displayName?.let { + user.displayName?.let { Text( text = it, style = ElementTheme.typography.fontHeadingLgBold, @@ -237,7 +239,7 @@ private fun RoomMemberActionsBottomSheet( ) } Text( - text = roomMember.userId.toString(), + text = user.userId.toString(), style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textSecondary, maxLines = 1, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt index ff1f9351f9..46ab482ad5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt @@ -44,6 +44,13 @@ fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String { } } +fun ProfileTimelineDetails.getDisplayName(): String? { + return when (this) { + is ProfileTimelineDetails.Ready -> displayName + else -> null + } +} + fun ProfileTimelineDetails.getAvatarUrl(): String? { return when (this) { is ProfileTimelineDetails.Ready -> avatarUrl From e405bf80a67c979a0dc89029ada1af2d0e9d7c75 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 16 May 2025 18:38:15 +0200 Subject: [PATCH 03/13] change (member moderation) : allow disabled action and render unban too --- .../features/messages/impl/MessagesNode.kt | 6 +- .../impl/members/RoomMemberListNode.kt | 6 +- .../impl/members/RoomMemberListPresenter.kt | 2 +- .../api/RoomMemberModerationEvents.kt | 2 +- .../api/RoomMemberModerationRenderer.kt | 3 +- .../api/RoomMemberModerationState.kt | 15 ++-- .../DefaultRoomMemberModerationRenderer.kt | 3 +- .../impl/InternalRoomMemberModerationState.kt | 3 +- .../impl/RoomMemberModerationPresenter.kt | 69 ++++++++++--------- .../impl/RoomMemberModerationStateProvider.kt | 23 +++++-- .../impl/RoomMemberModerationView.kt | 64 +++++++++++------ 11 files changed, 119 insertions(+), 77 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 5f2546dd50..6acf48e58b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -263,10 +263,10 @@ class MessagesNode @AssistedInject constructor( ) roomMemberModerationRenderer.Render( state = state.roomMemberModerationState, - onSelectAction = { action -> + onSelectAction = { action, target -> when (action) { - is ModerationAction.DisplayProfile -> onUserDataClick(action.user.userId) - else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action)) + is ModerationAction.DisplayProfile -> onUserDataClick(target.userId) + else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) } }, modifier = Modifier, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index fd9dc1cd51..cf2f89c41e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -75,10 +75,10 @@ class RoomMemberListNode @AssistedInject constructor( ) roomMemberModerationRenderer.Render( state = state.moderationState, - onSelectAction = { action -> + onSelectAction = { action, target -> when (action) { - is ModerationAction.DisplayProfile -> openRoomMemberDetails(action.user.userId) - else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action)) + is ModerationAction.DisplayProfile -> openRoomMemberDetails(target.userId) + else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) } }, modifier = Modifier, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 0066b80231..c637f338cb 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -167,7 +167,7 @@ class RoomMemberListPresenter @AssistedInject constructor( is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> if (event.roomMember.membership == RoomMembershipState.BAN) { - roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser(event.roomMember.toMatrixUser()))) + roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser())) } else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) { roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) } else { diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt index 277b4275d6..94c7477739 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt @@ -11,5 +11,5 @@ import io.element.android.libraries.matrix.api.user.MatrixUser interface RoomMemberModerationEvents { data class ShowActionsForUser(val user: MatrixUser) : RoomMemberModerationEvents - data class ProcessAction(val action: ModerationAction) : RoomMemberModerationEvents + data class ProcessAction(val action: ModerationAction, val targetUser: MatrixUser) : RoomMemberModerationEvents } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt index 2911bf1b8b..afe8ab0f8b 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt @@ -9,12 +9,13 @@ package io.element.android.features.roommembermoderation.api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import io.element.android.libraries.matrix.api.user.MatrixUser interface RoomMemberModerationRenderer { @Composable fun Render( state: RoomMemberModerationState, - onSelectAction: (ModerationAction) -> Unit, + onSelectAction: (ModerationAction, MatrixUser) -> Unit, modifier: Modifier, ) } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt index b31857b594..51f791ed69 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -7,17 +7,20 @@ package io.element.android.features.roommembermoderation.api -import io.element.android.libraries.matrix.api.user.MatrixUser - interface RoomMemberModerationState { val canKick: Boolean val canBan: Boolean val eventSink: (RoomMemberModerationEvents) -> Unit } +data class ModerationActionState( + val action: ModerationAction, + val isEnabled: Boolean, +) + sealed interface ModerationAction { - data class DisplayProfile(val user: MatrixUser) : ModerationAction - data class KickUser(val user: MatrixUser) : ModerationAction - data class BanUser(val user: MatrixUser) : ModerationAction - data class UnbanUser(val user: MatrixUser) : ModerationAction + data object DisplayProfile : ModerationAction + data object KickUser : ModerationAction + data object BanUser : ModerationAction + data object UnbanUser : ModerationAction } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt index 9a9cce3eb7..681a1eb733 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt @@ -15,6 +15,7 @@ import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.user.MatrixUser import timber.log.Timber import javax.inject.Inject @@ -23,7 +24,7 @@ class DefaultRoomMemberModerationRenderer @Inject constructor() : RoomMemberMode @Composable override fun Render( state: RoomMemberModerationState, - onSelectAction: (ModerationAction) -> Unit, + onSelectAction: (ModerationAction, MatrixUser) -> Unit, modifier: Modifier ) { if (state is InternalRoomMemberModerationState) { diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index 7e0c27a62d..b4bb8a3a27 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -8,6 +8,7 @@ package io.element.android.features.roommembermoderation.impl import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction @@ -20,7 +21,7 @@ data class InternalRoomMemberModerationState( override val canKick: Boolean, override val canBan: Boolean, val selectedUser: MatrixUser?, - val actions: ImmutableList, + val actions: ImmutableList, val kickUserAsyncAction: AsyncAction, val banUserAsyncAction: AsyncAction, val unbanUserAsyncAction: AsyncAction, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index fb9199ea54..0359a3120e 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction @@ -26,7 +27,8 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.RoomMember -import io.element.android.libraries.matrix.api.room.toMatrixUser +import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.roomMembers 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 @@ -64,38 +66,37 @@ class RoomMemberModerationPresenter @Inject constructor( var selectedUser by remember { mutableStateOf(null) } - val moderationActions = remember { mutableStateOf(persistentListOf()) } + val moderationActions = remember { mutableStateOf(persistentListOf()) } fun handleEvent(event: RoomMemberModerationEvents) { when (event) { is RoomMemberModerationEvents.ShowActionsForUser -> { coroutineScope.launch { selectedUser = event.user - moderationActions.value = persistentListOf(ModerationAction.DisplayProfile(event.user)) - room.getUpdatedMember(event.user.userId) - .onSuccess { - moderationActions.value = computeModerationActions( - member = it, - canKick = canKick.value, - canBan = canBan.value, - currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, - ) - } + val member = room.membersStateFlow.value.roomMembers()?.firstOrNull { + it.userId == event.user.userId + } + moderationActions.value = computeModerationActions( + member = member, + canKick = canKick.value, + canBan = canBan.value, + currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + ) } } is RoomMemberModerationEvents.ProcessAction -> { when (val action = event.action) { is ModerationAction.DisplayProfile -> Unit is ModerationAction.KickUser -> { - selectedUser = action.user + selectedUser = event.targetUser kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams } is ModerationAction.BanUser -> { - selectedUser = action.user + selectedUser = event.targetUser banUserAsyncAction.value = AsyncAction.ConfirmingNoParams } is ModerationAction.UnbanUser -> { - selectedUser = action.user + selectedUser = event.targetUser unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams } } @@ -112,18 +113,18 @@ class RoomMemberModerationPresenter @Inject constructor( } selectedUser = null } - is InternalRoomMemberModerationEvents.Reset -> { - selectedUser = null - kickUserAsyncAction.value = AsyncAction.Uninitialized - banUserAsyncAction.value = AsyncAction.Uninitialized - unbanUserAsyncAction.value = AsyncAction.Uninitialized - } is InternalRoomMemberModerationEvents.DoUnbanUser -> { selectedUser?.let { coroutineScope.unbanUser(it.userId, unbanUserAsyncAction) } selectedUser = null } + is InternalRoomMemberModerationEvents.Reset -> { + selectedUser = null + kickUserAsyncAction.value = AsyncAction.Uninitialized + banUserAsyncAction.value = AsyncAction.Uninitialized + unbanUserAsyncAction.value = AsyncAction.Uninitialized + } } } @@ -140,20 +141,27 @@ class RoomMemberModerationPresenter @Inject constructor( } private fun computeModerationActions( - member: RoomMember, + member: RoomMember?, canKick: Boolean, canBan: Boolean, currentUserMemberPowerLevel: Long, - ): PersistentList { - val memberAsUser = member.toMatrixUser() + ): PersistentList { return buildList { - add(ModerationAction.DisplayProfile(memberAsUser)) - val canModerateThisUser = member.powerLevel < currentUserMemberPowerLevel && member.membership.isActive() - if (canKick && canModerateThisUser) { - add(ModerationAction.KickUser(memberAsUser)) + add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true)) + // Assume the member is a regular user when it's unknown + val canModerateThisUser = (member?.powerLevel ?: 0) < currentUserMemberPowerLevel + // Assume the member is joined when it's unknown + val membership = member?.membership ?: RoomMembershipState.JOIN + if (canKick) { + val isKickEnabled = canModerateThisUser && membership.isActive() + add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled)) } - if (canBan && canModerateThisUser) { - add(ModerationAction.BanUser(memberAsUser)) + if (canBan) { + if (membership == RoomMembershipState.BAN) { + add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser)) + } else { + add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + } } }.toPersistentList() } @@ -204,5 +212,4 @@ class RoomMemberModerationPresenter @Inject constructor( } } } - } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt index 9d52eec503..cdfca792d6 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.roommembermoderation.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -25,24 +26,32 @@ class RoomMemberModerationStateProvider : PreviewParameterProvider = emptyList(), + actions: List = emptyList(), kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, banUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, unbanUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 31b474057d..a2a86e4d13 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -31,6 +31,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.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost @@ -58,7 +59,7 @@ import timber.log.Timber @Composable fun RoomMemberModerationView( state: InternalRoomMemberModerationState, - onSelectAction: (ModerationAction) -> Unit, + onSelectAction: (ModerationAction, MatrixUser) -> Unit, modifier: Modifier = Modifier, ) { Box(modifier = modifier) { @@ -201,8 +202,8 @@ private fun RoomMemberAsyncActions( @Composable private fun RoomMemberActionsBottomSheet( user: MatrixUser, - actions: ImmutableList, - onSelectAction: (ModerationAction) -> Unit, + actions: ImmutableList, + onSelectAction: (ModerationAction, MatrixUser) -> Unit, onDismiss: () -> Unit, ) { val coroutineScope = rememberCoroutineScope() @@ -223,8 +224,8 @@ private fun RoomMemberActionsBottomSheet( Avatar( avatarData = user.getAvatarData(size = AvatarSize.RoomListManageUser), modifier = Modifier - .padding(bottom = 28.dp) - .align(Alignment.CenterHorizontally) + .padding(bottom = 28.dp) + .align(Alignment.CenterHorizontally) ) user.displayName?.let { Text( @@ -234,8 +235,8 @@ private fun RoomMemberActionsBottomSheet( overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) - .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + .fillMaxWidth() ) } Text( @@ -246,35 +247,38 @@ private fun RoomMemberActionsBottomSheet( overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth() + .padding(horizontal = 16.dp) + .fillMaxWidth() ) Spacer(modifier = Modifier.height(32.dp)) - for (action in actions) { - when (action) { + for (actionState in actions) { + when (val action = actionState.action) { is ModerationAction.DisplayProfile -> { ListItem( headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_member_user_info)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), onClick = { coroutineScope.launch { - onSelectAction(action) + onSelectAction(action, user) bottomSheetState.hide() } - } + }, + enabled = actionState.isEnabled ) } is ModerationAction.KickUser -> { ListItem( headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_remove)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Close())), + style = ListItemStyle.Destructive, onClick = { coroutineScope.launch { bottomSheetState.hide() - onSelectAction(action) + onSelectAction(action, user) } - } + }, + enabled = actionState.isEnabled ) } is ModerationAction.BanUser -> { @@ -285,12 +289,26 @@ private fun RoomMemberActionsBottomSheet( onClick = { coroutineScope.launch { bottomSheetState.hide() - onSelectAction(action) + onSelectAction(action, user) } - } + }, + enabled = actionState.isEnabled + ) + } + is ModerationAction.UnbanUser -> { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_member_list_manage_member_unban_action)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Restart())), + style = ListItemStyle.Destructive, + onClick = { + coroutineScope.launch { + bottomSheetState.hide() + onSelectAction(action, user) + } + }, + enabled = actionState.isEnabled ) } - is ModerationAction.UnbanUser -> Unit } } } @@ -303,12 +321,14 @@ internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMemberModera ElementPreview { Box( modifier = Modifier - .fillMaxWidth() - .heightIn(min = 64.dp) + .fillMaxWidth() + .heightIn(min = 64.dp) ) { RoomMemberModerationView( state = state, - onSelectAction = {}, + onSelectAction = { _, _ -> + + }, ) } } From 0b6c5964a1dd2f0fd89e0ae9de7283d2a052d37b Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 19 May 2025 22:16:17 +0200 Subject: [PATCH 04/13] change (member moderation) : fix all existing tests --- .../messages/impl/MessagesPresenterTest.kt | 5 + .../messages/impl/MessagesViewTest.kt | 55 +-- .../pinned/list/PinnedMessagesListViewTest.kt | 3 +- .../impl/timeline/TimelineViewTest.kt | 3 +- .../impl/members/RoomMemberListNode.kt | 3 +- .../impl/members/RoomMemberListPresenter.kt | 11 +- .../members/RoomMemberListPresenterTest.kt | 74 ++-- .../RoomMembersModerationPresenterTest.kt | 351 ------------------ .../RoomMembersModerationViewTest.kt | 274 -------------- .../impl/InternalRoomMemberModerationState.kt | 3 - ...ernalRoomMemberModerationStateProvider.kt} | 6 +- .../impl/RoomMemberModerationView.kt | 2 +- 12 files changed, 64 insertions(+), 726 deletions(-) delete mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt delete mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt rename features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/{RoomMemberModerationStateProvider.kt => InternalRoomMemberModerationStateProvider.kt} (91%) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 2a3c34feee..c21431e2ea 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.features.roomcall.api.aStandByCallState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -1182,6 +1183,9 @@ class MessagesPresenterTest { textEditorState = aTextEditorStateMarkdown(initialText = "", initialFocus = false) ) }, + roomMemberModerationPresenter: Presenter = Presenter { + aRoomMemberModerationState() + }, encryptionService: FakeEncryptionService = FakeEncryptionService(), actionListEventSink: (ActionListEvents) -> Unit = {}, ): MessagesPresenter { @@ -1199,6 +1203,7 @@ class MessagesPresenterTest { linkPresenter = { aLinkState() }, pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() }, roomCallStatePresenter = { aStandByCallState() }, + roomMemberModerationPresenter = roomMemberModerationPresenter, syncService = FakeSyncService(), snackbarDispatcher = SnackbarDispatcher(), navigator = navigator, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 5080013d27..b0d22ad4c1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -53,6 +53,9 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl +import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings @@ -310,40 +313,42 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") - fun `clicking on the avatar of the sender of an Event invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + fun `clicking on the avatar of the sender of an Event emits the expected event`() { + val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder ) - val timelineItem = state.timelineState.timelineItems.first() - ensureCalledOnceWithParam( - param = (timelineItem as TimelineItem.Event).senderId - ) { callback -> - rule.setMessagesView( - state = state, - onUserDataClick = callback, + val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first() + rule.setMessagesView(state = state) + rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() + eventsRecorder.assertSingle( + MessagesEvents.OnUserClicked( + MatrixUser( + userId = timelineEvent.senderId, + displayName = timelineEvent.senderProfile.getDisplayName(), + avatarUrl = timelineEvent.senderProfile.getAvatarUrl() + ) ) - rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() - } + ) } @Test @Config(qualifiers = "h1024dp") - fun `clicking on the display name of the sender of an Event invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - val state = aMessagesState( - eventSink = eventsRecorder - ) - val timelineItem = state.timelineState.timelineItems.first() - ensureCalledOnceWithParam( - param = (timelineItem as TimelineItem.Event).senderId - ) { callback -> - rule.setMessagesView( - state = state, - onUserDataClick = callback, + fun `clicking on the display name of the sender of an Event emits expected event`() { + val eventsRecorder = EventsRecorder() + val state = aMessagesState(eventSink = eventsRecorder) + val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first() + rule.setMessagesView(state = state) + rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() + eventsRecorder.assertSingle( + MessagesEvents.OnUserClicked( + MatrixUser( + userId = timelineEvent.senderId, + displayName = timelineEvent.senderProfile.getDisplayName(), + avatarUrl = timelineEvent.senderProfile.getAvatarUrl() + ) ) - rule.onNodeWithTag(TestTags.timelineItemSenderName.value, useUnmergedTree = true).performClick() - } + ) } @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index 35ac685303..0200da24e2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder @@ -99,7 +100,7 @@ private fun AndroidComposeTestRule.setPinne state: PinnedMessagesListState, onBackClick: () -> Unit = EnsureNeverCalled(), onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), - onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), + onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(), onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(), onLinkLongClick: (Link) -> Unit = EnsureNeverCalledWithParam(), ) { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 208c0ab140..178523cf64 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId 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.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam @@ -175,7 +176,7 @@ class TimelineViewTest { private fun AndroidComposeTestRule.setTimelineView( state: TimelineState, timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), - onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), + onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(), onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(), onMessageClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onMessageLongClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index cf2f89c41e..3d7fecd0ae 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -29,7 +29,7 @@ import io.element.android.services.analytics.api.AnalyticsService class RoomMemberListNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - presenterFactory: RoomMemberListPresenter.Factory, + private val presenter: RoomMemberListPresenter, private val analyticsService: AnalyticsService, private val roomMemberModerationRenderer: RoomMemberModerationRenderer, ) : Node(buildContext, plugins = plugins), RoomMemberListNavigator { @@ -39,7 +39,6 @@ class RoomMemberListNode @AssistedInject constructor( } private val callbacks = plugins() - private val presenter = presenterFactory.create(this) init { lifecycle.subscribe( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index c637f338cb..4de57787c8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -16,8 +16,6 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents @@ -53,12 +51,7 @@ class RoomMemberListPresenter @AssistedInject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, - @Assisted private val navigator: RoomMemberListNavigator, ) : Presenter { - @AssistedFactory - interface Factory { - fun create(navigator: RoomMemberListNavigator): RoomMemberListPresenter - } @Composable override fun present(): RoomMemberListState { @@ -168,10 +161,8 @@ class RoomMemberListPresenter @AssistedInject constructor( is RoomMemberListEvents.RoomMemberSelected -> if (event.roomMember.membership == RoomMembershipState.BAN) { roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser())) - } else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) { - roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) } else { - navigator.openRoomMemberDetails(event.roomMember.userId) + roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) } } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index 82e5a910dc..52173f43ff 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -11,9 +11,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents -import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState -import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId @@ -24,7 +23,6 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo -import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,12 +40,12 @@ class RoomMemberListPresenterTest { fun `member loading is done automatically on start, but is async`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ).apply { - // Needed to avoid discarding the loaded members as a partial and invalid result - givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) - } + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ).apply { + // Needed to avoid discarding the loaded members as a partial and invalid result + givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) + } ) val presenter = createPresenter(joinedRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -97,9 +95,9 @@ class RoomMemberListPresenterTest { val presenter = createPresenter( joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -204,12 +202,12 @@ class RoomMemberListPresenterTest { } @Test - fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest { - val navigator = FakeRoomMemberListNavigator() - val roomMembersModerationStateLambda = { aRoomMembersModerationState(canDisplayModerationActions = false) } + fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest { + val roomMemberModerationPresenter= Presenter { + aRoomMemberModerationState(canBan = true, canKick = true) + } val presenter = createPresenter( - roomMembersModerationStateLambda = roomMembersModerationStateLambda, - navigator = navigator, + roomMemberModerationPresenter = roomMemberModerationPresenter, joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( updateMembersResult = { Result.success(Unit) }, @@ -222,36 +220,6 @@ class RoomMemberListPresenterTest { }.test { skipItems(1) awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor())) - assertThat(navigator.openRoomMemberDetailsCallCount).isEqualTo(1) - } - } - - @Test - fun `present - RoomMemberSelected will open the moderation options if the current user can use them`() = runTest { - val navigator = FakeRoomMemberListNavigator() - val eventsRecorder = EventsRecorder() - val roomMembersModerationStateLambda = { - aRoomMembersModerationState( - canDisplayModerationActions = true, - eventSink = eventsRecorder, - ) - } - val presenter = createPresenter( - roomMembersModerationStateLambda = roomMembersModerationStateLambda, - navigator = navigator, - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor())) - eventsRecorder.assertSingle(RoomMembersModerationEvents.SelectRoomMember(aVictor())) } } } @@ -277,19 +245,19 @@ private fun TestScope.createDataSource( private fun TestScope.createPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), joinedRoom: JoinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( + baseRoom = FakeBaseRoom( updateMembersResult = { Result.success(Unit) } ) ), roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), - roomMembersModerationStateLambda: () -> RoomMembersModerationState = { aRoomMembersModerationState() }, encryptedService: FakeEncryptionService = FakeEncryptionService(), - navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {} + roomMemberModerationPresenter: Presenter = Presenter { + aRoomMemberModerationState() + }, ) = RoomMemberListPresenter( room = joinedRoom, roomMemberListDataSource = roomMemberListDataSource, coroutineDispatchers = coroutineDispatchers, - roomMembersModerationPresenter = { roomMembersModerationStateLambda() }, + roomMembersModerationPresenter = roomMemberModerationPresenter, encryptionService = encryptedService, - navigator = navigator, ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt deleted file mode 100644 index 00239fdf82..0000000000 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test -import com.google.common.truth.Truth.assertThat -import im.vector.app.features.analytics.plan.RoomModeration -import io.element.android.features.roomdetails.impl.aJoinedRoom -import io.element.android.features.roomdetails.impl.members.aRoomMember -import io.element.android.features.roomdetails.impl.members.aVictor -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.core.UserId -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.RoomMembershipState -import io.element.android.libraries.matrix.test.A_REASON -import io.element.android.libraries.matrix.test.A_USER_ID -import io.element.android.libraries.matrix.test.A_USER_ID_2 -import io.element.android.libraries.matrix.test.room.FakeJoinedRoom -import io.element.android.libraries.matrix.test.room.aRoomInfo -import io.element.android.services.analytics.test.FakeAnalyticsService -import io.element.android.tests.testutils.lambda.lambdaRecorder -import io.element.android.tests.testutils.lambda.value -import io.element.android.tests.testutils.test -import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Test - -class RoomMembersModerationPresenterTest { - @Test - fun `canDisplayModerationActions - when room is DM is false`() = runTest { - val room = aJoinedRoom( - isPublic = true, - activeMemberCount = 2, - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ).apply { - givenRoomInfo(aRoomInfo(isDirect = true, activeMembersCount = 2)) - } - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - presenter.test { - assertThat(awaitItem().canDisplayModerationActions).isFalse() - } - } - - @Test - fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = aJoinedRoom( - activeMemberCount = 10, - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - presenter.test { - skipItems(1) - assertThat(awaitItem().canDisplayModerationActions).isTrue() - } - } - - @Test - fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = aJoinedRoom( - activeMemberCount = 10, - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - presenter.test { - skipItems(1) - assertThat(awaitItem().canDisplayModerationActions).isTrue() - } - } - - @Test - fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest { - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ) - val selectedMember = aVictor() - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - with(awaitItem()) { - assertThat(this.selectedRoomMember).isNotNull() - assertThat(this.selectedRoomMember?.userId).isEqualTo(selectedMember.userId) - assertThat(actions).containsExactly( - ModerationAction.DisplayProfile(selectedMember.userId), - ModerationAction.KickUser(selectedMember.userId), - ModerationAction.BanUser(selectedMember.userId) - ) - } - } - } - - @Test - fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest { - val room = aJoinedRoom( - sessionId = A_USER_ID, - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ) - val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - with(awaitItem()) { - assertThat(this.selectedRoomMember).isNotNull() - assertThat(this.selectedRoomMember?.userId).isEqualTo(selectedMember.userId) - assertThat(actions).containsExactly( - ModerationAction.DisplayProfile(selectedMember.userId), - ) - } - } - } - - @Test - fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest { - val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - ) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - with(awaitItem()) { - assertThat(selectedRoomMember).isNull() - assertThat(unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember)) - } - } - } - - @Test - fun `present - Kick requires confirmation and then kicks the user`() = runTest { - val analyticsService = FakeAnalyticsService() - val kickUserResult = lambdaRecorder> { _, _ -> Result.success(Unit) } - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - kickUserResult = kickUserResult, - ) - val selectedMember = aVictor() - val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - awaitItem().eventSink(RoomMembersModerationEvents.KickUser) - val confirmingState = awaitItem() - assertThat(confirmingState.kickUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) - // Confirm - confirmingState.eventSink(RoomMembersModerationEvents.DoKickUser(reason = A_REASON)) - skipItems(1) - val loadingState = awaitItem() - assertThat(loadingState.actions).isEmpty() - assertThat(loadingState.kickUserAsyncAction).isEqualTo(AsyncAction.Loading) - with(awaitItem()) { - assertThat(kickUserAsyncAction).isEqualTo(AsyncAction.Success(Unit)) - assertThat(selectedRoomMember).isNull() - } - assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.KickMember)) - kickUserResult.assertions().isCalledOnce().with( - value(selectedMember.userId), - value(A_REASON), - ) - } - } - - @Test - fun `present - BanUser requires confirmation and then bans the user`() = runTest { - val analyticsService = FakeAnalyticsService() - val banUserResult = lambdaRecorder> { _, _ -> Result.success(Unit) } - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - banUserResult = banUserResult, - ) - val selectedMember = aVictor() - val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - awaitItem().eventSink(RoomMembersModerationEvents.BanUser) - val confirmingState = awaitItem() - assertThat(confirmingState.banUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) - // Confirm - confirmingState.eventSink(RoomMembersModerationEvents.DoBanUser(reason = A_REASON)) - skipItems(1) - val loadingItem = awaitItem() - assertThat(loadingItem.actions).isEmpty() - assertThat(loadingItem.selectedRoomMember).isNull() - assertThat(loadingItem.banUserAsyncAction).isEqualTo(AsyncAction.Loading) - with(awaitItem()) { - assertThat(banUserAsyncAction).isEqualTo(AsyncAction.Success(Unit)) - assertThat(selectedRoomMember).isNull() - } - assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.BanMember)) - banUserResult.assertions().isCalledOnce().with( - value(selectedMember.userId), - value(A_REASON), - ) - } - } - - @Test - fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest { - val analyticsService = FakeAnalyticsService() - val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, - unBanUserResult = { _, _ -> Result.success(Unit) }, - ).apply { - givenRoomMembersState(RoomMembersState.Ready(persistentListOf(selectedMember))) - } - val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - // Displays unban confirmation dialog - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember)) - val confirmingState = awaitItem() - assertThat(confirmingState.selectedRoomMember).isNull() - assertThat(confirmingState.actions).isEmpty() - assertThat(confirmingState.unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember)) - // Confirms unban - confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(selectedMember.userId)) - assertThat(awaitItem().unbanUserAsyncAction).isEqualTo(AsyncAction.Loading) - with(awaitItem()) { - assertThat(unbanUserAsyncAction).isEqualTo(AsyncAction.Success(Unit)) - assertThat(selectedRoomMember).isNull() - } - assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.UnbanMember)) - } - } - - @Test - fun `present - Reset removes the selected user and actions`() = runTest { - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - userRoleResult = { Result.success(RoomMember.Role.USER) }, - ) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - // Select a user - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor())) - // Reset state - awaitItem().eventSink(RoomMembersModerationEvents.Reset) - val finalItem = awaitItem() - assertThat(finalItem.selectedRoomMember).isNull() - assertThat(finalItem.actions).isEmpty() - } - } - - @Test - fun `present - Reset resets any async actions`() = runTest { - val room = aJoinedRoom( - canKickResult = { Result.success(true) }, - canBanResult = { Result.success(true) }, - kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, - banUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, - unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, - userRoleResult = { Result.success(RoomMember.Role.USER) }, - ) - val presenter = createRoomMembersModerationPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialItem = awaitItem() - // Kick user and fail - awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor())) - awaitItem().eventSink(RoomMembersModerationEvents.DoKickUser(reason = "")) - skipItems(1) - assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) - assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) - // Reset it - initialItem.eventSink(RoomMembersModerationEvents.Reset) - assertThat(awaitItem().kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) - - // Ban user and fail - initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor())) - awaitItem().eventSink(RoomMembersModerationEvents.DoBanUser(reason = "")) - skipItems(1) - assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) - assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) - // Reset it - initialItem.eventSink(RoomMembersModerationEvents.Reset) - assertThat(awaitItem().banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) - - // Unban user and fail - initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor().copy(membership = RoomMembershipState.BAN))) - val confirmingState = awaitItem() - assertThat(confirmingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Confirming::class.java) - confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(aVictor().userId)) - assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) - assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) - // Reset it - initialItem.eventSink(RoomMembersModerationEvents.Reset) - assertThat(awaitItem().unbanUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) - } - } - - private fun TestScope.createRoomMembersModerationPresenter( - joinedRoom: FakeJoinedRoom = aJoinedRoom(), - dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), - analyticsService: FakeAnalyticsService = FakeAnalyticsService(), - ): RoomMembersModerationPresenter { - return RoomMembersModerationPresenter( - room = joinedRoom, - dispatchers = dispatchers, - analyticsService = analyticsService, - ) - } -} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt deleted file mode 100644 index b501c808c7..0000000000 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationViewTest.kt +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2024 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.roomdetails.impl.members.moderation - -import androidx.activity.ComponentActivity -import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performTextInput -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.roomdetails.impl.R -import io.element.android.features.roomdetails.impl.members.anAlice -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.test.A_REASON -import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalledWithParam -import io.element.android.tests.testutils.EventsRecorder -import io.element.android.tests.testutils.clickOn -import io.element.android.tests.testutils.ensureCalledOnceWithParam -import io.element.android.tests.testutils.pressBackKey -import org.junit.Ignore -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestRule -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -class RoomMembersModerationViewTest { - @get:Rule val rule = createAndroidComposeRule() - - @Ignore("This test is not passing yet, need to investigate") - @Test - fun `clicking on back emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - actions = listOf( - ModerationAction.DisplayProfile(roomMember.userId), - ), - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - rule.pressBackKey() - // Give time for the bottom sheet to animate - rule.mainClock.advanceTimeBy(1_000) - eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) - } - - @Test - fun `clicking on 'See user info' invokes the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - actions = listOf( - ModerationAction.DisplayProfile(roomMember.userId), - ), - eventSink = eventsRecorder - ) - ensureCalledOnceWithParam(roomMember.userId) { callback -> - rule.setRoomMembersModerationView( - state = state, - onDisplayMemberProfile = callback - ) - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_member_user_info) - } - } - - @Config(qualifiers = "h1024dp") - @Test - fun `clicking on 'Remove member' emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - actions = listOf( - ModerationAction.DisplayProfile(roomMember.userId), - ModerationAction.KickUser(roomMember.userId), - ), - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_remove) - // Give time for the bottom sheet to animate - rule.mainClock.advanceTimeBy(1_000) - eventsRecorder.assertSingle(RoomMembersModerationEvents.KickUser) - } - - @Test - fun `cancelling 'Remove member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - kickUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) - } - - @Test - fun `confirming 'Remove member' reason edition then validation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - kickUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - val reason = rule.activity.getString(CommonStrings.common_reason) - rule.onNodeWithText(reason).performTextInput(A_REASON) - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = A_REASON)) - } - - @Test - fun `confirming 'Remove member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - kickUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = "")) - } - - @Config(qualifiers = "h1024dp") - @Test - fun `clicking on 'Remove and ban member' emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - actions = listOf( - ModerationAction.DisplayProfile(roomMember.userId), - ModerationAction.KickUser(roomMember.userId), - ModerationAction.BanUser(roomMember.userId), - ), - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(R.string.screen_room_member_list_manage_member_remove_confirmation_ban) - // Give time for the bottom sheet to animate - rule.mainClock.advanceTimeBy(1_000) - eventsRecorder.assertSingle(RoomMembersModerationEvents.BanUser) - } - - @Test - fun `cancelling 'Remove and ban member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - banUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) - } - - @Test - fun `confirming 'Remove and ban member' reason edition emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - banUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - val reason = rule.activity.getString(CommonStrings.common_reason) - rule.onNodeWithText(reason).performTextInput(A_REASON) - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = A_REASON)) - } - - @Test - fun `confirming 'Remove and ban member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - banUserAsyncAction = AsyncAction.ConfirmingNoParams, - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = "")) - } - - @Test - fun `cancelling 'Unban member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember), - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset) - } - - @Test - fun `confirming 'Unban member' confirmation emits the expected event`() { - val eventsRecorder = EventsRecorder() - val roomMember = anAlice() - val state = aRoomMembersModerationState( - selectedRoomMember = roomMember, - unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember), - eventSink = eventsRecorder - ) - rule.setRoomMembersModerationView( - state = state, - ) - // Note: the string key semantics is not perfect here :/ - rule.clickOn(R.string.screen_room_member_list_manage_member_unban_action) - eventsRecorder.assertSingle(RoomMembersModerationEvents.UnbanUser(roomMember.userId)) - } -} - -private fun AndroidComposeTestRule.setRoomMembersModerationView( - state: RoomMembersModerationState, - onDisplayMemberProfile: (UserId) -> Unit = EnsureNeverCalledWithParam() -) { - setContent { - RoomMembersModerationView( - state = state, - onDisplayMemberProfile = onDisplayMemberProfile, - ) - } -} diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index b4bb8a3a27..86c816970c 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -7,13 +7,10 @@ package io.element.android.features.roommembermoderation.impl -import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt similarity index 91% rename from features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt rename to features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt index cdfca792d6..44f8bdc056 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationStateProvider.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt @@ -12,15 +12,11 @@ import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId -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.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.toPersistentList -class RoomMemberModerationStateProvider : PreviewParameterProvider { +class InternalRoomMemberModerationStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomMembersModerationState( diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index a2a86e4d13..7f285e1f76 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -317,7 +317,7 @@ private fun RoomMemberActionsBottomSheet( @PreviewsDayNight @Composable -internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) { +internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) { ElementPreview { Box( modifier = Modifier From b1441e1afdd7bcc1d5f60de19bedae201a20dda5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 21 May 2025 18:30:45 +0200 Subject: [PATCH 05/13] change (member moderation) : sync strings --- .../impl/RoomMemberModerationView.kt | 10 +++++----- .../impl/src/main/res/values/localazy.xml | 11 +++++------ .../ui-strings/src/main/res/values/localazy.xml | 17 ----------------- tools/localazy/config.json | 4 +--- 4 files changed, 11 insertions(+), 31 deletions(-) diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 7f285e1f76..04c1eac5a9 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -166,13 +166,13 @@ private fun RoomMemberAsyncActions( when (val action = state.unbanUserAsyncAction) { is AsyncAction.Confirming -> { ConfirmationDialog( - title = stringResource(R.string.screen_room_member_list_manage_member_unban_title), - content = stringResource(R.string.screen_room_member_list_manage_member_unban_message), - submitText = stringResource(R.string.screen_room_member_list_manage_member_unban_action), + title = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_title), + content = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_description), + submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_action), onSubmitClick = { val userDisplayName = selectedUser?.getBestName().orEmpty() asyncIndicatorState.enqueue { - AsyncIndicator.Loading(text = stringResource(R.string.screen_room_member_list_unbanning_user, userDisplayName)) + AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_unbanning_user, userDisplayName)) } state.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser) }, @@ -297,7 +297,7 @@ private fun RoomMemberActionsBottomSheet( } is ModerationAction.UnbanUser -> { ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_member_list_manage_member_unban_action)) }, + headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_unban)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Restart())), style = ListItemStyle.Destructive, onClick = { diff --git a/features/roommembermoderation/impl/src/main/res/values/localazy.xml b/features/roommembermoderation/impl/src/main/res/values/localazy.xml index 8e9b3defc1..16b013d537 100644 --- a/features/roommembermoderation/impl/src/main/res/values/localazy.xml +++ b/features/roommembermoderation/impl/src/main/res/values/localazy.xml @@ -12,10 +12,9 @@ "Remove from room" "Remove member and ban from joining in the future?" "Removing %1$s…" - "Ban from room" - "Only remove member" - "Unban" - "They will be able to join this room again if invited." - "Unban user" - "Unbanning %1$s" + "Unban from room" + "Unban" + "They would be able to join the room again if invited" + "Are you sure you want to unban this member?" + "Unbanning %1$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 2cd87e5f2b..6b5cea1c78 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -339,23 +339,6 @@ Are you sure you want to continue?" "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Ban from room" - "Ban" - "They won’t be able to join this room again if invited." - "Are you sure you want to ban this member?" - "Banning %1$s" - "Remove" - "They will be able to join this room again if invited." - "Are you sure you want to remove this member?" - "View profile" - "Remove from room" - "Remove member and ban from joining in the future?" - "Removing %1$s…" - "Unban from room" - "Unban" - "They would be able to join the room again if invited" - "Are you sure you want to unban this member?" - "Unbanning $1%s" "Failed selecting media, please try again." "Captions might not be visible to people using older apps." "Failed processing media to upload, please try again." diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 51db790fd8..c32c60e4bc 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -329,9 +329,7 @@ { "name" : ":features:roommembermoderation:impl", "includeRegex" : [ - "screen\\.bottom_sheet\\.manage_room_member\\..*", - "screen_room_member_list_manage_member.*", - "screen_room_member_list_unbanning_user" + "screen\\.bottom_sheet\\.manage_room_member\\..*" ] } ] From 1ac5674c18a63972810750b9a187407dac168ad5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 21 May 2025 18:31:08 +0200 Subject: [PATCH 06/13] change (member moderation) : clean and add tests on Presenter --- .../impl/members/RoomMemberListPresenter.kt | 4 +- .../impl/build.gradle.kts | 3 + .../impl/RoomMemberModerationPresenter.kt | 22 +- .../impl/RoomMemberModerationPresenterTest.kt | 338 ++++++++++++++++++ 4 files changed, 353 insertions(+), 14 deletions(-) create mode 100644 features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 4de57787c8..c6872bf9e0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import dagger.assisted.AssistedInject import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState @@ -44,8 +43,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext +import javax.inject.Inject -class RoomMemberListPresenter @AssistedInject constructor( +class RoomMemberListPresenter @Inject constructor( private val room: JoinedRoom, private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, diff --git a/features/roommembermoderation/impl/build.gradle.kts b/features/roommembermoderation/impl/build.gradle.kts index 3d566077eb..f70c7e968e 100644 --- a/features/roommembermoderation/impl/build.gradle.kts +++ b/features/roommembermoderation/impl/build.gradle.kts @@ -36,4 +36,7 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) testImplementation(projects.services.analytics.test) + testImplementation(libs.test.robolectric) + testImplementation(libs.androidx.compose.ui.test.junit) + } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 0359a3120e..2d8668bd81 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -71,21 +71,19 @@ class RoomMemberModerationPresenter @Inject constructor( fun handleEvent(event: RoomMemberModerationEvents) { when (event) { is RoomMemberModerationEvents.ShowActionsForUser -> { - coroutineScope.launch { - selectedUser = event.user - val member = room.membersStateFlow.value.roomMembers()?.firstOrNull { - it.userId == event.user.userId - } - moderationActions.value = computeModerationActions( - member = member, - canKick = canKick.value, - canBan = canBan.value, - currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, - ) + selectedUser = event.user + val member = room.membersStateFlow.value.roomMembers()?.firstOrNull { + it.userId == event.user.userId } + moderationActions.value = computeModerationActions( + member = member, + canKick = canKick.value, + canBan = canBan.value, + currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + ) } is RoomMemberModerationEvents.ProcessAction -> { - when (val action = event.action) { + when (event.action) { is ModerationAction.DisplayProfile -> Unit is ModerationAction.KickUser -> { selectedUser = event.targetUser diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt new file mode 100644 index 0000000000..5a38b76120 --- /dev/null +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -0,0 +1,338 @@ +/* + * Copyright 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.roommembermoderation.impl + +import app.cash.turbine.TurbineTestContext +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +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.RoomMembersState +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.room.FakeBaseRoom +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class RoomMemberModerationPresenterTest { + + @get:Rule + val warmUpRule = WarmUpRule() + + private val targetUser = MatrixUser(userId = A_USER_ID) + + @Test + fun `present - initial state`() = runTest { + val room = aJoinedRoom() + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + assertThat(initialState.canKick).isFalse() + assertThat(initialState.canBan).isFalse() + assertThat(initialState.selectedUser).isNull() + assertThat(initialState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.unbanUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.actions).isEmpty() + } + } + + @Test + fun `present - show actions when canBan=false, canKick=false`() = runTest { + val room = aJoinedRoom( + canBan = false, + canKick = false, + myUserRole = RoomMember.Role.USER, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel) + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + skipItems(1) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.actions).containsExactly( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ) + } + } + + @Test + fun `present - show actions when canBan=true, canKick=true, userRole=Admin and target member is unknown`() = runTest { + val room = aJoinedRoom( + canBan = true, + canKick = true, + myUserRole = RoomMember.Role.ADMIN, + targetRoomMember = null + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + skipItems(2) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.actions).containsExactly( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ModerationActionState(action = ModerationAction.KickUser, isEnabled = true), + ModerationActionState(action = ModerationAction.BanUser, isEnabled = true), + ) + } + } + + @Test + fun `show actions when canBan=true, canKick=true, userRole=Admin and target is User`() = runTest { + val room = aJoinedRoom( + canBan = true, + canKick = true, + myUserRole = RoomMember.Role.ADMIN, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel) + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + skipItems(2) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.actions).containsExactly( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ModerationActionState(action = ModerationAction.KickUser, isEnabled = true), + ModerationActionState(action = ModerationAction.BanUser, isEnabled = true), + ) + } + } + + @Test + fun `show actions when canBan=true, canKick=true, userRole=Moderator and target is Admin`() = runTest { + val room = aJoinedRoom( + canBan = true, + canKick = true, + myUserRole = RoomMember.Role.MODERATOR, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.ADMIN.powerLevel) + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + skipItems(2) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.actions).containsExactly( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ModerationActionState(action = ModerationAction.KickUser, isEnabled = false), + ModerationActionState(action = ModerationAction.BanUser, isEnabled = false), + ) + } + } + + @Test + fun `present - process kick action sets confirming state`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.KickUser + ) + ) + skipItems(1) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.kickUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) + } + } + + @Test + fun `present - process ban action sets confirming state`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.BanUser + ) + ) + skipItems(1) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.banUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) + } + } + + @Test + fun `present - process unban action sets confirming state`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.UnbanUser + ) + ) + skipItems(1) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.unbanUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams) + } + } + + @Test + fun `present - do kick user with success`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.KickUser + ) + ) + skipItems(2) + initialState.eventSink(InternalRoomMemberModerationEvents.DoKickUser("Reason")) + skipItems(1) + val loadingState = awaitState() + assertThat(loadingState.kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) + val successState = awaitState() + assertThat(successState.kickUserAsyncAction).isInstanceOf(AsyncAction.Success::class.java) + assertThat(successState.selectedUser).isNull() + } + } + + @Test + fun `present - do ban user with success`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.BanUser + ) + ) + skipItems(2) + initialState.eventSink(InternalRoomMemberModerationEvents.DoBanUser("Reason")) + skipItems(1) + val loadingState = awaitState() + assertThat(loadingState.banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) + val successState = awaitState() + assertThat(successState.banUserAsyncAction).isInstanceOf(AsyncAction.Success::class.java) + assertThat(successState.selectedUser).isNull() + } + } + + @Test + fun `present - do unban user with success`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.UnbanUser + ) + ) + skipItems(2) + initialState.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser) + skipItems(1) + val loadingState = awaitState() + assertThat(loadingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) + val successState = awaitState() + assertThat(successState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Success::class.java) + assertThat(successState.selectedUser).isNull() + } + } + + @Test + fun `present - do kick user with failure`() = runTest { + val error = RuntimeException("Test error") + val room = aJoinedRoom( + kickUserResult = Result.failure(error), + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction( + targetUser = targetUser, + action = ModerationAction.KickUser + ) + ) + skipItems(2) + initialState.eventSink(InternalRoomMemberModerationEvents.DoKickUser("Reason")) + skipItems(1) + val loadingState = awaitState() + assertThat(loadingState.kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) + val failureState = awaitState() + assertThat(failureState.kickUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + + @Test + fun `present - reset clears all async actions and selected user`() = runTest { + createRoomMemberModerationPresenter(room = aJoinedRoom()).test { + val initialState = awaitState() + initialState.eventSink( + RoomMemberModerationEvents.ProcessAction(targetUser = targetUser, action = ModerationAction.BanUser) + ) + skipItems(2) + initialState.eventSink(InternalRoomMemberModerationEvents.Reset) + skipItems(1) + val resetState = awaitState() + assertThat(resetState.selectedUser).isNull() + assertThat(resetState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + private fun aJoinedRoom( + canKick: Boolean = false, + canBan: Boolean = false, + myUserRole: RoomMember.Role = RoomMember.Role.USER, + kickUserResult: Result = Result.success(Unit), + banUserResult: Result = Result.success(Unit), + unBanUserResult: Result = Result.success(Unit), + targetRoomMember: RoomMember? = null, + ): JoinedRoom { + return FakeJoinedRoom( + kickUserResult = { _, _ -> kickUserResult }, + banUserResult = { _, _ -> banUserResult }, + unBanUserResult = { _, _ -> unBanUserResult }, + baseRoom = FakeBaseRoom( + canBanResult = { _ -> Result.success(canBan) }, + canKickResult = { _ -> Result.success(canKick) }, + userRoleResult = { Result.success(myUserRole) }, + ), + ).apply { + val roomMembers = listOfNotNull(targetRoomMember).toPersistentList() + givenRoomMembersState(state = RoomMembersState.Ready(roomMembers)) + } + } + + private fun TestScope.createRoomMemberModerationPresenter( + room: JoinedRoom, + dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), + analyticsService: AnalyticsService = FakeAnalyticsService(), + ): RoomMemberModerationPresenter { + return RoomMemberModerationPresenter( + room = room, + dispatchers = dispatchers, + analyticsService = analyticsService, + ) + } + + private suspend fun TurbineTestContext.awaitState(): InternalRoomMemberModerationState { + return awaitItem() as InternalRoomMemberModerationState + } +} From 5eef211b045b8607b6ea2a5ba140c2e0bbd308ab Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 24 May 2025 11:16:33 +0200 Subject: [PATCH 07/13] change (member moderation) : add test on RoomMemberModerationView --- .../impl/RoomMemberModerationViewTest.kt | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt new file mode 100644 index 0000000000..d17b7579b6 --- /dev/null +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt @@ -0,0 +1,226 @@ +/* + * Copyright 2024 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.roommembermoderation.impl + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.roommembermoderation.api.ModerationAction +import io.element.android.features.roommembermoderation.api.ModerationActionState +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.testtags.TestTags +import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnceWithTwoParams +import io.element.android.tests.testutils.pressTag +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RoomMemberModerationViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on display profile action calls onSelectAction`() { + val user = anAlice() + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithTwoParams(ModerationAction.DisplayProfile, user) { callback -> + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = user, + actions = listOf( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ), + eventSink = eventsRecorder + ), + onSelectAction = callback + ) + rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_member_user_info) + } + } + + @Test + fun `clicking on kick user action calls onSelectAction`() { + val user = anAlice() + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithTwoParams(ModerationAction.KickUser, user) { callback -> + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = user, + actions = listOf( + ModerationActionState(action = ModerationAction.KickUser, isEnabled = true), + ), + eventSink = eventsRecorder + ), + onSelectAction = callback + ) + rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) + // Gives time for bottomsheet to hide + rule.mainClock.advanceTimeBy(1_000) + } + } + + @Test + fun `clicking on ban user action calls onSelectAction`() { + val user = anAlice() + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithTwoParams(ModerationAction.BanUser, user) { callback -> + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = user, + actions = listOf( + ModerationActionState(action = ModerationAction.BanUser, isEnabled = true), + ), + eventSink = eventsRecorder + ), + onSelectAction = callback + ) + rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_ban) + // Gives time for bottomsheet to hide + rule.mainClock.advanceTimeBy(1_000) + } + } + + @Test + fun `clicking on unban user action calls onSelectAction`() { + val user = anAlice() + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithTwoParams(ModerationAction.UnbanUser, user) { callback -> + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = user, + actions = listOf( + ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true), + ), + eventSink = eventsRecorder + ), + onSelectAction = callback + ) + rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_unban) + // Gives time for bottomsheet to hide + rule.mainClock.advanceTimeBy(1_000) + } + } + + @Test + fun `clicking submit on kick confirmation dialog sends DoKickUser event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogPositive.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoKickUser(reason = "")) + } + + @Test + fun `clicking dismiss on kick confirmation dialog sends Reset event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + kickUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogNegative.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + } + + @Test + fun `clicking submit on ban confirmation dialog sends DoBanUser event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + banUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogPositive.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoBanUser(reason = "")) + } + + @Test + fun `clicking dismiss on ban confirmation dialog sends Reset event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + banUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogNegative.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + } + + @Test + fun `clicking confirm on unban confirmation dialog sends DoUnbanUser event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + unbanUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogPositive.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoUnbanUser) + } + + @Test + fun `clicking dismiss on unban confirmation dialog sends Reset event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + unbanUserAsyncAction = AsyncAction.ConfirmingNoParams, + eventSink = eventsRecorder + ), + ) + rule.pressTag(TestTags.dialogNegative.value) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + } + + @Test + fun `disabled actions are not clickable`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setRoomMemberModerationView( + aRoomMembersModerationState( + selectedUser = anAlice(), + actions = listOf( + ModerationActionState(action = ModerationAction.KickUser, isEnabled = false), + ), + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_bottom_sheet_manage_room_member_remove) + } +} + +private fun AndroidComposeTestRule.setRoomMemberModerationView( + state: InternalRoomMemberModerationState, + onSelectAction: (ModerationAction, MatrixUser) -> Unit = EnsureNeverCalledWithTwoParams(), +) { + setContent { + RoomMemberModerationView( + state = state, + onSelectAction = onSelectAction, + ) + } +} From 37bf88d53fd94a6dc08cfc9aae0ae1c616f87ba0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 24 May 2025 11:23:09 +0200 Subject: [PATCH 08/13] change (member moderation) : clean up --- .../android/features/messages/impl/MessagesEvents.kt | 1 - .../features/messages/impl/MessagesStateProvider.kt | 2 -- .../android/features/messages/impl/MessagesView.kt | 6 ++++-- .../impl/pinned/list/PinnedMessagesListView.kt | 1 - .../features/messages/impl/timeline/TimelineView.kt | 1 - .../impl/timeline/components/TimelineItemEventRow.kt | 1 - .../components/TimelineItemGroupedEventsRow.kt | 1 - .../impl/timeline/components/TimelineItemRow.kt | 1 - .../features/messages/impl/MessagesViewTest.kt | 1 - .../impl/pinned/list/PinnedMessagesListViewTest.kt | 1 - .../messages/impl/timeline/TimelineViewTest.kt | 1 - .../impl/members/RoomMemberListPresenter.kt | 3 --- .../roomdetails/impl/members/RoomMemberListView.kt | 1 - .../impl/members/RoomMemberListPresenterTest.kt | 12 +----------- features/roommembermoderation/impl/build.gradle.kts | 9 +++++++-- .../impl/InternalRoomMemberModerationEvents.kt | 2 +- .../impl/InternalRoomMemberModerationState.kt | 1 - .../impl/RoomMemberModerationPresenter.kt | 4 ++-- .../impl/RoomMemberModerationView.kt | 1 - .../impl/di/RoomMemberModerationModule.kt | 1 - .../impl/RoomMemberModerationPresenterTest.kt | 1 - 21 files changed, 15 insertions(+), 37 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt index f299deaf0e..2e035b6299 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt @@ -9,7 +9,6 @@ package io.element.android.features.messages.impl import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.user.MatrixUser diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index debee82b3a..c366a1e4cf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -44,7 +44,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.aTextEditorStateRich import kotlinx.collections.immutable.persistentListOf @@ -154,7 +153,6 @@ fun aMessagesState( fun aRoomMemberModerationState( canKick: Boolean = false, canBan: Boolean = false, - ) = object : RoomMemberModerationState { override val canKick: Boolean = canKick override val canBan: Boolean = canBan diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 262698894a..ee76562b2c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -209,9 +209,11 @@ fun MessagesView( .consumeWindowInsets(padding), onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, - onUserDataClick = { hidingKeyboard { + onUserDataClick = { + hidingKeyboard { state.eventSink(MessagesEvents.OnUserClicked(it)) - } }, + } + }, onLinkClick = { link, customTab -> if (customTab) { onLinkClick(link.url, true) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index e0356f1670..a5dae403eb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -48,7 +48,6 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.compose.LocalAnalyticsService diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index b14ff51ce2..b3b8331a16 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -69,7 +69,6 @@ import io.element.android.libraries.designsystem.theme.components.FloatingAction import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.utils.animateScrollToItemCenter import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.testtags.TestTags diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index e38d212041..1c9e8d5c2f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -88,7 +88,6 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl -import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 4e55d6c3ff..45ff052ee6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -34,7 +34,6 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.wysiwyg.link.Link diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 136cdcbfed..771940da0b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -42,7 +42,6 @@ import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.designsystem.theme.LocalBuildMeta import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.utils.time.isTalkbackActive diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index b0d22ad4c1..9d378f7de7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -67,7 +67,6 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParamsAndResul import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce -import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack import kotlinx.collections.immutable.persistentListOf import org.junit.Rule diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index 0200da24e2..4d66365a58 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -21,7 +21,6 @@ import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 178523cf64..110545dcae 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -24,7 +24,6 @@ import io.element.android.features.messages.impl.timeline.protection.TimelinePro import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState 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.UserId 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.user.MatrixUser diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index c6872bf9e0..ec63f383d3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -33,7 +33,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.room.canInviteAsState -import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf @@ -52,7 +51,6 @@ class RoomMemberListPresenter @Inject constructor( private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, ) : Presenter { - @Composable override fun present(): RoomMemberListState { var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } @@ -65,7 +63,6 @@ class RoomMemberListPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canInvite by room.canInviteAsState(syncUpdateFlow.value) - val isDm = room.isDmAsState() val roomModerationState = roomMembersModerationPresenter.present() val roomMemberIdentityStates by produceState(persistentMapOf()) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index fab12adb4a..c5d2a93e2a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -45,7 +45,6 @@ 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.roomdetails.impl.R -import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index 52173f43ff..f2436179f7 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -15,7 +15,6 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -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.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembersState @@ -203,7 +202,7 @@ class RoomMemberListPresenterTest { @Test fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest { - val roomMemberModerationPresenter= Presenter { + val roomMemberModerationPresenter = Presenter { aRoomMemberModerationState(canBan = true, canKick = true) } val presenter = createPresenter( @@ -224,15 +223,6 @@ class RoomMemberListPresenterTest { } } -private class FakeRoomMemberListNavigator : RoomMemberListNavigator { - var openRoomMemberDetailsCallCount = 0 - private set - - override fun openRoomMemberDetails(roomMemberId: UserId) { - openRoomMemberDetailsCallCount++ - } -} - @ExperimentalCoroutinesApi private fun TestScope.createDataSource( room: BaseRoom = FakeBaseRoom().apply { diff --git a/features/roommembermoderation/impl/build.gradle.kts b/features/roommembermoderation/impl/build.gradle.kts index f70c7e968e..93294988f6 100644 --- a/features/roommembermoderation/impl/build.gradle.kts +++ b/features/roommembermoderation/impl/build.gradle.kts @@ -13,6 +13,11 @@ plugins { android { namespace = "io.element.android.features.roommembermoderation.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } setupAnvil() @@ -29,7 +34,6 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) - testImplementation(libs.coroutines.core) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) @@ -38,5 +42,6 @@ dependencies { testImplementation(projects.services.analytics.test) testImplementation(libs.test.robolectric) testImplementation(libs.androidx.compose.ui.test.junit) - + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) + testImplementation(projects.libraries.testtags) } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt index 206a379698..1d1bdac4d9 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt @@ -9,7 +9,7 @@ package io.element.android.features.roommembermoderation.impl import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents -sealed interface InternalRoomMemberModerationEvents: RoomMemberModerationEvents { +sealed interface InternalRoomMemberModerationEvents : RoomMemberModerationEvents { data class DoKickUser(val reason: String) : InternalRoomMemberModerationEvents data class DoBanUser(val reason: String) : InternalRoomMemberModerationEvents data object DoUnbanUser : InternalRoomMemberModerationEvents diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index 86c816970c..2648090524 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -26,4 +26,3 @@ data class InternalRoomMemberModerationState( ) : RoomMemberModerationState { val canDisplayActions = actions.isNotEmpty() } - diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 2d8668bd81..52b2e85d05 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -48,7 +48,6 @@ class RoomMemberModerationPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, ) : Presenter { - @Composable override fun present(): RoomMemberModerationState { val coroutineScope = rememberCoroutineScope() @@ -147,7 +146,8 @@ class RoomMemberModerationPresenter @Inject constructor( return buildList { add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true)) // Assume the member is a regular user when it's unknown - val canModerateThisUser = (member?.powerLevel ?: 0) < currentUserMemberPowerLevel + val targetMemberPowerLevel = member?.powerLevel ?: 0 + val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel // Assume the member is joined when it's unknown val membership = member?.membership ?: RoomMembershipState.JOIN if (canKick) { diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 04c1eac5a9..6fa2615d09 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -327,7 +327,6 @@ internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMembe RoomMemberModerationView( state = state, onSelectAction = { _, _ -> - }, ) } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt index d8caadd71f..d2a5296b95 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt @@ -14,7 +14,6 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration import io.element.android.features.roommembermoderation.impl.RoomMemberModerationPresenter import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.di.SessionScope @ContributesTo(RoomScope::class) @Module diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt index 5a38b76120..f06b8f92b7 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -35,7 +35,6 @@ import org.junit.Rule import org.junit.Test class RoomMemberModerationPresenterTest { - @get:Rule val warmUpRule = WarmUpRule() From 5625e287701797b2d3159aa8124d71a46a562d32 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 26 May 2025 13:09:20 +0000 Subject: [PATCH 09/13] Update screenshots --- ...members.moderation_RoomMembersModerationView_Day_10_en.png | 3 --- ....members.moderation_RoomMembersModerationView_Day_1_en.png | 3 --- ....members.moderation_RoomMembersModerationView_Day_2_en.png | 3 --- ....members.moderation_RoomMembersModerationView_Day_7_en.png | 3 --- ....members.moderation_RoomMembersModerationView_Day_8_en.png | 3 --- ....members.moderation_RoomMembersModerationView_Day_9_en.png | 3 --- ...mbers.moderation_RoomMembersModerationView_Night_10_en.png | 3 --- ...embers.moderation_RoomMembersModerationView_Night_1_en.png | 3 --- ...embers.moderation_RoomMembersModerationView_Night_2_en.png | 3 --- ...embers.moderation_RoomMembersModerationView_Night_7_en.png | 3 --- ...embers.moderation_RoomMembersModerationView_Night_8_en.png | 3 --- ...embers.moderation_RoomMembersModerationView_Night_9_en.png | 3 --- ...details.impl.members_RoomMemberListViewBanned_Day_0_en.png | 4 ++-- ...details.impl.members_RoomMemberListViewBanned_Day_1_en.png | 4 ++-- ...details.impl.members_RoomMemberListViewBanned_Day_2_en.png | 4 ++-- ...tails.impl.members_RoomMemberListViewBanned_Night_0_en.png | 4 ++-- ...tails.impl.members_RoomMemberListViewBanned_Night_1_en.png | 4 ++-- ...tails.impl.members_RoomMemberListViewBanned_Night_2_en.png | 4 ++-- ...mbermoderation.impl_RoomMemberModerationView_Day_0_en.png} | 0 ...embermoderation.impl_RoomMemberModerationView_Day_1_en.png | 3 +++ ...embermoderation.impl_RoomMemberModerationView_Day_2_en.png | 3 +++ ...embermoderation.impl_RoomMemberModerationView_Day_3_en.png | 3 +++ ...mbermoderation.impl_RoomMemberModerationView_Day_4_en.png} | 0 ...mbermoderation.impl_RoomMemberModerationView_Day_5_en.png} | 0 ...mbermoderation.impl_RoomMemberModerationView_Day_6_en.png} | 0 ...mbermoderation.impl_RoomMemberModerationView_Day_7_en.png} | 0 ...ermoderation.impl_RoomMemberModerationView_Night_0_en.png} | 0 ...bermoderation.impl_RoomMemberModerationView_Night_1_en.png | 3 +++ ...bermoderation.impl_RoomMemberModerationView_Night_2_en.png | 3 +++ ...bermoderation.impl_RoomMemberModerationView_Night_3_en.png | 3 +++ ...ermoderation.impl_RoomMemberModerationView_Night_4_en.png} | 0 ...ermoderation.impl_RoomMemberModerationView_Night_5_en.png} | 0 ...ermoderation.impl_RoomMemberModerationView_Night_6_en.png} | 0 ...ermoderation.impl_RoomMemberModerationView_Night_7_en.png} | 0 34 files changed, 30 insertions(+), 48 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png => features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png} (100%) diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png deleted file mode 100644 index 1b6fb4bab8..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_10_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 -size 3642 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en.png deleted file mode 100644 index 408db0efb4..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6cbd2503dba04b18d854d0642f4da2cea12bfbc280760ab3306021b2de00302 -size 21382 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en.png deleted file mode 100644 index 9b35d63d7f..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d913c6d23a74ba9a117ccc55922f556b0a51deabcc030a7942bdede9297ae624 -size 24805 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png deleted file mode 100644 index 1b6fb4bab8..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 -size 3642 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png deleted file mode 100644 index 6f43e3db48..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f83d60c7e1d963ccef24668c219bd9df36494dc8bd8f57af7aa63a3b73ee6882 -size 7545 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png deleted file mode 100644 index d220520bfc..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b323796ccdf27e5c445aa02e3f7b73f65df961dd88f3ee480efcba14d8038281 -size 19426 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png deleted file mode 100644 index d6fd8eeb70..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_10_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd -size 3659 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en.png deleted file mode 100644 index 32f1f4e98c..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4cb84e446734d3b36d566ad42e0143c0733ceabe55acf4cb58a7ed28ee29be2e -size 20125 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en.png deleted file mode 100644 index cca7dcc511..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4da58fd502c79c18ddd82dc3be4f7c4c26fb1191d9ed499e447af061bd5a6fc5 -size 23445 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png deleted file mode 100644 index d6fd8eeb70..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd -size 3659 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png deleted file mode 100644 index 08349f1edb..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba213b12d181b04695b9271670cb7d96ce83dfccc3c2031ee002ba1cf180cc8d -size 6408 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png deleted file mode 100644 index 51ce6077b5..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9def02bcb5784681dc26327791510a9a25f0772644cdae363d7a4b90e69067c -size 17479 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png index 7d81d8d062..d180111d1c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42c0b3ad98e83591ac2defcd17a17e4250d00b5704fbb9e3fde425e2990b4613 -size 32545 +oid sha256:b0652d08a2c59e59853b3ee0625a2cee62cf68e8542de16f26e7174f18af620e +size 11465 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png index e07d1a7cd9..9627b16e6c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a60abe6da2ae9b7e2a6eb6ae3a8057d1476a8f76d867288436cfbd7a063002c8 -size 32840 +oid sha256:2350a560fcc78a3d1b22cd235746ac03b46eee2912184dbbe62683540dfee2b4 +size 11704 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png index 77d18480f4..d180111d1c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69fb66dd35d7cccf07e97135defc0dc46b9ff29ec33c6ed6b9ff3b6b0be5d873 -size 22036 +oid sha256:b0652d08a2c59e59853b3ee0625a2cee62cf68e8542de16f26e7174f18af620e +size 11465 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png index a182629276..70b26f1264 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daba36e47b9f2d685a7450265dba8abc687b8256df10d9c1445202c80345c52c -size 31877 +oid sha256:4a64ec55a66dac97601880e5211153c5a25985fe0184186ab3c8d8dbebd9973d +size 10834 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png index 1e228e66c5..46b47ed015 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbf5e7155b878f39e3cc561c50b89323ac9fcef9dbc7364e62cf95cc0ad91e81 -size 32321 +oid sha256:9bee4a7a63485b54555a203b8e4fc557e504b3efacb91d1c7c082647848c8c45 +size 11051 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png index 61ce9c07e3..70b26f1264 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78370686e4fa83ed1f2951a7d71738531b09b3808c66729bf6544d5b2b9a6c12 -size 20944 +oid sha256:4a64ec55a66dac97601880e5211153c5a25985fe0184186ab3c8d8dbebd9973d +size 10834 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png new file mode 100644 index 0000000000..2d38dfd46b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0c6367c53677d284694ed3e06f8d548a33464822094445344aa7cbd8bb3e88e +size 20687 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png new file mode 100644 index 0000000000..15a24a06d9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fbdf555e12892fa89fa73e77d86f7f2f1beab8328fd0b22498e37ee5f966c46 +size 23648 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png new file mode 100644 index 0000000000..b6be401d79 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02d7a5e6169a31c546745565e65766cedb4e70faa00ff73675a5781da96cefaa +size 23770 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_3_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_4_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_5_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Day_6_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png new file mode 100644 index 0000000000..a335623384 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34432e8a0eaecd71b4f0db3eeb4914a1e0450652d3c4bdc6d57745d530c51128 +size 19280 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png new file mode 100644 index 0000000000..92014e1c1c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3c8ed9cea0b3525ef565cbd34b3f7014b632f0c3eee9ebfc037d3ae08c44929 +size 22262 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png new file mode 100644 index 0000000000..52dd3b4301 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a02fe75a7c5489a1e2020169db6ac50c117db968f111ce863ced5a2f59c08d1 +size 22372 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_3_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_4_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_5_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members.moderation_RoomMembersModerationView_Night_6_en.png rename to tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png From 54fceaa73602c690daf4b6b054ce6e8fa930f17a Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 3 Jun 2025 10:58:25 +0200 Subject: [PATCH 10/13] change (room member moderation) : fix copyright --- .../roommembermoderation/api/RoomMemberModerationEvents.kt | 2 +- .../roommembermoderation/api/RoomMemberModerationState.kt | 2 +- .../impl/InternalRoomMemberModerationEvents.kt | 2 +- .../impl/InternalRoomMemberModerationState.kt | 2 +- .../impl/InternalRoomMemberModerationStateProvider.kt | 2 +- .../roommembermoderation/impl/RoomMemberModerationPresenter.kt | 2 +- .../roommembermoderation/impl/RoomMemberModerationView.kt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt index 94c7477739..da25ca41eb 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt index 51f791ed69..368aa283ad 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt index 1d1bdac4d9..902c2bd21f 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index 2648090524..3a650ee59c 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt index 44f8bdc056..bdebcc3a93 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 52b2e85d05..f0861a735e 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 6fa2615d09..5a99607729 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 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. From a92748d27346e6977cb74d60ec9acc41b66df289 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 3 Jun 2025 12:04:37 +0200 Subject: [PATCH 11/13] change (room member moderation) : add unban action test --- .../impl/RoomMemberModerationPresenterTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt index f06b8f92b7..3496612c35 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.RoomMembersState +import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom @@ -141,6 +142,28 @@ class RoomMemberModerationPresenterTest { } } + @Test + fun `show actions when canBan=true, canKick=true, userRole=Moderator and target is Banned`() = runTest { + val room = aJoinedRoom( + canBan = true, + canKick = true, + myUserRole = RoomMember.Role.MODERATOR, + targetRoomMember = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.BAN) + ) + createRoomMemberModerationPresenter(room = room).test { + val initialState = awaitState() + initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + skipItems(2) + val updatedState = awaitState() + assertThat(updatedState.selectedUser).isEqualTo(targetUser) + assertThat(updatedState.actions).containsExactly( + ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), + ModerationActionState(action = ModerationAction.KickUser, isEnabled = false), + ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true), + ) + } + } + @Test fun `present - process kick action sets confirming state`() = runTest { createRoomMemberModerationPresenter(room = aJoinedRoom()).test { From c8c5b85cf1bb6c3ee350fe3cf21fc07e7cee7077 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 3 Jun 2025 14:00:55 +0200 Subject: [PATCH 12/13] change (room member moderation) : sync translations --- .../src/main/res/values-be/translations.xml | 6 ------ .../src/main/res/values-cs/translations.xml | 11 +++++------ .../src/main/res/values-cy/translations.xml | 11 +++++------ .../src/main/res/values-de/translations.xml | 17 ++++++++--------- .../src/main/res/values-el/translations.xml | 6 ------ .../src/main/res/values-es/translations.xml | 14 ++++++++------ .../src/main/res/values-et/translations.xml | 11 +++++------ .../src/main/res/values-eu/translations.xml | 5 ----- .../src/main/res/values-fa/translations.xml | 6 ------ .../src/main/res/values-fi/translations.xml | 11 +++++------ .../src/main/res/values-fr/translations.xml | 11 +++++------ .../src/main/res/values-hu/translations.xml | 11 +++++------ .../src/main/res/values-in/translations.xml | 12 ++++++++++++ .../src/main/res/values-it/translations.xml | 6 ------ .../src/main/res/values-ka/translations.xml | 6 ------ .../src/main/res/values-nb/translations.xml | 11 +++++------ .../src/main/res/values-nl/translations.xml | 6 ------ .../src/main/res/values-pl/translations.xml | 6 ------ .../src/main/res/values-pt-rBR/translations.xml | 9 +++------ .../src/main/res/values-pt/translations.xml | 13 +++++++------ .../src/main/res/values-ro/translations.xml | 6 ------ .../src/main/res/values-ru/translations.xml | 6 ------ .../src/main/res/values-sk/translations.xml | 11 +++++------ .../src/main/res/values-sv/translations.xml | 11 +++++------ .../src/main/res/values-tr/translations.xml | 6 ------ .../src/main/res/values-uk/translations.xml | 11 +++++------ .../src/main/res/values-ur/translations.xml | 12 ++++++++++++ .../src/main/res/values-zh-rTW/translations.xml | 6 ------ .../src/main/res/values-zh/translations.xml | 6 ------ .../src/main/res/values-cs/translations.xml | 17 ----------------- .../src/main/res/values-cy/translations.xml | 17 ----------------- .../src/main/res/values-de/translations.xml | 17 ----------------- .../src/main/res/values-et/translations.xml | 17 ----------------- .../src/main/res/values-fi/translations.xml | 17 ----------------- .../src/main/res/values-fr/translations.xml | 17 ----------------- .../src/main/res/values-hu/translations.xml | 17 ----------------- .../src/main/res/values-nb/translations.xml | 17 ----------------- .../src/main/res/values-pt-rBR/translations.xml | 12 ------------ .../src/main/res/values-pt/translations.xml | 17 ----------------- .../src/main/res/values-sk/translations.xml | 17 ----------------- .../src/main/res/values-sv/translations.xml | 17 ----------------- .../src/main/res/values-uk/translations.xml | 17 ----------------- .../src/main/res/values-ur/translations.xml | 9 --------- .../ui-strings/src/main/res/values/localazy.xml | 17 ----------------- 44 files changed, 100 insertions(+), 406 deletions(-) create mode 100644 features/roommembermoderation/impl/src/main/res/values-in/translations.xml create mode 100644 features/roommembermoderation/impl/src/main/res/values-ur/translations.xml diff --git a/features/roommembermoderation/impl/src/main/res/values-be/translations.xml b/features/roommembermoderation/impl/src/main/res/values-be/translations.xml index 8a488cf10c..bd744e96e7 100644 --- a/features/roommembermoderation/impl/src/main/res/values-be/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-be/translations.xml @@ -9,10 +9,4 @@ "Выдаліць удзельніка з пакоя" "Выдаліць удзельніка і забараніць далучацца ў будучыні?" "Выдаленне %1$s…" - "Выдаліць і заблакіраваць удзельніка" - "Толькі выдаліць удзельніка" - "Разблакіраваць" - "Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." - "Разблакіраваць удзельніка" - "Разблакіроўка %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml index 490671e9ff..2c5e51972e 100644 --- a/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml @@ -12,10 +12,9 @@ "Odebrat z místnosti" "Odebrat člena a zakázat mu připojení v budoucnu?" "Odstraňování %1$s…" - "Odebrat a vykázat člena" - "Pouze odebrat člena" - "Zrušit vykázání" - "Pokud budou pozváni, budou se moci do této místnosti znovu připojit." - "Zrušit vykázání uživatele" - "Rušení vykázání %1$s" + "Zrušit vykázání z místnosti" + "Zrušit vykázání" + "Pokud by byli pozváni, mohli by se znovu připojit do místnosti" + "Opravdu chcete zrušit vykázání tohoto člena?" + "Rušení vykázání %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml b/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml index 55fc68adf0..cfa26c6e9b 100644 --- a/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-cy/translations.xml @@ -12,10 +12,9 @@ "Tynnu o\'r ystafell" "Dileu aelod a\'u gwahardd rhag ymuno yn y dyfodol?" "Wrthi\'n dileu %1$s…" - "Gwahardd o ystafell" - "Dileu aelod yn unig" - "Adfer" - "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Gwahardd defnyddiwr" - "Dad-wahardd %1$s" + "Dad-wahardd o\'r ystafell" + "Dad-wahardd" + "Bydden nhw\'n gallu ymuno â\'r ystafell eto os fydd rhywun yn eu gwahodd" + "Ydych chi\'n siŵr eich bod chi eisiau dadwahardd yr aelod hwn?" + "Dad-wahardd %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-de/translations.xml b/features/roommembermoderation/impl/src/main/res/values-de/translations.xml index c1fb73626d..3ded15cd8e 100644 --- a/features/roommembermoderation/impl/src/main/res/values-de/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-de/translations.xml @@ -3,19 +3,18 @@ "Mitglied entfernen und sperren" "Sperren" "Sie können dem Raum nicht mehr beitreten, selbst wenn sie eingeladen werden." - "Bist du sicher, dass du dieses Mitglied sperren möchtest?" + "Möchten Sie diesen Nutzer wirklich sperren?" "%1$s wird gesperrt." "Entfernen" "Sie können diesen Raum wieder betreten, wenn sie eingeladen werden." "Möchten Sie dieses Mitglied wirklich entfernen?" - "Benutzerinformationen anzeigen" + "Nutzerprofil anzeigen" "Mitglied entfernen" - "Mitglied entfernen und den erneuten Beitritt sperren?" + "Mitglied entfernen und für die Zukunft sperren?" "%1$s wird entfernt." - "Mitglied entfernen und sperren" - "Mitglied nur entfernen" - "Sperre aufheben" - "Die Nutzer können den Raum wieder beitreten, wenn sie dazu eingeladen werden." - "Benutzer entsperren" - "%1$s wird entsperrt." + "Sperre für diesen Chatroom aufheben" + "Sperre aufheben" + "Sie könnten den Chatroom wieder betreten, wenn sie wieder eingeladen würden." + "Möchten Sie die Sperre dieses Mitglieds wirklich aufheben?" + "Sperre für %1$s aufheben" diff --git a/features/roommembermoderation/impl/src/main/res/values-el/translations.xml b/features/roommembermoderation/impl/src/main/res/values-el/translations.xml index 85ea09cae4..5c81a56396 100644 --- a/features/roommembermoderation/impl/src/main/res/values-el/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-el/translations.xml @@ -12,10 +12,4 @@ "Αφαίρεση από το δωμάτιο" "Αφαίρεση μέλους και απαγόρευση συμμετοχής στο μέλλον;" "Αφαίρεση %1$s…" - "Αφαίρεση και αποκλεισμός μέλους" - "Μόνο αφαίρεση μέλους" - "Αναίρεση αποκλεισμού" - "Θα μπορεί να συμμετάσχει ξανά στο δωμάτιο εάν προσκληθεί." - "Άρση αποκλεισμού χρήστη" - "Άρση αποκλεισμού %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-es/translations.xml b/features/roommembermoderation/impl/src/main/res/values-es/translations.xml index 08a49a1425..04049e9e31 100644 --- a/features/roommembermoderation/impl/src/main/res/values-es/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-es/translations.xml @@ -5,14 +5,16 @@ "No podrán volver a unirse a esta sala si son invitados." "¿Estás seguro de que quieres vetar a este miembro?" "Vetando a %1$s" + "Echar" + "Podrá volver a unirse a esta sala si se le invita." + "¿Seguro que quieres echar a este miembro?" "Ver perfil" "Sacar de la sala" "¿Sacar al miembro y prohibirle unirse en el futuro?" "Eliminando %1$s…" - "Sacar y vetar a un miembro" - "Solo eliminar miembro" - "Quitar veto" - "Podrán volver a unirse a esta sala si son invitados de nuevo." - "Quitar veto al usuario" - "Levantando veto a %1$s" + "Eliminar veto en la sala" + "Eliminar veto" + "Podría volver a unirse a la sala si se le invita" + "¿Seguro que quieres levantarle el veto a este miembro?" + "Levantando veto a %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-et/translations.xml b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml index 98a05ea6b6..ffb1d79f88 100644 --- a/features/roommembermoderation/impl/src/main/res/values-et/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml @@ -12,10 +12,9 @@ "Eemalda kasutaja jututoast" "Kas eemaldama kasutaja ja seame talle tulevikuks suhtluskeelu?" "Eemaldame kasutajat %1$s…" - "Eemalda ja sea suhtluskeeld" - "Ainult eemalda kasutaja" - "Eemalda suhtluskeeld" - "Kutse olemasolul saab ta nüüd jututoaga uuesti liituda" - "Eemalda kasutaja suhtluskeeld" - "Eemaldame suhtluskeelu kasutajalt %1$s" + "Eemalda suhtluskeeld jututoas" + "Eemalda suhtluskeeld" + "Ta võib kutse saamisel liituda jututoaga uuesti" + "Kas oled kindel, et soovid selle liikme suhtluskeelu eemaldada?" + "Eemaldame suhtluskeelu kasutajalt %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml b/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml index c3d1f7c5a8..c49ae2d4d5 100644 --- a/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-eu/translations.xml @@ -8,9 +8,4 @@ "Kendu gelatik" "Kidea kendu eta etorkizunean sartzea debekatu?" "%1$s kentzen…" - "Kendu kidea eta ezarri debekua" - "Kendu kidea soilik" - "Kendu debekua" - "Kendu debekua erabiltzaileari" - "%1$s(r)i debekua kentzen" diff --git a/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml index d1b986598a..a51b49b232 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml @@ -12,10 +12,4 @@ "برداشتن از اتاق" "برداشتن عضو و تحریم پیوستن در آینده؟" "برداشتن %1$s…" - "برداشت و تحریم عضو" - "تنها برداشتن عضو" - "رفع انسداد" - "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." - "تحریم نکردن کاربر" - "رفع تحریم %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml index 050d20600a..4c93f73780 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml @@ -12,10 +12,9 @@ "Poista huoneesta" "Poistetaanko jäsen huoneesta ja kielletäänkö heitä liittymästä tulevaisuudessa?" "Poistetaan käyttäjää %1$s huoneesta…" - "Poista jäsen huoneesta ja anna porttikielto" - "Poista vain jäsen huoneesta" - "Poista porttikielto" - "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." - "Poista käyttäjän porttikielto" - "Poistetaan käyttäjän %1$s porttikieltoa" + "Poista porttikielto huoneesta" + "Poista porttikielto" + "He voivat liittyä huoneeseen uudelleen, jos heidät kutsutaan" + "Haluatko varmasti poistaa tämän jäsenen porttikiellon?" + "Poistetaan käyttäjän %1$s porttikieltoa" diff --git a/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml index 88cfd7b309..9aabe07e11 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml @@ -12,10 +12,9 @@ "Retirer le membre du salon" "Retirer le membre et interdire l’adhésion à l’avenir ?" "Enlever %1$s…" - "Retirer et bannir ce membre" - "Retirer le membre uniquement" - "Débannir" - "Il pourra rejoindre le salon à nouveau si il est invité." - "Débannir l’utilisateur" - "Débannissement de %1$s" + "Débannir du salon" + "Débannir" + "L’utilisateur pourra à nouveau rejoindre le salon s’il est invité." + "Êtes-vous sûr de vouloir débannir cet utilisateur?" + "Débannissement de %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml index bb2123e479..db36f41964 100644 --- a/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml @@ -12,10 +12,9 @@ "Eltávolítás a szobából" "Eltávolítja a tagot, és megtiltja a jövőbeni csatlakozást?" "%1$s eltávolítása…" - "Eltávolítás és a tag kitiltása" - "Csak a tag eltávolítása" - "Tiltás feloldása" - "Ehhez a szobához is csatlakozhat, ha meghívják." - "Felhasználó tiltásának feloldása" - "%1$s tiltásának feloldása" + "Visszaengedés a szobába" + "Kitiltás visszavonása" + "Újra beléphetnek a szobába, ha meghívják őket." + "Biztos, hogy feloldja a felhasználó kitiltását?" + "%1$s kitiltásának feloldása" diff --git a/features/roommembermoderation/impl/src/main/res/values-in/translations.xml b/features/roommembermoderation/impl/src/main/res/values-in/translations.xml new file mode 100644 index 0000000000..c40e811d9c --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-in/translations.xml @@ -0,0 +1,12 @@ + + + "Keluarkan dan cekal anggota" + "Cekal" + "Mereka tidak akan dapat bergabung ke ruangan ini lagi jika diundang." + "Apakah Anda yakin ingin mencekal anggota ini?" + "Mencekal %1$s" + "Tampilkan profil" + "Keluarkan dari ruangan" + "Keluarkan pengguna dan cekal pengguna bergabung lagi di masa mendatang?" + "Mengeluarkan %1$s…" + diff --git a/features/roommembermoderation/impl/src/main/res/values-it/translations.xml b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml index a1bdeed0b0..e4f2508747 100644 --- a/features/roommembermoderation/impl/src/main/res/values-it/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml @@ -9,10 +9,4 @@ "Rimuovi dalla stanza" "Rimuovere e vietare l\'accesso in futuro?" "Rimozione di %1$s…" - "Rimuovi ed escludi" - "Rimuovi soltanto" - "Riammetti" - "Potrà entrare nuovamente in questa stanza se invitato." - "Riammetti utente" - "Riammissione di %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml index 730fff2714..a2eb6d4bad 100644 --- a/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-ka/translations.xml @@ -9,10 +9,4 @@ "ოთახიდან გაგდება" "გსურთ წევრის გაგდება და მომავალში გაწევრიანების აკრძალვა?" "%1$s-ს გაგდება…" - "წევრის წაშლა და დაბლოკვა" - "მხოლოდ წევრის წაშლა" - "განბლოკვა" - "მოწვევის შემთხვევაში განბლოკილი მომხმარებელი ისევ შეძლებს ოთახს შეუერთდეს." - "მომხმარებლის განბლოკვა" - "%1$s-ს განბლოკვა" diff --git a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml index b25be530ec..323526170b 100644 --- a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml @@ -12,10 +12,9 @@ "Fjern fra rommet" "Fjerne medlem og utestenge fra å bli med i fremtiden?" "Fjerner %1$s…" - "Fjern og utesteng medlem" - "Bare fjern medlem" - "Opphev utestengelse" - "De vil kunne bli med i dette rommet igjen hvis de blir invitert." - "Opphev utestengelse av bruker" - "Oppheve utestengelsen av %1$s" + "Fjern utestengelsen fra rommet" + "Opphev utestengelsen" + "De vil kunne bli med i rommet igjen hvis de blir invitert" + "Er du sikker på at du vil oppheve utestengelsen av dette medlemmet?" + "Oppheve utestengelsen av %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml index f025cbb584..64317b83bf 100644 --- a/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-nl/translations.xml @@ -9,10 +9,4 @@ "Verwijderen uit kamer" "Lid verwijderen en toekomstige deelname verbieden?" "%1$s wordt verwijderd…" - "Lid verwijderen en verbannen" - "Alleen lid verwijderen" - "Ontbannen" - "Ze kunnen opnieuw tot de kamer toetreden als ze worden uitgenodigd." - "Ontban gebruiker" - "%1$s ontbannen" diff --git a/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml index 40bd309913..4b0098c34a 100644 --- a/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-pl/translations.xml @@ -12,10 +12,4 @@ "Usuń z pokoju" "Usunąć członka i zablokować możliwość dołączenia w przyszłości?" "Usuwanie %1$s…" - "Usuń i zbanuj członka" - "Tylko usuń członka" - "Odbanuj" - "Będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." - "Odbanuj użytkownika" - "Odbanowanie %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml index 764c313a96..036c87f2b7 100644 --- a/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml @@ -5,14 +5,11 @@ "Eles não poderão entrar nesta sala novamente se forem convidados." "Tem certeza de que quer banir este membro?" "Banindo %1$s" + "Remover" + "Eles poderão entrar nesta sala novamente se forem convidados." + "Tem certeza de que deseja remover este membro?" "Ver perfil" "Remover da sala" "Remover membro e banir de entrar novamente no futuro?" "Removendo %1$s…" - "Remover e banir membro" - "Somente remover membro" - "Desbanir" - "Eles poderão entrar nesta sala novamente se forem convidados." - "Desbanir usuário" - "Desbanindo %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml index 89ddd6f5ba..5d063c8ad2 100644 --- a/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml @@ -5,15 +5,16 @@ "Não poderão voltar a entrar nesta sala, mesmo se forem convidados." "Tens a certeza que queres banir este participante?" "A banir %1$s" + "Remover" "Poderão entrar na sala novamente se convidados." + "Tens a certeza que queres remover este membro?" "Ver perfil" "Remover da sala" "Remover participante e proibir que entre no futuro?" "A remover %1$s…" - "Remover e banir participante" - "Remover apenas" - "Anular banimento" - "Poderão juntar-se novamente a esta sala se forem convidados." - "Anular banimento do utilizador" - "A anular banimento de %1$s" + "Anular banimento da sala" + "Anular banimento" + "Poderão entrar novamente na sala se forem convidados" + "Tens a certeza que queres anular o banimento deste utilizador?" + "A anular banimento de %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml index 526db044db..27bb9c23bd 100644 --- a/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml @@ -9,10 +9,4 @@ "Înlăturați membrul" "Înlăturați membrul și interziceți-i să se alăture în viitor?" "Se elimină %1$s" - "Eliminați și interziceți membrul" - "Doar înlăturare" - "Anulare excludere" - "Se vor putea alătura din nou acestei săli dacă sunt invitați." - "Anulați interzicerea utilizatorului" - "Se anulează interzicerea lui %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml index 93fa22ac3f..ca4610dd31 100644 --- a/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml @@ -11,10 +11,4 @@ "Удалить участника из комнаты" "Удалить участника и запретить присоединяться в будущем?" "Удаление %1$s…" - "Удалить и заблокировать участника" - "Только удалить участника" - "Разблокировать" - "Они снова смогут присоединиться в эту комнату если их пригласят." - "Разбанить пользователя?" - "Разблокировка %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml index 8e7c4a1399..df5214c17c 100644 --- a/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml @@ -12,10 +12,9 @@ "Odstrániť z miestnosti" "Odstrániť člena a zakázať vstup v budúcnosti?" "Odstraňuje sa %1$s…" - "Odstrániť a zakázať člena" - "Iba odstrániť člena" - "Zrušiť zákaz" - "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." - "Zrušiť zákaz používateľa" - "Zrušenie zákazu %1$s" + "Zrušiť zákaz prístupu do miestnosti" + "Zrušiť zákaz" + "V prípade pozvania by sa mohli opäť pripojiť k miestnosti" + "Naozaj chcete zrušiť zablokovanie tohto člena?" + "Zrušenie zákazu pre %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml b/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml index bb512b1142..9f3aabe191 100644 --- a/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-sv/translations.xml @@ -12,10 +12,9 @@ "Ta bort från rummet" "Ta bort medlem och banna från att gå med i framtiden?" "Tar bort %1$s …" - "Ta bort och banna medlem" - "Ta bara bort medlem" - "Avbanna" - "Denne kommer kunna gå med i rummet igen om denne bjuds in" - "Avbanna användare" - "Avbannar %1$s" + "Avbanna från rummet" + "Avbanna" + "De skulle kunna gå med i rummet igen om de blev inbjudna" + "Är du säker på att du vill avbanna den här medlemmen?" + "Avbannar %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml index beee8fa720..73f17edd02 100644 --- a/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-tr/translations.xml @@ -9,10 +9,4 @@ "Odadan çıkar" "Üyeyi çıkarın ve gelecekte katılmasını yasaklayın?" "Kaldırılıyor %1$s…" - "Üyeyi çıkar ve yasakla" - "Yalnızca üyeyi kaldır" - "Yasağı Kaldır" - "Davet edildikleri takdirde bu odaya tekrar katılabileceklerdir." - "Kullanıcının yasağını kaldır" - "Yasak kaldırılıyor %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml b/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml index d6ca0625a8..6c48b0bde0 100644 --- a/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-uk/translations.xml @@ -12,10 +12,9 @@ "Вилучити з кімнати" "Вилучити учасника та заборонити приєднання в майбутньому?" "Вилучення %1$s…" - "Вилучити й заблокувати учасника" - "Лише вилучити учасника" - "Розблокувати" - "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." - "Розблокувати користувача" - "Розблокування %1$s" + "Розблокувати в кімнаті" + "Розблокувати" + "Вони зможуть знову приєднатися до кімнати, якщо їх запросять" + "Ви впевнені, що хочете розблокувати цього учасника?" + "Розблокування %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-ur/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ur/translations.xml new file mode 100644 index 0000000000..333c86f056 --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-ur/translations.xml @@ -0,0 +1,12 @@ + + + "کمرے سے محظور کریں" + "محظور کریں" + "اگر وہ مدعو کیا گیا تو وہ دوبارہ اس کمرے میں شامل نہیں ہوسکیں گے۔" + "کیا آپ کو یقین ہے کہ آپ اس رکن کو محظور کرنا چاہتے ہیں؟" + "%1$s کو محظور کر رہا ہے" + "نمایہ ملاحظہ کریں" + "کمرے سے ہٹائیں" + "رکن کو ہٹائیں اور مستقبل میں شمولیت پر پابندی لگائیں؟" + "%1$s کو ہٹا رہا ہے…" + diff --git a/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml index 6f1ab7d548..152e2d3a8f 100644 --- a/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml @@ -12,10 +12,4 @@ "踢出聊天室" "移除成員並禁止未來再度加入?" "正在踢出 %1$s…" - "踢出並加入黑名單" - "僅移除成員" - "解除黑名單" - "如果收到邀請,他們能再次加入聊天室。" - "解除黑名單" - "正在解除黑名單 %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml index cf89b13e06..20c002aa76 100644 --- a/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml @@ -12,10 +12,4 @@ "从聊天室移除" "删除成员并禁止重新加入?" "正在移除 %1$s……" - "移除并封禁成员" - "仅移除成员" - "取消封禁" - "如果受到邀请,他们可以重新加入聊天室。" - "解封用户" - "解除封禁 %1$s" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index c6c48baf17..6a3d4a861f 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -348,23 +348,6 @@ Opravdu chcete pokračovat?" "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Odebrat a vykázat člena" - "Vykázat" - "Nebudou se moci znovu připojit k této místnosti, pokud budou pozváni." - "Jste si jisti, že chcete vykázat tohoto člena?" - "Vykazování %1$s" - "Odebrat" - "Budou moci znovu vstoupit do této místnosti, pokud budou pozváni." - "Opravdu chcete tohoto člena odebrat?" - "Zobrazit profil" - "Odebrat z místnosti" - "Odebrat člena a zakázat mu připojení v budoucnu?" - "Odstraňování %1$s…" - "Zrušit vykázání z místnosti" - "Zrušit vykázání" - "Pokud by byli pozváni, mohli by se znovu připojit do místnosti" - "Opravdu chcete zrušit vykázání tohoto člena?" - "Rušení vykázání %1$s" "Výběr média se nezdařil, zkuste to prosím znovu." "Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace." "Nahrání média se nezdařilo, zkuste to prosím znovu." diff --git a/libraries/ui-strings/src/main/res/values-cy/translations.xml b/libraries/ui-strings/src/main/res/values-cy/translations.xml index 691ad643f4..c6747aca86 100644 --- a/libraries/ui-strings/src/main/res/values-cy/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cy/translations.xml @@ -363,23 +363,6 @@ Ydych chi\'n siŵr eich bod am barhau?" "Hei, siaradwch â mi ar %1$s: %2$s" "Android %1$s" "Rageshake i adrodd gwall" - "Gwahardd o ystafell" - "Atal" - "Fyddan nhw ddim yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Ydych chi\'n siŵr eich bod am wahardd yr aelod hwn?" - "Yn gwahardd %1$s" - "Tynnu" - "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Ydych chi\'n siŵr eich bod am ddileu\'r aelod hwn?" - "Gweld proffil" - "Tynnu o\'r ystafell" - "Dileu aelod a\'u gwahardd rhag ymuno yn y dyfodol?" - "Wrthi\'n dileu %1$s…" - "Dad-wahardd o\'r ystafell" - "Dad-wahardd" - "Bydden nhw\'n gallu ymuno â\'r ystafell eto os fydd rhywun yn eu gwahodd" - "Ydych chi\'n siŵr eich bod chi eisiau dadwahardd yr aelod hwn?" - "Dad-wahardd %1$s" "Wedi methu dewis cyfrwng, ceisiwch eto." "Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn." "Wedi methu â phrosesu cyfryngau i\'w llwytho, ceisiwch eto." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 05084459a9..afcc1f784f 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -343,23 +343,6 @@ Möchten Sie wirklich fortfahren?" "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - "Mitglied entfernen und sperren" - "Sperren" - "Sie können dem Raum nicht mehr beitreten, selbst wenn sie eingeladen werden." - "Möchten Sie diesen Nutzer wirklich sperren?" - "%1$s wird gesperrt." - "Entfernen" - "Sie können diesen Raum wieder betreten, wenn sie eingeladen werden." - "Möchten Sie dieses Mitglied wirklich entfernen?" - "Nutzerprofil anzeigen" - "Mitglied entfernen" - "Mitglied entfernen und für die Zukunft sperren?" - "%1$s wird entfernt." - "Sperre für diesen Chatroom aufheben" - "Sperre aufheben" - "Sie könnten den Chatroom wieder betreten, wenn sie wieder eingeladen würden." - "Möchten Sie die Sperre dieses Mitglieds wirklich aufheben?" - "Sperre für %1$s aufheben" "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index 93a75c7966..9547e7663a 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -343,23 +343,6 @@ Kas sa oled kindel, et soovid jätkata?" "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" "Veast teatamiseks raputa nutiseadet ägedalt" - "Eemalda ja sea suhtluskeeld" - "Sea suhtluskeeld" - "Ta ei saa selle jututoaga liituda isegi kutse olemasolul." - "Kas sa oled kindel, et soovid sellele kasutajale seada suhtluskeelu?" - "Seame kasutajale %1$s suhtluskeelu" - "Eemalda" - "Uue kutse saamisel on tal võimalik selle jututoaga uuesti liituda." - "Kas sa oled kindel, et soovid selle osaleja eemaldada?" - "Vaata profiili" - "Eemalda kasutaja jututoast" - "Kas eemaldama kasutaja ja seame talle tulevikuks suhtluskeelu?" - "Eemaldame kasutajat %1$s…" - "Eemalda suhtluskeeld jututoas" - "Eemalda suhtluskeeld" - "Ta võib kutse saamisel liituda jututoaga uuesti" - "Kas oled kindel, et soovid selle liikme suhtluskeelu eemaldada?" - "Eemaldame suhtluskeelu kasutajalt %1$s" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele." "Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti." diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index d482f465eb..4c3be6c5c3 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -343,23 +343,6 @@ Haluatko varmasti jatkaa?" "Hei, keskustele kanssani %1$s -sovelluksessa: %2$s" "%1$s Android" "Raivostunut ravistaminen ilmoittaa virheestä" - "Poista jäsen huoneesta ja anna porttikielto" - "Anna porttikielto" - "He eivät voi enää liittyä tähän huoneeseen, jos heidät kutsutaan." - "Haluatko varmasti antaa tälle jäsenelle porttikiellon?" - "Annetaan porttikieltoa käyttäjälle %1$s" - "Poista" - "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." - "Haluatko varmasti poistaa tämän jäsenen?" - "Näytä profiili" - "Poista huoneesta" - "Poistetaanko jäsen huoneesta ja kielletäänkö heitä liittymästä tulevaisuudessa?" - "Poistetaan käyttäjää %1$s huoneesta…" - "Poista porttikielto huoneesta" - "Poista porttikielto" - "He voivat liittyä huoneeseen uudelleen, jos heidät kutsutaan" - "Haluatko varmasti poistaa tämän jäsenen porttikiellon?" - "Poistetaan käyttäjän %1$s porttikieltoa" "Median valinta epäonnistui, yritä uudelleen." "Kuvatekstit eivät välttämättä näy ihmisille, jotka käyttävät vanhempia sovelluksia." "Median käsittely epäonnistui, yritä uudelleen." diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index 376a757ee5..d15958d55a 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -343,23 +343,6 @@ Raison : %1$s." "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" "Rageshake pour signaler un problème" - "Retirer et bannir ce membre" - "Bannir" - "Il ne pourra pas rejoindre le salon à nouveau, même si il est invité." - "Êtes-vous certain de vouloir bannir ce membre ?" - "Bannissement de %1$s" - "Retirer" - "Cet utilisateur pourra rejoindre le salon à nouveau si il est invité." - "Voulez-vous vraiment supprimer ce membre ?" - "Voir le profil" - "Retirer le membre du salon" - "Retirer le membre et interdire l’adhésion à l’avenir ?" - "Enlever %1$s…" - "Débannir du salon" - "Débannir" - "L’utilisateur pourra à nouveau rejoindre le salon s’il est invité." - "Êtes-vous sûr de vouloir débannir cet utilisateur?" - "Débannissement de %1$s" "Échec de la sélection du média, veuillez réessayer." "Les légendes peuvent ne pas être visibles pour les utilisateurs d’anciennes applications." "Échec du traitement des médias à télécharger, veuillez réessayer." diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index a528b0b066..ab14bca3b7 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -343,23 +343,6 @@ Biztos, hogy folytatja?" "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" - "Eltávolítás és a tag kitiltása" - "Kitiltás" - "Többé nem csatlakozhat ehhez a szobához, akkor sem, ha meghívják." - "Biztos, hogy kitiltja ezt a tagot?" - "%1$s kitiltása" - "Eltávolítás" - "Ehhez a szobához is csatlakozhat, ha meghívják." - "Biztos, hogy eltávolítja ezt a tagot?" - "Profil megtekintése" - "Eltávolítás a szobából" - "Eltávolítja a tagot, és megtiltja a jövőbeni csatlakozást?" - "%1$s eltávolítása…" - "Visszaengedés a szobába" - "Kitiltás visszavonása" - "Újra beléphetnek a szobába, ha meghívják őket." - "Biztos, hogy feloldja a felhasználó kitiltását?" - "%1$s kitiltásának feloldása" "Nem sikerült kiválasztani a médiát, próbálja újra." "Előfordulhat, hogy a feliratok nem láthatók a régebbi alkalmazásokat használók számára." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." diff --git a/libraries/ui-strings/src/main/res/values-nb/translations.xml b/libraries/ui-strings/src/main/res/values-nb/translations.xml index ccd0201788..573107cc01 100644 --- a/libraries/ui-strings/src/main/res/values-nb/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nb/translations.xml @@ -343,23 +343,6 @@ Er du sikker på at du vil fortsette?" "Hei, snakk med meg på %1$s: %2$s" "%1$s Android" "Rageshake for å rapportere feil" - "Fjern og utesteng medlem" - "Utesteng" - "De vil ikke kunne bli med i dette rommet igjen hvis de blir invitert." - "Er du sikker på at du vil utestenge dette medlemmet?" - "Utestenger %1$s" - "Fjern" - "De vil kunne bli med i dette rommet igjen hvis de blir invitert." - "Er du sikker på at du vil fjerne dette medlemmet?" - "Vis profil" - "Fjern fra rommet" - "Fjerne medlem og utestenge fra å bli med i fremtiden?" - "Fjerner %1$s…" - "Fjern utestengelsen fra rommet" - "Opphev utestengelsen" - "De vil kunne bli med i rommet igjen hvis de blir invitert" - "Er du sikker på at du vil oppheve utestengelsen av dette medlemmet?" - "Oppheve utestengelsen av %1$s" "Kunne ikke velge medium, prøv igjen." "Teksting er kanskje ikke synlig for personer som bruker eldre apper." "Kunne ikke behandle medier for opplasting, vennligst prøv igjen." diff --git a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml index f73041b2a7..d8fddecca2 100644 --- a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml @@ -343,18 +343,6 @@ Você tem certeza de que deseja continuar?" "Ei, fale comigo em %1$s: %2$s" "%1$s Android" "Rageshake para relatar um bug" - "Remover e banir membro" - "Banir" - "Eles não poderão entrar nesta sala novamente se forem convidados." - "Tem certeza de que quer banir este membro?" - "Banindo %1$s" - "Remover" - "Eles poderão entrar nesta sala novamente se forem convidados." - "Tem certeza de que deseja remover este membro?" - "Ver perfil" - "Remover da sala" - "Remover membro e banir de entrar novamente no futuro?" - "Removendo %1$s…" "Falha ao selecionar a mídia, tente novamente." "As legendas podem não ser visíveis para pessoas que usam aplicativos mais antigos." "Falha ao processar mídia para upload. Tente novamente." diff --git a/libraries/ui-strings/src/main/res/values-pt/translations.xml b/libraries/ui-strings/src/main/res/values-pt/translations.xml index d1cf2a258f..d8931a4a99 100644 --- a/libraries/ui-strings/src/main/res/values-pt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt/translations.xml @@ -343,23 +343,6 @@ Tens a certeza de que queres continuar?" "Alô! Fala comigo na %1$s: %2$s" "%1$s Android" "Agita o dispositivo em fúria para comunicar um problema" - "Remover e banir participante" - "Banir" - "Não poderão voltar a entrar nesta sala, mesmo se forem convidados." - "Tens a certeza que queres banir este participante?" - "A banir %1$s" - "Remover" - "Poderão entrar na sala novamente se convidados." - "Tens a certeza que queres remover este membro?" - "Ver perfil" - "Remover da sala" - "Remover participante e proibir que entre no futuro?" - "A remover %1$s…" - "Anular banimento da sala" - "Anular banimento" - "Poderão entrar novamente na sala se forem convidados" - "Tens a certeza que queres anular o banimento deste utilizador?" - "A anular banimento de %1$s" "Falha ao selecionar multimédia, por favor tente novamente." "As legendas poderão não ser visíveis em versões mais antigas da aplicação." "Falha ao processar multimédia para carregamento, por favor tente novamente." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 3f00756e10..fa2ccf4b58 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -348,23 +348,6 @@ Naozaj chcete pokračovať?" "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" "Zúrivo potriasť pre nahlásenie chyby" - "Odstrániť a zakázať člena" - "Zakázať" - "Nebudú sa môcť pripojiť k tejto miestnosti znova ani ak budú pozvaní." - "Ste si istý, že chcete zakázať tohto člena?" - "Zakazuje sa %1$s" - "Odstrániť" - "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." - "Ste si istý, že chcete odstrániť tohto člena?" - "Zobraziť profil" - "Odstrániť z miestnosti" - "Odstrániť člena a zakázať vstup v budúcnosti?" - "Odstraňuje sa %1$s…" - "Zrušiť zákaz prístupu do miestnosti" - "Zrušiť zákaz" - "V prípade pozvania by sa mohli opäť pripojiť k miestnosti" - "Naozaj chcete zrušiť zablokovanie tohto člena?" - "Zrušenie zákazu pre %1$s" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 75333d408e..2733eed812 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -343,23 +343,6 @@ Anledning:%1$s." "Hallå, prata med mig på %1$s: %2$s" "%1$s Android" "Raseriskaka för att rapportera bugg" - "Ta bort och banna medlem" - "Banna" - "Denne kommer inte att kunna gå med i det här rummet igen om denne bjuds in." - "Är du säker på att du vill banna den här medlemmen?" - "Bannar %1$s" - "Ta bort" - "Denne kommer kunna gå med i rummet igen om denne bjuds in" - "Är du säker på att du vill ta bort den här medlemmen?" - "Visa profil" - "Ta bort från rummet" - "Ta bort medlem och banna från att gå med i framtiden?" - "Tar bort %1$s …" - "Avbanna från rummet" - "Avbanna" - "De skulle kunna gå med i rummet igen om de blev inbjudna" - "Är du säker på att du vill avbanna den här medlemmen?" - "Avbannar %1$s" "Misslyckades att välja media, vänligen pröva igen." "Bildtexter kanske inte är synliga för personer som använder äldre appar." "Misslyckades att bearbeta media för uppladdning, vänligen pröva igen." diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index 69c5104375..b317dd24d4 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -348,23 +348,6 @@ "Вітаю, поспілкуйтеся зі мною в %1$s: %2$s" "%1$s Android" "Повідомити про ваду за допомогою Rageshake" - "Вилучити й заблокувати учасника" - "Заблокувати" - "Він не зможе приєднатися до цієї кімнати знову, якщо його запросять." - "Ви точно хочете заблокувати цього користувача?" - "Блокування %1$s" - "Вилучити" - "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." - "Ви дійсно хочете вилучити цього учасника?" - "Переглянути профіль" - "Вилучити з кімнати" - "Вилучити учасника та заборонити приєднання в майбутньому?" - "Вилучення %1$s…" - "Розблокувати в кімнаті" - "Розблокувати" - "Вони зможуть знову приєднатися до кімнати, якщо їх запросять" - "Ви впевнені, що хочете розблокувати цього учасника?" - "Розблокування %1$s" "Не вдалося вибрати медіафайл, спробуйте ще раз." "Користувачі старих застосунків можуть не бачити підписи." "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." diff --git a/libraries/ui-strings/src/main/res/values-ur/translations.xml b/libraries/ui-strings/src/main/res/values-ur/translations.xml index 56864f29b1..e71a5e2014 100644 --- a/libraries/ui-strings/src/main/res/values-ur/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ur/translations.xml @@ -271,15 +271,6 @@ "ارے، مجھ سے %1$s پر بات کریں: %2$s" "%1$s Android" "خطاء کی اطلاع دینے کیلئے غصے سے ہلائیں" - "کمرے سے محظور کریں" - "محظور کریں" - "اگر وہ مدعو کیا گیا تو وہ دوبارہ اس کمرے میں شامل نہیں ہوسکیں گے۔" - "کیا آپ کو یقین ہے کہ آپ اس رکن کو محظور کرنا چاہتے ہیں؟" - "%1$s کو محظور کر رہا ہے" - "نمایہ ملاحظہ کریں" - "کمرے سے ہٹائیں" - "رکن کو ہٹائیں اور مستقبل میں شمولیت پر پابندی لگائیں؟" - "%1$s کو ہٹا رہا ہے…" "وسائط منتخب کرنا ناکام، برائے مہربانی دوبارہ کوشش کریں۔" "وسائط کا معالجہ برائے ترفیع ناکام، برائے مہربانی دوبارہ کوشش کریں۔" "وسائط رفع کرنے میں ناکام، برائے مہربانی دوبارہ کوشش کریں۔" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 90100bbcd7..1548788ce4 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -343,23 +343,6 @@ Are you sure you want to continue?" "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Ban from room" - "Ban" - "They won’t be able to join this room again if invited." - "Are you sure you want to ban this member?" - "Banning %1$s" - "Remove" - "They will be able to join this room again if invited." - "Are you sure you want to remove this member?" - "View profile" - "Remove from room" - "Remove member and ban from joining in the future?" - "Removing %1$s…" - "Unban from room" - "Unban" - "They would be able to join the room again if invited" - "Are you sure you want to unban this member?" - "Unbanning %1$s" "Failed selecting media, please try again." "Captions might not be visible to people using older apps." "Failed processing media to upload, please try again." From 24c29c54a3fafc260c926cb5d3e5eb2c9ee48bca Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 3 Jun 2025 12:13:14 +0000 Subject: [PATCH 13/13] Update screenshots --- ...login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png | 4 ++-- ...gin.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png index 60ec9cc17b..fb57633a41 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f90a8ecae26714e76d5536682716a15aefc3d4c8836617add9a0ea946e7d242 -size 31486 +oid sha256:48b327608bde4f150450306fb3a3b2508dd437b43e301fda58a8bc279ba093f3 +size 31326 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png index 0581885f19..20ae4c9cdd 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34cd9f4558efc143b4ab3bec96342a42cfad6906da0d46e75d78db36b7e59bd6 -size 30242 +oid sha256:c31eaa5cbb8850fd523915a401e3b9ade2a58845b76cf2c7c4fec78685479159 +size 30164