mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Permalink Tweaks (#2768)
* Revert back to creating a new flow when re-selecting the same room. Fix the composer bug by adding IDs to the all of the module AnyViews. * Don't show the live timeline when starting with a focussed event.
This commit is contained in:
@@ -102,7 +102,6 @@
|
|||||||
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; };
|
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; };
|
||||||
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; };
|
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; };
|
||||||
1583E2D766E4485FF91662FC /* PermalinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */; };
|
1583E2D766E4485FF91662FC /* PermalinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */; };
|
||||||
158A2D528CC78C4E7A8ED608 /* MockRoomTimelineControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */; };
|
|
||||||
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; };
|
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; };
|
||||||
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D38391154120264910D19528 /* PollMock.swift */; };
|
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D38391154120264910D19528 /* PollMock.swift */; };
|
||||||
1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; };
|
1702981A8085BE4FB0EC001B /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33116993D54FADC0C721C1F /* Application.swift */; };
|
||||||
@@ -749,6 +748,7 @@
|
|||||||
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
|
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
|
||||||
B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; };
|
B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; };
|
||||||
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; };
|
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; };
|
||||||
|
B31E5493C99381D4E204438B /* RoomTimelineControllerFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */; };
|
||||||
B3D652AA1654270742072FB3 /* DeveloperOptionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */; };
|
B3D652AA1654270742072FB3 /* DeveloperOptionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */; };
|
||||||
B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1CCDEE545CB6453B084BF /* FormButtonStyles.swift */; };
|
B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1CCDEE545CB6453B084BF /* FormButtonStyles.swift */; };
|
||||||
B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */; };
|
B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */; };
|
||||||
@@ -1563,7 +1563,6 @@
|
|||||||
7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = "<group>"; };
|
7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = "<group>"; };
|
70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = "<group>"; };
|
||||||
713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineControllerFactory.swift; sourceTree = "<group>"; };
|
|
||||||
718D8767035D37E2DB5CC550 /* QRCodeLoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
718D8767035D37E2DB5CC550 /* QRCodeLoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
7199693797B66245EF97BCF5 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
7199693797B66245EF97BCF5 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreen.swift; sourceTree = "<group>"; };
|
71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreen.swift; sourceTree = "<group>"; };
|
||||||
@@ -1967,6 +1966,7 @@
|
|||||||
D3F219838588C62198E726E3 /* LABiometryType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LABiometryType.swift; sourceTree = "<group>"; };
|
D3F219838588C62198E726E3 /* LABiometryType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LABiometryType.swift; sourceTree = "<group>"; };
|
||||||
D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreen.swift; sourceTree = "<group>"; };
|
D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreen.swift; sourceTree = "<group>"; };
|
||||||
D45C9EAA86423D7D3126DE4F /* VoiceMessageRecorderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorderProtocol.swift; sourceTree = "<group>"; };
|
D45C9EAA86423D7D3126DE4F /* VoiceMessageRecorderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactoryMock.swift; sourceTree = "<group>"; };
|
||||||
D49B9785E3AD7D1C15A29F2F /* MediaSourceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSourceProxy.swift; sourceTree = "<group>"; };
|
D49B9785E3AD7D1C15A29F2F /* MediaSourceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSourceProxy.swift; sourceTree = "<group>"; };
|
||||||
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineItem.swift; sourceTree = "<group>"; };
|
D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
@@ -2650,7 +2650,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */,
|
52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */,
|
||||||
71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */,
|
|
||||||
9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */,
|
9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */,
|
||||||
E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */,
|
E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */,
|
||||||
18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */,
|
18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */,
|
||||||
@@ -2681,6 +2680,7 @@
|
|||||||
F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */,
|
F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */,
|
||||||
1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */,
|
1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */,
|
||||||
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */,
|
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */,
|
||||||
|
D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */,
|
||||||
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */,
|
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */,
|
||||||
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
|
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
|
||||||
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
|
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
|
||||||
@@ -6160,7 +6160,6 @@
|
|||||||
152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */,
|
152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */,
|
||||||
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */,
|
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */,
|
||||||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */,
|
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */,
|
||||||
158A2D528CC78C4E7A8ED608 /* MockRoomTimelineControllerFactory.swift in Sources */,
|
|
||||||
B721125D17A0BA86794F29FB /* MockServerSelectionScreenState.swift in Sources */,
|
B721125D17A0BA86794F29FB /* MockServerSelectionScreenState.swift in Sources */,
|
||||||
AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */,
|
AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */,
|
||||||
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */,
|
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */,
|
||||||
@@ -6367,6 +6366,7 @@
|
|||||||
AA050DF4AEE54A641BA7CA22 /* RoomSummaryProviderProtocol.swift in Sources */,
|
AA050DF4AEE54A641BA7CA22 /* RoomSummaryProviderProtocol.swift in Sources */,
|
||||||
2ABF11717C64054CEF2819A3 /* RoomTimelineController.swift in Sources */,
|
2ABF11717C64054CEF2819A3 /* RoomTimelineController.swift in Sources */,
|
||||||
38896D54D6D675534E606195 /* RoomTimelineControllerFactory.swift in Sources */,
|
38896D54D6D675534E606195 /* RoomTimelineControllerFactory.swift in Sources */,
|
||||||
|
B31E5493C99381D4E204438B /* RoomTimelineControllerFactoryMock.swift in Sources */,
|
||||||
7ECF12D5DCD69F67BD3E3842 /* RoomTimelineControllerFactoryProtocol.swift in Sources */,
|
7ECF12D5DCD69F67BD3E3842 /* RoomTimelineControllerFactoryProtocol.swift in Sources */,
|
||||||
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */,
|
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */,
|
||||||
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */,
|
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */,
|
||||||
|
|||||||
@@ -379,9 +379,11 @@ private struct NavigationSplitCoordinatorView: View {
|
|||||||
// through the NavigationSplitCoordinator as well.
|
// through the NavigationSplitCoordinator as well.
|
||||||
.sheet(item: $navigationSplitCoordinator.sheetModule) { module in
|
.sheet(item: $navigationSplitCoordinator.sheetModule) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
.fullScreenCover(item: $navigationSplitCoordinator.fullScreenCoverModule) { module in
|
.fullScreenCover(item: $navigationSplitCoordinator.fullScreenCoverModule) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
// Handle `horizontalSizeClass` changes breaking the navigation bar
|
// Handle `horizontalSizeClass` changes breaking the navigation bar
|
||||||
// https://github.com/element-hq/element-x-ios/issues/617
|
// https://github.com/element-hq/element-x-ios/issues/617
|
||||||
@@ -408,8 +410,10 @@ private struct NavigationSplitCoordinatorView: View {
|
|||||||
var navigationStack: some View {
|
var navigationStack: some View {
|
||||||
NavigationStack(path: navigationSplitCoordinator.compactLayoutStackModulesBinding) {
|
NavigationStack(path: navigationSplitCoordinator.compactLayoutStackModulesBinding) {
|
||||||
navigationSplitCoordinator.compactLayoutRootModule?.coordinator?.toPresentable()
|
navigationSplitCoordinator.compactLayoutRootModule?.coordinator?.toPresentable()
|
||||||
|
.id(navigationSplitCoordinator.compactLayoutRootModule?.id) // Is a nil ID ok?
|
||||||
.navigationDestination(for: NavigationModule.self) { module in
|
.navigationDestination(for: NavigationModule.self) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,19 +423,24 @@ private struct NavigationSplitCoordinatorView: View {
|
|||||||
NavigationSplitView(columnVisibility: $columnVisibility) {
|
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||||
if let sidebarModule = navigationSplitCoordinator.sidebarModule {
|
if let sidebarModule = navigationSplitCoordinator.sidebarModule {
|
||||||
sidebarModule.coordinator?.toPresentable()
|
sidebarModule.coordinator?.toPresentable()
|
||||||
|
.id(sidebarModule.id)
|
||||||
} else {
|
} else {
|
||||||
navigationSplitCoordinator.placeholderModule.coordinator?.toPresentable()
|
navigationSplitCoordinator.placeholderModule.coordinator?.toPresentable()
|
||||||
|
.id(navigationSplitCoordinator.placeholderModule.id)
|
||||||
}
|
}
|
||||||
} detail: {
|
} detail: {
|
||||||
if let detailModule = navigationSplitCoordinator.detailModule {
|
if let detailModule = navigationSplitCoordinator.detailModule {
|
||||||
detailModule.coordinator?.toPresentable()
|
detailModule.coordinator?.toPresentable()
|
||||||
|
.id(detailModule.id)
|
||||||
} else {
|
} else {
|
||||||
navigationSplitCoordinator.placeholderModule.coordinator?.toPresentable()
|
navigationSplitCoordinator.placeholderModule.coordinator?.toPresentable()
|
||||||
|
.id(navigationSplitCoordinator.placeholderModule.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationSplitViewStyle(.balanced)
|
.navigationSplitViewStyle(.balanced)
|
||||||
.navigationDestination(for: NavigationModule.self) { module in
|
.navigationDestination(for: NavigationModule.self) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
.animation(.elementDefault, value: navigationSplitCoordinator.sidebarModule)
|
.animation(.elementDefault, value: navigationSplitCoordinator.sidebarModule)
|
||||||
.animation(.elementDefault, value: navigationSplitCoordinator.detailModule)
|
.animation(.elementDefault, value: navigationSplitCoordinator.detailModule)
|
||||||
@@ -713,15 +722,19 @@ private struct NavigationStackCoordinatorView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $navigationStackCoordinator.stackModules) {
|
NavigationStack(path: $navigationStackCoordinator.stackModules) {
|
||||||
navigationStackCoordinator.rootModule?.coordinator?.toPresentable()
|
navigationStackCoordinator.rootModule?.coordinator?.toPresentable()
|
||||||
|
.id(navigationStackCoordinator.rootModule?.id) // Is a nil ID ok?
|
||||||
.navigationDestination(for: NavigationModule.self) { module in
|
.navigationDestination(for: NavigationModule.self) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(item: $navigationStackCoordinator.sheetModule) { module in
|
.sheet(item: $navigationStackCoordinator.sheetModule) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
.fullScreenCover(item: $navigationStackCoordinator.fullScreenCoverModule) { module in
|
.fullScreenCover(item: $navigationStackCoordinator.fullScreenCoverModule) { module in
|
||||||
module.coordinator?.toPresentable()
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
}
|
}
|
||||||
.animation(.elementDefault, value: navigationStackCoordinator.rootModule)
|
.animation(.elementDefault, value: navigationStackCoordinator.rootModule)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ enum RoomFlowCoordinatorEntryPoint: Hashable {
|
|||||||
case eventID(String)
|
case eventID(String)
|
||||||
/// The flow will start by showing the room's details.
|
/// The flow will start by showing the room's details.
|
||||||
case roomDetails
|
case roomDetails
|
||||||
|
|
||||||
|
var isEventID: Bool {
|
||||||
|
guard case .eventID = self else { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next type_body_length
|
// swiftlint:disable:next type_body_length
|
||||||
@@ -508,7 +513,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .initial, .roomDetails(isRoot: true), .joinRoomScreen:
|
case .initial, .roomDetails(isRoot: true), .joinRoomScreen:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return // The room is already on the stack, no need to present it again
|
// The room is already on the stack, no need to present it again
|
||||||
|
|
||||||
|
// Check if we need to focus on an event
|
||||||
|
if let focussedEventID {
|
||||||
|
roomScreenCoordinator?.focusOnEvent(eventID: focussedEventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,6 +536,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||||
|
|
||||||
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||||
|
initialFocussedEventID: focussedEventID,
|
||||||
timelineItemFactory: timelineItemFactory)
|
timelineItemFactory: timelineItemFactory)
|
||||||
self.timelineController = timelineController
|
self.timelineController = timelineController
|
||||||
|
|
||||||
@@ -1012,6 +1025,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||||
|
|
||||||
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||||
|
initialFocussedEventID: nil,
|
||||||
timelineItemFactory: timelineItemFactory)
|
timelineItemFactory: timelineItemFactory)
|
||||||
|
|
||||||
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy,
|
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||||
|
|||||||
@@ -272,15 +272,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentHomeScreen()
|
presentHomeScreen()
|
||||||
attemptStartingOnboarding()
|
attemptStartingOnboarding()
|
||||||
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let entryPoint), .roomList):
|
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let entryPoint), .roomList):
|
||||||
if selectedRoomID == roomID {
|
if selectedRoomID == roomID,
|
||||||
if let roomFlowCoordinator {
|
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
|
||||||
let route: AppRoute = switch entryPoint {
|
let roomFlowCoordinator {
|
||||||
case .room: .room(roomID: roomID)
|
let route: AppRoute = switch entryPoint {
|
||||||
case .eventID(let eventID): .event(roomID: roomID, eventID: eventID)
|
case .room: .room(roomID: roomID)
|
||||||
case .roomDetails: .roomDetails(roomID: roomID)
|
case .roomDetails: .roomDetails(roomID: roomID)
|
||||||
}
|
case .eventID(let eventID): .event(roomID: roomID, eventID: eventID) // ignored.
|
||||||
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
|
||||||
}
|
}
|
||||||
|
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
||||||
} else {
|
} else {
|
||||||
Task { await self.startRoomFlow(roomID: roomID, entryPoint: entryPoint, animated: animated) }
|
Task { await self.startRoomFlow(roomID: roomID, entryPoint: entryPoint, animated: animated) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9652,6 +9652,77 @@ class RoomSummaryProviderMock: RoomSummaryProviderProtocol {
|
|||||||
setFilterClosure?(filter)
|
setFilterClosure?(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class RoomTimelineControllerFactoryMock: RoomTimelineControllerFactoryProtocol {
|
||||||
|
|
||||||
|
//MARK: - buildRoomTimelineController
|
||||||
|
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingCallsCount = 0
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCalled: Bool {
|
||||||
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount > 0
|
||||||
|
}
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments: (roomProxy: RoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol)?
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedInvocations: [(roomProxy: RoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol)] = []
|
||||||
|
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingReturnValue: RoomTimelineControllerProtocol!
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReturnValue: RoomTimelineControllerProtocol! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: RoomTimelineControllerProtocol? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryClosure: ((RoomProxyProtocol, String?, RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol)?
|
||||||
|
|
||||||
|
func buildRoomTimelineController(roomProxy: RoomProxyProtocol, initialFocussedEventID: String?, timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount += 1
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments = (roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory)
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedInvocations.append((roomProxy: roomProxy, initialFocussedEventID: initialFocussedEventID, timelineItemFactory: timelineItemFactory))
|
||||||
|
if let buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryClosure = buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryClosure {
|
||||||
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryClosure(roomProxy, initialFocussedEventID, timelineItemFactory)
|
||||||
|
} else {
|
||||||
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
class RoomTimelineProviderMock: RoomTimelineProviderProtocol {
|
class RoomTimelineProviderMock: RoomTimelineProviderProtocol {
|
||||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
|
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
|
||||||
get { return underlyingUpdatePublisher }
|
get { return underlyingUpdatePublisher }
|
||||||
|
|||||||
+13
-6
@@ -16,11 +16,18 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
struct RoomTimelineControllerFactoryMockConfiguration {
|
||||||
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
|
var timelineController: MockRoomTimelineController?
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
|
}
|
||||||
let timelineController = MockRoomTimelineController()
|
|
||||||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
extension RoomTimelineControllerFactoryMock {
|
||||||
return timelineController
|
convenience init(configuration: RoomTimelineControllerFactoryMockConfiguration) {
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReturnValue = configuration.timelineController ?? {
|
||||||
|
let timelineController = MockRoomTimelineController()
|
||||||
|
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||||
|
return timelineController
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,7 @@ struct RoomDetailsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(L10n.screenRoomDetailsTitle)
|
.navigationTitle(L10n.screenRoomDetailsTitle)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.track(screen: .RoomDetails)
|
.track(screen: .RoomDetails)
|
||||||
.interactiveQuickLook(item: $context.mediaPreviewItem, shouldHideControls: true)
|
.interactiveQuickLook(item: $context.mediaPreviewItem, shouldHideControls: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,28 +95,28 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
bindings: .init(reactionsCollapsed: [:])),
|
bindings: .init(reactionsCollapsed: [:])),
|
||||||
imageProvider: mediaProvider)
|
imageProvider: mediaProvider)
|
||||||
|
|
||||||
// This may change to load the detached timeline directly.
|
if focussedEventID != nil {
|
||||||
if let focussedEventID {
|
// The timeline controller will start loading a detached timeline.
|
||||||
Task { await focusOnEvent(eventID: focussedEventID) }
|
showFocusLoadingIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
setupDirectRoomSubscriptionsIfNeeded()
|
setupDirectRoomSubscriptionsIfNeeded()
|
||||||
|
|
||||||
state.timelineItemMenuActionProvider = { [weak self] itemId -> TimelineItemMenuActions? in
|
state.timelineItemMenuActionProvider = { [weak self] itemID -> TimelineItemMenuActions? in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.roomScreenInteractionHandler.timelineItemMenuActionsForItemId(itemId)
|
return self.roomScreenInteractionHandler.timelineItemMenuActionsForItemId(itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.audioPlayerStateProvider = { [weak self] itemId -> AudioPlayerState? in
|
state.audioPlayerStateProvider = { [weak self] itemID -> AudioPlayerState? in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.roomScreenInteractionHandler.audioPlayerState(for: itemId)
|
return self.roomScreenInteractionHandler.audioPlayerState(for: itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTimelineViews()
|
buildTimelineViews()
|
||||||
@@ -191,8 +191,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
case .focusLive:
|
case .focusLive:
|
||||||
focusLive()
|
focusLive()
|
||||||
case .scrolledToFocussedItem:
|
case .scrolledToFocussedItem:
|
||||||
// Use a Task to mutate view state after the current view update.
|
Task { // Use a Task to mutate view state after the current view update.
|
||||||
Task { state.timelineViewState.focussedEventNeedsDisplay = false }
|
state.timelineViewState.focussedEventNeedsDisplay = false
|
||||||
|
hideFocusLoadingIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(roomProxy: RoomProxyProtocol,
|
init(roomProxy: RoomProxyProtocol,
|
||||||
|
initialFocussedEventID: String?,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
appSettings: AppSettings) {
|
appSettings: AppSettings) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
@@ -54,9 +55,24 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
activeTimeline = roomProxy.timeline
|
activeTimeline = roomProxy.timeline
|
||||||
activeTimelineProvider = liveTimelineProvider
|
activeTimelineProvider = liveTimelineProvider
|
||||||
configureActiveTimelineProvider()
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||||
|
|
||||||
|
guard let initialFocussedEventID else {
|
||||||
|
configureActiveTimelineProvider()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Task {
|
||||||
|
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .paginating)))
|
||||||
|
switch await focusOnEvent(initialFocussedEventID, timelineSize: 100) {
|
||||||
|
case .success:
|
||||||
|
break
|
||||||
|
case .failure:
|
||||||
|
// Setup the live timeline as a fallback.
|
||||||
|
configureActiveTimelineProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
@@ -78,7 +94,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
func focusLive() {
|
func focusLive() {
|
||||||
activeTimeline = roomProxy.timeline
|
activeTimeline = roomProxy.timeline
|
||||||
activeTimelineProvider = liveTimelineProvider
|
activeTimelineProvider = liveTimelineProvider
|
||||||
callbacks.send(.isLive(true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
|
|||||||
+2
@@ -18,8 +18,10 @@ import Foundation
|
|||||||
|
|
||||||
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||||
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
|
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
|
||||||
|
initialFocussedEventID: String?,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
|
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
|
||||||
RoomTimelineController(roomProxy: roomProxy,
|
RoomTimelineController(roomProxy: roomProxy,
|
||||||
|
initialFocussedEventID: initialFocussedEventID,
|
||||||
timelineItemFactory: timelineItemFactory,
|
timelineItemFactory: timelineItemFactory,
|
||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -19,5 +19,9 @@ import Foundation
|
|||||||
@MainActor
|
@MainActor
|
||||||
protocol RoomTimelineControllerFactoryProtocol {
|
protocol RoomTimelineControllerFactoryProtocol {
|
||||||
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
|
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
|
||||||
|
initialFocussedEventID: String?,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol
|
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sourcery: AutoMockable
|
||||||
|
extension RoomTimelineControllerFactoryProtocol { }
|
||||||
|
|||||||
@@ -503,7 +503,7 @@ class MockScreen: Identifiable {
|
|||||||
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings),
|
appSettings: ServiceLocator.shared.settings),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(),
|
||||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
roomTimelineControllerFactory: RoomTimelineControllerFactoryMock(configuration: .init()),
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import Combine
|
|||||||
@MainActor
|
@MainActor
|
||||||
class RoomFlowCoordinatorTests: XCTestCase {
|
class RoomFlowCoordinatorTests: XCTestCase {
|
||||||
var clientProxy: ClientProxyMock!
|
var clientProxy: ClientProxyMock!
|
||||||
|
var timelineControllerFactory: RoomTimelineControllerFactoryMock!
|
||||||
var roomFlowCoordinator: RoomFlowCoordinator!
|
var roomFlowCoordinator: RoomFlowCoordinator!
|
||||||
var navigationStackCoordinator: NavigationStackCoordinator!
|
var navigationStackCoordinator: NavigationStackCoordinator!
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
@@ -216,7 +217,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||||
|
|
||||||
try await process(route: .event(roomID: "1", eventID: "2"))
|
try await process(route: .childEvent(roomID: "1", eventID: "2"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||||
|
|
||||||
@@ -269,6 +270,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
private func setupRoomFlowCoordinator(asChildFlow: Bool = false, roomType: RoomType? = nil) async {
|
private func setupRoomFlowCoordinator(asChildFlow: Bool = false, roomType: RoomType? = nil) async {
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
|
timelineControllerFactory = RoomTimelineControllerFactoryMock(configuration: .init())
|
||||||
|
|
||||||
clientProxy.roomPreviewForIdentifierClosure = { [roomType] roomID in
|
clientProxy.roomPreviewForIdentifierClosure = { [roomType] roomID in
|
||||||
switch roomType {
|
switch roomType {
|
||||||
@@ -309,7 +311,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
roomFlowCoordinator = await RoomFlowCoordinator(roomID: roomID,
|
roomFlowCoordinator = await RoomFlowCoordinator(roomID: roomID,
|
||||||
userSession: userSession,
|
userSession: userSession,
|
||||||
isChildFlow: asChildFlow,
|
isChildFlow: asChildFlow,
|
||||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
roomTimelineControllerFactory: timelineControllerFactory,
|
||||||
navigationStackCoordinator: navigationStackCoordinator,
|
navigationStackCoordinator: navigationStackCoordinator,
|
||||||
emojiProvider: EmojiProvider(),
|
emojiProvider: EmojiProvider(),
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
||||||
|
XCTAssertFalse(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
|
|
||||||
// When focussing on an item that isn't loaded.
|
// 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.timelineViewState.isLive }
|
||||||
@@ -182,6 +183,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
||||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t4")
|
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t4")
|
||||||
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFocusLoadedItem() async throws {
|
func testFocusLoadedItem() async throws {
|
||||||
@@ -196,6 +198,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
||||||
|
XCTAssertFalse(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
|
|
||||||
// When focussing on a loaded item.
|
// 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.timelineViewState.isLive }
|
||||||
@@ -206,6 +209,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t1")
|
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t1")
|
||||||
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFocusLive() async throws {
|
func testFocusLive() async throws {
|
||||||
@@ -225,6 +229,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
||||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t4")
|
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t4")
|
||||||
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
|
|
||||||
// When switching back to a live timeline.
|
// When switching back to a live timeline.
|
||||||
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineViewState.isLive }
|
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineViewState.isLive }
|
||||||
@@ -235,6 +240,15 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEventID)
|
||||||
|
XCTAssertFalse(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitialFocusViewState() async throws {
|
||||||
|
let timelineController = MockRoomTimelineController()
|
||||||
|
|
||||||
|
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
|
||||||
|
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEventID, "t10")
|
||||||
|
XCTAssertTrue(viewModel.context.viewState.timelineViewState.focussedEventNeedsDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
@@ -464,8 +478,11 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func makeViewModel(roomProxy: RoomProxyProtocol? = nil, timelineController: RoomTimelineControllerProtocol) -> RoomScreenViewModel {
|
private func makeViewModel(roomProxy: RoomProxyProtocol? = nil,
|
||||||
|
focussedEventID: String? = nil,
|
||||||
|
timelineController: RoomTimelineControllerProtocol) -> RoomScreenViewModel {
|
||||||
RoomScreenViewModel(roomProxy: roomProxy ?? RoomProxyMock(with: .init(name: "")),
|
RoomScreenViewModel(roomProxy: roomProxy ?? RoomProxyMock(with: .init(name: "")),
|
||||||
|
focussedEventID: focussedEventID,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import Combine
|
|||||||
@MainActor
|
@MainActor
|
||||||
class UserSessionFlowCoordinatorTests: XCTestCase {
|
class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||||
var clientProxy: ClientProxyMock!
|
var clientProxy: ClientProxyMock!
|
||||||
|
var timelineControllerFactory: RoomTimelineControllerFactoryMock!
|
||||||
var userSessionFlowCoordinator: UserSessionFlowCoordinator!
|
var userSessionFlowCoordinator: UserSessionFlowCoordinator!
|
||||||
var navigationRootCoordinator: NavigationRootCoordinator!
|
var navigationRootCoordinator: NavigationRootCoordinator!
|
||||||
var notificationManager: NotificationManagerMock!
|
var notificationManager: NotificationManagerMock!
|
||||||
@@ -35,6 +36,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
|||||||
override func setUp() async throws {
|
override func setUp() async throws {
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
|
timelineControllerFactory = RoomTimelineControllerFactoryMock(configuration: .init())
|
||||||
let mediaProvider = MockMediaProvider()
|
let mediaProvider = MockMediaProvider()
|
||||||
let voiceMessageMediaManager = VoiceMessageMediaManagerMock()
|
let voiceMessageMediaManager = VoiceMessageMediaManagerMock()
|
||||||
let userSession = MockUserSession(clientProxy: clientProxy,
|
let userSession = MockUserSession(clientProxy: clientProxy,
|
||||||
@@ -49,7 +51,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
|||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: AppLockServiceMock(),
|
appLockService: AppLockServiceMock(),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(),
|
||||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
roomTimelineControllerFactory: timelineControllerFactory,
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
@@ -216,22 +218,40 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testEventRoutes() async throws {
|
func testEventRoutes() async throws {
|
||||||
|
// A regular event route should set its room as the root of the stack and focus on the event.
|
||||||
try await process(route: .event(roomID: "1", eventID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
try await process(route: .event(roomID: "1", eventID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||||
XCTAssertNotNil(detailCoordinator)
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount, 1)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments?.initialFocussedEventID, "1")
|
||||||
|
|
||||||
|
// A child event route should push a new room screen onto the stack and focus on the event.
|
||||||
userSessionFlowCoordinator.handleAppRoute(.childEvent(roomID: "2", eventID: "2"), animated: true)
|
userSessionFlowCoordinator.handleAppRoute(.childEvent(roomID: "2", eventID: "2"), animated: true)
|
||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 1)
|
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 1)
|
||||||
XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomScreenCoordinator)
|
||||||
XCTAssertNotNil(detailCoordinator)
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount, 2)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments?.initialFocussedEventID, "2")
|
||||||
|
|
||||||
|
// A subsequent regular event route should clear the stack and set the new room as the root of the stack.
|
||||||
try await process(route: .event(roomID: "3", eventID: "3"), expectedState: .roomList(selectedRoomID: "3"))
|
try await process(route: .event(roomID: "3", eventID: "3"), expectedState: .roomList(selectedRoomID: "3"))
|
||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||||
XCTAssertNotNil(detailCoordinator)
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount, 3)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments?.initialFocussedEventID, "3")
|
||||||
|
|
||||||
|
// A regular event route for the same room should set a new instance of the room as the root of the stack.
|
||||||
|
try await process(route: .event(roomID: "3", eventID: "4"), expectedState: .roomList(selectedRoomID: "3"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryCallsCount, 4)
|
||||||
|
XCTAssertEqual(timelineControllerFactory.buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryReceivedArguments?.initialFocussedEventID, "4",
|
||||||
|
"A new timeline should be created for the same room ID, so that the screen isn't stale while loading.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
Reference in New Issue
Block a user