mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:06:00 +00:00
Media gallery - part 1(#3588)
* Introduce a `MediaEventsTimelineFlowCoordinator` * Update SDK API and architecture * Add a feature flag, add translations * Move the media events timeline presentation under the room flow coordinator state machine * Rename `TimelineViewState.timelineViewState` of type `TimelineState` to `timelineState` * Enabled SwiftLint's `trailing_closure` rule and fix the warnings.
This commit is contained in:
@@ -79,7 +79,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(attributedString.runs.count, 3)
|
||||
|
||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
||||
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||
|
||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(attributedString.runs.count, 3)
|
||||
|
||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
||||
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||
|
||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(attributedString.runs.count, 3)
|
||||
|
||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
||||
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||
|
||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(attributedString.runs.count, 3)
|
||||
|
||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
||||
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||
|
||||
XCTAssertEqual(link, "https://matrix.org")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class BlockedUsersScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.blockedUsers.contains { $0.displayName != nil } }
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
||||
@@ -35,7 +35,7 @@ class BlockedUsersScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { $0.blockedUsers.contains { $0.displayName != nil } }
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
||||
|
||||
@@ -54,10 +54,10 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
||||
.map(\.composerMode)
|
||||
.removeDuplicates()
|
||||
.dropFirst()
|
||||
.sink(receiveValue: { composerMode in
|
||||
.sink { composerMode in
|
||||
XCTAssertEqual(composerMode, mode)
|
||||
expectation.fulfill()
|
||||
})
|
||||
}
|
||||
|
||||
viewModel.process(timelineAction: .setMode(mode: mode))
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class JoinRoomScreenViewModelTests: XCTestCase {
|
||||
topic: nil,
|
||||
avatarURL: nil,
|
||||
memberCount: 0,
|
||||
isHistoryWorldReadable: false,
|
||||
isHistoryWorldReadable: nil,
|
||||
isJoined: false,
|
||||
isInvited: false,
|
||||
isPublic: false,
|
||||
|
||||
@@ -34,7 +34,7 @@ class MessageForwardingScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertNil(context.viewState.rooms.first(where: { $0.id == forwardingItem.roomID }), "The source room ID shouldn't be shown")
|
||||
XCTAssertNil(context.viewState.rooms.first { $0.id == forwardingItem.roomID }, "The source room ID shouldn't be shown")
|
||||
}
|
||||
|
||||
func testRoomSelection() {
|
||||
|
||||
@@ -29,7 +29,7 @@ class PollFormScreenViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||
|
||||
// Cancellation should work without confirmation
|
||||
let deferred = deferFulfillment(viewModel.actions, until: { _ in true })
|
||||
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||
context.send(viewAction: .cancel)
|
||||
let action = try await deferred.fulfill()
|
||||
XCTAssertNil(context.alertInfo)
|
||||
@@ -45,7 +45,7 @@ class PollFormScreenViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||
|
||||
// Cancellation should work without confirmation
|
||||
let deferred = deferFulfillment(viewModel.actions, until: { _ in true })
|
||||
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||
context.send(viewAction: .cancel)
|
||||
let action = try await deferred.fulfill()
|
||||
XCTAssertNil(context.alertInfo)
|
||||
|
||||
@@ -161,8 +161,8 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
// Then no warning should be shown, and the call to update the users should be made straight away.
|
||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 2)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == existingModerator.id && $0.powerLevel == 0 }), true)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 50 }), true)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == existingModerator.id && $0.powerLevel == 0 }, true)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 50 }, true)
|
||||
}
|
||||
|
||||
func testSavePromotedAdministrator() async throws {
|
||||
@@ -189,7 +189,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
// Then the user should be made into an administrator.
|
||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 1)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 100 }), true)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 100 }, true)
|
||||
}
|
||||
|
||||
private func setupViewModel(mode: RoomMemberDetails.Role) {
|
||||
|
||||
@@ -322,7 +322,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
topic: nil,
|
||||
avatarURL: nil,
|
||||
memberCount: 0,
|
||||
isHistoryWorldReadable: false,
|
||||
isHistoryWorldReadable: nil,
|
||||
isJoined: false,
|
||||
isInvited: true,
|
||||
isPublic: false,
|
||||
|
||||
@@ -49,7 +49,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .editOwnUserRole)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
|
||||
context.alertInfo?.verticalButtons?.first(where: { $0.title.localizedStandardContains("moderator") })?.action?()
|
||||
context.alertInfo?.verticalButtons?.first { $0.title.localizedStandardContains("moderator") }?.action?()
|
||||
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
@@ -64,7 +64,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .editOwnUserRole)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
|
||||
context.alertInfo?.verticalButtons?.first(where: { $0.title.localizedStandardContains("member") })?.action?()
|
||||
context.alertInfo?.verticalButtons?.first { $0.title.localizedStandardContains("member") }?.action?()
|
||||
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped together.
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
}
|
||||
|
||||
func testMessageGroupingMultipleSenders() {
|
||||
@@ -73,12 +73,12 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped by sender.
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithLeadingReactions() {
|
||||
@@ -99,9 +99,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the first message should not be grouped but the other two should.
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithInnerReactions() {
|
||||
@@ -122,9 +122,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the first and second messages should be grouped and the last one should not.
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithTrailingReactions() {
|
||||
@@ -145,9 +145,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped together.
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||
}
|
||||
|
||||
// MARK: - Focussing
|
||||
@@ -162,18 +162,18 @@ class TimelineViewModelTests: XCTestCase {
|
||||
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
|
||||
// When focussing on an item that isn't loaded.
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineViewState.isLive }
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineState.isLive }
|
||||
await viewModel.focusOnEvent(eventID: "t4")
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then a new timeline should be loaded and the room focussed on that event.
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
}
|
||||
|
||||
func testFocusLoadedItem() async throws {
|
||||
@@ -186,18 +186,18 @@ class TimelineViewModelTests: XCTestCase {
|
||||
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
|
||||
// When focussing on a loaded item.
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { !$0.timelineViewState.isLive }
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { !$0.timelineState.isLive }
|
||||
await viewModel.focusOnEvent(eventID: "t1")
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the timeline should remain live and the item should be focussed.
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t1", appearance: .animated))
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t1", appearance: .animated))
|
||||
}
|
||||
|
||||
func testFocusLive() async throws {
|
||||
@@ -210,30 +210,30 @@ class TimelineViewModelTests: XCTestCase {
|
||||
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineViewState.isLive }
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineState.isLive }
|
||||
await viewModel.focusOnEvent(eventID: "t4")
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
|
||||
// When switching back to a live timeline.
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineViewState.isLive }
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineState.isLive }
|
||||
viewModel.context.send(viewAction: .focusLive)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the timeline should switch back to being live and the event focus should be removed.
|
||||
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
}
|
||||
|
||||
func testInitialFocusViewState() async throws {
|
||||
let timelineController = MockRoomTimelineController()
|
||||
|
||||
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t10", appearance: .immediate))
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t10", appearance: .immediate))
|
||||
}
|
||||
|
||||
// MARK: - Read Receipts
|
||||
|
||||
Reference in New Issue
Block a user