Feature : Report room (#4654)
* feature (report room) : introduce all presentation classes. * feature (report room) : branch entry point in the room list * refactor (matrix ui) : move some code from appnav to matrix ui * feature (report room) : add api on room * feature (report room) : adjust ui * feature (report room) : branch api * feature (decline invite and block) : move things around and introduce presentation classes * feature (decline invite and block) : continue to move things * feature (report room) : remove reference to "conversation" for now * feature (report room) : add report room action to room detail screen * feature (report room) : enabled button state * feature (report room) : improve code and reuse * feature (report room) : add feature flag * feature (report room) : change feature flag to static bool * feature (report room) : add tests * feature (report room) : fix ui with new api on ListItem * feature (report room) : clean up and add more tests. * Update screenshots * feature (report room) : more test and fix issue * feature (report room) : update strings * feature (report room) : fix konsist preview * feature (report room) : disable feature * Update screenshots * var -> val * Improve preview of AcceptDeclineInviteView * Improve preview consistency * Add missing test on DismissErrorAndHideContent * Update screenshots * Add missing tests --------- Co-authored-by: ElementBot <android@element.io> Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
parent
db6bc7c04f
commit
c2568f84d2
229 changed files with 3995 additions and 1210 deletions
|
|
@ -28,7 +28,7 @@ class DefaultRoomListEntryPoint @Inject constructor() : RoomListEntryPoint {
|
|||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<RoomListNode>(buildContext, plugins)
|
||||
return parentNode.createNode<RoomListFlowNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,14 +34,17 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun RoomListContextMenu(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
canReportRoom: Boolean,
|
||||
eventSink: (RoomListEvents.ContextMenuEvents) -> Unit,
|
||||
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
||||
onReportRoomClick: (roomId: RoomId) -> Unit
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { eventSink(RoomListEvents.HideContextMenu) },
|
||||
) {
|
||||
RoomListModalBottomSheetContent(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = canReportRoom,
|
||||
onRoomMarkReadClick = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
eventSink(RoomListEvents.MarkAsRead(contextMenu.roomId))
|
||||
|
|
@ -65,6 +68,10 @@ fun RoomListContextMenu(
|
|||
eventSink(RoomListEvents.HideContextMenu)
|
||||
eventSink(RoomListEvents.ClearCacheOfRoom(contextMenu.roomId))
|
||||
},
|
||||
onReportRoomClick = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
onReportRoomClick(contextMenu.roomId)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -72,12 +79,14 @@ fun RoomListContextMenu(
|
|||
@Composable
|
||||
private fun RoomListModalBottomSheetContent(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
canReportRoom: Boolean,
|
||||
onRoomSettingsClick: () -> Unit,
|
||||
onLeaveRoomClick: () -> Unit,
|
||||
onFavoriteChange: (isFavorite: Boolean) -> Unit,
|
||||
onRoomMarkReadClick: () -> Unit,
|
||||
onRoomMarkUnreadClick: () -> Unit,
|
||||
onClearCacheRoomClick: () -> Unit,
|
||||
onReportRoomClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
|
@ -157,16 +166,24 @@ private fun RoomListModalBottomSheetContent(
|
|||
),
|
||||
style = ListItemStyle.Primary,
|
||||
)
|
||||
if (canReportRoom) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(text = stringResource(CommonStrings.action_report_room))
|
||||
},
|
||||
modifier = Modifier.clickable { onReportRoomClick() },
|
||||
leadingContent = ListItemContent.Icon(
|
||||
iconSource = IconSource.Vector(
|
||||
CompoundIcons.ChatProblem(),
|
||||
contentDescription = stringResource(CommonStrings.action_report_room),
|
||||
)
|
||||
),
|
||||
style = ListItemStyle.Destructive,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
val leaveText = stringResource(
|
||||
id = if (contextMenu.isDm) {
|
||||
CommonStrings.action_leave_conversation
|
||||
} else {
|
||||
CommonStrings.action_leave_room
|
||||
}
|
||||
)
|
||||
Text(text = leaveText)
|
||||
Text(text = stringResource(CommonStrings.action_leave_room))
|
||||
},
|
||||
modifier = Modifier.clickable { onLeaveRoomClick() },
|
||||
leadingContent = ListItemContent.Icon(
|
||||
|
|
@ -201,11 +218,13 @@ internal fun RoomListModalBottomSheetContentPreview(
|
|||
) = ElementPreview {
|
||||
RoomListModalBottomSheetContent(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = true,
|
||||
onRoomMarkReadClick = {},
|
||||
onRoomMarkUnreadClick = {},
|
||||
onRoomSettingsClick = {},
|
||||
onLeaveRoomClick = {},
|
||||
onFavoriteChange = {},
|
||||
onClearCacheRoomClick = {},
|
||||
onReportRoomClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright 2023, 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.roomlist.impl
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomListDeclineInviteMenu(
|
||||
menu: RoomListState.DeclineInviteMenu.Shown,
|
||||
canReportRoom: Boolean,
|
||||
onDeclineAndBlockClick: (RoomListRoomSummary) -> Unit,
|
||||
eventSink: (RoomListEvents) -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { eventSink(RoomListEvents.HideDeclineInviteMenu) },
|
||||
) {
|
||||
RoomListDeclineInviteMenuContent(
|
||||
roomName = menu.roomSummary.name ?: menu.roomSummary.roomId.value,
|
||||
onDeclineClick = {
|
||||
eventSink(RoomListEvents.HideDeclineInviteMenu)
|
||||
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, false))
|
||||
},
|
||||
onDeclineAndBlockClick = {
|
||||
eventSink(RoomListEvents.HideDeclineInviteMenu)
|
||||
if (canReportRoom) {
|
||||
onDeclineAndBlockClick(menu.roomSummary)
|
||||
} else {
|
||||
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, true))
|
||||
}
|
||||
},
|
||||
onCancelClick = {
|
||||
eventSink(RoomListEvents.HideDeclineInviteMenu)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomListDeclineInviteMenuContent(
|
||||
roomName: String,
|
||||
onDeclineClick: () -> Unit,
|
||||
onDeclineAndBlockClick: () -> Unit,
|
||||
onCancelClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_invites_decline_chat_title),
|
||||
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.screen_invites_decline_chat_message, roomName),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(Modifier.height(22.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onDeclineClick,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline_and_block),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
destructive = true,
|
||||
onClick = onDeclineAndBlockClick
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_cancel),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onCancelClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This component should be seen in [RoomListView] @Preview but it doesn't show up.
|
||||
// see: https://issuetracker.google.com/issues/283843380
|
||||
// Remove this preview when the issue is fixed.
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomListDeclineInviteMenuContentPreview() = ElementPreview {
|
||||
RoomListDeclineInviteMenuContent(
|
||||
roomName = "Room name",
|
||||
onCancelClick = {},
|
||||
onDeclineClick = {},
|
||||
onDeclineAndBlockClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,9 +15,12 @@ sealed interface RoomListEvents {
|
|||
data object DismissRequestVerificationPrompt : RoomListEvents
|
||||
data object DismissBanner : RoomListEvents
|
||||
data object ToggleSearchResults : RoomListEvents
|
||||
data class AcceptInvite(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data class DeclineInvite(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
|
||||
data class AcceptInvite(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data class DeclineInvite(val roomSummary: RoomListRoomSummary, val blockUser: Boolean) : RoomListEvents
|
||||
data class ShowDeclineInviteMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data object HideDeclineInviteMenu : RoomListEvents
|
||||
|
||||
sealed interface ContextMenuEvents : RoomListEvents
|
||||
data object HideContextMenu : ContextMenuEvents
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright 2023, 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.roomlist.impl
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
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.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
|
||||
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.deeplink.usecase.InviteFriendsUseCase
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class RoomListFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: RoomListPresenter,
|
||||
private val inviteFriendsUseCase: InviteFriendsUseCase,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
||||
) : BaseFlowNode<RoomListFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.Home))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Root : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class ReportRoom(val roomId: RoomId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class DeclineInviteAndBlockUser(val inviteData: InviteData) : NavTarget
|
||||
}
|
||||
|
||||
private fun onRoomClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onOpenSettings() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
||||
}
|
||||
|
||||
private fun onCreateRoomClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
|
||||
}
|
||||
|
||||
private fun onSetUpRecoveryClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSetUpRecoveryClick() }
|
||||
}
|
||||
|
||||
private fun onSessionConfirmRecoveryKeyClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSessionConfirmRecoveryKeyClick() }
|
||||
}
|
||||
|
||||
private fun onRoomSettingsClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomSettingsClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onReportRoomClick(roomId: RoomId) {
|
||||
backstack.push(NavTarget.ReportRoom(roomId))
|
||||
}
|
||||
|
||||
private fun onDeclineInviteAndBlockUserClick(roomSummary: RoomListRoomSummary) {
|
||||
backstack.push(NavTarget.DeclineInviteAndBlockUser(roomSummary.toInviteData()))
|
||||
}
|
||||
|
||||
private fun onMenuActionClick(activity: Activity, roomListMenuAction: RoomListMenuAction) {
|
||||
when (roomListMenuAction) {
|
||||
RoomListMenuAction.InviteFriends -> {
|
||||
inviteFriendsUseCase.execute(activity)
|
||||
}
|
||||
RoomListMenuAction.ReportBug -> {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onReportBugClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun rootNode(buildContext: BuildContext): Node {
|
||||
return node(buildContext) { modifier ->
|
||||
val state = presenter.present()
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
|
||||
RoomListView(
|
||||
state = state,
|
||||
onRoomClick = this::onRoomClick,
|
||||
onSettingsClick = this::onOpenSettings,
|
||||
onCreateRoomClick = this::onCreateRoomClick,
|
||||
onSetUpRecoveryClick = this::onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
||||
onRoomSettingsClick = this::onRoomSettingsClick,
|
||||
onMenuActionClick = { onMenuActionClick(activity, it) },
|
||||
onReportRoomClick = this::onReportRoomClick,
|
||||
onDeclineInviteAndBlockUser = this::onDeclineInviteAndBlockUserClick,
|
||||
modifier = modifier,
|
||||
) {
|
||||
acceptDeclineInviteView.Render(
|
||||
state = state.acceptDeclineInviteState,
|
||||
onAcceptInviteSuccess = this::onRoomClick,
|
||||
onDeclineInviteSuccess = { },
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
directLogoutView.Render(state.directLogoutState)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView()
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.ReportRoom -> reportRoomEntryPoint.createNode(this, buildContext, navTarget.roomId)
|
||||
is NavTarget.DeclineInviteAndBlockUser -> declineInviteAndBlockUserEntryPoint.createNode(this, buildContext, navTarget.inviteData)
|
||||
NavTarget.Root -> rootNode(buildContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 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.roomlist.impl
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
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.invite.api.response.AcceptDeclineInviteView
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.libraries.deeplink.usecase.InviteFriendsUseCase
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class RoomListNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: RoomListPresenter,
|
||||
private val inviteFriendsUseCase: InviteFriendsUseCase,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.Home))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onRoomClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onOpenSettings() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
||||
}
|
||||
|
||||
private fun onCreateRoomClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
|
||||
}
|
||||
|
||||
private fun onSetUpRecoveryClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSetUpRecoveryClick() }
|
||||
}
|
||||
|
||||
private fun onSessionConfirmRecoveryKeyClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSessionConfirmRecoveryKeyClick() }
|
||||
}
|
||||
|
||||
private fun onRoomSettingsClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomSettingsClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onMenuActionClick(activity: Activity, roomListMenuAction: RoomListMenuAction) {
|
||||
when (roomListMenuAction) {
|
||||
RoomListMenuAction.InviteFriends -> {
|
||||
inviteFriendsUseCase.execute(activity)
|
||||
}
|
||||
RoomListMenuAction.ReportBug -> {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onReportBugClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
|
||||
RoomListView(
|
||||
state = state,
|
||||
onRoomClick = this::onRoomClick,
|
||||
onSettingsClick = this::onOpenSettings,
|
||||
onCreateRoomClick = this::onCreateRoomClick,
|
||||
onSetUpRecoveryClick = this::onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
||||
onRoomSettingsClick = this::onRoomSettingsClick,
|
||||
onMenuActionClick = { onMenuActionClick(activity, it) },
|
||||
modifier = modifier,
|
||||
) {
|
||||
acceptDeclineInviteView.Render(
|
||||
state = state.acceptDeclineInviteState,
|
||||
onAcceptInvite = this::onRoomClick,
|
||||
onDeclineInvite = { },
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
directLogoutView.Render(state.directLogoutState)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
|
@ -24,17 +23,17 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.appconfig.MatrixConfiguration
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.DeclineInvite
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent.ShowConfirmation
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
|
@ -124,6 +123,7 @@ class RoomListPresenter @Inject constructor(
|
|||
}.collectAsState(initial = false)
|
||||
|
||||
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }
|
||||
|
||||
val directLogoutState = logoutPresenter.present()
|
||||
|
||||
|
|
@ -141,20 +141,22 @@ class RoomListPresenter @Inject constructor(
|
|||
is RoomListEvents.HideContextMenu -> {
|
||||
contextMenu.value = RoomListState.ContextMenu.Hidden
|
||||
}
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(ShowConfirmation(event.roomId))
|
||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
|
||||
is RoomListEvents.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
|
||||
is RoomListEvents.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
|
||||
is RoomListEvents.AcceptInvite -> {
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(event.roomListRoomSummary.toInviteData())
|
||||
AcceptInvite(event.roomSummary.toInviteData())
|
||||
)
|
||||
}
|
||||
is RoomListEvents.DeclineInvite -> {
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(event.roomListRoomSummary.toInviteData())
|
||||
DeclineInvite(event.roomSummary.toInviteData(), blockUser = event.blockUser, shouldConfirm = false)
|
||||
)
|
||||
}
|
||||
is RoomListEvents.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary)
|
||||
RoomListEvents.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden
|
||||
is RoomListEvents.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId)
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +171,7 @@ class RoomListPresenter @Inject constructor(
|
|||
snackbarMessage = snackbarMessage,
|
||||
hasNetworkConnection = isOnline,
|
||||
contextMenu = contextMenu.value,
|
||||
declineInviteMenu = declineInviteMenu.value,
|
||||
leaveRoomState = leaveRoomState,
|
||||
filtersState = filtersState,
|
||||
canReportBug = canReportBug,
|
||||
|
|
@ -177,6 +180,7 @@ class RoomListPresenter @Inject constructor(
|
|||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
directLogoutState = directLogoutState,
|
||||
hideInvitesAvatars = hideInvitesAvatar,
|
||||
canReportRoom = MatrixConfiguration.CAN_REPORT_ROOM,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
@ -253,18 +257,18 @@ class RoomListPresenter @Inject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch {
|
||||
val initialState = RoomListState.ContextMenu.Shown(
|
||||
roomId = event.roomListRoomSummary.roomId,
|
||||
roomName = event.roomListRoomSummary.name,
|
||||
isDm = event.roomListRoomSummary.isDm,
|
||||
isFavorite = event.roomListRoomSummary.isFavorite,
|
||||
roomId = event.roomSummary.roomId,
|
||||
roomName = event.roomSummary.name,
|
||||
isDm = event.roomSummary.isDm,
|
||||
isFavorite = event.roomSummary.isFavorite,
|
||||
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
||||
hasNewContent = event.roomListRoomSummary.hasNewContent,
|
||||
hasNewContent = event.roomSummary.hasNewContent,
|
||||
eventCacheFeatureFlagEnabled = appPreferencesStore.isDeveloperModeEnabledFlow().first() &&
|
||||
featureFlagService.isFeatureEnabled(FeatureFlags.EventCache),
|
||||
)
|
||||
contextMenuState.value = initialState
|
||||
|
||||
client.getRoom(event.roomListRoomSummary.roomId)?.use { room ->
|
||||
client.getRoom(event.roomSummary.roomId)?.use { room ->
|
||||
|
||||
val isShowingContextMenuFlow = snapshotFlow { contextMenuState.value is RoomListState.ContextMenu.Shown }
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -342,14 +346,3 @@ class RoomListPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun RoomListRoomSummary.toInviteData(): InviteData? {
|
||||
if (inviteSender == null) return null
|
||||
return InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = isDm,
|
||||
senderId = inviteSender.userId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
|
|
@ -28,6 +28,7 @@ data class RoomListState(
|
|||
val hasNetworkConnection: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val contextMenu: ContextMenu,
|
||||
val declineInviteMenu: DeclineInviteMenu,
|
||||
val leaveRoomState: LeaveRoomState,
|
||||
val filtersState: RoomListFiltersState,
|
||||
val canReportBug: Boolean,
|
||||
|
|
@ -36,6 +37,7 @@ data class RoomListState(
|
|||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val hideInvitesAvatars: Boolean,
|
||||
val canReportRoom: Boolean,
|
||||
val eventSink: (RoomListEvents) -> Unit,
|
||||
) {
|
||||
val displayFilters = contentState is RoomListContentState.Rooms
|
||||
|
|
@ -53,6 +55,11 @@ data class RoomListState(
|
|||
val hasNewContent: Boolean,
|
||||
) : ContextMenu
|
||||
}
|
||||
|
||||
sealed interface DeclineInviteMenu {
|
||||
data object Hidden : DeclineInviteMenu
|
||||
data class Shown(val roomSummary: RoomListRoomSummary) : DeclineInviteMenu
|
||||
}
|
||||
}
|
||||
|
||||
enum class SecurityBannerState {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
|
|
@ -54,6 +54,7 @@ internal fun aRoomListState(
|
|||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
contextMenu: RoomListState.ContextMenu = RoomListState.ContextMenu.Hidden,
|
||||
declineInviteMenu: RoomListState.DeclineInviteMenu = RoomListState.DeclineInviteMenu.Hidden,
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
searchState: RoomListSearchState = aRoomListSearchState(),
|
||||
filtersState: RoomListFiltersState = aRoomListFiltersState(),
|
||||
|
|
@ -62,6 +63,7 @@ internal fun aRoomListState(
|
|||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
directLogoutState: DirectLogoutState = aDirectLogoutState(),
|
||||
hideInvitesAvatars: Boolean = false,
|
||||
canReportRoom: Boolean = true,
|
||||
eventSink: (RoomListEvents) -> Unit = {}
|
||||
) = RoomListState(
|
||||
matrixUser = matrixUser,
|
||||
|
|
@ -69,6 +71,7 @@ internal fun aRoomListState(
|
|||
hasNetworkConnection = hasNetworkConnection,
|
||||
snackbarMessage = snackbarMessage,
|
||||
contextMenu = contextMenu,
|
||||
declineInviteMenu = declineInviteMenu,
|
||||
leaveRoomState = leaveRoomState,
|
||||
filtersState = filtersState,
|
||||
canReportBug = canReportBug,
|
||||
|
|
@ -77,6 +80,7 @@ internal fun aRoomListState(
|
|||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
directLogoutState = directLogoutState,
|
||||
hideInvitesAvatars = hideInvitesAvatars,
|
||||
canReportRoom = canReportRoom,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ fun RoomListView(
|
|||
onCreateRoomClick: () -> Unit,
|
||||
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
||||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||
onReportRoomClick: (roomId: RoomId) -> Unit,
|
||||
onDeclineInviteAndBlockUser: (roomSummary: RoomListRoomSummary) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
acceptDeclineInviteView: @Composable () -> Unit,
|
||||
) {
|
||||
|
|
@ -60,8 +62,18 @@ fun RoomListView(
|
|||
if (state.contextMenu is RoomListState.ContextMenu.Shown) {
|
||||
RoomListContextMenu(
|
||||
contextMenu = state.contextMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
onRoomSettingsClick = onRoomSettingsClick,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
)
|
||||
}
|
||||
if (state.declineInviteMenu is RoomListState.DeclineInviteMenu.Shown) {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = state.declineInviteMenu,
|
||||
canReportRoom = state.canReportRoom,
|
||||
eventSink = state.eventSink,
|
||||
onDeclineAndBlockClick = onDeclineInviteAndBlockUser,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +189,9 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class)
|
|||
onConfirmRecoveryKeyClick = {},
|
||||
onCreateRoomClick = {},
|
||||
onRoomSettingsClick = {},
|
||||
onReportRoomClick = {},
|
||||
onMenuActionClick = {},
|
||||
onDeclineInviteAndBlockUser = {},
|
||||
acceptDeclineInviteView = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ internal fun RoomSummaryRow(
|
|||
eventSink(RoomListEvents.AcceptInvite(room))
|
||||
},
|
||||
onDeclineClick = {
|
||||
eventSink(RoomListEvents.DeclineInvite(room))
|
||||
eventSink(RoomListEvents.ShowDeclineInviteMenu(room))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.roomlist.impl.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -45,4 +46,10 @@ data class RoomListRoomSummary(
|
|||
numberOfUnreadMentions > 0 ||
|
||||
numberOfUnreadNotifications > 0 ||
|
||||
isMarkedUnread
|
||||
|
||||
fun toInviteData() = InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = isDm,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@ class RoomListContextMenuTest {
|
|||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_roomlist_mark_as_read)
|
||||
|
|
@ -51,8 +53,10 @@ class RoomListContextMenuTest {
|
|||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_roomlist_mark_as_unread)
|
||||
|
|
@ -64,26 +68,6 @@ class RoomListContextMenuTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Leave dm generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(isDm = true)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_leave_conversation)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideContextMenu,
|
||||
RoomListEvents.LeaveRoom(contextMenu.roomId),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Leave room generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
|
|
@ -91,8 +75,10 @@ class RoomListContextMenuTest {
|
|||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_leave_room)
|
||||
|
|
@ -104,6 +90,25 @@ class RoomListContextMenuTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Report room invokes the expected callback and generates expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown()
|
||||
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = true,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick = callback,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_report_room)
|
||||
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
|
||||
callback.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Settings invokes the expected callback and generates expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
|
|
@ -112,8 +117,10 @@ class RoomListContextMenuTest {
|
|||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = callback,
|
||||
onReportRoomClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.common_settings)
|
||||
|
|
@ -129,8 +136,10 @@ class RoomListContextMenuTest {
|
|||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClick = callback,
|
||||
onReportRoomClick = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.common_favourite)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.roomlist.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RoomListDeclineInviteMenuTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on decline emits the expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||
rule.setContent {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = menu,
|
||||
canReportRoom = false,
|
||||
onDeclineAndBlockClick = EnsureNeverCalledWithParam(),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_decline)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideDeclineInviteMenu,
|
||||
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||
rule.setContent {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = menu,
|
||||
canReportRoom = true,
|
||||
onDeclineAndBlockClick = EnsureCalledOnceWithParam(menu.roomSummary, Unit),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_decline_and_block)
|
||||
val expectedEvents = listOf(RoomListEvents.HideDeclineInviteMenu)
|
||||
eventsRecorder.assertList(expectedEvents)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||
rule.setContent {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = menu,
|
||||
canReportRoom = false,
|
||||
onDeclineAndBlockClick = EnsureNeverCalledWithParam(),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_decline_and_block)
|
||||
val expectedEvents = listOf(
|
||||
RoomListEvents.HideDeclineInviteMenu,
|
||||
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = true),
|
||||
)
|
||||
eventsRecorder.assertList(expectedEvents)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on cancel emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||
rule.setContent {
|
||||
RoomListDeclineInviteMenu(
|
||||
menu = menu,
|
||||
canReportRoom = false,
|
||||
onDeclineAndBlockClick = EnsureNeverCalledWithParam(),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertList(listOf(RoomListEvents.HideDeclineInviteMenu))
|
||||
}
|
||||
}
|
||||
|
|
@ -13,9 +13,9 @@ import app.cash.turbine.test
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
|
|
@ -66,6 +66,7 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
|||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
|
|
@ -577,7 +578,8 @@ class RoomListPresenterTest {
|
|||
roomListService = roomListService,
|
||||
)
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED
|
||||
currentUserMembership = CurrentUserMembership.INVITED,
|
||||
inviter = aRoomMember(),
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
|
|
@ -593,16 +595,16 @@ class RoomListPresenterTest {
|
|||
val roomListRoomSummary = state.contentAsRooms().summaries.first {
|
||||
it.id == roomSummary.roomId.value
|
||||
}
|
||||
|
||||
state.eventSink(RoomListEvents.AcceptInvite(roomListRoomSummary))
|
||||
state.eventSink(RoomListEvents.DeclineInvite(roomListRoomSummary))
|
||||
state.eventSink(RoomListEvents.DeclineInvite(roomListRoomSummary, blockUser = false))
|
||||
|
||||
val inviteData = roomListRoomSummary.toInviteData()
|
||||
|
||||
assert(eventSinkRecorder)
|
||||
.isCalledExactly(2)
|
||||
.withSequence(
|
||||
listOf(value(AcceptDeclineInviteEvents.AcceptInvite(inviteData))),
|
||||
listOf(value(AcceptDeclineInviteEvents.DeclineInvite(inviteData))),
|
||||
listOf(value(AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.test.performClick
|
|||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -226,7 +227,10 @@ class RoomListViewTest {
|
|||
rule.clickOn(CommonStrings.action_accept)
|
||||
rule.clickOn(CommonStrings.action_decline)
|
||||
eventsRecorder.assertList(
|
||||
listOf(RoomListEvents.AcceptInvite(invitedRoom), RoomListEvents.DeclineInvite(invitedRoom)),
|
||||
listOf(
|
||||
RoomListEvents.AcceptInvite(invitedRoom),
|
||||
RoomListEvents.ShowDeclineInviteMenu(invitedRoom),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +244,8 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
|
|||
onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onMenuActionClick: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onDeclineInviteAndBlockUser: (RoomListRoomSummary) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
setContent {
|
||||
RoomListView(
|
||||
|
|
@ -251,6 +257,8 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
|
|||
onCreateRoomClick = onCreateRoomClick,
|
||||
onRoomSettingsClick = onRoomSettingsClick,
|
||||
onMenuActionClick = onMenuActionClick,
|
||||
onDeclineInviteAndBlockUser = onDeclineInviteAndBlockUser,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
acceptDeclineInviteView = { },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue