Add multi selection to the Forward action (#6190)

* Start message selection from the Forward action

Behind the multi-select developer option, choosing Forward on a message
now starts a selection with that message checked, instead of the
dedicated Select menu action which is removed. Only forwardable
messages can be selected: remote, decrypted, non-redacted events that
aren't state, unsupported, poll or live location items.

The chrome follows the Figma exploration: the room header stays, an X
in the trailing toolbar position ends the selection, rows show only the
checkbox (no highlight, 10pt gap to the bubble), and the composer is
replaced by a single primary "Forward N messages" button. The limits
are now 10 messages into 5 rooms.

Tapping the button forwards the selection in timeline order, never in
the order the messages were tapped. MessageForwardingItem carries
several contents and the forwarding screen sends them one after the
other per room, reporting a single error toast on partial failures.
The selection is kept while the forwarding screen is open so cancelling
it returns to the selection; it is cleared by the flow coordinator once
the messages are sent, or by the X in the timeline.

New plural action_forward_messages lives in Untranslated for now;
action_select and screen_room_selection_count are no longer used.

* Tidy the selection code after review

Reuse ToolbarButton's cancel role for the selection's toolbar item so it
matches the other screens on every OS version, and disable the room
header while selecting so the room details can't cover the selection.

Move the forwardable/selected item helpers next to the existing
nonisolated array extension, drop the unused MessageForwardingItem
initialisers and the single-item makeForwardingItem overload, and
simplify the forwarding loop now that a room either sends or isn't
reachable.

The action bar preview builds its own view model instead of putting
the shared mock into selection mode for every later preview, and a
test locks that Forward keeps forwarding directly in the pinned
timeline.

* Select messages to forward from the pinned timeline too

Forward is offered in the pinned events timeline, so with the flag on
it starts a selection there as well, with the same cancel item in the
toolbar and the same forward button at the bottom. Forwarding from
that sheet already dismisses the whole sheet, so the selection is
discarded with it. Only the media timelines keep forwarding a single
item directly.

* Confirm with a toast when messages are forwarded to several rooms

When a single room is picked the flow opens it, which is confirmation
enough. When several rooms are picked the user stays in the origin room,
so a "Messages forwarded" toast tells them it worked, as in the designs.
The new screen_room_message_forwarded / screen_room_messages_forwarded
strings live in Untranslated for now.

* Apply review suggestions to multi-message forwarding

Strings
- The forward button and the multi-room toast now use the Localazy
  plurals action_forward_messages and screen_room_messages_forwarded;
  the toast shows the count. The Untranslated copies and the unused
  action_select are gone.

Selection
- Drop the dead startSelection view action and the redundant
  canSelectMessages guard; tests start a selection through Forward
  like the app does.
- Remove two comments on the selection state that restated the code.

Forwarding
- After a send, clear the selection in the room and any open thread
  instead of working out which one started it; only one can be
  selecting at a time.
- Explain that stickers are excluded because the SDK can only forward
  m.room.message content.

* Rename the timeline selection to message selection

"Selection" alone was too generic in a timeline that also has text and
room selections. The selection state, toolbar, action bar and
accessibility types, the timeline state's property, the view actions
and the public clear method now say "message selection". Private
helpers keep their short names.

* Rename MessageForwardingItem to MessageForwardingPayload

The type now carries several messages, so "item" no longer described
it. The factory method and the variables and action labels holding it
follow the new name.
This commit is contained in:
Mauro
2026-09-25 13:51:14 +02:00
committed by GitHub
parent fc8ee9d93d
commit ad1d7a3010
71 changed files with 826 additions and 388 deletions
@@ -1404,6 +1404,13 @@ extension PreviewTests {
}
}
@Test
func timelineMessageSelectionActionBar() async throws {
for (index, preview) in TimelineMessageSelectionActionBar_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
}
}
@Test
func timelineReactionView() async throws {
for (index, preview) in TimelineReactionView_Previews._allPreviews.enumerated() {
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2113272aba349068ef746f57f993aa8f1cced5ce3be84acd7dcb83522dd34df7
size 229720
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0cb91f8da5aec4cfcf57682d55fab5dbf55b602e8ab03bc4c5e2cb7607cd26fe
size 236621
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38711b2757b8cbf0e8e03449fd76e932e841e54a84d810ab138fc5329e42d28e
size 168069
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0459720ea7376e5c62a7e29f0c69d06fa50e4f67a5c2d0c70abd008913370dc2
size 160328
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a8c4d7de9bde6e8b8ed405139ef0b1a93c526efc0495dc9e060f7746e3851a27
size 307560
oid sha256:c71750ad42a0bf4ba7000886933a7b469b66f0a2e83ad2d9af3cdb93cc6d78bb
size 296680
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b736b917580ffb895f52e571f7ec6b88fa79618ef7d967af4be7a1dc1297d74a
size 314629
oid sha256:e590117b3cef004295b78b851a8bab6a847e032d81a6a733165aa3d671fc8d39
size 302175
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70802dcebe90358926b889013d49f094111426817c4a3c5739e5bf6c56a7f755
size 185662
oid sha256:4a5286947f95971372a4e5cc146df21821dc18374462b80da063c7b014189de6
size 173954
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3cf29299b5003b889898629a533303496911b212b42e9e8dc10912ec7ac5e519
size 179793
oid sha256:2821e658003cb2d10e89c35a212ce44f2b543d030d8d2f698b660929f1fd8fa0
size 166352
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f4ba074e3d13bc6ecfefe9e91e0515d0fa0a793908ae38fe400c845c389d79fd
size 103633
oid sha256:4f1d033633be525d00add117f22cbd644738f557566652105e5bad06fa93f550
size 100876
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d1e836cbb773ab61e7c5470fec38a51dce0572b5a779eabdcd86a0171b7aa04
size 106075
oid sha256:453b6c26d0af9fc4f52acd6878eb521ea391803d859ca8d22ff6537b43af443a
size 103386
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aed3e91bdd708c6738cc2e82e8271988761adc46c8192e491cb5e7bcf4f33031
size 59392
oid sha256:0a39d152d8c2325e6600a335916be5af29a7d195df61ae33c7c2da138c3a225d
size 59166
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ecafd3c5f7ca87afbc5b88211205b0a5b8e950595fdc5f526456628b3f1c32d3
size 61305
oid sha256:0e86d354c76d520c1d45d263b5d25851d3db42b2767440876a322525c849479c
size 60994
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:acf13cf6aa2414522d9de6d68d631e1334072d72231be8ec552dbc1ebf02efb4
size 17224
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:31a3a51a1f5756e5b25ede739f05d33586c1e459d4fb5c147d69324a539f21ff
size 17343
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8b6d05bd21a076f815bfcd766317e4ca1860c54a91f86faf30dbdbd503d2329f
size 11595
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b945f049c4f2325bb6da028586a901c7b5144cce57ff49d55678c34e123a20e7
size 11653