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:
committed by
GitHub
parent
52a94f79eb
commit
e9f065c479
@@ -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) {
|
||||
|
||||
@@ -46,9 +46,13 @@ import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toThreadId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
@@ -57,6 +61,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.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
@@ -67,6 +72,7 @@ import io.element.android.libraries.matrix.test.A_CAPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
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.core.aBuildMeta
|
||||
@@ -1158,6 +1164,74 @@ class MessagesPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply in thread for an event in a thread`() = runTest {
|
||||
val openThreadLambda = lambdaRecorder { _: ThreadId, _: EventId? -> }
|
||||
val presenter = createMessagesPresenter(
|
||||
navigator = FakeMessagesNavigator(onOpenThreadLambda = openThreadLambda),
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.Threads.key to true)
|
||||
),
|
||||
)
|
||||
presenter.testWithLifecycleOwner {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(
|
||||
action = TimelineItemAction.ReplyInThread,
|
||||
event = aMessageEvent(threadInfo = EventThreadInfo(A_THREAD_ID, null))
|
||||
))
|
||||
awaitItem()
|
||||
openThreadLambda.assertions().isCalledOnce().with(value(A_THREAD_ID), value(null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply in thread to start a new thread`() = runTest {
|
||||
val openThreadLambda = lambdaRecorder { _: ThreadId, _: EventId? -> }
|
||||
val presenter = createMessagesPresenter(
|
||||
navigator = FakeMessagesNavigator(onOpenThreadLambda = openThreadLambda),
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.Threads.key to true)
|
||||
),
|
||||
)
|
||||
presenter.testWithLifecycleOwner {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(
|
||||
action = TimelineItemAction.ReplyInThread,
|
||||
event = aMessageEvent(
|
||||
// The event id will be used as the thread id instead
|
||||
eventId = AN_EVENT_ID,
|
||||
threadInfo = EventThreadInfo(null, null),
|
||||
)
|
||||
))
|
||||
awaitItem()
|
||||
openThreadLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID.toThreadId()), value(null))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply in a thread with threads disabled`() = runTest {
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.Threads.key to false)
|
||||
),
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
presenter.testWithLifecycleOwner {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent()))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createMessagesPresenter(
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
joinedRoom: FakeJoinedRoom = FakeJoinedRoom(
|
||||
@@ -1189,6 +1263,7 @@ class MessagesPresenterTest {
|
||||
aRoomMemberModerationState()
|
||||
},
|
||||
encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||
actionListEventSink: (ActionListEvents) -> Unit = {},
|
||||
): MessagesPresenter {
|
||||
return MessagesPresenter(
|
||||
@@ -1217,6 +1292,7 @@ class MessagesPresenterTest {
|
||||
permalinkParser = permalinkParser,
|
||||
encryptionService = encryptionService,
|
||||
analyticsService = analyticsService,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
@@ -35,6 +37,7 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_CAPTION
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.A_TRANSACTION_ID
|
||||
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.aRoomInfo
|
||||
@@ -1245,8 +1248,12 @@ class ActionListPresenterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for threaded timeline`() = runTest {
|
||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, timelineMode = Timeline.Mode.Thread(A_THREAD_ID))
|
||||
fun `present - compute for threaded timeline with threads enabled`() = runTest {
|
||||
val presenter = createActionListPresenter(
|
||||
isDeveloperModeEnabled = false,
|
||||
timelineMode = Timeline.Mode.Thread(A_THREAD_ID),
|
||||
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -1290,12 +1297,171 @@ class ActionListPresenterTest {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for remote timeline item with threads enabled`() = runTest {
|
||||
val presenter = createActionListPresenter(
|
||||
isDeveloperModeEnabled = false,
|
||||
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
eventId = AN_EVENT_ID,
|
||||
isMine = true,
|
||||
isEditable = false,
|
||||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(messageEvent.isRemote).isTrue()
|
||||
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = messageEvent,
|
||||
userEventPermissions = aUserEventPermissions(
|
||||
canRedactOwn = true,
|
||||
canRedactOther = false,
|
||||
canSendMessage = true,
|
||||
canSendReaction = true,
|
||||
canPinUnpin = true
|
||||
)
|
||||
)
|
||||
)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
event = messageEvent,
|
||||
sentTimeFull = "0 Full true",
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for remote timeline item already in thread with threads enabled`() = runTest {
|
||||
val presenter = createActionListPresenter(
|
||||
isDeveloperModeEnabled = false,
|
||||
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
eventId = AN_EVENT_ID,
|
||||
isMine = true,
|
||||
isEditable = false,
|
||||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
threadInfo = EventThreadInfo(A_THREAD_ID, null),
|
||||
)
|
||||
|
||||
assertThat(messageEvent.isRemote).isTrue()
|
||||
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = messageEvent,
|
||||
userEventPermissions = aUserEventPermissions(
|
||||
canRedactOwn = true,
|
||||
canRedactOther = false,
|
||||
canSendMessage = true,
|
||||
canSendReaction = true,
|
||||
canPinUnpin = true
|
||||
)
|
||||
)
|
||||
)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
event = messageEvent,
|
||||
sentTimeFull = "0 Full true",
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for local timeline item with threads enabled`() = runTest {
|
||||
val presenter = createActionListPresenter(
|
||||
isDeveloperModeEnabled = false,
|
||||
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
eventId = null,
|
||||
transactionId = A_TRANSACTION_ID,
|
||||
isMine = true,
|
||||
isEditable = false,
|
||||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(messageEvent.isRemote).isFalse()
|
||||
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = messageEvent,
|
||||
userEventPermissions = aUserEventPermissions(
|
||||
canRedactOwn = true,
|
||||
canRedactOther = false,
|
||||
canSendMessage = true,
|
||||
canSendReaction = true,
|
||||
canPinUnpin = true
|
||||
)
|
||||
)
|
||||
)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
event = messageEvent,
|
||||
sentTimeFull = "0 Full true",
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
// Can't reply in thread for local events
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActionListPresenter(
|
||||
isDeveloperModeEnabled: Boolean,
|
||||
room: BaseRoom = FakeBaseRoom(),
|
||||
timelineMode: Timeline.Mode = Timeline.Mode.Live,
|
||||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||
): ActionListPresenter {
|
||||
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
|
||||
return DefaultActionListPresenter(
|
||||
@@ -1305,5 +1471,6 @@ private fun createActionListPresenter(
|
||||
userSendFailureFactory = VerifiedUserSendFailureFactory(room),
|
||||
dateFormatter = FakeDateFormatter(),
|
||||
timelineMode = timelineMode,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ enum class FeatureFlags(
|
||||
// False so it's displayed in the developer options screen
|
||||
isFinished = false,
|
||||
),
|
||||
HideThreadedEvents(
|
||||
Threads(
|
||||
key = "feature.thread_timeline",
|
||||
title = "Threads",
|
||||
description = "Renders thread messages as a dedicated timeline. Restarting the app is required for this setting to fully take effect.",
|
||||
|
||||
@@ -132,7 +132,7 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
)
|
||||
)
|
||||
.enableShareHistoryOnInvite(featureFlagService.isFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite))
|
||||
.threadsEnabled(featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents), threadSubscriptions = false)
|
||||
.threadsEnabled(featureFlagService.isFeatureEnabled(FeatureFlags.Threads), threadSubscriptions = false)
|
||||
.run {
|
||||
// Apply sliding sync version settings
|
||||
when (slidingSyncType) {
|
||||
|
||||
@@ -156,7 +156,7 @@ class JoinedRustRoom(
|
||||
override suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline> = withContext(roomDispatcher) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents)
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
val focus = when (createTimelineParams) {
|
||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents(
|
||||
maxEventsToLoad = 100u,
|
||||
|
||||
@@ -108,7 +108,7 @@ class RustRoomFactory(
|
||||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withContext null
|
||||
|
||||
if (sdkRoom.membership() == Membership.JOINED) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents)
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
// Init the live timeline in the SDK from the Room
|
||||
val timeline = sdkRoom.timelineWithConfiguration(
|
||||
TimelineConfiguration(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user