mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Allow the user to forward the same message to multiple rooms at once
This will send the item to the rooms in order they were chosen and only deep link into the target room if single, otherwise it will just dismiss the forwarding screen after sending.
This commit is contained in:
@@ -12,7 +12,7 @@ import Foundation
|
|||||||
enum PinnedEventsTimelineFlowCoordinatorAction {
|
enum PinnedEventsTimelineFlowCoordinatorAction {
|
||||||
case finished
|
case finished
|
||||||
case displayUser(userID: String)
|
case displayUser(userID: String)
|
||||||
case forwardedMessageToRoom(roomID: String)
|
case forwardedMessageToRooms(roomIDs: [String])
|
||||||
case displayRoomScreenWithFocussedPin(eventID: String, threadRootEventID: String?)
|
case displayRoomScreenWithFocussedPin(eventID: String, threadRootEventID: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +148,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
case .sent(let roomID):
|
case .sent(let roomIDs):
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
actionsSubject.send(.forwardedMessageToRoom(roomID: roomID))
|
actionsSubject.send(.forwardedMessageToRooms(roomIDs: roomIDs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -1282,10 +1282,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
case .sent(let roomID):
|
case .sent(let roomIDs):
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
// Timelines are cached - the local echo will be visible when fetching the room by its ID.
|
processPostMessageForwardingTo(rooms: roomIDs)
|
||||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -1524,6 +1523,15 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func processPostMessageForwardingTo(rooms roomIDs: [String]) {
|
||||||
|
// Only open forwarding target room if single.
|
||||||
|
guard roomIDs.count == 1, let roomID = roomIDs.last else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Other flows
|
// MARK: - Other flows
|
||||||
|
|
||||||
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) {
|
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) {
|
||||||
@@ -1582,9 +1590,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .displayUser(let userID):
|
case .displayUser(let userID):
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
stateMachine.tryEvent(.startMembersFlow(entryPoint: .roomMember(userID: userID)))
|
stateMachine.tryEvent(.startMembersFlow(entryPoint: .roomMember(userID: userID)))
|
||||||
case .forwardedMessageToRoom(let roomID):
|
case .forwardedMessageToRooms(let roomIDs):
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
processPostMessageForwardingTo(rooms: roomIDs)
|
||||||
case .displayRoomScreenWithFocussedPin(let eventID, let threadRootEventID):
|
case .displayRoomScreenWithFocussedPin(let eventID, let threadRootEventID):
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
if let threadRootEventID {
|
if let threadRootEventID {
|
||||||
|
|||||||
+3
-3
@@ -18,7 +18,7 @@ struct MessageForwardingScreenCoordinatorParameters {
|
|||||||
|
|
||||||
enum MessageForwardingScreenCoordinatorAction {
|
enum MessageForwardingScreenCoordinatorAction {
|
||||||
case dismiss
|
case dismiss
|
||||||
case sent(roomID: String)
|
case sent(roomIDs: [String])
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MessageForwardingScreenCoordinator: CoordinatorProtocol {
|
final class MessageForwardingScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -42,8 +42,8 @@ final class MessageForwardingScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
self?.actionsSubject.send(.dismiss)
|
self?.actionsSubject.send(.dismiss)
|
||||||
case .sent(let roomID):
|
case .sent(let roomIDs):
|
||||||
self?.actionsSubject.send(.sent(roomID: roomID))
|
self?.actionsSubject.send(.sent(roomIDs: roomIDs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+2
-2
@@ -11,12 +11,12 @@ import MatrixRustSDK
|
|||||||
|
|
||||||
enum MessageForwardingScreenViewModelAction {
|
enum MessageForwardingScreenViewModelAction {
|
||||||
case dismiss
|
case dismiss
|
||||||
case sent(roomID: String)
|
case sent(roomIDs: [String])
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MessageForwardingScreenViewState: BindableState {
|
struct MessageForwardingScreenViewState: BindableState {
|
||||||
var rooms: [MessageForwardingRoom] = []
|
var rooms: [MessageForwardingRoom] = []
|
||||||
var selectedRoomID: String?
|
var selectedRoomIDs = [String]()
|
||||||
var bindings = MessageForwardingScreenViewStateBindings()
|
var bindings = MessageForwardingScreenViewStateBindings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-6
@@ -63,7 +63,11 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me
|
|||||||
case .send:
|
case .send:
|
||||||
Task { await forward() }
|
Task { await forward() }
|
||||||
case .selectRoom(let roomID):
|
case .selectRoom(let roomID):
|
||||||
state.selectedRoomID = roomID
|
if let index = state.selectedRoomIDs.firstIndex(of: roomID) {
|
||||||
|
state.selectedRoomIDs.remove(at: index)
|
||||||
|
} else {
|
||||||
|
state.selectedRoomIDs.append(roomID)
|
||||||
|
}
|
||||||
case .reachedTop:
|
case .reachedTop:
|
||||||
updateVisibleRange(edge: .top)
|
updateVisibleRange(edge: .top)
|
||||||
case .reachedBottom:
|
case .reachedBottom:
|
||||||
@@ -114,23 +118,30 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func forward() async {
|
private func forward() async {
|
||||||
guard let roomID = state.selectedRoomID else {
|
guard !state.selectedRoomIDs.isEmpty else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var succeededRoomIdentifiers = [String]()
|
||||||
|
|
||||||
|
for roomID in state.selectedRoomIDs {
|
||||||
guard case let .joined(targetRoomProxy) = await clientProxy.roomForIdentifier(roomID) else {
|
guard case let .joined(targetRoomProxy) = await clientProxy.roomForIdentifier(roomID) else {
|
||||||
MXLog.error("Failed retrieving room to forward to with id: \(roomID)")
|
MXLog.error("Failed retrieving room to forward to with id: \(roomID)")
|
||||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .failure(let error) = await targetRoomProxy.timeline.sendMessageEventContent(forwardingItem.content) {
|
if case .failure(let error) = await targetRoomProxy.timeline.sendMessageEventContent(forwardingItem.content) {
|
||||||
MXLog.error("Failed forwarding message with error: \(error)")
|
MXLog.error("Failed forwarding message with error: \(error)")
|
||||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timelines are cached - the local echo will be visible when fetching the room by its ID.
|
succeededRoomIdentifiers.append(roomID)
|
||||||
actionsSubject.send(.sent(roomID: roomID))
|
}
|
||||||
|
|
||||||
|
if !succeededRoomIdentifiers.isEmpty {
|
||||||
|
actionsSubject.send(.sent(roomIDs: succeededRoomIdentifiers))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct MessageForwardingScreen: View {
|
|||||||
Section {
|
Section {
|
||||||
ForEach(context.viewState.rooms) { room in
|
ForEach(context.viewState.rooms) { room in
|
||||||
MessageForwardingListRow(room: room,
|
MessageForwardingListRow(room: room,
|
||||||
isSelected: context.viewState.selectedRoomID == room.id,
|
isSelected: context.viewState.selectedRoomIDs.contains(room.id),
|
||||||
context: context)
|
context: context)
|
||||||
}
|
}
|
||||||
// Replace these with ScrollView's `scrollPosition` when dropping iOS 16.
|
// Replace these with ScrollView's `scrollPosition` when dropping iOS 16.
|
||||||
@@ -46,7 +46,7 @@ struct MessageForwardingScreen: View {
|
|||||||
Button(L10n.actionSend) {
|
Button(L10n.actionSend) {
|
||||||
context.send(viewAction: .send)
|
context.send(viewAction: .send)
|
||||||
}
|
}
|
||||||
.disabled(context.viewState.selectedRoomID == nil)
|
.disabled(context.viewState.selectedRoomIDs.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.searchController(query: $context.searchQuery, showsCancelButton: false)
|
.searchController(query: $context.searchQuery, showsCancelButton: false)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct MessageForwardingScreenViewModelTests {
|
|||||||
@Test
|
@Test
|
||||||
mutating func roomSelection() {
|
mutating func roomSelection() {
|
||||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||||
#expect(context.viewState.selectedRoomID == "2")
|
#expect(context.viewState.selectedRoomIDs == ["2"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -54,12 +54,12 @@ struct MessageForwardingScreenViewModelTests {
|
|||||||
@Test
|
@Test
|
||||||
mutating func forwarding() async throws {
|
mutating func forwarding() async throws {
|
||||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||||
#expect(context.viewState.selectedRoomID == "2")
|
#expect(context.viewState.selectedRoomIDs == ["2"])
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||||
switch action {
|
switch action {
|
||||||
case .sent(let roomID):
|
case .sent(let roomIDs):
|
||||||
return roomID == "2"
|
return roomIDs == ["2"]
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user