From a10aa6cbe36da52fb05bfbbb2df19af2afd8dad1 Mon Sep 17 00:00:00 2001 From: bxdxnn <267911624+bxdxnn@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:26:46 +0000 Subject: [PATCH 1/3] Support thread root in drafts --- .../android/features/messages/impl/MessagesNode.kt | 2 +- .../impl/messagecomposer/MessageComposerPresenter.kt | 12 ++++++------ .../messages/impl/threads/ThreadedMessagesNode.kt | 2 +- .../MessageComposerPresenterSlashCommandTest.kt | 3 +++ .../messagecomposer/MessageComposerPresenterTest.kt | 2 ++ 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index a9ce2f5ba1..ef0e7100e2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -107,7 +107,7 @@ class MessagesNode( private val timelineController = TimelineController(room, room.liveTimeline) private val presenter = presenterFactory.create( navigator = this, - composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = false), + composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = false, threadRoot = null), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), actionListPresenter = actionListPresenterFactory.create( postProcessor = TimelineItemActionPostProcessor.Default, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index e226318345..9e9fc15eec 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.core.EventId +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.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkParser @@ -110,6 +111,7 @@ class MessageComposerPresenter( @Assisted private val navigator: MessagesNavigator, @Assisted private val timelineController: TimelineController, @Assisted private val isInThread: Boolean, + @Assisted private val threadRoot: ThreadId?, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val room: JoinedRoom, private val mediaPickerProvider: PickerProvider, @@ -139,6 +141,7 @@ class MessageComposerPresenter( timelineController: TimelineController, navigator: MessagesNavigator, isInThread: Boolean, + threadRoot: ThreadId?, ): MessageComposerPresenter } @@ -234,8 +237,7 @@ class MessageComposerPresenter( LaunchedEffect(Unit) { val draft = draftService.loadDraft( roomId = room.roomId, - // TODO support threads in composer - threadRoot = null, + threadRoot = threadRoot, isVolatile = false ) if (draft != null) { @@ -652,8 +654,7 @@ class MessageComposerPresenter( roomId = room.roomId, draft = draft, isVolatile = isVolatile, - // TODO support threads in composer - threadRoot = null, + threadRoot = threadRoot, ) } @@ -816,8 +817,7 @@ class MessageComposerPresenter( // Use the volatile draft only when coming from edit mode otherwise. val draft = draftService.loadDraft( roomId = room.roomId, - // TODO support threads in composer - threadRoot = null, + threadRoot = threadRoot, isVolatile = true ).takeIf { fromEdit } if (draft != null) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index be573fa92f..ade22eed41 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -112,7 +112,7 @@ class ThreadedMessagesNode( this.timelineController = timelineController return presenterFactory.create( navigator = this, - composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = true), + composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = true, threadRoot = inputs.threadRootEventId), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), // TODO add special processor for threaded timeline actionListPresenter = actionListPresenterFactory.create( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt index 116a1cfb5d..2a0ba9ff1b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt @@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +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.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkParser @@ -272,11 +273,13 @@ class MessageComposerPresenterSlashCommandTest { draftService: ComposerDraftService = FakeComposerDraftService(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), isInThread: Boolean = false, + threadRoot: ThreadId? = null, slashCommandService: SlashCommandService = FakeSlashCommandService(), ) = MessageComposerPresenter( navigator = navigator, sessionCoroutineScope = this, isInThread = isInThread, + threadRoot = threadRoot, room = room, mediaPickerProvider = pickerProvider, sessionPreferencesStore = sessionPreferencesStore, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index e8d106a80f..ab9886a368 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -1535,11 +1535,13 @@ class MessageComposerPresenterTest { draftService: ComposerDraftService = FakeComposerDraftService(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), isInThread: Boolean = false, + threadRoot: ThreadId? = null, slashCommandService: SlashCommandService = FakeSlashCommandService(), ) = MessageComposerPresenter( navigator = navigator, sessionCoroutineScope = this, isInThread = isInThread, + threadRoot = threadRoot, room = room, mediaPickerProvider = pickerProvider, sessionPreferencesStore = sessionPreferencesStore, From 8b4158c8245fc72c2320398684570291a96a0c4d Mon Sep 17 00:00:00 2001 From: bxdxnn <267911624+bxdxnn@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:17:45 +0000 Subject: [PATCH 2/3] Address review --- .../io/element/android/features/messages/impl/MessagesNode.kt | 2 +- .../messages/impl/messagecomposer/MessageComposerPresenter.kt | 3 +-- .../features/messages/impl/threads/ThreadedMessagesNode.kt | 2 +- .../MessageComposerPresenterSlashCommandTest.kt | 2 -- .../impl/messagecomposer/MessageComposerPresenterTest.kt | 2 -- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index ef0e7100e2..ff039da1b4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -107,7 +107,7 @@ class MessagesNode( private val timelineController = TimelineController(room, room.liveTimeline) private val presenter = presenterFactory.create( navigator = this, - composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = false, threadRoot = null), + composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), actionListPresenter = actionListPresenterFactory.create( postProcessor = TimelineItemActionPostProcessor.Default, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 9e9fc15eec..3526a6fda1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -110,7 +110,6 @@ import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes class MessageComposerPresenter( @Assisted private val navigator: MessagesNavigator, @Assisted private val timelineController: TimelineController, - @Assisted private val isInThread: Boolean, @Assisted private val threadRoot: ThreadId?, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val room: JoinedRoom, @@ -140,11 +139,11 @@ class MessageComposerPresenter( fun create( timelineController: TimelineController, navigator: MessagesNavigator, - isInThread: Boolean, threadRoot: ThreadId?, ): MessageComposerPresenter } + private val isInThread: Boolean get() = threadRoot != null private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode()) private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index ade22eed41..f26a47ce63 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -112,7 +112,7 @@ class ThreadedMessagesNode( this.timelineController = timelineController return presenterFactory.create( navigator = this, - composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = true, threadRoot = inputs.threadRootEventId), + composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = inputs.threadRootEventId), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), // TODO add special processor for threaded timeline actionListPresenter = actionListPresenterFactory.create( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt index 2a0ba9ff1b..0b3da9647e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterSlashCommandTest.kt @@ -272,13 +272,11 @@ class MessageComposerPresenterSlashCommandTest { isRichTextEditorEnabled: Boolean = true, draftService: ComposerDraftService = FakeComposerDraftService(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), - isInThread: Boolean = false, threadRoot: ThreadId? = null, slashCommandService: SlashCommandService = FakeSlashCommandService(), ) = MessageComposerPresenter( navigator = navigator, sessionCoroutineScope = this, - isInThread = isInThread, threadRoot = threadRoot, room = room, mediaPickerProvider = pickerProvider, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index ab9886a368..9fd0ccfee3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -1534,13 +1534,11 @@ class MessageComposerPresenterTest { isRichTextEditorEnabled: Boolean = true, draftService: ComposerDraftService = FakeComposerDraftService(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), - isInThread: Boolean = false, threadRoot: ThreadId? = null, slashCommandService: SlashCommandService = FakeSlashCommandService(), ) = MessageComposerPresenter( navigator = navigator, sessionCoroutineScope = this, - isInThread = isInThread, threadRoot = threadRoot, room = room, mediaPickerProvider = pickerProvider, From 3bc7da0e01125c99952a90ff97b5b9c35e8de137 Mon Sep 17 00:00:00 2001 From: bxdxnn <267911624+bxdxnn@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:29:58 +0300 Subject: [PATCH 3/3] Apply suggestion from @bmarty Co-authored-by: Benoit Marty --- .../messages/impl/messagecomposer/MessageComposerPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 3526a6fda1..4b9ee28e35 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -143,7 +143,7 @@ class MessageComposerPresenter( ): MessageComposerPresenter } - private val isInThread: Boolean get() = threadRoot != null + private val isInThread = threadRoot != null private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode()) private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)