mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
Event Permalink Tweaks (#2754)
* Handle errors focussing the timeline. * Add a test for focussing on an existing timeline item.
This commit is contained in:
@@ -234,11 +234,16 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) {
|
switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) {
|
||||||
case .success:
|
case .success:
|
||||||
state.timelineViewState.focussedEventID = eventID
|
state.timelineViewState.focussedEventID = eventID
|
||||||
case .failure:
|
case .failure(let error):
|
||||||
MXLog.error("Failed to focus on event \(eventID)")
|
MXLog.error("Failed to focus on event \(eventID)")
|
||||||
|
|
||||||
|
if case .eventNotFound = error {
|
||||||
|
displayError(.toast(L10n.errorMessageNotFound))
|
||||||
|
} else {
|
||||||
displayError(.toast(L10n.commonFailed))
|
displayError(.toast(L10n.commonFailed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
|||||||
@@ -163,8 +163,20 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
do {
|
do {
|
||||||
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
|
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
|
||||||
return .success(TimelineProxy(timeline: timeline, isLive: false))
|
return .success(TimelineProxy(timeline: timeline, isLive: false))
|
||||||
|
} catch let error as FocusEventError {
|
||||||
|
switch error {
|
||||||
|
case .InvalidEventId(_, let error):
|
||||||
|
MXLog.error("Invalid event \(eventID) Error: \(error)")
|
||||||
|
return .failure(.eventNotFound)
|
||||||
|
case .EventNotFound:
|
||||||
|
MXLog.error("Event \(eventID) not found.")
|
||||||
|
return .failure(.eventNotFound)
|
||||||
|
case .Other(let message):
|
||||||
|
MXLog.error("Failed to create a timeline focussed on event \(eventID) Error: \(message)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed to create a timeline focussed on: \(eventID) with error: \(error)")
|
MXLog.error("Unexpected error: \(error)")
|
||||||
return .failure(.sdkError(error))
|
return .failure(.sdkError(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ enum RoomProxyError: Error {
|
|||||||
|
|
||||||
case invalidURL
|
case invalidURL
|
||||||
case invalidMedia
|
case invalidMedia
|
||||||
|
case eventNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomProxyAction {
|
enum RoomProxyAction {
|
||||||
|
|||||||
@@ -241,11 +241,19 @@ enum RoomTimelineItemFixtures {
|
|||||||
static var outgoingPolls: [RoomTimelineItemProtocol] {
|
static var outgoingPolls: [RoomTimelineItemProtocol] {
|
||||||
[PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)]
|
[PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var permalinkChunk: [RoomTimelineItemProtocol] {
|
||||||
|
(1...20).map { index in
|
||||||
|
TextRoomTimelineItem(id: .init(timelineID: "\(index)", eventID: "$\(index)"),
|
||||||
|
text: "Message ID \(index)",
|
||||||
|
senderDisplayName: index > 10 ? "Alice" : "Bob")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension TextRoomTimelineItem {
|
private extension TextRoomTimelineItem {
|
||||||
init(text: String, senderDisplayName: String) {
|
init(id: TimelineItemIdentifier? = nil, text: String, senderDisplayName: String) {
|
||||||
self.init(id: .random,
|
self.init(id: id ?? .random,
|
||||||
timestamp: "10:47 am",
|
timestamp: "10:47 am",
|
||||||
isOutgoing: senderDisplayName == "Alice",
|
isOutgoing: senderDisplayName == "Alice",
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
|||||||
@@ -66,10 +66,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
activeTimeline = timeline
|
activeTimeline = timeline
|
||||||
activeTimelineProvider = timeline.timelineProvider
|
activeTimelineProvider = timeline.timelineProvider
|
||||||
return .success(())
|
return .success(())
|
||||||
case .failure:
|
case .failure(let error):
|
||||||
|
if case .eventNotFound = error {
|
||||||
|
return .failure(.eventNotFound)
|
||||||
|
} else {
|
||||||
return .failure(.generic)
|
return .failure(.generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func focusLive() {
|
func focusLive() {
|
||||||
activeTimeline = roomProxy.timeline
|
activeTimeline = roomProxy.timeline
|
||||||
|
|||||||
+1
@@ -32,6 +32,7 @@ enum RoomTimelineControllerAction {
|
|||||||
|
|
||||||
enum RoomTimelineControllerError: Error {
|
enum RoomTimelineControllerError: Error {
|
||||||
case generic
|
case generic
|
||||||
|
case eventNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ class MockScreen: Identifiable {
|
|||||||
let windowManager: SecureWindowManagerProtocol
|
let windowManager: SecureWindowManagerProtocol
|
||||||
let navigationRootCoordinator: NavigationRootCoordinator
|
let navigationRootCoordinator: NavigationRootCoordinator
|
||||||
|
|
||||||
|
private var client: UITestsSignalling.Client?
|
||||||
|
|
||||||
private var retainedState = [Any]()
|
private var retainedState = [Any]()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
@@ -393,6 +395,37 @@ class MockScreen: Identifiable {
|
|||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
|
case .roomLayoutHighlight:
|
||||||
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
let timelineController = MockRoomTimelineController()
|
||||||
|
timelineController.timelineItems = RoomTimelineItemFixtures.permalinkChunk
|
||||||
|
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(name: "Timeline highlight", avatarURL: URL.picturesDirectory)),
|
||||||
|
timelineController: timelineController,
|
||||||
|
mediaProvider: MockMediaProvider(),
|
||||||
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
emojiProvider: EmojiProvider(),
|
||||||
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
|
appMediator: AppMediatorMock.default,
|
||||||
|
appSettings: ServiceLocator.shared.settings)
|
||||||
|
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||||
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let client = try UITestsSignalling.Client(mode: .app)
|
||||||
|
client.signals.sink { [weak self] signal in
|
||||||
|
guard case .timeline(.focusOnEvent(let eventID)) = signal else { return }
|
||||||
|
coordinator.focusOnEvent(eventID: eventID)
|
||||||
|
try? client.send(.success)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
} catch {
|
||||||
|
fatalError("Failure setting up signalling: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client = client
|
||||||
|
return navigationStackCoordinator
|
||||||
case .roomWithDisclosedPolls:
|
case .roomWithDisclosedPolls:
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ enum UITestsScreenIdentifier: String {
|
|||||||
case roomLayoutBottom
|
case roomLayoutBottom
|
||||||
case roomLayoutMiddle
|
case roomLayoutMiddle
|
||||||
case roomLayoutTop
|
case roomLayoutTop
|
||||||
|
case roomLayoutHighlight
|
||||||
case roomMembersListScreenPendingInvites
|
case roomMembersListScreenPendingInvites
|
||||||
case roomPlainNoAvatar
|
case roomPlainNoAvatar
|
||||||
case roomRolesAndPermissionsFlow
|
case roomRolesAndPermissionsFlow
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ enum UITestsSignal: Codable, Equatable {
|
|||||||
case success
|
case success
|
||||||
|
|
||||||
case timeline(Timeline)
|
case timeline(Timeline)
|
||||||
enum Timeline: Codable {
|
enum Timeline: Codable, Equatable {
|
||||||
/// Ask the app to back paginate.
|
/// Ask the app to back paginate.
|
||||||
case paginate
|
case paginate
|
||||||
/// Ask the app to simulate an incoming message.
|
/// Ask the app to simulate an incoming message.
|
||||||
case incomingMessage
|
case incomingMessage
|
||||||
|
/// Ask the app to simulate focussing on an event ID.
|
||||||
|
case focusOnEvent(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Posts a notification.
|
/// Posts a notification.
|
||||||
|
|||||||
@@ -110,6 +110,25 @@ class RoomScreenUITests: XCTestCase {
|
|||||||
try await app.assertScreenshot(.roomLayoutBottom, step: 1)
|
try await app.assertScreenshot(.roomLayoutBottom, step: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testTimelineLayoutHighlightExisting() async throws {
|
||||||
|
let client = try UITestsSignalling.Client(mode: .tests)
|
||||||
|
|
||||||
|
let app = Application.launch(.roomLayoutHighlight)
|
||||||
|
|
||||||
|
await client.waitForApp()
|
||||||
|
defer { try? client.stop() }
|
||||||
|
|
||||||
|
// Some time for the timeline to settle.
|
||||||
|
try await Task.sleep(for: .seconds(1))
|
||||||
|
// When tapping a permalink to an item in the timeline.
|
||||||
|
try await performOperation(.focusOnEvent("$5"), using: client)
|
||||||
|
// Some time for the timeline to settle.
|
||||||
|
try await Task.sleep(for: .seconds(1))
|
||||||
|
|
||||||
|
// Then the item should become highlighted.
|
||||||
|
try await app.assertScreenshot(.roomLayoutHighlight, step: 0)
|
||||||
|
}
|
||||||
|
|
||||||
func testTimelineReadReceipts() async throws {
|
func testTimelineReadReceipts() async throws {
|
||||||
let app = Application.launch(.roomSmallTimelineWithReadReceipts)
|
let app = Application.launch(.roomSmallTimelineWithReadReceipts)
|
||||||
|
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:85ccfdd71e9fa2e59b65b9731c8e0135e7e44349d7f3debdb5afcff0262576d0
|
||||||
|
size 233181
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:79e3b444b2682f3697c1f27c898be7a3ed50d230c60d5c0fd33bfe9125056802
|
||||||
|
size 331437
|
||||||
Reference in New Issue
Block a user