Allow replying to any remote message in a thread (#5201)
* Allow replying to any remote message in a thread. This will open the thread screen based on the selected event: - If it was already part of a thread, it will open that thread. - Otherwise, it'll open the thread timeline screen so you can start a thread from the event. * Add the feature flag to decide which action to perform. Also, rename the feature flag to something easier to understand. * Display the reply in thread action based on the feature flag too --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
d97b2ab79c
commit
7a5a197e7e
12 changed files with 290 additions and 17 deletions
|
|
@ -63,6 +63,9 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.toThreadId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
|
|
@ -115,6 +118,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
private val permalinkParser: PermalinkParser,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val encryptionService: EncryptionService,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<MessagesState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
|
@ -318,8 +322,17 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
|
||||
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState)
|
||||
TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent)
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState, timelineProtectionState)
|
||||
TimelineItemAction.Reply -> handleActionReply(targetEvent, composerState, timelineProtectionState)
|
||||
TimelineItemAction.ReplyInThread -> {
|
||||
val displayThreads = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
if (displayThreads) {
|
||||
// Get either the thread id this event is in, or the event id if it's not in a thread so we can start one
|
||||
val threadId = targetEvent.threadInfo.threadRootId ?: targetEvent.eventId!!.toThreadId()
|
||||
navigator.onOpenThread(threadId, null)
|
||||
} else {
|
||||
handleActionReply(targetEvent, composerState, timelineProtectionState)
|
||||
}
|
||||
}
|
||||
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
|
||||
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
|
||||
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import io.element.android.libraries.architecture.Presenter
|
|||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
|
|
@ -68,6 +70,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
private val room: BaseRoom,
|
||||
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
|
||||
private val dateFormatter: DateFormatter,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : ActionListPresenter {
|
||||
@AssistedFactory
|
||||
@ContributesBinding(RoomScope::class)
|
||||
|
|
@ -95,6 +98,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
room.roomInfoFlow.map { it.pinnedEventIds }
|
||||
}.collectAsState(initial = persistentListOf())
|
||||
|
||||
val isThreadsEnabled = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
|
||||
|
||||
fun handleEvents(event: ActionListEvents) {
|
||||
when (event) {
|
||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||
|
|
@ -104,6 +109,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||
pinnedEventIds = pinnedEventIds,
|
||||
target = target,
|
||||
isThreadsEnabled = isThreadsEnabled.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +125,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
usersEventPermissions: UserEventPermissions,
|
||||
isDeveloperModeEnabled: Boolean,
|
||||
pinnedEventIds: ImmutableList<EventId>,
|
||||
target: MutableState<ActionListState.Target>
|
||||
target: MutableState<ActionListState.Target>,
|
||||
isThreadsEnabled: Boolean,
|
||||
) = launch {
|
||||
target.value = ActionListState.Target.Loading(timelineItem)
|
||||
|
||||
|
|
@ -128,6 +135,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
usersEventPermissions = usersEventPermissions,
|
||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||
isEventPinned = pinnedEventIds.contains(timelineItem.eventId),
|
||||
isThreadsEnabled = isThreadsEnabled,
|
||||
)
|
||||
|
||||
val verifiedUserSendFailure = userSendFailureFactory.create(timelineItem.localSendState)
|
||||
|
|
@ -155,14 +163,23 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
|||
usersEventPermissions: UserEventPermissions,
|
||||
isDeveloperModeEnabled: Boolean,
|
||||
isEventPinned: Boolean,
|
||||
isThreadsEnabled: Boolean,
|
||||
): List<TimelineItemAction> {
|
||||
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
||||
return buildSet {
|
||||
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
||||
if (timelineMode !is Timeline.Mode.Thread && timelineItem.threadInfo.threadRootId != null) {
|
||||
if (isThreadsEnabled && timelineMode !is Timeline.Mode.Thread && timelineItem.isRemote) {
|
||||
// If threads are enabled, we can reply in thread if the item is remote
|
||||
add(TimelineItemAction.ReplyInThread)
|
||||
} else {
|
||||
add(TimelineItemAction.Reply)
|
||||
} else {
|
||||
if (!isThreadsEnabled && timelineItem.threadInfo.threadRootId != null) {
|
||||
// If threads are not enabled, we can reply in a thread if the item is already in the thread
|
||||
add(TimelineItemAction.ReplyInThread)
|
||||
} else {
|
||||
// Otherwise, we can only reply in the room
|
||||
add(TimelineItemAction.Reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timelineItem.isRemote && timelineItem.content.canBeForwarded()) {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
|||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
|
||||
|
||||
val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.HideThreadedEvents).collectAsState(false)
|
||||
val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
|
||||
|
||||
var pinnedMessageItems by remember {
|
||||
mutableStateOf<AsyncData<ImmutableList<TimelineItem>>>(AsyncData.Uninitialized)
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
}.collectAsState(initial = true)
|
||||
|
||||
val displayThreadSummaries by produceState(false) {
|
||||
value = featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents)
|
||||
value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
}
|
||||
|
||||
fun handleEvents(event: TimelineEvents) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue