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,6 +28,7 @@ import io.element.android.features.call.api.ElementCallEntryPoint
|
|||
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||
import io.element.android.features.poll.api.history.PollHistoryEntryPoint
|
||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
||||
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
|
||||
|
|
@ -71,6 +72,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
||||
private val mediaGalleryEntryPoint: MediaGalleryEntryPoint,
|
||||
private val verifySessionEntryPoint: VerifySessionEntryPoint,
|
||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = plugins.filterIsInstance<RoomDetailsEntryPoint.Params>().first().initialElement.toNavTarget(),
|
||||
|
|
@ -127,6 +129,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data class VerifyUser(val userId: UserId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ReportRoom : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
|
|
@ -189,6 +194,10 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||
elementCallEntryPoint.startCall(inputs)
|
||||
}
|
||||
|
||||
override fun openReportRoom() {
|
||||
backstack.push(NavTarget.ReportRoom)
|
||||
}
|
||||
}
|
||||
createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback))
|
||||
}
|
||||
|
|
@ -340,6 +349,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
})
|
||||
.build()
|
||||
}
|
||||
is NavTarget.ReportRoom -> {
|
||||
reportRoomEntryPoint.createNode(this, buildContext, room.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
fun openSecurityAndPrivacy()
|
||||
fun openDmUserProfile(userId: UserId)
|
||||
fun onJoinCall()
|
||||
fun openReportRoom()
|
||||
}
|
||||
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
|
@ -132,6 +133,10 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.openDmUserProfile(userId) }
|
||||
}
|
||||
|
||||
private fun onReportRoomClick() {
|
||||
callbacks.forEach { it.openReportRoom() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -166,6 +171,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
onKnockRequestsClick = ::openKnockRequestsLists,
|
||||
onSecurityAndPrivacyClick = ::openSecurityAndPrivacy,
|
||||
onProfileClick = ::onProfileClick,
|
||||
onReportRoomClick = ::onReportRoomClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.appconfig.MatrixConfiguration
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
|
||||
|
|
@ -88,12 +88,12 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
val joinRule by remember { derivedStateOf { roomInfo.joinRule } }
|
||||
|
||||
val canShowPinnedMessages = isPinnedMessagesFeatureEnabled()
|
||||
var canShowMediaGallery by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
canShowMediaGallery = featureFlagService.isFeatureEnabled(FeatureFlags.MediaGallery)
|
||||
}
|
||||
val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } }
|
||||
|
||||
val canShowMediaGallery by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaGallery)
|
||||
}.collectAsState(false)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
|
||||
if (canShowNotificationSettings.value) {
|
||||
|
|
@ -208,6 +208,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
knockRequestsCount = knockRequestsCount,
|
||||
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
|
||||
hasMemberVerificationViolations = hasMemberVerificationViolations,
|
||||
canReportRoom = MatrixConfiguration.CAN_REPORT_ROOM,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ data class RoomDetailsState(
|
|||
val knockRequestsCount: Int?,
|
||||
val canShowSecurityAndPrivacy: Boolean,
|
||||
val hasMemberVerificationViolations: Boolean,
|
||||
val canReportRoom: Boolean,
|
||||
val eventSink: (RoomDetailsEvent) -> Unit
|
||||
) {
|
||||
val roomBadges = buildList {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ fun aRoomDetailsState(
|
|||
knockRequestsCount: Int? = null,
|
||||
canShowSecurityAndPrivacy: Boolean = true,
|
||||
hasMemberVerificationViolations: Boolean = false,
|
||||
canReportRoom: Boolean = true,
|
||||
eventSink: (RoomDetailsEvent) -> Unit = {},
|
||||
) = RoomDetailsState(
|
||||
roomId = roomId,
|
||||
|
|
@ -146,6 +147,7 @@ fun aRoomDetailsState(
|
|||
knockRequestsCount = knockRequestsCount,
|
||||
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
|
||||
hasMemberVerificationViolations = hasMemberVerificationViolations,
|
||||
canReportRoom = canReportRoom,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ fun RoomDetailsView(
|
|||
onKnockRequestsClick: () -> Unit,
|
||||
onSecurityAndPrivacyClick: () -> Unit,
|
||||
onProfileClick: (UserId) -> Unit,
|
||||
onReportRoomClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
|
@ -255,8 +256,9 @@ fun RoomDetailsView(
|
|||
}
|
||||
|
||||
OtherActionsSection(
|
||||
isDm = state.roomType is RoomDetailsType.Dm,
|
||||
onLeaveRoom = { state.eventSink(RoomDetailsEvent.LeaveRoom) }
|
||||
canReportRoom = state.canReportRoom,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
onLeaveRoomClick = { state.eventSink(RoomDetailsEvent.LeaveRoom) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -669,22 +671,29 @@ private fun MediaGalleryItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun OtherActionsSection(isDm: Boolean, onLeaveRoom: () -> Unit) {
|
||||
private fun OtherActionsSection(
|
||||
canReportRoom: Boolean,
|
||||
onReportRoomClick: () -> Unit,
|
||||
onLeaveRoomClick: () -> Unit,
|
||||
) {
|
||||
PreferenceCategory(showTopDivider = true) {
|
||||
if (canReportRoom) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(stringResource(CommonStrings.action_report_room))
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChatProblem())),
|
||||
style = ListItemStyle.Destructive,
|
||||
onClick = onReportRoomClick,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
val leaveText = stringResource(
|
||||
id = if (isDm) {
|
||||
R.string.screen_room_details_leave_conversation_title
|
||||
} else {
|
||||
R.string.screen_room_details_leave_room_title
|
||||
}
|
||||
)
|
||||
Text(leaveText)
|
||||
Text(stringResource(CommonStrings.action_leave_room))
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave())),
|
||||
style = ListItemStyle.Destructive,
|
||||
onClick = onLeaveRoom,
|
||||
onClick = onLeaveRoomClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -719,5 +728,6 @@ private fun ContentToPreview(state: RoomDetailsState) {
|
|||
onKnockRequestsClick = {},
|
||||
onSecurityAndPrivacyClick = {},
|
||||
onProfileClick = {},
|
||||
onReportRoomClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,6 +283,20 @@ class RoomDetailsViewTest {
|
|||
eventsRecorder.assertSingle(RoomDetailsEvent.LeaveRoom)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1500dp")
|
||||
@Test
|
||||
fun `click on report room invokes expected callback`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setRoomDetailView(
|
||||
state = aRoomDetailsState(
|
||||
eventSink = EventsRecorder(expectEvents = false),
|
||||
),
|
||||
onReportRoomClick = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_report_room)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `click on knock requests invokes expected callback`() {
|
||||
|
|
@ -333,6 +347,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
|
|||
onKnockRequestsClick: () -> Unit = EnsureNeverCalled(),
|
||||
onSecurityAndPrivacyClick: () -> Unit = EnsureNeverCalled(),
|
||||
onProfileClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
RoomDetailsView(
|
||||
|
|
@ -352,6 +367,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
|
|||
onKnockRequestsClick = onKnockRequestsClick,
|
||||
onSecurityAndPrivacyClick = onSecurityAndPrivacyClick,
|
||||
onProfileClick = onProfileClick,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue