mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-11 13:59:13 +00:00
SDK Changes Refactor (#1312)
* internal id * notification refactor * client proxy refactor * required self * better identifier system * using the event id when required * tests fixed * tested some stuff * fixed merge conflict * improved the test wait * animation disabled * Bump Rust SDK version to 1.0.98-alpha (#1310) * code improvement * pause sync * pr suggestions * result * Apply suggestions from code review Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * enum based debug identifier --------- Co-authored-by: aringenbach <80891108+aringenbach@users.noreply.github.com> Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
parent
adb253ff66
commit
76506752b4
@ -278,7 +278,6 @@
|
|||||||
6860721DB3091BE08164C132 /* MapAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B48B7AD4908C5C374517B892 /* MapAssets.xcassets */; };
|
6860721DB3091BE08164C132 /* MapAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B48B7AD4908C5C374517B892 /* MapAssets.xcassets */; };
|
||||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
||||||
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
|
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
|
||||||
69ABFBAF05D7EF11E7C88CEA /* EncryptionSyncListenerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68356CB936A8814A3FEA66A8 /* EncryptionSyncListenerProxy.swift */; };
|
|
||||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||||
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; };
|
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; };
|
||||||
6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; };
|
6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; };
|
||||||
@ -723,7 +722,6 @@
|
|||||||
F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */; };
|
F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */; };
|
||||||
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; };
|
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; };
|
||||||
F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */; };
|
F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */; };
|
||||||
F91B4629E4AF51A4FE8E7608 /* EncryptionSyncListenerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68356CB936A8814A3FEA66A8 /* EncryptionSyncListenerProxy.swift */; };
|
|
||||||
F94000E3D91B11C527DA8807 /* UserProfileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923485F85E1D765EF9D20E88 /* UserProfileCell.swift */; };
|
F94000E3D91B11C527DA8807 /* UserProfileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923485F85E1D765EF9D20E88 /* UserProfileCell.swift */; };
|
||||||
F9842667B68DC6FA1F9ECCBB /* NSItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EFC8C634469F9262659C7 /* NSItemProvider.swift */; };
|
F9842667B68DC6FA1F9ECCBB /* NSItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EFC8C634469F9262659C7 /* NSItemProvider.swift */; };
|
||||||
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
|
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
|
||||||
@ -1071,7 +1069,6 @@
|
|||||||
667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessor.swift; sourceTree = "<group>"; };
|
669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessor.swift; sourceTree = "<group>"; };
|
||||||
66F2402D738694F98729A441 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = "<group>"; };
|
66F2402D738694F98729A441 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||||
68356CB936A8814A3FEA66A8 /* EncryptionSyncListenerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSyncListenerProxy.swift; sourceTree = "<group>"; };
|
|
||||||
6861FE915C7B5466E6962BBA /* StartChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreen.swift; sourceTree = "<group>"; };
|
6861FE915C7B5466E6962BBA /* StartChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreen.swift; sourceTree = "<group>"; };
|
||||||
686BCFA37AC6C67FF973CE67 /* OnboardingBackgroundImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundImage.swift; sourceTree = "<group>"; };
|
686BCFA37AC6C67FF973CE67 /* OnboardingBackgroundImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundImage.swift; sourceTree = "<group>"; };
|
||||||
69219A908D7C22E6EE6689AE /* UserNotificationCenterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterSpy.swift; sourceTree = "<group>"; };
|
69219A908D7C22E6EE6689AE /* UserNotificationCenterSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterSpy.swift; sourceTree = "<group>"; };
|
||||||
@ -2649,7 +2646,6 @@
|
|||||||
832FC81F760220239E285294 /* Proxy */ = {
|
832FC81F760220239E285294 /* Proxy */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
68356CB936A8814A3FEA66A8 /* EncryptionSyncListenerProxy.swift */,
|
|
||||||
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */,
|
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */,
|
||||||
);
|
);
|
||||||
path = Proxy;
|
path = Proxy;
|
||||||
@ -4004,7 +4000,6 @@
|
|||||||
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */,
|
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */,
|
||||||
DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */,
|
DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */,
|
||||||
24A75F72EEB7561B82D726FD /* Date.swift in Sources */,
|
24A75F72EEB7561B82D726FD /* Date.swift in Sources */,
|
||||||
F91B4629E4AF51A4FE8E7608 /* EncryptionSyncListenerProxy.swift in Sources */,
|
|
||||||
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */,
|
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */,
|
||||||
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */,
|
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */,
|
||||||
A3E390675E9730C176B59E1B /* ImageProviderProtocol.swift in Sources */,
|
A3E390675E9730C176B59E1B /* ImageProviderProtocol.swift in Sources */,
|
||||||
@ -4262,7 +4257,6 @@
|
|||||||
9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */,
|
9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */,
|
||||||
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */,
|
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */,
|
||||||
B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */,
|
B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */,
|
||||||
69ABFBAF05D7EF11E7C88CEA /* EncryptionSyncListenerProxy.swift in Sources */,
|
|
||||||
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */,
|
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */,
|
||||||
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
|
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
|
||||||
63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */,
|
63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */,
|
||||||
@ -5289,7 +5283,7 @@
|
|||||||
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = "1.0.96-alpha";
|
version = "1.0.98-alpha";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
|
||||||
|
@ -111,8 +111,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "8bc8015237083035cb5f4a00d1eedb9ebbbb83c6",
|
"revision" : "985708733af7d2db1684f90f0a954854ca3a83ad",
|
||||||
"version" : "1.0.96-alpha"
|
"version" : "1.0.98-alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES"
|
||||||
|
runPostActionsOnFailure = "NO">
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
@ -29,6 +30,12 @@
|
|||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
codeCoverageEnabled = "YES"
|
codeCoverageEnabled = "YES"
|
||||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||||
|
<TestPlans>
|
||||||
|
<TestPlanReference
|
||||||
|
default = "YES"
|
||||||
|
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan">
|
||||||
|
</TestPlanReference>
|
||||||
|
</TestPlans>
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
@ -38,6 +45,10 @@
|
|||||||
ReferencedContainer = "container:ElementX.xcodeproj">
|
ReferencedContainer = "container:ElementX.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
<CodeCoverageTargets>
|
<CodeCoverageTargets>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
@ -47,12 +58,6 @@
|
|||||||
ReferencedContainer = "container:ElementX.xcodeproj">
|
ReferencedContainer = "container:ElementX.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</CodeCoverageTargets>
|
</CodeCoverageTargets>
|
||||||
<TestPlans>
|
|
||||||
<TestPlanReference
|
|
||||||
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan"
|
|
||||||
default = "YES">
|
|
||||||
</TestPlanReference>
|
|
||||||
</TestPlans>
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
@ -74,6 +79,8 @@
|
|||||||
ReferencedContainer = "container:ElementX.xcodeproj">
|
ReferencedContainer = "container:ElementX.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
<EnvironmentVariables>
|
<EnvironmentVariables>
|
||||||
<EnvironmentVariable
|
<EnvironmentVariable
|
||||||
key = "RUST_BACKTRACE"
|
key = "RUST_BACKTRACE"
|
||||||
@ -113,6 +120,8 @@
|
|||||||
ReferencedContainer = "container:ElementX.xcodeproj">
|
ReferencedContainer = "container:ElementX.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
|
@ -542,7 +542,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
// MARK: - Application State
|
// MARK: - Application State
|
||||||
|
|
||||||
private func stopSync() {
|
private func stopSync() {
|
||||||
userSession?.clientProxy.stopSync()
|
userSession?.clientProxy.pauseSync()
|
||||||
|
|
||||||
backgroundAppRefreshTask?.setTaskCompleted(success: true)
|
backgroundAppRefreshTask?.setTaskCompleted(success: true)
|
||||||
backgroundAppRefreshTask = nil
|
backgroundAppRefreshTask = nil
|
||||||
@ -602,7 +602,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
|
|
||||||
@objc
|
@objc
|
||||||
private func applicationWillTerminate() {
|
private func applicationWillTerminate() {
|
||||||
userSession?.clientProxy.stopSync()
|
userSession?.clientProxy.pauseSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
@ -616,7 +616,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
|||||||
backgroundTask = backgroundTaskService.startBackgroundTask(withName: "SuspendApp: \(UUID().uuidString)") { [weak self] in
|
backgroundTask = backgroundTaskService.startBackgroundTask(withName: "SuspendApp: \(UUID().uuidString)") { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
userSession?.clientProxy.stopSync()
|
userSession?.clientProxy.pauseSync()
|
||||||
|
|
||||||
backgroundTask?.stop()
|
backgroundTask?.stop()
|
||||||
backgroundTask = nil
|
backgroundTask = nil
|
||||||
|
@ -207,8 +207,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
|
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentMessageForwarding(let eventID), .messageForwarding):
|
case (.room, .presentMessageForwarding(let itemID), .messageForwarding):
|
||||||
presentMessageForwarding(for: eventID)
|
presentMessageForwarding(for: itemID)
|
||||||
case (.messageForwarding, .dismissMessageForwarding, .room):
|
case (.messageForwarding, .dismissMessageForwarding, .room):
|
||||||
break
|
break
|
||||||
case (.room, .presentMapNavigator(let mode), .mapNavigator):
|
case (.room, .presentMapNavigator(let mode), .mapNavigator):
|
||||||
@ -401,14 +401,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentReportContent(for itemID: String, from senderID: String) {
|
private func presentReportContent(for itemID: TimelineItemIdentifier, from senderID: String) {
|
||||||
guard let roomProxy else {
|
guard let roomProxy, let eventID = itemID.eventID else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationCoordinator = NavigationStackCoordinator()
|
let navigationCoordinator = NavigationStackCoordinator()
|
||||||
let userIndicatorController = UserIndicatorController(rootCoordinator: navigationCoordinator)
|
let userIndicatorController = UserIndicatorController(rootCoordinator: navigationCoordinator)
|
||||||
let parameters = ReportContentScreenCoordinatorParameters(itemID: itemID,
|
let parameters = ReportContentScreenCoordinatorParameters(eventID: eventID,
|
||||||
senderID: senderID,
|
senderID: senderID,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
userIndicatorController: userIndicatorController)
|
userIndicatorController: userIndicatorController)
|
||||||
@ -479,17 +479,17 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentEmojiPicker(for itemId: String) {
|
private func presentEmojiPicker(for itemID: TimelineItemIdentifier) {
|
||||||
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
|
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
|
||||||
itemId: itemId)
|
itemID: itemID)
|
||||||
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
|
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
|
||||||
coordinator.callback = { [weak self] action in
|
coordinator.callback = { [weak self] action in
|
||||||
switch action {
|
switch action {
|
||||||
case let .emojiSelected(emoji: emoji, itemId: itemId):
|
case let .emojiSelected(emoji: emoji, itemID: itemID):
|
||||||
MXLog.debug("Selected \(emoji) for \(itemId)")
|
MXLog.debug("Selected \(emoji) for \(itemID)")
|
||||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
Task {
|
Task {
|
||||||
await self?.timelineController?.toggleReaction(emoji, to: itemId)
|
await self?.timelineController?.toggleReaction(emoji, to: itemID)
|
||||||
}
|
}
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
@ -547,8 +547,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentMessageForwarding(for eventID: String) {
|
private func presentMessageForwarding(for itemID: TimelineItemIdentifier) {
|
||||||
guard let roomProxy, let roomSummaryProvider = userSession.clientProxy.roomSummaryProvider else {
|
guard let roomProxy, let roomSummaryProvider = userSession.clientProxy.roomSummaryProvider, let eventID = itemID.eventID else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,14 +624,14 @@ private extension RoomFlowCoordinator {
|
|||||||
enum State: StateType {
|
enum State: StateType {
|
||||||
case initial
|
case initial
|
||||||
case room(roomID: String)
|
case room(roomID: String)
|
||||||
case reportContent(roomID: String, itemID: String, senderID: String)
|
case reportContent(roomID: String, itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case roomDetails(roomID: String, isRoot: Bool)
|
case roomDetails(roomID: String, isRoot: Bool)
|
||||||
case mediaUploadPicker(roomID: String, source: MediaPickerScreenSource)
|
case mediaUploadPicker(roomID: String, source: MediaPickerScreenSource)
|
||||||
case mediaUploadPreview(roomID: String, fileURL: URL)
|
case mediaUploadPreview(roomID: String, fileURL: URL)
|
||||||
case emojiPicker(roomID: String, itemID: String)
|
case emojiPicker(roomID: String, itemID: TimelineItemIdentifier)
|
||||||
case mapNavigator(roomID: String)
|
case mapNavigator(roomID: String)
|
||||||
case roomMemberDetails(roomID: String, member: HashableRoomMemberWrapper)
|
case roomMemberDetails(roomID: String, member: HashableRoomMemberWrapper)
|
||||||
case messageForwarding(roomID: String, itemID: String)
|
case messageForwarding(roomID: String, itemID: TimelineItemIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventUserInfo {
|
struct EventUserInfo {
|
||||||
@ -643,7 +643,7 @@ private extension RoomFlowCoordinator {
|
|||||||
case presentRoom(roomID: String)
|
case presentRoom(roomID: String)
|
||||||
case dismissRoom
|
case dismissRoom
|
||||||
|
|
||||||
case presentReportContent(itemID: String, senderID: String)
|
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case dismissReportContent
|
case dismissReportContent
|
||||||
|
|
||||||
case presentRoomDetails(roomID: String)
|
case presentRoomDetails(roomID: String)
|
||||||
@ -655,7 +655,7 @@ private extension RoomFlowCoordinator {
|
|||||||
case presentMediaUploadPreview(fileURL: URL)
|
case presentMediaUploadPreview(fileURL: URL)
|
||||||
case dismissMediaUploadPreview
|
case dismissMediaUploadPreview
|
||||||
|
|
||||||
case presentEmojiPicker(itemID: String)
|
case presentEmojiPicker(itemID: TimelineItemIdentifier)
|
||||||
case dismissEmojiPicker
|
case dismissEmojiPicker
|
||||||
|
|
||||||
case presentMapNavigator(interactionMode: StaticLocationInteractionMode)
|
case presentMapNavigator(interactionMode: StaticLocationInteractionMode)
|
||||||
@ -664,7 +664,7 @@ private extension RoomFlowCoordinator {
|
|||||||
case presentRoomMemberDetails(member: HashableRoomMemberWrapper)
|
case presentRoomMemberDetails(member: HashableRoomMemberWrapper)
|
||||||
case dismissRoomMemberDetails
|
case dismissRoomMemberDetails
|
||||||
|
|
||||||
case presentMessageForwarding(itemID: String)
|
case presentMessageForwarding(itemID: TimelineItemIdentifier)
|
||||||
case dismissMessageForwarding
|
case dismissMessageForwarding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,23 @@ class SDKClientMock: SDKClientProtocol {
|
|||||||
return accountDataEventTypeReturnValue
|
return accountDataEventTypeReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - `app`
|
||||||
|
|
||||||
|
public var appCallsCount = 0
|
||||||
|
public var appCalled: Bool {
|
||||||
|
return appCallsCount > 0
|
||||||
|
}
|
||||||
|
public var appReturnValue: AppBuilder!
|
||||||
|
public var appClosure: (() -> AppBuilder)?
|
||||||
|
|
||||||
|
public func `app`() -> AppBuilder {
|
||||||
|
appCallsCount += 1
|
||||||
|
if let appClosure = appClosure {
|
||||||
|
return appClosure()
|
||||||
|
} else {
|
||||||
|
return appReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - `avatarUrl`
|
//MARK: - `avatarUrl`
|
||||||
|
|
||||||
public var avatarUrlThrowableError: Error?
|
public var avatarUrlThrowableError: Error?
|
||||||
@ -240,29 +257,21 @@ class SDKClientMock: SDKClientProtocol {
|
|||||||
return getMediaThumbnailMediaSourceWidthHeightReturnValue
|
return getMediaThumbnailMediaSourceWidthHeightReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - `getNotificationItem`
|
//MARK: - `getNotificationSettings`
|
||||||
|
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesThrowableError: Error?
|
public var getNotificationSettingsCallsCount = 0
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesCallsCount = 0
|
public var getNotificationSettingsCalled: Bool {
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesCalled: Bool {
|
return getNotificationSettingsCallsCount > 0
|
||||||
return getNotificationItemRoomIdEventIdFilterByPushRulesCallsCount > 0
|
|
||||||
}
|
}
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesReceivedArguments: (`roomId`: String, `eventId`: String, `filterByPushRules`: Bool)?
|
public var getNotificationSettingsReturnValue: NotificationSettings!
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesReceivedInvocations: [(`roomId`: String, `eventId`: String, `filterByPushRules`: Bool)] = []
|
public var getNotificationSettingsClosure: (() -> NotificationSettings)?
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesReturnValue: NotificationItem?
|
|
||||||
public var getNotificationItemRoomIdEventIdFilterByPushRulesClosure: ((String, String, Bool) throws -> NotificationItem?)?
|
|
||||||
|
|
||||||
public func `getNotificationItem`(`roomId`: String, `eventId`: String, `filterByPushRules`: Bool) throws -> NotificationItem? {
|
public func `getNotificationSettings`() -> NotificationSettings {
|
||||||
if let error = getNotificationItemRoomIdEventIdFilterByPushRulesThrowableError {
|
getNotificationSettingsCallsCount += 1
|
||||||
throw error
|
if let getNotificationSettingsClosure = getNotificationSettingsClosure {
|
||||||
}
|
return getNotificationSettingsClosure()
|
||||||
getNotificationItemRoomIdEventIdFilterByPushRulesCallsCount += 1
|
|
||||||
getNotificationItemRoomIdEventIdFilterByPushRulesReceivedArguments = (roomId: roomId, eventId: eventId, filterByPushRules: filterByPushRules)
|
|
||||||
getNotificationItemRoomIdEventIdFilterByPushRulesReceivedInvocations.append((roomId: roomId, eventId: eventId, filterByPushRules: filterByPushRules))
|
|
||||||
if let getNotificationItemRoomIdEventIdFilterByPushRulesClosure = getNotificationItemRoomIdEventIdFilterByPushRulesClosure {
|
|
||||||
return try getNotificationItemRoomIdEventIdFilterByPushRulesClosure(`roomId`, `eventId`, `filterByPushRules`)
|
|
||||||
} else {
|
} else {
|
||||||
return getNotificationItemRoomIdEventIdFilterByPushRulesReturnValue
|
return getNotificationSettingsReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - `getProfile`
|
//MARK: - `getProfile`
|
||||||
@ -384,54 +393,21 @@ class SDKClientMock: SDKClientProtocol {
|
|||||||
logoutCallsCount += 1
|
logoutCallsCount += 1
|
||||||
try logoutClosure?()
|
try logoutClosure?()
|
||||||
}
|
}
|
||||||
//MARK: - `mainEncryptionSync`
|
//MARK: - `notificationClient`
|
||||||
|
|
||||||
public var mainEncryptionSyncIdListenerThrowableError: Error?
|
public var notificationClientCallsCount = 0
|
||||||
public var mainEncryptionSyncIdListenerCallsCount = 0
|
public var notificationClientCalled: Bool {
|
||||||
public var mainEncryptionSyncIdListenerCalled: Bool {
|
return notificationClientCallsCount > 0
|
||||||
return mainEncryptionSyncIdListenerCallsCount > 0
|
|
||||||
}
|
}
|
||||||
public var mainEncryptionSyncIdListenerReceivedArguments: (`id`: String, `listener`: EncryptionSyncListener)?
|
public var notificationClientReturnValue: NotificationClientBuilder!
|
||||||
public var mainEncryptionSyncIdListenerReceivedInvocations: [(`id`: String, `listener`: EncryptionSyncListener)] = []
|
public var notificationClientClosure: (() -> NotificationClientBuilder)?
|
||||||
public var mainEncryptionSyncIdListenerReturnValue: EncryptionSync!
|
|
||||||
public var mainEncryptionSyncIdListenerClosure: ((String, EncryptionSyncListener) throws -> EncryptionSync)?
|
|
||||||
|
|
||||||
public func `mainEncryptionSync`(`id`: String, `listener`: EncryptionSyncListener) throws -> EncryptionSync {
|
public func `notificationClient`() -> NotificationClientBuilder {
|
||||||
if let error = mainEncryptionSyncIdListenerThrowableError {
|
notificationClientCallsCount += 1
|
||||||
throw error
|
if let notificationClientClosure = notificationClientClosure {
|
||||||
}
|
return notificationClientClosure()
|
||||||
mainEncryptionSyncIdListenerCallsCount += 1
|
|
||||||
mainEncryptionSyncIdListenerReceivedArguments = (id: id, listener: listener)
|
|
||||||
mainEncryptionSyncIdListenerReceivedInvocations.append((id: id, listener: listener))
|
|
||||||
if let mainEncryptionSyncIdListenerClosure = mainEncryptionSyncIdListenerClosure {
|
|
||||||
return try mainEncryptionSyncIdListenerClosure(`id`, `listener`)
|
|
||||||
} else {
|
} else {
|
||||||
return mainEncryptionSyncIdListenerReturnValue
|
return notificationClientReturnValue
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - `notificationEncryptionSync`
|
|
||||||
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersThrowableError: Error?
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersCallsCount = 0
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersCalled: Bool {
|
|
||||||
return notificationEncryptionSyncIdListenerNumItersCallsCount > 0
|
|
||||||
}
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersReceivedArguments: (`id`: String, `listener`: EncryptionSyncListener, `numIters`: UInt8)?
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersReceivedInvocations: [(`id`: String, `listener`: EncryptionSyncListener, `numIters`: UInt8)] = []
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersReturnValue: EncryptionSync!
|
|
||||||
public var notificationEncryptionSyncIdListenerNumItersClosure: ((String, EncryptionSyncListener, UInt8) throws -> EncryptionSync)?
|
|
||||||
|
|
||||||
public func `notificationEncryptionSync`(`id`: String, `listener`: EncryptionSyncListener, `numIters`: UInt8) throws -> EncryptionSync {
|
|
||||||
if let error = notificationEncryptionSyncIdListenerNumItersThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
notificationEncryptionSyncIdListenerNumItersCallsCount += 1
|
|
||||||
notificationEncryptionSyncIdListenerNumItersReceivedArguments = (id: id, listener: listener, numIters: numIters)
|
|
||||||
notificationEncryptionSyncIdListenerNumItersReceivedInvocations.append((id: id, listener: listener, numIters: numIters))
|
|
||||||
if let notificationEncryptionSyncIdListenerNumItersClosure = notificationEncryptionSyncIdListenerNumItersClosure {
|
|
||||||
return try notificationEncryptionSyncIdListenerNumItersClosure(`id`, `listener`, `numIters`)
|
|
||||||
} else {
|
|
||||||
return notificationEncryptionSyncIdListenerNumItersReturnValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - `restoreSession`
|
//MARK: - `restoreSession`
|
||||||
@ -454,48 +430,6 @@ class SDKClientMock: SDKClientProtocol {
|
|||||||
restoreSessionSessionReceivedInvocations.append(`session`)
|
restoreSessionSessionReceivedInvocations.append(`session`)
|
||||||
try restoreSessionSessionClosure?(`session`)
|
try restoreSessionSessionClosure?(`session`)
|
||||||
}
|
}
|
||||||
//MARK: - `roomListService`
|
|
||||||
|
|
||||||
public var roomListServiceThrowableError: Error?
|
|
||||||
public var roomListServiceCallsCount = 0
|
|
||||||
public var roomListServiceCalled: Bool {
|
|
||||||
return roomListServiceCallsCount > 0
|
|
||||||
}
|
|
||||||
public var roomListServiceReturnValue: RoomListService!
|
|
||||||
public var roomListServiceClosure: (() throws -> RoomListService)?
|
|
||||||
|
|
||||||
public func `roomListService`() throws -> RoomListService {
|
|
||||||
if let error = roomListServiceThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
roomListServiceCallsCount += 1
|
|
||||||
if let roomListServiceClosure = roomListServiceClosure {
|
|
||||||
return try roomListServiceClosure()
|
|
||||||
} else {
|
|
||||||
return roomListServiceReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - `roomListServiceWithEncryption`
|
|
||||||
|
|
||||||
public var roomListServiceWithEncryptionThrowableError: Error?
|
|
||||||
public var roomListServiceWithEncryptionCallsCount = 0
|
|
||||||
public var roomListServiceWithEncryptionCalled: Bool {
|
|
||||||
return roomListServiceWithEncryptionCallsCount > 0
|
|
||||||
}
|
|
||||||
public var roomListServiceWithEncryptionReturnValue: RoomListService!
|
|
||||||
public var roomListServiceWithEncryptionClosure: (() throws -> RoomListService)?
|
|
||||||
|
|
||||||
public func `roomListServiceWithEncryption`() throws -> RoomListService {
|
|
||||||
if let error = roomListServiceWithEncryptionThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
roomListServiceWithEncryptionCallsCount += 1
|
|
||||||
if let roomListServiceWithEncryptionClosure = roomListServiceWithEncryptionClosure {
|
|
||||||
return try roomListServiceWithEncryptionClosure()
|
|
||||||
} else {
|
|
||||||
return roomListServiceWithEncryptionReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - `rooms`
|
//MARK: - `rooms`
|
||||||
|
|
||||||
public var roomsCallsCount = 0
|
public var roomsCallsCount = 0
|
||||||
@ -615,22 +549,6 @@ class SDKClientMock: SDKClientProtocol {
|
|||||||
setDisplayNameNameReceivedInvocations.append(`name`)
|
setDisplayNameNameReceivedInvocations.append(`name`)
|
||||||
try setDisplayNameNameClosure?(`name`)
|
try setDisplayNameNameClosure?(`name`)
|
||||||
}
|
}
|
||||||
//MARK: - `setNotificationDelegate`
|
|
||||||
|
|
||||||
public var setNotificationDelegateNotificationDelegateCallsCount = 0
|
|
||||||
public var setNotificationDelegateNotificationDelegateCalled: Bool {
|
|
||||||
return setNotificationDelegateNotificationDelegateCallsCount > 0
|
|
||||||
}
|
|
||||||
public var setNotificationDelegateNotificationDelegateReceivedNotificationDelegate: NotificationDelegate?
|
|
||||||
public var setNotificationDelegateNotificationDelegateReceivedInvocations: [NotificationDelegate?] = []
|
|
||||||
public var setNotificationDelegateNotificationDelegateClosure: ((NotificationDelegate?) -> Void)?
|
|
||||||
|
|
||||||
public func `setNotificationDelegate`(`notificationDelegate`: NotificationDelegate?) {
|
|
||||||
setNotificationDelegateNotificationDelegateCallsCount += 1
|
|
||||||
setNotificationDelegateNotificationDelegateReceivedNotificationDelegate = notificationDelegate
|
|
||||||
setNotificationDelegateNotificationDelegateReceivedInvocations.append(`notificationDelegate`)
|
|
||||||
setNotificationDelegateNotificationDelegateClosure?(`notificationDelegate`)
|
|
||||||
}
|
|
||||||
//MARK: - `setPusher`
|
//MARK: - `setPusher`
|
||||||
|
|
||||||
public var setPusherIdentifiersKindAppDisplayNameDeviceDisplayNameProfileTagLangThrowableError: Error?
|
public var setPusherIdentifiersKindAppDisplayNameDeviceDisplayNameProfileTagLangThrowableError: Error?
|
||||||
|
@ -18,11 +18,11 @@ import SwiftUI
|
|||||||
|
|
||||||
struct EmojiPickerScreenCoordinatorParameters {
|
struct EmojiPickerScreenCoordinatorParameters {
|
||||||
let emojiProvider: EmojiProviderProtocol
|
let emojiProvider: EmojiProviderProtocol
|
||||||
let itemId: String
|
let itemID: TimelineItemIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EmojiPickerScreenCoordinatorAction {
|
enum EmojiPickerScreenCoordinatorAction {
|
||||||
case emojiSelected(emoji: String, itemId: String)
|
case emojiSelected(emoji: String, itemID: TimelineItemIdentifier)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case let .emojiSelected(emoji: emoji):
|
case let .emojiSelected(emoji: emoji):
|
||||||
self.callback?(.emojiSelected(emoji: emoji, itemId: self.parameters.itemId))
|
self.callback?(.emojiSelected(emoji: emoji, itemID: self.parameters.itemID))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
self.callback?(.dismiss)
|
self.callback?(.dismiss)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import Combine
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ReportContentScreenCoordinatorParameters {
|
struct ReportContentScreenCoordinatorParameters {
|
||||||
let itemID: String
|
let eventID: String
|
||||||
let senderID: String
|
let senderID: String
|
||||||
let roomProxy: RoomProxyProtocol
|
let roomProxy: RoomProxyProtocol
|
||||||
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
||||||
@ -39,7 +39,7 @@ final class ReportContentScreenCoordinator: CoordinatorProtocol {
|
|||||||
init(parameters: ReportContentScreenCoordinatorParameters) {
|
init(parameters: ReportContentScreenCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = ReportContentScreenViewModel(itemID: parameters.itemID, senderID: parameters.senderID, roomProxy: parameters.roomProxy)
|
viewModel = ReportContentScreenViewModel(eventID: parameters.eventID, senderID: parameters.senderID, roomProxy: parameters.roomProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
@ -20,7 +20,7 @@ import SwiftUI
|
|||||||
typealias ReportContentScreenViewModelType = StateStoreViewModel<ReportContentScreenViewState, ReportContentScreenViewAction>
|
typealias ReportContentScreenViewModelType = StateStoreViewModel<ReportContentScreenViewState, ReportContentScreenViewAction>
|
||||||
|
|
||||||
class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportContentScreenViewModelProtocol {
|
class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportContentScreenViewModelProtocol {
|
||||||
private let itemID: String
|
private let eventID: String
|
||||||
private let senderID: String
|
private let senderID: String
|
||||||
private let roomProxy: RoomProxyProtocol
|
private let roomProxy: RoomProxyProtocol
|
||||||
private let actionsSubject: PassthroughSubject<ReportContentScreenViewModelAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<ReportContentScreenViewModelAction, Never> = .init()
|
||||||
@ -29,8 +29,8 @@ class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportCont
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(itemID: String, senderID: String, roomProxy: RoomProxyProtocol) {
|
init(eventID: String, senderID: String, roomProxy: RoomProxyProtocol) {
|
||||||
self.itemID = itemID
|
self.eventID = eventID
|
||||||
self.senderID = senderID
|
self.senderID = senderID
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportCont
|
|||||||
private func submitReport() async {
|
private func submitReport() async {
|
||||||
actionsSubject.send(.submitStarted)
|
actionsSubject.send(.submitStarted)
|
||||||
|
|
||||||
if case let .failure(error) = await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
|
if case let .failure(error) = await roomProxy.reportContent(eventID, reason: state.bindings.reasonText) {
|
||||||
MXLog.error("Submit Report Content failed: \(error)")
|
MXLog.error("Submit Report Content failed: \(error)")
|
||||||
actionsSubject.send(.submitFailed(error: error))
|
actionsSubject.send(.submitFailed(error: error))
|
||||||
return
|
return
|
||||||
|
@ -84,7 +84,7 @@ struct ReportContentScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct ReportContentScreen_Previews: PreviewProvider {
|
struct ReportContentScreen_Previews: PreviewProvider {
|
||||||
static let viewModel = ReportContentScreenViewModel(itemID: "",
|
static let viewModel = ReportContentScreenViewModel(eventID: "",
|
||||||
senderID: "",
|
senderID: "",
|
||||||
roomProxy: RoomProxyMock(with: .init(displayName: nil)))
|
roomProxy: RoomProxyMock(with: .init(displayName: nil)))
|
||||||
|
|
||||||
|
@ -25,15 +25,15 @@ struct RoomScreenCoordinatorParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum RoomScreenCoordinatorAction {
|
enum RoomScreenCoordinatorAction {
|
||||||
case presentReportContent(itemID: String, senderID: String)
|
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case presentMediaUploadPicker(MediaPickerScreenSource)
|
case presentMediaUploadPicker(MediaPickerScreenSource)
|
||||||
case presentMediaUploadPreviewScreen(URL)
|
case presentMediaUploadPreviewScreen(URL)
|
||||||
case presentRoomDetails
|
case presentRoomDetails
|
||||||
case presentLocationPicker
|
case presentLocationPicker
|
||||||
case presentLocationViewer(body: String, geoURI: GeoURI, description: String?)
|
case presentLocationViewer(body: String, geoURI: GeoURI, description: String?)
|
||||||
case presentEmojiPicker(itemID: String)
|
case presentEmojiPicker(itemID: TimelineItemIdentifier)
|
||||||
case presentRoomMemberDetails(member: RoomMemberProxyProtocol)
|
case presentRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||||
case presentMessageForwarding(itemID: String)
|
case presentMessageForwarding(itemID: TimelineItemIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||||
|
@ -22,22 +22,22 @@ import OrderedCollections
|
|||||||
|
|
||||||
enum RoomScreenViewModelAction {
|
enum RoomScreenViewModelAction {
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
case displayEmojiPicker(itemID: String)
|
case displayEmojiPicker(itemID: TimelineItemIdentifier)
|
||||||
case displayReportContent(itemID: String, senderID: String)
|
case displayReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case displayCameraPicker
|
case displayCameraPicker
|
||||||
case displayMediaPicker
|
case displayMediaPicker
|
||||||
case displayDocumentPicker
|
case displayDocumentPicker
|
||||||
case displayLocationPicker
|
case displayLocationPicker
|
||||||
case displayMediaUploadPreviewScreen(url: URL)
|
case displayMediaUploadPreviewScreen(url: URL)
|
||||||
case displayRoomMemberDetails(member: RoomMemberProxyProtocol)
|
case displayRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||||
case displayMessageForwarding(itemID: String)
|
case displayMessageForwarding(itemID: TimelineItemIdentifier)
|
||||||
case displayLocation(body: String, geoURI: GeoURI, description: String?)
|
case displayLocation(body: String, geoURI: GeoURI, description: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomScreenComposerMode: Equatable {
|
enum RoomScreenComposerMode: Equatable {
|
||||||
case `default`
|
case `default`
|
||||||
case reply(itemID: String, replyDetails: TimelineItemReplyDetails)
|
case reply(itemID: TimelineItemIdentifier, replyDetails: TimelineItemReplyDetails)
|
||||||
case edit(originalItemId: String)
|
case edit(originalItemId: TimelineItemIdentifier)
|
||||||
|
|
||||||
var isEdit: Bool {
|
var isEdit: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
@ -52,21 +52,21 @@ enum RoomScreenComposerMode: Equatable {
|
|||||||
enum RoomScreenViewAction {
|
enum RoomScreenViewAction {
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
case paginateBackwards
|
case paginateBackwards
|
||||||
case itemAppeared(id: String)
|
case itemAppeared(itemID: TimelineItemIdentifier)
|
||||||
case itemDisappeared(id: String)
|
case itemDisappeared(itemID: TimelineItemIdentifier)
|
||||||
case itemTapped(id: String)
|
case itemTapped(itemID: TimelineItemIdentifier)
|
||||||
case linkClicked(url: URL)
|
case linkClicked(url: URL)
|
||||||
case sendMessage
|
case sendMessage
|
||||||
case toggleReaction(key: String, eventID: String)
|
case toggleReaction(key: String, itemID: TimelineItemIdentifier)
|
||||||
case cancelReply
|
case cancelReply
|
||||||
case cancelEdit
|
case cancelEdit
|
||||||
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
||||||
case markRoomAsRead
|
case markRoomAsRead
|
||||||
|
|
||||||
case timelineItemMenu(itemID: String)
|
case timelineItemMenu(itemID: TimelineItemIdentifier)
|
||||||
case timelineItemMenuAction(itemID: String, action: TimelineItemMenuAction)
|
case timelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
||||||
|
|
||||||
case displayEmojiPicker(itemID: String)
|
case displayEmojiPicker(itemID: TimelineItemIdentifier)
|
||||||
|
|
||||||
case displayCameraPicker
|
case displayCameraPicker
|
||||||
case displayMediaPicker
|
case displayMediaPicker
|
||||||
@ -76,10 +76,10 @@ enum RoomScreenViewAction {
|
|||||||
case handlePasteOrDrop(provider: NSItemProvider)
|
case handlePasteOrDrop(provider: NSItemProvider)
|
||||||
case tappedOnUser(userID: String)
|
case tappedOnUser(userID: String)
|
||||||
|
|
||||||
case reactionSummary(itemID: String, key: String)
|
case reactionSummary(itemID: TimelineItemIdentifier, key: String)
|
||||||
|
|
||||||
case retrySend(transactionID: String?)
|
case retrySend(itemID: TimelineItemIdentifier)
|
||||||
case cancelSend(transactionID: String?)
|
case cancelSend(itemID: TimelineItemIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomScreenViewState: BindableState {
|
struct RoomScreenViewState: BindableState {
|
||||||
@ -97,7 +97,7 @@ struct RoomScreenViewState: BindableState {
|
|||||||
|
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
|
|
||||||
var timelineItemMenuActionProvider: (@MainActor (_ itemId: String) -> TimelineItemMenuActions?)?
|
var timelineItemMenuActionProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> TimelineItemMenuActions?)?
|
||||||
|
|
||||||
var composerMode: RoomScreenComposerMode = .default
|
var composerMode: RoomScreenComposerMode = .default
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ struct RoomScreenViewState: BindableState {
|
|||||||
bindings.composerText.count == 0
|
bindings.composerText.count == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemIDs: [String] {
|
var timelineIDs: [String] {
|
||||||
itemsDictionary.keys.elements
|
itemsDictionary.keys.elements
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ struct RoomScreenViewStateBindings {
|
|||||||
|
|
||||||
/// The state of wether reactions listed on the timeline are expanded/collapsed.
|
/// The state of wether reactions listed on the timeline are expanded/collapsed.
|
||||||
/// Key is itemID, value is the collapsed state.
|
/// Key is itemID, value is the collapsed state.
|
||||||
var reactionsCollapsed: [String: Bool]
|
var reactionsCollapsed: [TimelineItemIdentifier: Bool]
|
||||||
|
|
||||||
/// A media item that will be previewed with QuickLook.
|
/// A media item that will be previewed with QuickLook.
|
||||||
var mediaPreviewItem: MediaPreviewItem?
|
var mediaPreviewItem: MediaPreviewItem?
|
||||||
@ -146,14 +146,14 @@ struct RoomScreenViewStateBindings {
|
|||||||
var reactionSummaryInfo: ReactionSummaryInfo?
|
var reactionSummaryInfo: ReactionSummaryInfo?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineItemActionMenuInfo: Identifiable, Equatable {
|
struct TimelineItemActionMenuInfo: Equatable, Identifiable {
|
||||||
static func == (lhs: TimelineItemActionMenuInfo, rhs: TimelineItemActionMenuInfo) -> Bool {
|
static func == (lhs: TimelineItemActionMenuInfo, rhs: TimelineItemActionMenuInfo) -> Bool {
|
||||||
lhs.id == rhs.id
|
lhs.id == rhs.id
|
||||||
}
|
}
|
||||||
|
|
||||||
let item: EventBasedTimelineItemProtocol
|
let item: EventBasedTimelineItemProtocol
|
||||||
|
|
||||||
var id: String {
|
var id: TimelineItemIdentifier {
|
||||||
item.id
|
item.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,7 +161,7 @@ struct TimelineItemActionMenuInfo: Identifiable, Equatable {
|
|||||||
struct SendFailedConfirmationDialogInfo: ConfirmationDialogProtocol {
|
struct SendFailedConfirmationDialogInfo: ConfirmationDialogProtocol {
|
||||||
let title = L10n.screenRoomRetrySendMenuTitle
|
let title = L10n.screenRoomRetrySendMenuTitle
|
||||||
|
|
||||||
let transactionID: String?
|
let itemID: TimelineItemIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReactionSummaryInfo: Identifiable {
|
struct ReactionSummaryInfo: Identifiable {
|
||||||
|
@ -120,14 +120,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
case .tappedOnUser(userID: let userID):
|
case .tappedOnUser(userID: let userID):
|
||||||
Task { await handleTappedUser(userID: userID) }
|
Task { await handleTappedUser(userID: userID) }
|
||||||
case .displayEmojiPicker(let itemID):
|
case .displayEmojiPicker(let itemID):
|
||||||
guard let item = state.itemsDictionary[itemID], item.isReactable else { return }
|
guard let item = state.itemsDictionary[itemID.timelineID], item.isReactable else { return }
|
||||||
callback?(.displayEmojiPicker(itemID: itemID))
|
callback?(.displayEmojiPicker(itemID: itemID))
|
||||||
case .reactionSummary(let itemId, let key):
|
case .reactionSummary(let itemID, let key):
|
||||||
showReactionSummary(for: itemId, selectedKey: key)
|
showReactionSummary(for: itemID, selectedKey: key)
|
||||||
case .retrySend(let transactionID):
|
case .retrySend(let itemID):
|
||||||
Task { await handleRetrySend(transactionID: transactionID) }
|
Task { await handleRetrySend(itemID: itemID) }
|
||||||
case .cancelSend(let transactionID):
|
case .cancelSend(let itemID):
|
||||||
Task { await handleCancelSend(transactionID: transactionID) }
|
Task { await handleCancelSend(itemID: itemID) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,9 +221,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
_ = await timelineController.markRoomAsRead()
|
_ = await timelineController.markRoomAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func itemTapped(with itemId: String) async {
|
private func itemTapped(with itemID: TimelineItemIdentifier) async {
|
||||||
state.showLoading = true
|
state.showLoading = true
|
||||||
let action = await timelineController.processItemTap(itemId)
|
let action = await timelineController.processItemTap(itemID)
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .displayMediaFile(let file, let title):
|
case .displayMediaFile(let file, let title):
|
||||||
@ -252,19 +252,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
if itemGroup.count == 1 {
|
if itemGroup.count == 1 {
|
||||||
if let firstItem = itemGroup.first {
|
if let firstItem = itemGroup.first {
|
||||||
timelineItemsDictionary.updateValue(updateViewModel(item: firstItem, groupStyle: .single),
|
timelineItemsDictionary.updateValue(updateViewModel(item: firstItem, groupStyle: .single),
|
||||||
forKey: firstItem.id)
|
forKey: firstItem.id.timelineID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (index, item) in itemGroup.enumerated() {
|
for (index, item) in itemGroup.enumerated() {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .first),
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .first),
|
||||||
forKey: item.id)
|
forKey: item.id.timelineID)
|
||||||
} else if index == itemGroup.count - 1 {
|
} else if index == itemGroup.count - 1 {
|
||||||
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .last),
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .last),
|
||||||
forKey: item.id)
|
forKey: item.id.timelineID)
|
||||||
} else {
|
} else {
|
||||||
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .middle),
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .middle),
|
||||||
forKey: item.id)
|
forKey: item.id.timelineID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,7 +274,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateViewModel(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewModel {
|
private func updateViewModel(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewModel {
|
||||||
if let timelineItemViewModel = state.itemsDictionary[item.id] {
|
if let timelineItemViewModel = state.itemsDictionary[item.id.timelineID] {
|
||||||
timelineItemViewModel.groupStyle = groupStyle
|
timelineItemViewModel.groupStyle = groupStyle
|
||||||
timelineItemViewModel.type = .init(item: item)
|
timelineItemViewModel.type = .init(item: item)
|
||||||
return timelineItemViewModel
|
return timelineItemViewModel
|
||||||
@ -360,7 +360,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: TimelineItemActionMenu
|
// MARK: TimelineItemActionMenu
|
||||||
|
|
||||||
private func showTimelineItemActionMenu(for itemID: String) {
|
private func showTimelineItemActionMenu(for itemID: TimelineItemIdentifier) {
|
||||||
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
// Don't show a menu for non-event based items.
|
// Don't show a menu for non-event based items.
|
||||||
@ -370,8 +370,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
state.bindings.actionMenuInfo = .init(item: eventTimelineItem)
|
state.bindings.actionMenuInfo = .init(item: eventTimelineItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func timelineItemMenuActionsForItemId(_ itemId: String) -> TimelineItemMenuActions? {
|
private func timelineItemMenuActionsForItemId(_ itemID: TimelineItemIdentifier) -> TimelineItemMenuActions? {
|
||||||
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemId }),
|
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
||||||
let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
// Don't show a context menu for non-event based items.
|
// Don't show a context menu for non-event based items.
|
||||||
return nil
|
return nil
|
||||||
@ -395,7 +395,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
]
|
]
|
||||||
|
|
||||||
if item.isMessage {
|
if item.isMessage {
|
||||||
actions.append(.forward(itemID: itemId))
|
actions.append(.forward(itemID: itemID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.isEditable {
|
if item.isEditable {
|
||||||
@ -426,7 +426,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||||
private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: String) {
|
private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||||
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
return
|
return
|
||||||
@ -449,7 +449,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
setComposerMode(.edit(originalItemId: messageTimelineItem.id))
|
setComposerMode(.edit(originalItemId: messageTimelineItem.id))
|
||||||
case .copyPermalink:
|
case .copyPermalink:
|
||||||
do {
|
do {
|
||||||
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventTimelineItem.id, roomIdentifier: timelineController.roomID,
|
guard let eventID = eventTimelineItem.id.eventID else {
|
||||||
|
displayError(.alert(L10n.errorFailedCreatingThePermalink))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventID, roomIdentifier: timelineController.roomID,
|
||||||
baseURL: appSettings.permalinkBaseURL)
|
baseURL: appSettings.permalinkBaseURL)
|
||||||
UIPasteboard.general.url = permalink
|
UIPasteboard.general.url = permalink
|
||||||
} catch {
|
} catch {
|
||||||
@ -457,9 +462,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
case .redact:
|
case .redact:
|
||||||
Task {
|
Task {
|
||||||
if eventTimelineItem.hasFailedToSend,
|
if eventTimelineItem.hasFailedToSend {
|
||||||
let transactionID = eventTimelineItem.properties.transactionID {
|
await timelineController.cancelSend(itemID)
|
||||||
await timelineController.cancelSend(transactionID)
|
|
||||||
} else {
|
} else {
|
||||||
await timelineController.redact(itemID)
|
await timelineController.redact(itemID)
|
||||||
}
|
}
|
||||||
@ -567,16 +571,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleRetrySend(transactionID: String?) async {
|
private func handleRetrySend(itemID: TimelineItemIdentifier) async {
|
||||||
guard let transactionID else {
|
guard let transactionID = itemID.transactionID else {
|
||||||
|
MXLog.error("Failed Retry Send: missing transaction ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await roomProxy.retrySend(transactionID: transactionID)
|
await roomProxy.retrySend(transactionID: transactionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleCancelSend(transactionID: String?) async {
|
private func handleCancelSend(itemID: TimelineItemIdentifier) async {
|
||||||
guard let transactionID else {
|
guard let transactionID = itemID.transactionID else {
|
||||||
|
MXLog.error("Failed Cancel Send: missing transaction ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +649,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Reaction summary
|
// MARK: - Reaction summary
|
||||||
|
|
||||||
private func showReactionSummary(for itemID: String, selectedKey: String) {
|
private func showReactionSummary(for itemID: TimelineItemIdentifier, selectedKey: String) {
|
||||||
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
return
|
return
|
||||||
@ -664,7 +670,7 @@ extension RoomScreenViewModel.Context {
|
|||||||
/// A function to make it easier to bind to reactions expand/collapsed state
|
/// A function to make it easier to bind to reactions expand/collapsed state
|
||||||
/// - Parameter itemID: The id of the timeline item the reacted to
|
/// - Parameter itemID: The id of the timeline item the reacted to
|
||||||
/// - Returns: Wether the reactions should show in the collapsed state, true by default.
|
/// - Returns: Wether the reactions should show in the collapsed state, true by default.
|
||||||
func reactionsCollapsedBinding(for itemID: String) -> Binding<Bool> {
|
func reactionsCollapsedBinding(for itemID: TimelineItemIdentifier) -> Binding<Bool> {
|
||||||
Binding(get: {
|
Binding(get: {
|
||||||
self.reactionsCollapsed[itemID] ?? true
|
self.reactionsCollapsed[itemID] ?? true
|
||||||
}, set: {
|
}, set: {
|
||||||
|
@ -215,7 +215,7 @@ struct MessageComposer_Previews: PreviewProvider {
|
|||||||
MessageComposer(text: .constant("Some message"),
|
MessageComposer(text: .constant("Some message"),
|
||||||
focused: .constant(false),
|
focused: .constant(false),
|
||||||
sendingDisabled: false,
|
sendingDisabled: false,
|
||||||
mode: .edit(originalItemId: UUID().uuidString),
|
mode: .edit(originalItemId: .init(timelineID: UUID().uuidString)),
|
||||||
sendAction: { },
|
sendAction: { },
|
||||||
pasteAction: { _ in },
|
pasteAction: { _ in },
|
||||||
replyCancellationAction: { },
|
replyCancellationAction: { },
|
||||||
@ -224,7 +224,7 @@ struct MessageComposer_Previews: PreviewProvider {
|
|||||||
MessageComposer(text: .constant(""),
|
MessageComposer(text: .constant(""),
|
||||||
focused: .constant(false),
|
focused: .constant(false),
|
||||||
sendingDisabled: false,
|
sendingDisabled: false,
|
||||||
mode: .reply(itemID: UUID().uuidString,
|
mode: .reply(itemID: .init(timelineID: UUID().uuidString),
|
||||||
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
||||||
contentType: .text(.init(body: "Text: Where the wild things are")))),
|
contentType: .text(.init(body: "Text: Where the wild things are")))),
|
||||||
sendAction: { },
|
sendAction: { },
|
||||||
@ -256,7 +256,7 @@ struct MessageComposer_Previews: PreviewProvider {
|
|||||||
MessageComposer(text: .constant(""),
|
MessageComposer(text: .constant(""),
|
||||||
focused: .constant(false),
|
focused: .constant(false),
|
||||||
sendingDisabled: false,
|
sendingDisabled: false,
|
||||||
mode: .reply(itemID: UUID().uuidString,
|
mode: .reply(itemID: .init(timelineID: UUID().uuidString),
|
||||||
replyDetails: replyDetails),
|
replyDetails: replyDetails),
|
||||||
sendAction: { },
|
sendAction: { },
|
||||||
pasteAction: { _ in },
|
pasteAction: { _ in },
|
||||||
|
@ -71,12 +71,12 @@ struct RoomScreen: View {
|
|||||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
.confirmationDialog(item: $context.sendFailedConfirmationDialogInfo, titleVisibility: .visible) { item in
|
.confirmationDialog(item: $context.sendFailedConfirmationDialogInfo, titleVisibility: .visible) { info in
|
||||||
Button(L10n.screenRoomRetrySendMenuSendAgainAction) {
|
Button(L10n.screenRoomRetrySendMenuSendAgainAction) {
|
||||||
context.send(viewAction: .retrySend(transactionID: item.transactionID))
|
context.send(viewAction: .retrySend(itemID: info.itemID))
|
||||||
}
|
}
|
||||||
Button(L10n.screenRoomRetrySendMenuRemoveAction, role: .destructive) {
|
Button(L10n.screenRoomRetrySendMenuRemoveAction, role: .destructive) {
|
||||||
context.send(viewAction: .cancelSend(transactionID: item.transactionID))
|
context.send(viewAction: .cancelSend(itemID: info.itemID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
context.send(viewAction: .displayEmojiPicker(itemID: timelineItem.id))
|
context.send(viewAction: .displayEmojiPicker(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
context.send(viewAction: .itemTapped(id: timelineItem.id))
|
context.send(viewAction: .itemTapped(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
// We need a tap gesture before this long one so that it doesn't
|
// We need a tap gesture before this long one so that it doesn't
|
||||||
// steal away the gestures from the scroll view
|
// steal away the gestures from the scroll view
|
||||||
@ -178,7 +178,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
if timelineItem.hasFailedToSend {
|
if timelineItem.hasFailedToSend {
|
||||||
backgroundedLocalizedSendInfo
|
backgroundedLocalizedSendInfo
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
context.sendFailedConfirmationDialogInfo = .init(transactionID: timelineItem.properties.transactionID)
|
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
backgroundedLocalizedSendInfo
|
backgroundedLocalizedSendInfo
|
||||||
@ -335,7 +335,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static var replies: some View {
|
static var replies: some View {
|
||||||
VStack {
|
VStack {
|
||||||
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: "",
|
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -344,7 +344,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
|||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
||||||
|
|
||||||
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: "",
|
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -67,7 +67,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
|||||||
context.send(viewAction: .displayEmojiPicker(itemID: timelineItem.id))
|
context.send(viewAction: .displayEmojiPicker(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
context.send(viewAction: .itemTapped(id: timelineItem.id))
|
context.send(viewAction: .itemTapped(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
// We need a tap gesture before this long one so that it doesn't
|
// We need a tap gesture before this long one so that it doesn't
|
||||||
// steal away the gestures from the scroll view
|
// steal away the gestures from the scroll view
|
||||||
|
@ -38,7 +38,7 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
struct TimelineItemStyler_Previews: PreviewProvider {
|
struct TimelineItemStyler_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static let base = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
static let base = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
|
||||||
static let sentNonLast: TextRoomTimelineItem = {
|
static let sentNonLast: TextRoomTimelineItem = {
|
||||||
var result = base
|
var result = base
|
||||||
@ -53,8 +53,8 @@ struct TimelineItemStyler_Previews: PreviewProvider {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sendingLast: TextRoomTimelineItem = {
|
static let sendingLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.itemIDs.last ?? UUID().uuidString
|
let id = viewModel.state.timelineIDs.last ?? UUID().uuidString
|
||||||
var result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
var result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
result.properties.deliveryStatus = .sending
|
result.properties.deliveryStatus = .sending
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
@ -66,22 +66,22 @@ struct TimelineItemStyler_Previews: PreviewProvider {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sentLast: TextRoomTimelineItem = {
|
static let sentLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.itemIDs.last ?? UUID().uuidString
|
let id = viewModel.state.timelineIDs.last ?? UUID().uuidString
|
||||||
let result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
let result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let ltrString = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house!"))
|
static let ltrString = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house!"))
|
||||||
|
|
||||||
static let rtlString = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת!"))
|
static let rtlString = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת!"))
|
||||||
|
|
||||||
static let ltrStringThatContainsRtl = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת! -- house!"))
|
static let ltrStringThatContainsRtl = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת! -- house!"))
|
||||||
|
|
||||||
static let rtlStringThatContainsLtr = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת! -- house! -- באמת!"))
|
static let rtlStringThatContainsLtr = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת! -- house! -- באמת!"))
|
||||||
|
|
||||||
static let ltrStringThatFinishesInRtl = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת!"))
|
static let ltrStringThatFinishesInRtl = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת!"))
|
||||||
|
|
||||||
static let rtlStringThatFinishesInLtr = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת! -- house!"))
|
static let rtlStringThatFinishesInLtr = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת! -- house!"))
|
||||||
|
|
||||||
static var testView: some View {
|
static var testView: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -23,7 +23,7 @@ struct TimelineItemStatusView: View {
|
|||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
private var isLastOutgoingMessage: Bool {
|
private var isLastOutgoingMessage: Bool {
|
||||||
context.viewState.itemIDs.last == timelineItem.id &&
|
context.viewState.timelineIDs.last == timelineItem.id.timelineID &&
|
||||||
timelineItem.isOutgoing
|
timelineItem.isOutgoing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ struct TimelineItemStatusView: View {
|
|||||||
.foregroundColor(.compound.iconCriticalPrimary)
|
.foregroundColor(.compound.iconCriticalPrimary)
|
||||||
.frame(width: 16, height: 16)
|
.frame(width: 16, height: 16)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
context.sendFailedConfirmationDialogInfo = .init(transactionID: timelineItem.properties.transactionID)
|
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ struct TimelineReactionsView: View {
|
|||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
@Environment(\.layoutDirection) private var layoutDirection: LayoutDirection
|
@Environment(\.layoutDirection) private var layoutDirection: LayoutDirection
|
||||||
|
|
||||||
let itemID: String
|
let itemID: TimelineItemIdentifier
|
||||||
let reactions: [AggregatedReaction]
|
let reactions: [AggregatedReaction]
|
||||||
@Binding var collapsed: Bool
|
@Binding var collapsed: Bool
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ struct TimelineReactionsView: View {
|
|||||||
CollapsibleFlowLayout(itemSpacing: 4, rowSpacing: 4, collapsed: collapsed, rowsBeforeCollapsible: 2) {
|
CollapsibleFlowLayout(itemSpacing: 4, rowSpacing: 4, collapsed: collapsed, rowsBeforeCollapsible: 2) {
|
||||||
ForEach(reactions, id: \.self) { reaction in
|
ForEach(reactions, id: \.self) { reaction in
|
||||||
TimelineReactionButton(itemID: itemID, reaction: reaction) { key in
|
TimelineReactionButton(itemID: itemID, reaction: reaction) { key in
|
||||||
context.send(viewAction: .toggleReaction(key: key, eventID: itemID))
|
context.send(viewAction: .toggleReaction(key: key, itemID: itemID))
|
||||||
} showReactionSummary: { key in
|
} showReactionSummary: { key in
|
||||||
context.send(viewAction: .reactionSummary(itemID: itemID, key: key))
|
context.send(viewAction: .reactionSummary(itemID: itemID, key: key))
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ struct TimelineCollapseButtonLabel: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineReactionButton: View {
|
struct TimelineReactionButton: View {
|
||||||
let itemID: String
|
let itemID: TimelineItemIdentifier
|
||||||
let reaction: AggregatedReaction
|
let reaction: AggregatedReaction
|
||||||
let toggleReaction: (String) -> Void
|
let toggleReaction: (String) -> Void
|
||||||
let showReactionSummary: (String) -> Void
|
let showReactionSummary: (String) -> Void
|
||||||
@ -131,11 +131,11 @@ struct TimelineReactionViewPreviewsContainer: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
TimelineReactionsView(itemID: "1", reactions: Array(AggregatedReaction.mockReactions.prefix(3)), collapsed: .constant(true))
|
TimelineReactionsView(itemID: .init(timelineID: "1"), reactions: Array(AggregatedReaction.mockReactions.prefix(3)), collapsed: .constant(true))
|
||||||
Divider()
|
Divider()
|
||||||
TimelineReactionsView(itemID: "2", reactions: AggregatedReaction.mockReactions, collapsed: $collapseState1)
|
TimelineReactionsView(itemID: .init(timelineID: "2"), reactions: AggregatedReaction.mockReactions, collapsed: $collapseState1)
|
||||||
Divider()
|
Divider()
|
||||||
TimelineReactionsView(itemID: "3", reactions: AggregatedReaction.mockReactions, collapsed: $collapseState2)
|
TimelineReactionsView(itemID: .init(timelineID: "3"), reactions: AggregatedReaction.mockReactions, collapsed: $collapseState2)
|
||||||
.environment(\.layoutDirection, .rightToLeft)
|
.environment(\.layoutDirection, .rightToLeft)
|
||||||
}
|
}
|
||||||
.background(Color.red)
|
.background(Color.red)
|
||||||
|
@ -78,7 +78,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider {
|
|||||||
ReadReceipt(userID: RoomMemberProxyMock.mockDan.userID, formattedTimestamp: "Way, way before")]
|
ReadReceipt(userID: RoomMemberProxyMock.mockDan.userID, formattedTimestamp: "Way, way before")]
|
||||||
|
|
||||||
static func mockTimelineItem(with receipts: [ReadReceipt]) -> TextRoomTimelineItem {
|
static func mockTimelineItem(with receipts: [ReadReceipt]) -> TextRoomTimelineItem {
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"),
|
isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"),
|
||||||
|
@ -44,7 +44,7 @@ struct AudioRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
AudioRoomTimelineView(timelineItem: AudioRoomTimelineItem(id: UUID().uuidString,
|
AudioRoomTimelineView(timelineItem: AudioRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -71,8 +71,8 @@ private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupS
|
|||||||
|
|
||||||
struct CollapsibleRoomTimelineView_Previews: PreviewProvider {
|
struct CollapsibleRoomTimelineView_Previews: PreviewProvider {
|
||||||
static let item = CollapsibleTimelineItem(items: [
|
static let item = CollapsibleTimelineItem(items: [
|
||||||
SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"),
|
SeparatorRoomTimelineItem(id: .init(timelineID: "First separator"), text: "This is a separator"),
|
||||||
SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator")
|
SeparatorRoomTimelineItem(id: .init(timelineID: "Second separator"), text: "This is another separator")
|
||||||
])
|
])
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
@ -55,7 +55,7 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, senderId: String) -> EmoteRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, senderId: String) -> EmoteRoomTimelineItem {
|
||||||
EmoteRoomTimelineItem(id: UUID().uuidString,
|
EmoteRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -55,7 +55,7 @@ private struct EncryptedHistoryLabelStyle: LabelStyle {
|
|||||||
|
|
||||||
struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider {
|
struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let item = EncryptedHistoryRoomTimelineItem(id: UUID().uuidString)
|
let item = EncryptedHistoryRoomTimelineItem(id: .init(timelineID: UUID().uuidString))
|
||||||
EncryptedHistoryRoomTimelineView(timelineItem: item)
|
EncryptedHistoryRoomTimelineView(timelineItem: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> EncryptedRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> EncryptedRoomTimelineItem {
|
||||||
EncryptedRoomTimelineItem(id: UUID().uuidString,
|
EncryptedRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: text,
|
body: text,
|
||||||
encryptionType: .unknown,
|
encryptionType: .unknown,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
|
@ -45,21 +45,21 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
VStack(spacing: 20.0) {
|
VStack(spacing: 20.0) {
|
||||||
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
|
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "document.pdf", source: nil, thumbnailSource: nil, contentType: nil)))
|
content: .init(body: "document.pdf", source: nil, thumbnailSource: nil, contentType: nil)))
|
||||||
|
|
||||||
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
|
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "document.docx", source: nil, thumbnailSource: nil, contentType: nil)))
|
content: .init(body: "document.docx", source: nil, thumbnailSource: nil, contentType: nil)))
|
||||||
|
|
||||||
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
|
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -66,21 +66,21 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
VStack(spacing: 20.0) {
|
VStack(spacing: 20.0) {
|
||||||
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
|
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil)))
|
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil)))
|
||||||
|
|
||||||
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
|
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil)))
|
content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil)))
|
||||||
|
|
||||||
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
|
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -91,14 +91,14 @@ struct LocationRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: UUID().uuidString,
|
LocationRoomTimelineView(timelineItem: .init(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "Fallback geo uri description")))
|
content: .init(body: "Fallback geo uri description")))
|
||||||
|
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: UUID().uuidString,
|
LocationRoomTimelineView(timelineItem: .init(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -66,7 +66,7 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, senderId: String) -> NoticeRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, senderId: String) -> NoticeRoomTimelineItem {
|
||||||
NoticeRoomTimelineItem(id: UUID().uuidString,
|
NoticeRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -41,8 +41,8 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider {
|
|||||||
static let item = ReadMarkerRoomTimelineItem()
|
static let item = ReadMarkerRoomTimelineItem()
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: "Separator", text: "Today")), groupStyle: .single))
|
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: .init(timelineID: "Separator"), text: "Today")), groupStyle: .single))
|
||||||
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: "",
|
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: .init(timelineID: ""),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -51,8 +51,8 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
ReadMarkerRoomTimelineView(timelineItem: item)
|
ReadMarkerRoomTimelineView(timelineItem: item)
|
||||||
|
|
||||||
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: "Separator", text: "Today")), groupStyle: .single))
|
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: .init(timelineID: "Separator"), text: "Today")), groupStyle: .single))
|
||||||
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: "",
|
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: .init(timelineID: ""),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -42,7 +42,7 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, senderId: String) -> RedactedRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, senderId: String) -> RedactedRoomTimelineItem {
|
||||||
RedactedRoomTimelineItem(id: UUID().uuidString,
|
RedactedRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: text,
|
body: text,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
@ -31,7 +31,7 @@ struct SeparatorRoomTimelineView: View {
|
|||||||
|
|
||||||
struct SeparatorRoomTimelineView_Previews: PreviewProvider {
|
struct SeparatorRoomTimelineView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let item = SeparatorRoomTimelineItem(id: "Separator", text: "This is a separator")
|
let item = SeparatorRoomTimelineItem(id: .init(timelineID: "Separator"), text: "This is a separator")
|
||||||
SeparatorRoomTimelineView(timelineItem: item)
|
SeparatorRoomTimelineView(timelineItem: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ struct StateRoomTimelineView_Previews: PreviewProvider {
|
|||||||
StateRoomTimelineView(timelineItem: item)
|
StateRoomTimelineView(timelineItem: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
static let item = StateRoomTimelineItem(id: UUID().uuidString,
|
static let item = StateRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: "Alice joined",
|
body: "Alice joined",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
@ -58,7 +58,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
VStack(spacing: 20.0) {
|
VStack(spacing: 20.0) {
|
||||||
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
|
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: "Some image",
|
body: "Some image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@ -66,7 +66,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
|||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
imageURL: URL.picturesDirectory))
|
imageURL: URL.picturesDirectory))
|
||||||
|
|
||||||
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
|
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: "Some other image",
|
body: "Some other image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@ -74,7 +74,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
|||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
imageURL: URL.picturesDirectory))
|
imageURL: URL.picturesDirectory))
|
||||||
|
|
||||||
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
|
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: "Blurhashed image",
|
body: "Blurhashed image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
@ -71,7 +71,7 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> TextRoomTimelineItem {
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
isOutgoing: isOutgoing,
|
isOutgoing: isOutgoing,
|
||||||
isEditable: isOutgoing,
|
isEditable: isOutgoing,
|
||||||
|
@ -63,7 +63,7 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> UnsupportedRoomTimelineItem {
|
private static func itemWith(text: String, timestamp: String, isOutgoing: Bool, senderId: String) -> UnsupportedRoomTimelineItem {
|
||||||
UnsupportedRoomTimelineItem(id: UUID().uuidString,
|
UnsupportedRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
body: text,
|
body: text,
|
||||||
eventType: "Some Event Type",
|
eventType: "Some Event Type",
|
||||||
error: "Something went wrong",
|
error: "Something went wrong",
|
||||||
|
@ -77,21 +77,21 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static var body: some View {
|
static var body: some View {
|
||||||
VStack(spacing: 20.0) {
|
VStack(spacing: 20.0) {
|
||||||
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
|
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "Some video", duration: 21, source: nil, thumbnailSource: nil)))
|
content: .init(body: "Some video", duration: 21, source: nil, thumbnailSource: nil)))
|
||||||
|
|
||||||
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
|
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "Bob"),
|
sender: .init(id: "Bob"),
|
||||||
content: .init(body: "Some other video", duration: 22, source: nil, thumbnailSource: nil)))
|
content: .init(body: "Some other video", duration: 22, source: nil, thumbnailSource: nil)))
|
||||||
|
|
||||||
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
|
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -46,7 +46,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
|||||||
case copyPermalink
|
case copyPermalink
|
||||||
case redact
|
case redact
|
||||||
case reply
|
case reply
|
||||||
case forward(itemID: String)
|
case forward(itemID: TimelineItemIdentifier)
|
||||||
case viewSource
|
case viewSource
|
||||||
case retryDecryption(sessionID: String)
|
case retryDecryption(sessionID: String)
|
||||||
case report
|
case report
|
||||||
@ -188,7 +188,7 @@ public struct TimelineItemMenu: View {
|
|||||||
private func reactionButton(for emoji: String) -> some View {
|
private func reactionButton(for emoji: String) -> some View {
|
||||||
Button {
|
Button {
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
context.send(viewAction: .toggleReaction(key: emoji, eventID: item.id))
|
context.send(viewAction: .toggleReaction(key: emoji, itemID: item.id))
|
||||||
} label: {
|
} label: {
|
||||||
Text(emoji)
|
Text(emoji)
|
||||||
.padding(8.0)
|
.padding(8.0)
|
||||||
|
@ -72,7 +72,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextMenuActionProvider: (@MainActor (_ itemId: String) -> TimelineItemMenuActions?)?
|
var contextMenuActionProvider: (@MainActor (_ itemID: TimelineItemIdentifier) -> TimelineItemMenuActions?)?
|
||||||
|
|
||||||
@Binding private var scrollToBottomButtonVisible: Bool
|
@Binding private var scrollToBottomButtonVisible: Bool
|
||||||
|
|
||||||
@ -214,10 +214,10 @@ class TimelineTableViewController: UIViewController {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
||||||
.onAppear {
|
.onAppear {
|
||||||
coordinator.send(viewAction: .itemAppeared(id: id))
|
coordinator.send(viewAction: .itemAppeared(itemID: viewModel.id))
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
coordinator.send(viewAction: .itemDisappeared(id: id))
|
coordinator.send(viewAction: .itemDisappeared(itemID: viewModel.id))
|
||||||
}
|
}
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
coordinator.send(viewAction: .linkClicked(url: url))
|
coordinator.send(viewAction: .linkClicked(url: url))
|
||||||
@ -231,7 +231,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSource?.defaultRowAnimation = .fade
|
// dataSource?.defaultRowAnimation = .automatic
|
||||||
|
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
}
|
}
|
||||||
@ -248,6 +248,8 @@ class TimelineTableViewController: UIViewController {
|
|||||||
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, String>()
|
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, String>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
snapshot.appendItems(timelineItemsIDs)
|
snapshot.appendItems(timelineItemsIDs)
|
||||||
|
|
||||||
|
MXLog.verbose("DIFF: \(snapshot.itemIdentifiers.difference(from: dataSource.snapshot().itemIdentifiers))")
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
// Probably redundant now we observe content size changes…
|
// Probably redundant now we observe content size changes…
|
||||||
@ -421,7 +423,7 @@ extension TimelineTableViewController {
|
|||||||
private extension UITableView {
|
private extension UITableView {
|
||||||
/// Returns the frame of the cell for a particular timeline item.
|
/// Returns the frame of the cell for a particular timeline item.
|
||||||
func cellFrame(for id: String) -> CGRect? {
|
func cellFrame(for id: String) -> CGRect? {
|
||||||
guard let timelineCell = visibleCells.last(where: { ($0 as? TimelineItemCell)?.item?.id == id }) else {
|
guard let timelineCell = visibleCells.last(where: { ($0 as? TimelineItemCell)?.item?.id.timelineID == id }) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
private var roomListService: RoomListService?
|
private var roomListService: RoomListService?
|
||||||
private var roomListStateUpdateTaskHandle: TaskHandle?
|
private var roomListStateUpdateTaskHandle: TaskHandle?
|
||||||
|
|
||||||
private var encryptionSyncService: EncryptionSync?
|
private var appService: App?
|
||||||
private var isEncryptionSyncing = false
|
private var appServiceUpdateTaskHandle: TaskHandle?
|
||||||
|
|
||||||
var roomSummaryProvider: RoomSummaryProviderProtocol?
|
var roomSummaryProvider: RoomSummaryProviderProtocol?
|
||||||
var inviteSummaryProvider: RoomSummaryProviderProtocol?
|
var inviteSummaryProvider: RoomSummaryProviderProtocol?
|
||||||
@ -53,7 +53,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
client.setDelegate(delegate: nil)
|
client.setDelegate(delegate: nil)
|
||||||
stopSync()
|
pauseSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
||||||
@ -73,7 +73,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
self?.callbacks.send(.updateRestorationToken)
|
self?.callbacks.send(.updateRestorationToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
await configureRoomListService()
|
await configureAppService()
|
||||||
|
|
||||||
loadUserAvatarURLFromCache()
|
loadUserAvatarURLFromCache()
|
||||||
}
|
}
|
||||||
@ -110,13 +110,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isSyncing: Bool {
|
var isSyncing: Bool {
|
||||||
let isRoomListServiceSyncing = roomListService?.isSyncing() ?? false
|
roomListService?.isSyncing() ?? false
|
||||||
|
|
||||||
if ServiceLocator.shared.settings.isEncryptionSyncEnabled {
|
|
||||||
return isRoomListServiceSyncing && isEncryptionSyncing
|
|
||||||
} else {
|
|
||||||
return isRoomListServiceSyncing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSync() {
|
func startSync() {
|
||||||
@ -125,30 +119,23 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startEncryptionSyncService()
|
do {
|
||||||
roomListService?.sync()
|
try appService?.start()
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed starting app service with error: \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopSync() {
|
func pauseSync() {
|
||||||
MXLog.info("Stopping sync")
|
MXLog.info("Stopping sync")
|
||||||
stopEncryptionSyncService()
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try roomListService?.stopSync()
|
try appService?.pause()
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed stopping room list service with error: \(error)")
|
MXLog.error("Failed pausing app service with error: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopEncryptionSyncService() {
|
|
||||||
guard isEncryptionSyncing else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isEncryptionSyncing = false
|
|
||||||
encryptionSyncService?.stop()
|
|
||||||
MXLog.info("Stopping Encryption Sync service")
|
|
||||||
}
|
|
||||||
|
|
||||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||||
await Task.dispatch(on: clientQueue) {
|
await Task.dispatch(on: clientQueue) {
|
||||||
do {
|
do {
|
||||||
@ -366,7 +353,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private func restartSync() {
|
private func restartSync() {
|
||||||
stopSync()
|
pauseSync()
|
||||||
startSync()
|
startSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,90 +372,83 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startEncryptionSyncService() {
|
private func configureAppService() async {
|
||||||
guard appSettings.isEncryptionSyncEnabled else {
|
guard appService == nil else {
|
||||||
return
|
|
||||||
}
|
|
||||||
configureEncryptionSyncService()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureEncryptionSyncService() {
|
|
||||||
do {
|
|
||||||
let listener = EncryptionSyncListenerProxy { [weak self] reason in
|
|
||||||
switch reason {
|
|
||||||
case .done:
|
|
||||||
MXLog.info("Encryption Sync has finished for user: \(self?.userID ?? "unknown")")
|
|
||||||
case .error(let msg):
|
|
||||||
MXLog.error("Encryption Sync has terminated for user: \(self?.userID ?? "unknown") for reason: \(msg)")
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Task {
|
|
||||||
self.configureEncryptionSyncService()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let encryptionSync = try client.mainEncryptionSync(id: "Main App", listener: listener)
|
|
||||||
isEncryptionSyncing = true
|
|
||||||
encryptionSyncService = encryptionSync
|
|
||||||
MXLog.info("Encryption sync started for user: \(userID)")
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Configure encryption sync failed with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureRoomListService() async {
|
|
||||||
guard roomListService == nil else {
|
|
||||||
fatalError("This shouldn't be called more than once")
|
fatalError("This shouldn't be called more than once")
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let roomListService = try appSettings.isEncryptionSyncEnabled ? client.roomListService() : client.roomListServiceWithEncryption()
|
let appService = try client
|
||||||
roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
|
.app()
|
||||||
guard let self else { return }
|
.withEncryptionSync(withCrossProcessLock: appSettings.isEncryptionSyncEnabled,
|
||||||
MXLog.info("Received room list update: \(state)")
|
appIdentifier: "MainApp")
|
||||||
|
.finish()
|
||||||
// Restart the room list sync on every error for now
|
let roomListService = appService.roomListService()
|
||||||
if state == .error {
|
|
||||||
self.restartSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The invites are available only when entering `running`
|
|
||||||
if state == .running {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
|
|
||||||
try await self.inviteSummaryProvider?.setRoomList(roomListService.invites())
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed configuring invites room list with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anything that's not `running` is interpreted as "Loading data"
|
|
||||||
if state == .running {
|
|
||||||
self.callbacks.send(.receivedSyncUpdate)
|
|
||||||
} else {
|
|
||||||
self.callbacks.send(.startedUpdating)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||||
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
eventStringBuilder: eventStringBuilder,
|
||||||
name: "AllRooms")
|
name: "AllRooms")
|
||||||
|
|
||||||
try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
|
try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
|
||||||
|
|
||||||
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
eventStringBuilder: eventStringBuilder,
|
||||||
name: "Invites")
|
name: "Invites")
|
||||||
|
|
||||||
|
self.appService = appService
|
||||||
self.roomListService = roomListService
|
self.roomListService = roomListService
|
||||||
|
|
||||||
|
appServiceUpdateTaskHandle = createAppServiceObserver(appService)
|
||||||
|
roomListStateUpdateTaskHandle = createRoomListServiceObserver(roomListService)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed building room list service with error: \(error)")
|
MXLog.error("Failed building room list service with error: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func createAppServiceObserver(_ appService: App) -> TaskHandle {
|
||||||
|
appService.state(listener: AppStateObserverProxy { [weak self] state in
|
||||||
|
guard let self else { return }
|
||||||
|
MXLog.info("Received app service update: \(state)")
|
||||||
|
switch state {
|
||||||
|
case .error:
|
||||||
|
restartSync()
|
||||||
|
case .terminated, .running:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
|
||||||
|
roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
|
||||||
|
MXLog.info("Received room list update: \(state)")
|
||||||
|
guard let self,
|
||||||
|
state != .error,
|
||||||
|
state != .terminated else {
|
||||||
|
// The app service is responsible of handling error and termination
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The invites are available only when entering `running`
|
||||||
|
if state == .running {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
guard let roomListService = self.roomListService else {
|
||||||
|
MXLog.error("Room list service is not configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
|
||||||
|
try await self.inviteSummaryProvider?.setRoomList(roomListService.invites())
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed configuring invites room list with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callbacks.send(.receivedSyncUpdate)
|
||||||
|
} else {
|
||||||
|
callbacks.send(.startedUpdating)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) {
|
private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) {
|
||||||
do {
|
do {
|
||||||
let roomListItem = try roomListService?.room(roomId: identifier)
|
let roomListItem = try roomListService?.room(roomId: identifier)
|
||||||
@ -496,10 +476,22 @@ extension ClientProxy: MediaLoaderProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AppStateObserverProxy: AppStateObserver {
|
||||||
|
private let onUpdateClosure: (AppState) -> Void
|
||||||
|
|
||||||
|
init(onUpdateClosure: @escaping (AppState) -> Void) {
|
||||||
|
self.onUpdateClosure = onUpdateClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
func onUpdate(state: AppState) {
|
||||||
|
onUpdateClosure(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class RoomListStateListenerProxy: RoomListServiceStateListener {
|
private class RoomListStateListenerProxy: RoomListServiceStateListener {
|
||||||
private let onUpdateClosure: (RoomListServiceState) -> Void
|
private let onUpdateClosure: (RoomListServiceState) -> Void
|
||||||
|
|
||||||
init(_ onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
|
init(onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
|
||||||
self.onUpdateClosure = onUpdateClosure
|
self.onUpdateClosure = onUpdateClosure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
func startSync()
|
func startSync()
|
||||||
|
|
||||||
func stopSync()
|
func pauseSync()
|
||||||
|
|
||||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError>
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError>
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class MockClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
func stopSync(completionHandler: () -> Void) { }
|
func stopSync(completionHandler: () -> Void) { }
|
||||||
|
|
||||||
func stopSync() { }
|
func pauseSync() { }
|
||||||
|
|
||||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||||
.failure(.failedRetrievingDirectRoom)
|
.failure(.failedRetrievingDirectRoom)
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2023 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import MatrixRustSDK
|
|
||||||
|
|
||||||
final class EncryptionSyncListenerProxy: EncryptionSyncListener {
|
|
||||||
private let didTerminateClosure: (EncryptionSyncTerminationReason) -> Void
|
|
||||||
|
|
||||||
init(_ didTerminateClosure: @escaping (EncryptionSyncTerminationReason) -> Void) {
|
|
||||||
self.didTerminateClosure = didTerminateClosure
|
|
||||||
}
|
|
||||||
|
|
||||||
func didTerminate(reason: EncryptionSyncTerminationReason) {
|
|
||||||
didTerminateClosure(reason)
|
|
||||||
}
|
|
||||||
}
|
|
@ -67,15 +67,12 @@ extension NotificationItemProxyProtocol {
|
|||||||
struct NotificationItemProxy: NotificationItemProxyProtocol {
|
struct NotificationItemProxy: NotificationItemProxyProtocol {
|
||||||
let notificationItem: NotificationItem
|
let notificationItem: NotificationItem
|
||||||
let receiverID: String
|
let receiverID: String
|
||||||
|
let roomID: String
|
||||||
|
|
||||||
var event: TimelineEventProxyProtocol {
|
var event: TimelineEventProxyProtocol {
|
||||||
TimelineEventProxy(timelineEvent: notificationItem.event)
|
TimelineEventProxy(timelineEvent: notificationItem.event)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomID: String {
|
|
||||||
notificationItem.roomInfo.id
|
|
||||||
}
|
|
||||||
|
|
||||||
var senderDisplayName: String? {
|
var senderDisplayName: String? {
|
||||||
notificationItem.senderInfo.displayName
|
notificationItem.senderInfo.displayName
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,22 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fetchLastMessage(roomListItem: RoomListItemProtocol) -> EventTimelineItem? {
|
||||||
|
class FetchResult {
|
||||||
|
var latestRoomEvent: EventTimelineItem?
|
||||||
|
}
|
||||||
|
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
let result = FetchResult()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
result.latestRoomEvent = await roomListItem.latestEvent()
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
semaphore.wait()
|
||||||
|
return result.latestRoomEvent
|
||||||
|
}
|
||||||
|
|
||||||
private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary {
|
private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary {
|
||||||
guard let roomListItem = try? roomListService.room(roomId: identifier) else {
|
guard let roomListItem = try? roomListService.room(roomId: identifier) else {
|
||||||
MXLog.error("\(name): Failed finding room with id: \(identifier)")
|
MXLog.error("\(name): Failed finding room with id: \(identifier)")
|
||||||
@ -150,8 +166,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
var attributedLastMessage: AttributedString?
|
var attributedLastMessage: AttributedString?
|
||||||
var lastMessageFormattedTimestamp: String?
|
var lastMessageFormattedTimestamp: String?
|
||||||
|
|
||||||
if let latestRoomMessage = roomListItem.latestEvent() {
|
if let latestRoomMessage = fetchLastMessage(roomListItem: roomListItem) {
|
||||||
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
|
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage, id: 0)
|
||||||
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
|
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
|
||||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
|
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
|
||||||
}
|
}
|
||||||
|
@ -19,15 +19,15 @@ import Foundation
|
|||||||
enum RoomTimelineItemFixtures {
|
enum RoomTimelineItemFixtures {
|
||||||
/// The default timeline items used in Xcode previews etc.
|
/// The default timeline items used in Xcode previews etc.
|
||||||
static var `default`: [RoomTimelineItemProtocol] = [
|
static var `default`: [RoomTimelineItemProtocol] = [
|
||||||
SeparatorRoomTimelineItem(id: "Yesterday", text: "Yesterday"),
|
SeparatorRoomTimelineItem(id: .init(timelineID: "Yesterday"), text: "Yesterday"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "10:10 AM",
|
timestamp: "10:10 AM",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "", displayName: "Jacob"),
|
sender: .init(id: "", displayName: "Jacob"),
|
||||||
content: .init(body: "That looks so good!"),
|
content: .init(body: "That looks so good!"),
|
||||||
properties: RoomTimelineItemProperties(isEdited: true)),
|
properties: RoomTimelineItemProperties(isEdited: true)),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "10:11 AM",
|
timestamp: "10:11 AM",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -36,7 +36,7 @@ enum RoomTimelineItemFixtures {
|
|||||||
properties: RoomTimelineItemProperties(reactions: [
|
properties: RoomTimelineItemProperties(reactions: [
|
||||||
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me"])
|
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me"])
|
||||||
])),
|
])),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "10:11 AM",
|
timestamp: "10:11 AM",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -46,21 +46,21 @@ enum RoomTimelineItemFixtures {
|
|||||||
AggregatedReaction(accountOwnerID: "me", key: "🙏", senders: ["helena"]),
|
AggregatedReaction(accountOwnerID: "me", key: "🙏", senders: ["helena"]),
|
||||||
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me", "helena", "jacob"])
|
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me", "helena", "jacob"])
|
||||||
])),
|
])),
|
||||||
SeparatorRoomTimelineItem(id: "Today", text: "Today"),
|
SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "5 PM",
|
timestamp: "5 PM",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "", displayName: "Helena"),
|
sender: .init(id: "", displayName: "Helena"),
|
||||||
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
|
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
|
||||||
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
|
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "5 PM",
|
timestamp: "5 PM",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
sender: .init(id: "", displayName: "Bob"),
|
sender: .init(id: "", displayName: "Bob"),
|
||||||
content: .init(body: "And John's speech was amazing!")),
|
content: .init(body: "And John's speech was amazing!")),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "5 PM",
|
timestamp: "5 PM",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
@ -71,7 +71,7 @@ enum RoomTimelineItemFixtures {
|
|||||||
ReadReceipt(userID: "bob", formattedTimestamp: nil),
|
ReadReceipt(userID: "bob", formattedTimestamp: nil),
|
||||||
ReadReceipt(userID: "charlie", formattedTimestamp: nil),
|
ReadReceipt(userID: "charlie", formattedTimestamp: nil),
|
||||||
ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
|
ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "5 PM",
|
timestamp: "5 PM",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -210,7 +210,7 @@ enum RoomTimelineItemFixtures {
|
|||||||
|
|
||||||
private extension TextRoomTimelineItem {
|
private extension TextRoomTimelineItem {
|
||||||
init(text: String, senderDisplayName: String) {
|
init(text: String, senderDisplayName: String) {
|
||||||
self.init(id: UUID().uuidString,
|
self.init(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "10:47 am",
|
timestamp: "10:47 am",
|
||||||
isOutgoing: senderDisplayName == "Alice",
|
isOutgoing: senderDisplayName == "Alice",
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
|
@ -169,37 +169,47 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension TimelineItem {
|
private extension TimelineItem {
|
||||||
var debugIdentifier: String {
|
var debugIdentifier: DebugIdentifier {
|
||||||
if let virtualTimelineItem = asVirtual() {
|
if let virtualTimelineItem = asVirtual() {
|
||||||
return virtualTimelineItem.debugIdentifier
|
return virtualTimelineItem.debugIdentifier
|
||||||
} else if let eventTimelineItem = asEvent() {
|
} else if let eventTimelineItem = asEvent() {
|
||||||
return eventTimelineItem.uniqueIdentifier()
|
return .event(timelineID: String(uniqueId()),
|
||||||
|
eventID: eventTimelineItem.eventId(),
|
||||||
|
transactionID: eventTimelineItem.transactionId())
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UnknownTimelineItem"
|
return .unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension TimelineItemProxy {
|
private extension TimelineItemProxy {
|
||||||
var debugIdentifier: String {
|
var debugIdentifier: DebugIdentifier {
|
||||||
switch self {
|
switch self {
|
||||||
case .event(let eventTimelineItem):
|
case .event(let eventTimelineItem):
|
||||||
return eventTimelineItem.item.uniqueIdentifier()
|
return .event(timelineID: eventTimelineItem.id.timelineID,
|
||||||
|
eventID: eventTimelineItem.id.eventID,
|
||||||
|
transactionID: eventTimelineItem.id.transactionID)
|
||||||
case .virtual(let virtualTimelineItem):
|
case .virtual(let virtualTimelineItem):
|
||||||
return virtualTimelineItem.debugIdentifier
|
return virtualTimelineItem.debugIdentifier
|
||||||
case .unknown:
|
case .unknown:
|
||||||
return "UnknownTimelineItem"
|
return .unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension VirtualTimelineItem {
|
private extension VirtualTimelineItem {
|
||||||
var debugIdentifier: String {
|
var debugIdentifier: DebugIdentifier {
|
||||||
switch self {
|
switch self {
|
||||||
case .dayDivider(let timestamp):
|
case .dayDivider(let timestamp):
|
||||||
return "DayDiviver(\(timestamp))"
|
return .virtual("DayDiviver(\(timestamp))")
|
||||||
case .readMarker:
|
case .readMarker:
|
||||||
return "ReadMarker"
|
return .virtual("ReadMarker")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DebugIdentifier {
|
||||||
|
case event(timelineID: String?, eventID: String?, transactionID: String?)
|
||||||
|
case virtual(String)
|
||||||
|
case unknown
|
||||||
|
}
|
||||||
|
@ -51,23 +51,23 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> { .success(()) }
|
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> { .success(()) }
|
||||||
|
|
||||||
func processItemAppearance(_ itemID: String) async { }
|
func processItemAppearance(_ itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func processItemDisappearance(_ itemID: String) async { }
|
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction { .none }
|
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction { .none }
|
||||||
|
|
||||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async { }
|
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async { }
|
||||||
|
|
||||||
func toggleReaction(_ reaction: String, to itemID: String) async { }
|
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func editMessage(_ newMessage: String, original itemID: String) async { }
|
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func redact(_ itemID: String) async { }
|
func redact(_ itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func cancelSend(_ transactionID: String) async { }
|
func cancelSend(_ itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
|
||||||
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
|
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> {
|
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> {
|
||||||
guard roomProxy.hasUnreadNotifications,
|
guard roomProxy.hasUnreadNotifications,
|
||||||
let eventID = timelineItems.last?.id
|
let eventID = timelineItems.last?.id.eventID
|
||||||
else { return .success(()) }
|
else { return .success(()) }
|
||||||
|
|
||||||
switch await roomProxy.sendReadReceipt(for: eventID) {
|
switch await roomProxy.sendReadReceipt(for: eventID) {
|
||||||
@ -95,7 +95,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processItemAppearance(_ itemID: String) async {
|
func processItemAppearance(_ itemID: TimelineItemIdentifier) async {
|
||||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -105,9 +105,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processItemDisappearance(_ itemID: String) { }
|
func processItemDisappearance(_ itemID: TimelineItemIdentifier) { }
|
||||||
|
|
||||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction {
|
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction {
|
||||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
@ -121,14 +121,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async {
|
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async {
|
||||||
|
var inReplyTo: String?
|
||||||
if itemID == nil {
|
if itemID == nil {
|
||||||
MXLog.info("Send message in \(roomID)")
|
MXLog.info("Send message in \(roomID)")
|
||||||
} else {
|
} else if let eventID = itemID?.eventID {
|
||||||
|
inReplyTo = eventID
|
||||||
MXLog.info("Send reply in \(roomID)")
|
MXLog.info("Send reply in \(roomID)")
|
||||||
|
} else {
|
||||||
|
MXLog.error("Send reply in \(roomID) failed: missing event ID")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch await roomProxy.sendMessage(message, inReplyTo: itemID) {
|
switch await roomProxy.sendMessage(message, inReplyTo: inReplyTo) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Finished sending message")
|
MXLog.info("Finished sending message")
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@ -136,9 +141,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleReaction(_ reaction: String, to itemID: String) async {
|
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async {
|
||||||
MXLog.info("Toggle reaction in \(roomID)")
|
MXLog.info("Toggle reaction in \(roomID)")
|
||||||
switch await roomProxy.toggleReaction(reaction, to: itemID) {
|
guard let eventID = itemID.eventID else {
|
||||||
|
MXLog.error("Failed toggling reaction: missing eventID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await roomProxy.toggleReaction(reaction, to: eventID) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Finished toggling reaction")
|
MXLog.info("Finished toggling reaction")
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@ -146,28 +156,32 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func editMessage(_ newMessage: String, original itemID: String) async {
|
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async {
|
||||||
MXLog.info("Edit message in \(roomID)")
|
MXLog.info("Edit message in \(roomID)")
|
||||||
if let timelineItem = timelineItems.first(where: { $0.id == itemID }),
|
if let timelineItem = timelineItems.first(where: { $0.id == itemID }),
|
||||||
let item = timelineItem as? EventBasedTimelineItemProtocol,
|
let item = timelineItem as? EventBasedTimelineItemProtocol,
|
||||||
item.hasFailedToSend,
|
item.hasFailedToSend {
|
||||||
let transactionID = item.properties.transactionID {
|
|
||||||
MXLog.info("Editing a failed echo, will cancel and resend it as a new message")
|
MXLog.info("Editing a failed echo, will cancel and resend it as a new message")
|
||||||
await cancelSend(transactionID)
|
await cancelSend(itemID)
|
||||||
await sendMessage(newMessage)
|
await sendMessage(newMessage)
|
||||||
} else {
|
} else if let eventID = itemID.eventID {
|
||||||
switch await roomProxy.editMessage(newMessage, original: itemID) {
|
switch await roomProxy.editMessage(newMessage, original: eventID) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Finished editing message")
|
MXLog.info("Finished editing message")
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Failed editing message with error: \(error)")
|
MXLog.error("Failed editing message with error: \(error)")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
MXLog.error("Editing failed: missing identifiers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func redact(_ itemID: String) async {
|
func redact(_ itemID: TimelineItemIdentifier) async {
|
||||||
MXLog.info("Send redaction in \(roomID)")
|
MXLog.info("Send redaction in \(roomID)")
|
||||||
switch await roomProxy.redact(itemID) {
|
guard let eventID = itemID.eventID else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch await roomProxy.redact(eventID) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Finished redacting message")
|
MXLog.info("Finished redacting message")
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@ -175,14 +189,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelSend(_ transactionID: String) async {
|
func cancelSend(_ itemID: TimelineItemIdentifier) async {
|
||||||
|
guard let transactionID = itemID.transactionID else {
|
||||||
|
MXLog.error("Failed cancelling send, missing transaction ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
MXLog.info("Cancelling send in \(roomID)")
|
MXLog.info("Cancelling send in \(roomID)")
|
||||||
await roomProxy.cancelSend(transactionID: transactionID)
|
await roomProxy.cancelSend(transactionID: transactionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle this parallel to the timeline items so we're not forced
|
// Handle this parallel to the timeline items so we're not forced
|
||||||
// to bundle the Rust side objects within them
|
// to bundle the Rust side objects within them
|
||||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
|
||||||
for timelineItemProxy in timelineProvider.itemProxies {
|
for timelineItemProxy in timelineProvider.itemProxies {
|
||||||
switch timelineItemProxy {
|
switch timelineItemProxy {
|
||||||
case .event(let item):
|
case .event(let item):
|
||||||
@ -339,7 +357,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
// Separators without stable identifiers cause UI glitches
|
// Separators without stable identifiers cause UI glitches
|
||||||
let identifier = "\(chunkIndex)-\(dateString)"
|
let identifier = "\(chunkIndex)-\(dateString)"
|
||||||
return SeparatorRoomTimelineItem(id: identifier, text: dateString)
|
return SeparatorRoomTimelineItem(id: .init(timelineID: identifier), text: dateString)
|
||||||
case .readMarker:
|
case .readMarker:
|
||||||
return ReadMarkerRoomTimelineItem()
|
return ReadMarkerRoomTimelineItem()
|
||||||
}
|
}
|
||||||
@ -373,12 +391,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func fetchEventDetails(for timelineItem: EventBasedMessageTimelineItemProtocol, refetchOnError: Bool) {
|
private func fetchEventDetails(for timelineItem: EventBasedMessageTimelineItemProtocol, refetchOnError: Bool) {
|
||||||
|
guard let eventID = timelineItem.id.eventID else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch timelineItem.replyDetails {
|
switch timelineItem.replyDetails {
|
||||||
case .notLoaded:
|
case .notLoaded:
|
||||||
roomProxy.fetchDetails(for: timelineItem.id)
|
roomProxy.fetchDetails(for: eventID)
|
||||||
case .error:
|
case .error:
|
||||||
if refetchOnError {
|
if refetchOnError {
|
||||||
roomProxy.fetchDetails(for: timelineItem.id)
|
roomProxy.fetchDetails(for: eventID)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -41,27 +41,27 @@ protocol RoomTimelineControllerProtocol {
|
|||||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||||
|
|
||||||
func processItemAppearance(_ itemID: String) async
|
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func processItemDisappearance(_ itemID: String) async
|
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction
|
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction
|
||||||
|
|
||||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||||
|
|
||||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError>
|
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError>
|
||||||
|
|
||||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async
|
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async
|
||||||
|
|
||||||
func editMessage(_ newMessage: String, original itemID: String) async
|
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func toggleReaction(_ reaction: String, to itemID: String) async
|
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func redact(_ itemID: String) async
|
func redact(_ itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func cancelSend(_ transactionID: String) async
|
func cancelSend(_ itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo
|
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo
|
||||||
|
|
||||||
func retryDecryption(for sessionID: String) async
|
func retryDecryption(for sessionID: String) async
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,4 @@ struct RoomTimelineItemProperties: Hashable {
|
|||||||
var deliveryStatus: TimelineItemDeliveryStatus?
|
var deliveryStatus: TimelineItemDeliveryStatus?
|
||||||
/// The read receipts of the item, ordered from newest to oldest
|
/// The read receipts of the item, ordered from newest to oldest
|
||||||
var orderedReadReceipts: [ReadReceipt] = []
|
var orderedReadReceipts: [ReadReceipt] = []
|
||||||
/// The original transaction id transmitted by the client
|
|
||||||
var transactionID: String?
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
struct TimelineItemIdentifier: Hashable {
|
||||||
|
/// Stable id across state changes of the timeline item, it uniquely identifies an item in a timeline.
|
||||||
|
/// It's value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
||||||
|
let timelineID: String
|
||||||
|
|
||||||
|
/// Uniquely identifies the timeline item from the server side.
|
||||||
|
/// Only available for EventTimelineItem and only when the item is returned by the server.
|
||||||
|
var eventID: String?
|
||||||
|
|
||||||
|
/// Uniquely identfies the local echo of the timeline item.
|
||||||
|
/// Only available for sent EventTimelineItem that have not been returned by the server yet.
|
||||||
|
var transactionID: String?
|
||||||
|
}
|
||||||
|
|
||||||
/// A light wrapper around timeline items returned from Rust.
|
/// A light wrapper around timeline items returned from Rust.
|
||||||
enum TimelineItemProxy {
|
enum TimelineItemProxy {
|
||||||
case event(EventTimelineItemProxy)
|
case event(EventTimelineItemProxy)
|
||||||
@ -25,7 +39,7 @@ enum TimelineItemProxy {
|
|||||||
|
|
||||||
init(item: MatrixRustSDK.TimelineItem) {
|
init(item: MatrixRustSDK.TimelineItem) {
|
||||||
if let eventItem = item.asEvent() {
|
if let eventItem = item.asEvent() {
|
||||||
self = .event(EventTimelineItemProxy(item: eventItem))
|
self = .event(EventTimelineItemProxy(item: eventItem, id: item.uniqueId()))
|
||||||
} else if let virtualItem = item.asVirtual() {
|
} else if let virtualItem = item.asVirtual() {
|
||||||
self = .virtual(virtualItem)
|
self = .virtual(virtualItem)
|
||||||
} else {
|
} else {
|
||||||
@ -44,17 +58,11 @@ enum TimelineItemDeliveryStatus: Hashable {
|
|||||||
/// A light wrapper around event timeline items returned from Rust.
|
/// A light wrapper around event timeline items returned from Rust.
|
||||||
struct EventTimelineItemProxy {
|
struct EventTimelineItemProxy {
|
||||||
let item: MatrixRustSDK.EventTimelineItem
|
let item: MatrixRustSDK.EventTimelineItem
|
||||||
|
let id: TimelineItemIdentifier
|
||||||
|
|
||||||
init(item: MatrixRustSDK.EventTimelineItem) {
|
init(item: MatrixRustSDK.EventTimelineItem, id: UInt64) {
|
||||||
self.item = item
|
self.item = item
|
||||||
}
|
self.id = TimelineItemIdentifier(timelineID: String(id), eventID: item.eventId(), transactionID: item.transactionId())
|
||||||
|
|
||||||
var id: String {
|
|
||||||
item.uniqueIdentifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
var transactionID: String? {
|
|
||||||
item.transactionId()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var deliveryStatus: TimelineItemDeliveryStatus? {
|
var deliveryStatus: TimelineItemDeliveryStatus? {
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Hashable {
|
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
|
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
|
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
let isEditable: Bool
|
let isEditable: Bool
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Identifiable, Hashable {
|
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let items: [RoomTimelineItemProtocol]
|
let items: [RoomTimelineItemProtocol]
|
||||||
let itemIDs: [String]
|
let itemIDs: [TimelineItemIdentifier]
|
||||||
|
|
||||||
init(items: [RoomTimelineItemProtocol]) {
|
init(items: [RoomTimelineItemProtocol]) {
|
||||||
self.items = items
|
self.items = items
|
||||||
@ -38,12 +38,4 @@ struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Identifiable, Hashable
|
|||||||
// Technically not a correct implementation of equality as the items themselves could be updated.
|
// Technically not a correct implementation of equality as the items themselves could be updated.
|
||||||
lhs.id == rhs.id && lhs.itemIDs == rhs.itemIDs
|
lhs.id == rhs.id && lhs.itemIDs == rhs.itemIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Hashable
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
// Technically not a correct implementation of hashing as the items themselves could be updated.
|
|
||||||
hasher.combine(id)
|
|
||||||
hasher.combine(itemIDs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||||
enum EncryptionType: Hashable {
|
enum EncryptionType: Hashable {
|
||||||
case megolmV1AesSha2(sessionId: String)
|
case megolmV1AesSha2(sessionId: String)
|
||||||
case olmV1Curve25519AesSha2(senderKey: String)
|
case olmV1Curve25519AesSha2(senderKey: String)
|
||||||
case unknown
|
case unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let body: String
|
let body: String
|
||||||
let encryptionType: EncryptionType
|
let encryptionType: EncryptionType
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let body: String
|
let body: String
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let body: String
|
let body: String
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let body: String
|
let body: String
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let body: String
|
let body: String
|
||||||
|
|
||||||
let eventType: String
|
let eventType: String
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||||
let id = "paginationIndicatorTimelineItemIdentifier"
|
let id = TimelineItemIdentifier(timelineID: "paginationIndicatorTimelineItemIdentifier")
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||||
let id = "readMarkerTimelineItemIdentifier"
|
let id = TimelineItemIdentifier(timelineID: "readMarkerTimelineItemIdentifier")
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||||
let id: String
|
let id: TimelineItemIdentifier
|
||||||
let text: String
|
let text: String
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||||
let id: String = UUID().uuidString
|
let id = TimelineItemIdentifier(timelineID: UUID().uuidString)
|
||||||
let name: String?
|
let name: String?
|
||||||
}
|
}
|
||||||
|
@ -138,8 +138,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
blurhash: imageInfo.blurhash,
|
blurhash: imageInfo.blurhash,
|
||||||
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
|
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -190,8 +189,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -208,8 +206,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -226,8 +223,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -244,8 +240,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -262,8 +257,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -280,8 +274,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -298,8 +291,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@ -316,13 +308,12 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||||
transactionID: eventItemProxy.transactionID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
||||||
reactions.map { reaction in
|
reactions.map { reaction in
|
||||||
AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: reaction.senders)
|
AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: reaction.senders.map(\.senderId))
|
||||||
}
|
}
|
||||||
.sorted { a, b in
|
.sorted { a, b in
|
||||||
// Sort by count and then by key for a consistence experience.
|
// Sort by count and then by key for a consistence experience.
|
||||||
|
@ -18,5 +18,5 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol RoomTimelineItemProtocol {
|
protocol RoomTimelineItemProtocol {
|
||||||
var id: String { get }
|
var id: TimelineItemIdentifier { get }
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ final class RoomTimelineItemViewModel: Identifiable, Equatable, ObservableObject
|
|||||||
@Published var type: RoomTimelineItemType
|
@Published var type: RoomTimelineItemType
|
||||||
@Published var groupStyle: TimelineGroupStyle
|
@Published var groupStyle: TimelineGroupStyle
|
||||||
|
|
||||||
var id: String {
|
var id: TimelineItemIdentifier {
|
||||||
type.id
|
type.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ enum RoomTimelineItemType: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var id: String {
|
var id: TimelineItemIdentifier {
|
||||||
switch self {
|
switch self {
|
||||||
case .text(let item as RoomTimelineItemProtocol),
|
case .text(let item as RoomTimelineItemProtocol),
|
||||||
.separator(let item as RoomTimelineItemProtocol),
|
.separator(let item as RoomTimelineItemProtocol),
|
||||||
|
@ -474,7 +474,7 @@ class MockScreen: Identifiable {
|
|||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .reportContent:
|
case .reportContent:
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let coordinator = ReportContentScreenCoordinator(parameters: .init(itemID: "test",
|
let coordinator = ReportContentScreenCoordinator(parameters: .init(eventID: "test",
|
||||||
senderID: RoomMemberProxyMock.mockAlice.userID,
|
senderID: RoomMemberProxyMock.mockAlice.userID,
|
||||||
roomProxy: RoomProxyMock(with: .init(displayName: "test"))))
|
roomProxy: RoomProxyMock(with: .init(displayName: "test"))))
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
@ -71,26 +71,9 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)")
|
MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let userSession = try NSEUserSession(credentials: credentials)
|
let userSession = try NSEUserSession(credentials: credentials, isEncryptionSyncEnabled: settings.isEncryptionSyncEnabled)
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
var itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId)
|
guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else {
|
||||||
if settings.isEncryptionSyncEnabled,
|
|
||||||
itemProxy?.isEncrypted == true,
|
|
||||||
let _ = try? userSession.startEncryptionSync() {
|
|
||||||
// TODO: The following wait with a timeout should be handled by the SDK
|
|
||||||
// We try to decrypt the notification for 10 seconds at most
|
|
||||||
let date = Date()
|
|
||||||
repeat {
|
|
||||||
// if the sync terminated we try one last time then we break from the loop
|
|
||||||
guard userSession.isSyncing else {
|
|
||||||
itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId)
|
|
||||||
} while itemProxy?.isEncrypted == true && date.timeIntervalSinceNow > -10
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let itemProxy else {
|
|
||||||
MXLog.info("\(tag) no notification for the event, discard")
|
MXLog.info("\(tag) no notification for the event, discard")
|
||||||
return discard()
|
return discard()
|
||||||
}
|
}
|
||||||
@ -146,7 +129,6 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
private func cleanUp() {
|
private func cleanUp() {
|
||||||
handler = nil
|
handler = nil
|
||||||
modifiedContent = nil
|
modifiedContent = nil
|
||||||
userSession?.stopEncryptionSync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -18,56 +18,39 @@ import Foundation
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
final class NSEUserSession {
|
final class NSEUserSession {
|
||||||
private let client: ClientProtocol
|
private let baseClient: Client
|
||||||
|
private let notificationClient: NotificationClient
|
||||||
private let userID: String
|
private let userID: String
|
||||||
private var encryptionSyncService: EncryptionSync?
|
private(set) lazy var mediaProvider: MediaProviderProtocol = MediaProvider(mediaLoader: MediaLoader(client: baseClient),
|
||||||
private(set) lazy var mediaProvider: MediaProviderProtocol = MediaProvider(mediaLoader: MediaLoader(client: client),
|
|
||||||
imageCache: .onlyOnDisk,
|
imageCache: .onlyOnDisk,
|
||||||
backgroundTaskService: nil)
|
backgroundTaskService: nil)
|
||||||
|
|
||||||
var isSyncing: Bool {
|
init(credentials: KeychainCredentials, isEncryptionSyncEnabled: Bool) throws {
|
||||||
encryptionSyncService != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
init(credentials: KeychainCredentials) throws {
|
|
||||||
userID = credentials.userID
|
userID = credentials.userID
|
||||||
let builder = ClientBuilder()
|
baseClient = try ClientBuilder()
|
||||||
.basePath(path: URL.sessionsBaseDirectory.path)
|
.basePath(path: URL.sessionsBaseDirectory.path)
|
||||||
.username(username: credentials.userID)
|
.username(username: credentials.userID)
|
||||||
|
.build()
|
||||||
|
|
||||||
client = try builder.build()
|
try baseClient.restoreSession(session: credentials.restorationToken.session)
|
||||||
try client.restoreSession(session: credentials.restorationToken.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
func startEncryptionSync() throws {
|
notificationClient = baseClient
|
||||||
let listener = EncryptionSyncListenerProxy { [weak self] reason in
|
.notificationClient()
|
||||||
MXLog.info("NSE: Encryption sync terminated for user: \(self?.userID ?? "unknown") with reason: \(reason)")
|
.retryDecryption(withCrossProcessLock: isEncryptionSyncEnabled)
|
||||||
self?.encryptionSyncService = nil
|
.finish()
|
||||||
}
|
|
||||||
encryptionSyncService = try client.notificationEncryptionSync(id: "NSE", listener: listener, numIters: 2)
|
|
||||||
MXLog.info("NSE: Encryption sync started for user: \(userID)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationItemProxy(roomID: String, eventID: String) async -> NotificationItemProxyProtocol? {
|
func notificationItemProxy(roomID: String, eventID: String) async -> NotificationItemProxyProtocol? {
|
||||||
await Task.dispatch(on: .global()) {
|
await Task.dispatch(on: .global()) {
|
||||||
do {
|
do {
|
||||||
guard let notification = try self.client.getNotificationItem(roomId: roomID, eventId: eventID, filterByPushRules: false) else {
|
guard let notification = try self.notificationClient.getNotification(roomId: roomID, eventId: eventID) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return NotificationItemProxy(notificationItem: notification, receiverID: self.userID)
|
return NotificationItemProxy(notificationItem: notification, receiverID: self.userID, roomID: roomID)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("NSE: Could not get notification's content creating an empty notification instead, error: \(error)")
|
MXLog.error("NSE: Could not get notification's content creating an empty notification instead, error: \(error)")
|
||||||
return EmptyNotificationItemProxy(eventID: eventID, roomID: roomID, receiverID: self.userID)
|
return EmptyNotificationItemProxy(eventID: eventID, roomID: roomID, receiverID: self.userID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopEncryptionSync() {
|
|
||||||
encryptionSyncService?.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
MXLog.info("NSE: NSEUserSession deinit called for user: \(userID)")
|
|
||||||
stopEncryptionSync()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -252,39 +252,39 @@ class LoggingTests: XCTestCase {
|
|||||||
func testTimelineContentIsRedacted() throws {
|
func testTimelineContentIsRedacted() throws {
|
||||||
// Given timeline items that contain text
|
// Given timeline items that contain text
|
||||||
let textAttributedString = "TextAttributed"
|
let textAttributedString = "TextAttributed"
|
||||||
let textMessage = TextRoomTimelineItem(id: "mytextmessage",
|
let textMessage = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "sender"),
|
sender: .init(id: "sender"),
|
||||||
content: .init(body: "TextString", formattedBody: AttributedString(textAttributedString)))
|
content: .init(body: "TextString", formattedBody: AttributedString(textAttributedString)))
|
||||||
let noticeAttributedString = "NoticeAttributed"
|
let noticeAttributedString = "NoticeAttributed"
|
||||||
let noticeMessage = NoticeRoomTimelineItem(id: "mynoticemessage",
|
let noticeMessage = NoticeRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "sender"),
|
sender: .init(id: "sender"),
|
||||||
content: .init(body: "NoticeString", formattedBody: AttributedString(noticeAttributedString)))
|
content: .init(body: "NoticeString", formattedBody: AttributedString(noticeAttributedString)))
|
||||||
let emoteAttributedString = "EmoteAttributed"
|
let emoteAttributedString = "EmoteAttributed"
|
||||||
let emoteMessage = EmoteRoomTimelineItem(id: "myemotemessage",
|
let emoteMessage = EmoteRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "sender"),
|
sender: .init(id: "sender"),
|
||||||
content: .init(body: "EmoteString", formattedBody: AttributedString(emoteAttributedString)))
|
content: .init(body: "EmoteString", formattedBody: AttributedString(emoteAttributedString)))
|
||||||
let imageMessage = ImageRoomTimelineItem(id: "myimagemessage",
|
let imageMessage = ImageRoomTimelineItem(id: .init(timelineID: "myimagemessage"),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "sender"),
|
sender: .init(id: "sender"),
|
||||||
content: .init(body: "ImageString", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), thumbnailSource: nil))
|
content: .init(body: "ImageString", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), thumbnailSource: nil))
|
||||||
let videoMessage = VideoRoomTimelineItem(id: "myvideomessage",
|
let videoMessage = VideoRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "sender"),
|
sender: .init(id: "sender"),
|
||||||
content: .init(body: "VideoString", duration: 0, source: nil, thumbnailSource: nil))
|
content: .init(body: "VideoString", duration: 0, source: nil, thumbnailSource: nil))
|
||||||
let fileMessage = FileRoomTimelineItem(id: "myfilemessage",
|
let fileMessage = FileRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
@ -310,25 +310,25 @@ class LoggingTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content = try String(contentsOf: logFile)
|
let content = try String(contentsOf: logFile)
|
||||||
XCTAssertTrue(content.contains(textMessage.id))
|
XCTAssertTrue(content.contains(textMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(textMessage.body))
|
XCTAssertFalse(content.contains(textMessage.body))
|
||||||
XCTAssertFalse(content.contains(textAttributedString))
|
XCTAssertFalse(content.contains(textAttributedString))
|
||||||
|
|
||||||
XCTAssertTrue(content.contains(noticeMessage.id))
|
XCTAssertTrue(content.contains(noticeMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(noticeMessage.body))
|
XCTAssertFalse(content.contains(noticeMessage.body))
|
||||||
XCTAssertFalse(content.contains(noticeAttributedString))
|
XCTAssertFalse(content.contains(noticeAttributedString))
|
||||||
|
|
||||||
XCTAssertTrue(content.contains(emoteMessage.id))
|
XCTAssertTrue(content.contains(emoteMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(emoteMessage.body))
|
XCTAssertFalse(content.contains(emoteMessage.body))
|
||||||
XCTAssertFalse(content.contains(emoteAttributedString))
|
XCTAssertFalse(content.contains(emoteAttributedString))
|
||||||
|
|
||||||
XCTAssertTrue(content.contains(imageMessage.id))
|
XCTAssertTrue(content.contains(imageMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(imageMessage.body))
|
XCTAssertFalse(content.contains(imageMessage.body))
|
||||||
|
|
||||||
XCTAssertTrue(content.contains(videoMessage.id))
|
XCTAssertTrue(content.contains(videoMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(videoMessage.body))
|
XCTAssertFalse(content.contains(videoMessage.body))
|
||||||
|
|
||||||
XCTAssertTrue(content.contains(fileMessage.id))
|
XCTAssertTrue(content.contains(fileMessage.id.timelineID))
|
||||||
XCTAssertFalse(content.contains(fileMessage.body))
|
XCTAssertFalse(content.contains(fileMessage.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import XCTest
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class ReportContentScreenViewModelTests: XCTestCase {
|
class ReportContentScreenViewModelTests: XCTestCase {
|
||||||
let itemID = "test-id"
|
let eventID = "test-id"
|
||||||
let senderID = "@meany:server.com"
|
let senderID = "@meany:server.com"
|
||||||
let reportReason = "I don't like it."
|
let reportReason = "I don't like it."
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
|||||||
// Given the report content view for some content.
|
// Given the report content view for some content.
|
||||||
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
|
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
|
||||||
roomProxy.reportContentReasonReturnValue = .success(())
|
roomProxy.reportContentReasonReturnValue = .success(())
|
||||||
let viewModel = ReportContentScreenViewModel(itemID: itemID,
|
let viewModel = ReportContentScreenViewModel(eventID: eventID,
|
||||||
senderID: senderID,
|
senderID: senderID,
|
||||||
roomProxy: roomProxy)
|
roomProxy: roomProxy)
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
// Then the content should be reported, but the user should not be included.
|
// Then the content should be reported, but the user should not be included.
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, itemID, "The event ID should match the content being reported.")
|
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, eventID, "The event ID should match the content being reported.")
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
|
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
|
||||||
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 0, "A call to ignore a user should not have been made.")
|
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 0, "A call to ignore a user should not have been made.")
|
||||||
XCTAssertNil(roomProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.")
|
XCTAssertNil(roomProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.")
|
||||||
@ -54,7 +54,7 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
|||||||
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
|
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
|
||||||
roomProxy.reportContentReasonReturnValue = .success(())
|
roomProxy.reportContentReasonReturnValue = .success(())
|
||||||
roomProxy.ignoreUserReturnValue = .success(())
|
roomProxy.ignoreUserReturnValue = .success(())
|
||||||
let viewModel = ReportContentScreenViewModel(itemID: itemID,
|
let viewModel = ReportContentScreenViewModel(eventID: eventID,
|
||||||
senderID: senderID,
|
senderID: senderID,
|
||||||
roomProxy: roomProxy)
|
roomProxy: roomProxy)
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
// Then the content should be reported, and the user should be ignored.
|
// Then the content should be reported, and the user should be ignored.
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, itemID, "The event ID should match the content being reported.")
|
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, eventID, "The event ID should match the content being reported.")
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
|
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
|
||||||
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 1, "A call should have been made to ignore the sender.")
|
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 1, "A call should have been made to ignore the sender.")
|
||||||
XCTAssertEqual(roomProxy.ignoreUserReceivedUserID, senderID, "The ignored user ID should match the sender.")
|
XCTAssertEqual(roomProxy.ignoreUserReceivedUserID, senderID, "The ignored user ID should match the sender.")
|
||||||
|
@ -289,7 +289,8 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
viewModel.context.send(viewAction: .retrySend(transactionID: "test retry send id"))
|
viewModel.context.send(viewAction: .retrySend(itemID: .init(timelineID: UUID().uuidString, transactionID: "test retry send id")))
|
||||||
|
await Task.yield()
|
||||||
try? await Task.sleep(for: .microseconds(500))
|
try? await Task.sleep(for: .microseconds(500))
|
||||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1)
|
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1)
|
||||||
XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"])
|
XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"])
|
||||||
@ -308,7 +309,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
viewModel.context.send(viewAction: .retrySend(transactionID: nil))
|
viewModel.context.send(viewAction: .retrySend(itemID: .init(timelineID: UUID().uuidString)))
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 0)
|
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 0)
|
||||||
}
|
}
|
||||||
@ -326,7 +327,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
viewModel.context.send(viewAction: .cancelSend(transactionID: "test cancel send id"))
|
viewModel.context.send(viewAction: .cancelSend(itemID: .init(timelineID: UUID().uuidString, transactionID: "test cancel send id")))
|
||||||
try? await Task.sleep(for: .microseconds(500))
|
try? await Task.sleep(for: .microseconds(500))
|
||||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1)
|
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1)
|
||||||
XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"])
|
XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"])
|
||||||
@ -345,12 +346,12 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
viewModel.context.send(viewAction: .cancelSend(transactionID: nil))
|
viewModel.context.send(viewAction: .cancelSend(itemID: .init(timelineID: UUID().uuidString)))
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 0)
|
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMarkAsRead() async {
|
func testMarkAsRead() async throws {
|
||||||
// Setup
|
// Setup
|
||||||
let notificationCenterMock = NotificationCenterMock()
|
let notificationCenterMock = NotificationCenterMock()
|
||||||
let timelineController = MockRoomTimelineController()
|
let timelineController = MockRoomTimelineController()
|
||||||
@ -365,7 +366,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
notificationCenterProtocol: notificationCenterMock)
|
notificationCenterProtocol: notificationCenterMock)
|
||||||
|
|
||||||
viewModel.context.send(viewAction: .markRoomAsRead)
|
viewModel.context.send(viewAction: .markRoomAsRead)
|
||||||
await Task.yield()
|
try await Task.sleep(for: .microseconds(100))
|
||||||
XCTAssertEqual(notificationCenterMock.postNameObjectReceivedArguments?.aName, .roomMarkedAsRead)
|
XCTAssertEqual(notificationCenterMock.postNameObjectReceivedArguments?.aName, .roomMarkedAsRead)
|
||||||
let roomID = notificationCenterMock.postNameObjectReceivedArguments?.anObject as? String
|
let roomID = notificationCenterMock.postNameObjectReceivedArguments?.anObject as? String
|
||||||
XCTAssertEqual(roomID, roomProxyMock.id)
|
XCTAssertEqual(roomID, roomProxyMock.id)
|
||||||
@ -375,7 +376,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
private extension TextRoomTimelineItem {
|
private extension TextRoomTimelineItem {
|
||||||
init(text: String, sender: String, addReactions: Bool = false) {
|
init(text: String, sender: String, addReactions: Bool = false) {
|
||||||
let reactions = addReactions ? [AggregatedReaction(accountOwnerID: "bob", key: "🦄", senders: [sender])] : []
|
let reactions = addReactions ? [AggregatedReaction(accountOwnerID: "bob", key: "🦄", senders: [sender])] : []
|
||||||
self.init(id: UUID().uuidString,
|
self.init(id: .init(timelineID: UUID().uuidString),
|
||||||
timestamp: "10:47 am",
|
timestamp: "10:47 am",
|
||||||
isOutgoing: sender == "bob",
|
isOutgoing: sender == "bob",
|
||||||
isEditable: sender == "bob",
|
isEditable: sender == "bob",
|
||||||
|
@ -20,7 +20,7 @@ import XCTest
|
|||||||
final class TextBasedRoomTimelineTests: XCTestCase {
|
final class TextBasedRoomTimelineTests: XCTestCase {
|
||||||
func testTextRoomTimelineItemWhitespaceEnd() {
|
func testTextRoomTimelineItemWhitespaceEnd() {
|
||||||
let timestamp = "Now"
|
let timestamp = "Now"
|
||||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
let timelineItem = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
view.underlyingTimelineItem = timelineItem
|
view.underlyingTimelineItem = timelineItem
|
||||||
view.timelineStyle = .bubbles
|
view.timelineStyle = .bubbles
|
||||||
@ -30,7 +30,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
||||||
let timestamp = "10:00 AM"
|
let timestamp = "10:00 AM"
|
||||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
let timelineItem = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
view.underlyingTimelineItem = timelineItem
|
view.underlyingTimelineItem = timelineItem
|
||||||
view.timelineStyle = .bubbles
|
view.timelineStyle = .bubbles
|
||||||
@ -39,7 +39,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndPlain() {
|
func testTextRoomTimelineItemWhitespaceEndPlain() {
|
||||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
let timelineItem = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: "Now", isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
view.underlyingTimelineItem = timelineItem
|
view.underlyingTimelineItem = timelineItem
|
||||||
view.timelineStyle = .plain
|
view.timelineStyle = .plain
|
||||||
@ -49,7 +49,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
||||||
let timestamp = "Now"
|
let timestamp = "Now"
|
||||||
var timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
var timelineItem = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
timelineItem.properties.isEdited = true
|
timelineItem.properties.isEdited = true
|
||||||
let editedCount = L10n.commonEditedSuffix.count
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
@ -61,7 +61,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
||||||
let timestamp = "Now"
|
let timestamp = "Now"
|
||||||
var timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
var timelineItem = TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString), timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
timelineItem.properties.isEdited = true
|
timelineItem.properties.isEdited = true
|
||||||
timelineItem.properties.deliveryStatus = .sendingFailed
|
timelineItem.properties.deliveryStatus = .sendingFailed
|
||||||
let editedCount = L10n.commonEditedSuffix.count
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
|
@ -44,7 +44,7 @@ include:
|
|||||||
packages:
|
packages:
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/matrix-org/matrix-rust-components-swift
|
url: https://github.com/matrix-org/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.96-alpha
|
exactVersion: 1.0.98-alpha
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
DesignKit:
|
DesignKit:
|
||||||
path: DesignKit
|
path: DesignKit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user