mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Reverting to TableView but with SwiftUI compatibility (#1407)
* revert but also allows us to keep the existing SwiftUI implementation if we need it * code improvement * pr review * FF is volatile * message that communicates that is volatile
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -480,6 +480,8 @@
|
|||||||
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */; };
|
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */; };
|
||||||
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; };
|
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; };
|
||||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||||
|
A70DACFC2A7146D2007F184C /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70DACFB2A7146D2007F184C /* TimelineTableViewController.swift */; };
|
||||||
|
A70DACFE2A7146DE007F184C /* UITimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70DACFD2A7146DE007F184C /* UITimelineView.swift */; };
|
||||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||||
@@ -866,7 +868,7 @@
|
|||||||
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
||||||
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
||||||
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
||||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -1007,7 +1009,7 @@
|
|||||||
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
||||||
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
||||||
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
||||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||||
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -1189,7 +1191,7 @@
|
|||||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||||
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
||||||
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1255,6 +1257,8 @@
|
|||||||
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetectionTests.swift; sourceTree = "<group>"; };
|
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetectionTests.swift; sourceTree = "<group>"; };
|
||||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
A70DACFB2A7146D2007F184C /* TimelineTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
A70DACFD2A7146DE007F184C /* UITimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITimelineView.swift; sourceTree = "<group>"; };
|
||||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -1299,7 +1303,7 @@
|
|||||||
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
||||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||||
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||||
@@ -1380,7 +1384,7 @@
|
|||||||
CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||||
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
||||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||||
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||||
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
|
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
||||||
@@ -1454,7 +1458,7 @@
|
|||||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
|
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
|
||||||
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
@@ -1468,7 +1472,7 @@
|
|||||||
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
||||||
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
|
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
|
||||||
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
||||||
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
||||||
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
|
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = "<group>"; };
|
F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -3209,6 +3213,8 @@
|
|||||||
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */,
|
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */,
|
||||||
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */,
|
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */,
|
||||||
1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */,
|
1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */,
|
||||||
|
A70DACFD2A7146DE007F184C /* UITimelineView.swift */,
|
||||||
|
A70DACFB2A7146D2007F184C /* TimelineTableViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -4535,6 +4541,7 @@
|
|||||||
899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */,
|
899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */,
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */,
|
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */,
|
||||||
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */,
|
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */,
|
||||||
|
A70DACFE2A7146DE007F184C /* UITimelineView.swift in Sources */,
|
||||||
5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */,
|
5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */,
|
||||||
6448F8D1D3CA4CD27BB4CADD /* RoomMemberProxy.swift in Sources */,
|
6448F8D1D3CA4CD27BB4CADD /* RoomMemberProxy.swift in Sources */,
|
||||||
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */,
|
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */,
|
||||||
@@ -4674,6 +4681,7 @@
|
|||||||
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */,
|
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */,
|
||||||
6FF51EB400DBA0668FC38B97 /* TimelineStartRoomTimelineView.swift in Sources */,
|
6FF51EB400DBA0668FC38B97 /* TimelineStartRoomTimelineView.swift in Sources */,
|
||||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
|
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
|
||||||
|
A70DACFC2A7146D2007F184C /* TimelineTableViewController.swift in Sources */,
|
||||||
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */,
|
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */,
|
||||||
500CB65ED116B81DA52FDAEE /* TimelineView.swift in Sources */,
|
500CB65ED116B81DA52FDAEE /* TimelineView.swift in Sources */,
|
||||||
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */,
|
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ final class AppSettings {
|
|||||||
case readReceiptsEnabled
|
case readReceiptsEnabled
|
||||||
case hasShownWelcomeScreen
|
case hasShownWelcomeScreen
|
||||||
case notificationSettingsEnabled
|
case notificationSettingsEnabled
|
||||||
|
case swiftUITimelineEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||||
@@ -213,4 +214,7 @@ final class AppSettings {
|
|||||||
|
|
||||||
@UserPreference(key: UserDefaultsKeys.notificationSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.notificationSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||||
var notificationSettingsEnabled
|
var notificationSettingsEnabled
|
||||||
|
|
||||||
|
@UserPreference(key: UserDefaultsKeys.swiftUITimelineEnabled, defaultValue: false, storageType: .volatile)
|
||||||
|
var swiftUITimelineEnabled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ enum RoomScreenViewAction {
|
|||||||
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 paginateBackwards
|
||||||
|
|
||||||
case timelineItemMenu(itemID: TimelineItemIdentifier)
|
case timelineItemMenu(itemID: TimelineItemIdentifier)
|
||||||
case timelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
case timelineItemMenuAction(itemID: TimelineItemIdentifier, action: TimelineItemMenuAction)
|
||||||
@@ -92,6 +93,7 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var isEncryptedOneToOneRoom = false
|
var isEncryptedOneToOneRoom = false
|
||||||
var timelineViewState = TimelineViewState() // check the doc before changing this
|
var timelineViewState = TimelineViewState() // check the doc before changing this
|
||||||
var composerMode: RoomScreenComposerMode = .default
|
var composerMode: RoomScreenComposerMode = .default
|
||||||
|
var swiftUITimelineEnabled = false
|
||||||
|
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
|
|
||||||
@@ -177,6 +179,10 @@ struct RoomMemberState {
|
|||||||
struct TimelineViewState {
|
struct TimelineViewState {
|
||||||
var canBackPaginate = true
|
var canBackPaginate = true
|
||||||
var isBackPaginating = false
|
var isBackPaginating = false
|
||||||
|
|
||||||
|
// These can be removed when we have full swiftUI and moved as @State values in the view
|
||||||
|
var scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
var itemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>()
|
var itemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>()
|
||||||
|
|
||||||
var timelineIDs: [String] {
|
var timelineIDs: [String] {
|
||||||
@@ -186,6 +192,4 @@ struct TimelineViewState {
|
|||||||
var itemViewStates: [RoomTimelineItemViewState] {
|
var itemViewStates: [RoomTimelineItemViewState] {
|
||||||
itemsDictionary.values.elements
|
itemsDictionary.values.elements
|
||||||
}
|
}
|
||||||
|
|
||||||
var paginateAction: (() -> Void)?
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
||||||
bindings: .init(composerText: "", composerFocused: false, reactionsCollapsed: [:])),
|
bindings: .init(composerText: "", composerFocused: false, reactionsCollapsed: [:])),
|
||||||
imageProvider: mediaProvider)
|
imageProvider: mediaProvider)
|
||||||
|
|
||||||
state.timelineViewState.paginateAction = { [weak self] in
|
|
||||||
self?.paginateBackwards()
|
|
||||||
}
|
|
||||||
|
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
|
|
||||||
state.timelineItemMenuActionProvider = { [weak self] itemId -> TimelineItemMenuActions? in
|
state.timelineItemMenuActionProvider = { [weak self] itemId -> TimelineItemMenuActions? in
|
||||||
@@ -140,12 +136,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
Task { await handleRetrySend(itemID: itemID) }
|
Task { await handleRetrySend(itemID: itemID) }
|
||||||
case .cancelSend(let itemID):
|
case .cancelSend(let itemID):
|
||||||
Task { await handleCancelSend(itemID: itemID) }
|
Task { await handleCancelSend(itemID: itemID) }
|
||||||
|
case .paginateBackwards:
|
||||||
|
paginateBackwards()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func setupSubscriptions() {
|
private func setupSubscriptions() {
|
||||||
|
appSettings.$swiftUITimelineEnabled
|
||||||
|
.weakAssign(to: \.state.swiftUITimelineEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
timelineController.callbacks
|
timelineController.callbacks
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] callback in
|
.sink { [weak self] callback in
|
||||||
@@ -274,19 +276,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(RoomTimelineItemViewState(item: firstItem, groupStyle: .single),
|
timelineItemsDictionary.updateValue(updateViewstate(item: firstItem, groupStyle: .single),
|
||||||
forKey: firstItem.id.timelineID)
|
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(RoomTimelineItemViewState(item: item, groupStyle: .first),
|
timelineItemsDictionary.updateValue(updateViewstate(item: item, groupStyle: .first),
|
||||||
forKey: item.id.timelineID)
|
forKey: item.id.timelineID)
|
||||||
} else if index == itemGroup.count - 1 {
|
} else if index == itemGroup.count - 1 {
|
||||||
timelineItemsDictionary.updateValue(RoomTimelineItemViewState(item: item, groupStyle: .last),
|
timelineItemsDictionary.updateValue(updateViewstate(item: item, groupStyle: .last),
|
||||||
forKey: item.id.timelineID)
|
forKey: item.id.timelineID)
|
||||||
} else {
|
} else {
|
||||||
timelineItemsDictionary.updateValue(RoomTimelineItemViewState(item: item, groupStyle: .middle),
|
timelineItemsDictionary.updateValue(updateViewstate(item: item, groupStyle: .middle),
|
||||||
forKey: item.id.timelineID)
|
forKey: item.id.timelineID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,6 +298,16 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
state.timelineViewState.itemsDictionary = timelineItemsDictionary
|
state.timelineViewState.itemsDictionary = timelineItemsDictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateViewstate(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewState {
|
||||||
|
if let timelineItemViewState = state.timelineViewState.itemsDictionary[item.id.timelineID] {
|
||||||
|
timelineItemViewState.groupStyle = groupStyle
|
||||||
|
timelineItemViewState.type = .init(item: item)
|
||||||
|
return timelineItemViewState
|
||||||
|
} else {
|
||||||
|
return RoomTimelineItemViewState(item: item, groupStyle: groupStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
|
private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
|
||||||
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
|
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -83,12 +83,48 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var timeline: some View {
|
private var timeline: some View {
|
||||||
TimelineView(viewState: context.viewState.timelineViewState)
|
timelineSwitch
|
||||||
.id(context.viewState.roomId)
|
.id(context.viewState.roomId)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
.environment(\.timelineStyle, context.viewState.timelineStyle)
|
.environment(\.timelineStyle, context.viewState.timelineStyle)
|
||||||
.environment(\.readReceiptsEnabled, context.viewState.readReceiptsEnabled)
|
.environment(\.readReceiptsEnabled, context.viewState.readReceiptsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var timelineSwitch: some View {
|
||||||
|
if context.viewState.swiftUITimelineEnabled {
|
||||||
|
TimelineView(viewState: context.viewState.timelineViewState,
|
||||||
|
scrollToBottomButtonVisible: $context.scrollToBottomButtonVisible) {
|
||||||
|
context.send(viewAction: .paginateBackwards)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UITimelineView()
|
||||||
|
.overlay(alignment: .bottomTrailing) {
|
||||||
|
scrollToBottomButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scrollToBottomButton: some View {
|
||||||
|
Button { context.viewState.timelineViewState.scrollToBottomPublisher.send(()) } label: {
|
||||||
|
Image(systemName: "chevron.down")
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.compound.iconSecondary)
|
||||||
|
.padding(13)
|
||||||
|
.offset(y: 1)
|
||||||
|
.background {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.compound.iconOnSolidPrimary)
|
||||||
|
// Intentionally using system primary colour to get white/black.
|
||||||
|
.shadow(color: .primary.opacity(0.33), radius: 2.0)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.opacity(context.scrollToBottomButtonVisible ? 1.0 : 0.0)
|
||||||
|
.accessibilityHidden(!context.scrollToBottomButtonVisible)
|
||||||
|
.animation(.elementDefault, value: context.scrollToBottomButtonVisible)
|
||||||
|
}
|
||||||
|
|
||||||
private var messageComposer: some View {
|
private var messageComposer: some View {
|
||||||
MessageComposer(text: $context.composerText,
|
MessageComposer(text: $context.composerText,
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022 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 Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
import OrderedCollections
|
||||||
|
|
||||||
|
/// A table view cell that displays a timeline item in a room. The cell is intended
|
||||||
|
/// to be configured to display a SwiftUI view and not use any UIKit.
|
||||||
|
class TimelineItemCell: UITableViewCell {
|
||||||
|
static let reuseIdentifier = "TimelineItemCell"
|
||||||
|
|
||||||
|
var item: RoomTimelineItemViewState?
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
item = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A table view controller that displays the timeline of a room.
|
||||||
|
///
|
||||||
|
/// This class subclasses `UIViewController` as `UITableViewController` adds some
|
||||||
|
/// extra keyboard handling magic that wasn't playing well with SwiftUI (as of iOS 16.1).
|
||||||
|
/// Also this TableViewController uses a **flipped tableview**
|
||||||
|
class TimelineTableViewController: UIViewController {
|
||||||
|
private let coordinator: UITimelineView.Coordinator
|
||||||
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
|
||||||
|
var timelineStyle: TimelineStyle
|
||||||
|
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>() {
|
||||||
|
didSet {
|
||||||
|
applySnapshot()
|
||||||
|
|
||||||
|
if timelineItemsDictionary.isEmpty {
|
||||||
|
paginateBackwardsPublisher.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The mode of the message composer. This is used to render selected
|
||||||
|
/// items in the timeline when replying, editing etc.
|
||||||
|
var composerMode: RoomScreenComposerMode = .default
|
||||||
|
|
||||||
|
/// Whether or not the timeline has more messages to back paginate.
|
||||||
|
var canBackPaginate = true
|
||||||
|
|
||||||
|
/// Whether or not the timeline is waiting for more messages to be added to the top.
|
||||||
|
var isBackPaginating = false {
|
||||||
|
didSet {
|
||||||
|
// Paginate again if the threshold hasn't been satisfied.
|
||||||
|
paginateBackwardsPublisher.send(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextMenuActionProvider: (@MainActor (_ itemID: TimelineItemIdentifier) -> TimelineItemMenuActions?)?
|
||||||
|
|
||||||
|
@Binding private var scrollToBottomButtonVisible: Bool
|
||||||
|
|
||||||
|
private var timelineItemsIDs: [String] {
|
||||||
|
timelineItemsDictionary.keys.elements.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The table's diffable data source.
|
||||||
|
private var dataSource: UITableViewDiffableDataSource<TimelineSection, String>?
|
||||||
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
/// A publisher used to throttle back pagination requests.
|
||||||
|
///
|
||||||
|
/// Our view actions get wrapped in a `Task` so it is possible that a second call in
|
||||||
|
/// quick succession can execute before ``isBackPaginating`` becomes `true`.
|
||||||
|
private let paginateBackwardsPublisher = PassthroughSubject<Void, Never>()
|
||||||
|
/// Whether or not the view has been shown on screen yet.
|
||||||
|
private var hasAppearedOnce = false
|
||||||
|
/// Whether the scroll and the animations should happen
|
||||||
|
private var shouldAnimate = false
|
||||||
|
|
||||||
|
init(coordinator: UITimelineView.Coordinator,
|
||||||
|
timelineStyle: TimelineStyle,
|
||||||
|
scrollToBottomButtonVisible: Binding<Bool>,
|
||||||
|
scrollToBottomPublisher: PassthroughSubject<Void, Never>) {
|
||||||
|
self.coordinator = coordinator
|
||||||
|
self.timelineStyle = timelineStyle
|
||||||
|
_scrollToBottomButtonVisible = scrollToBottomButtonVisible
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
|
tableView.register(TimelineItemCell.self, forCellReuseIdentifier: TimelineItemCell.reuseIdentifier)
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.allowsSelection = false
|
||||||
|
tableView.keyboardDismissMode = .onDrag
|
||||||
|
tableView.backgroundColor = UIColor(.compound.bgCanvasDefault)
|
||||||
|
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
|
||||||
|
view.addSubview(tableView)
|
||||||
|
|
||||||
|
// Prevents XCUITest from invoking the diffable dataSource's cellProvider
|
||||||
|
// for each possible cell, causing layout issues
|
||||||
|
tableView.accessibilityElementsHidden = Tests.shouldDisableTimelineAccessibility
|
||||||
|
|
||||||
|
scrollToBottomPublisher
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.scrollToBottom(animated: true)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
paginateBackwardsPublisher
|
||||||
|
.collect(.byTime(DispatchQueue.main, 0.1))
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.paginateBackwardsIfNeeded()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
configureDataSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) is not available.") }
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
guard !hasAppearedOnce else { return }
|
||||||
|
tableView.contentOffset.y = -1
|
||||||
|
hasAppearedOnce = true
|
||||||
|
paginateBackwardsPublisher.send()
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||||
|
self.shouldAnimate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillLayoutSubviews() {
|
||||||
|
super.viewWillLayoutSubviews()
|
||||||
|
|
||||||
|
guard tableView.frame.size != view.frame.size else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.frame = CGRect(origin: .zero, size: view.frame.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures a diffable data source for the timeline's table view.
|
||||||
|
private func configureDataSource() {
|
||||||
|
dataSource = .init(tableView: tableView) { [weak self] tableView, indexPath, id in
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: TimelineItemCell.reuseIdentifier, for: indexPath)
|
||||||
|
guard let self, let cell = cell as? TimelineItemCell else { return cell }
|
||||||
|
|
||||||
|
// A local reference to avoid capturing self in the cell configuration.
|
||||||
|
let coordinator = self.coordinator
|
||||||
|
|
||||||
|
let viewState = timelineItemsDictionary[id]
|
||||||
|
cell.item = viewState
|
||||||
|
guard let viewState else {
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
cell.contentConfiguration = UIHostingConfiguration {
|
||||||
|
RoomTimelineItemView(viewState: viewState)
|
||||||
|
.id(id)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
||||||
|
.onAppear {
|
||||||
|
coordinator.send(viewAction: .itemAppeared(itemID: viewState.identifier))
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coordinator.send(viewAction: .itemDisappeared(itemID: viewState.identifier))
|
||||||
|
}
|
||||||
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
coordinator.send(viewAction: .linkClicked(url: url))
|
||||||
|
return .systemAction
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.margins(.all, self.timelineStyle.rowInsets)
|
||||||
|
.minSize(height: 1)
|
||||||
|
.background(Color.clear)
|
||||||
|
|
||||||
|
// Flipping the cell can create some issues with cell resizing, so flip the content View
|
||||||
|
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource?.defaultRowAnimation = .fade
|
||||||
|
tableView.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the table view with the latest items from the ``timelineItems`` array. After
|
||||||
|
/// updating the data, the table will be scrolled to the bottom if it was visible otherwise
|
||||||
|
/// the scroll position will be updated to maintain the position of the last visible item.
|
||||||
|
private func applySnapshot() {
|
||||||
|
guard let dataSource else { return }
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, String>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
snapshot.appendItems(timelineItemsIDs)
|
||||||
|
|
||||||
|
let currentSnapshot = dataSource.snapshot()
|
||||||
|
MXLog.verbose("DIFF: \(snapshot.itemIdentifiers.difference(from: currentSnapshot.itemIdentifiers))")
|
||||||
|
|
||||||
|
// We only animate when new items come at the end of the timeline
|
||||||
|
let animated = shouldAnimate &&
|
||||||
|
snapshot.itemIdentifiers.first != currentSnapshot.itemIdentifiers.first
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scrolls to the bottom of the timeline.
|
||||||
|
private func scrollToBottom(animated: Bool) {
|
||||||
|
guard !timelineItemsIDs.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableView.scrollToRow(at: IndexPath(item: 0, section: 0), at: .top, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scrolls to the top of the timeline.
|
||||||
|
private func scrollToTop(animated: Bool) {
|
||||||
|
guard !timelineItemsIDs.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableView.scrollToRow(at: IndexPath(item: timelineItemsIDs.count - 1, section: 0), at: .bottom, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether or a backwards pagination is needed and requests one if so.
|
||||||
|
///
|
||||||
|
/// Prefer not to call this directly, instead using ``paginateBackwardsPublisher`` to throttle requests.
|
||||||
|
private func paginateBackwardsIfNeeded() {
|
||||||
|
guard canBackPaginate,
|
||||||
|
!isBackPaginating,
|
||||||
|
tableView.contentOffset.y > tableView.contentSize.height - tableView.visibleSize.height * 2.0
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
coordinator.send(viewAction: .paginateBackwards)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UITableViewDelegate
|
||||||
|
|
||||||
|
extension TimelineTableViewController: UITableViewDelegate {
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
paginateBackwardsPublisher.send(())
|
||||||
|
|
||||||
|
// Dispatch to fix runtime warning about making changes during a view update.
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
let scrollToBottomButtonVisible = scrollView.contentOffset.y > 0
|
||||||
|
|
||||||
|
// Only update the binding on changes to avoid needlessly recomputing the hierarchy when scrolling.
|
||||||
|
if self.scrollToBottomButtonVisible != scrollToBottomButtonVisible {
|
||||||
|
self.scrollToBottomButtonVisible = scrollToBottomButtonVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We never want the table view to be fully at the bottom to allow the status bar tap to work properly
|
||||||
|
if scrollView.contentOffset.y == 0 {
|
||||||
|
scrollView.contentOffset.y = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
||||||
|
scrollToTop(animated: true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Layout Types
|
||||||
|
|
||||||
|
extension TimelineTableViewController {
|
||||||
|
/// The sections of the table view used in the diffable data source.
|
||||||
|
enum TimelineSection {
|
||||||
|
case main
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022 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 SwiftUI
|
||||||
|
|
||||||
|
/// A table view wrapper that displays the timeline of a room.
|
||||||
|
struct UITimelineView: UIViewControllerRepresentable {
|
||||||
|
@EnvironmentObject private var viewModelContext: RoomScreenViewModel.Context
|
||||||
|
@Environment(\.timelineStyle) private var timelineStyle
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> TimelineTableViewController {
|
||||||
|
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
|
||||||
|
timelineStyle: timelineStyle,
|
||||||
|
scrollToBottomButtonVisible: $viewModelContext.scrollToBottomButtonVisible,
|
||||||
|
scrollToBottomPublisher: viewModelContext.viewState.timelineViewState.scrollToBottomPublisher)
|
||||||
|
return tableViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: TimelineTableViewController, context: Context) {
|
||||||
|
context.coordinator.update(tableViewController: uiViewController, timelineStyle: timelineStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(viewModelContext: viewModelContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Coordinator
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class Coordinator {
|
||||||
|
let context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
|
init(viewModelContext: RoomScreenViewModel.Context) {
|
||||||
|
context = viewModelContext
|
||||||
|
|
||||||
|
if viewModelContext.viewState.timelineViewState.itemViewStates.isEmpty {
|
||||||
|
viewModelContext.send(viewAction: .paginateBackwards)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the specified table view's properties from the current view state.
|
||||||
|
func update(tableViewController: TimelineTableViewController, timelineStyle: TimelineStyle) {
|
||||||
|
if tableViewController.timelineStyle != timelineStyle {
|
||||||
|
tableViewController.timelineStyle = timelineStyle
|
||||||
|
}
|
||||||
|
if tableViewController.timelineItemsDictionary != context.viewState.timelineViewState.itemsDictionary {
|
||||||
|
tableViewController.timelineItemsDictionary = context.viewState.timelineViewState.itemsDictionary
|
||||||
|
}
|
||||||
|
if tableViewController.canBackPaginate != context.viewState.timelineViewState.canBackPaginate {
|
||||||
|
tableViewController.canBackPaginate = context.viewState.timelineViewState.canBackPaginate
|
||||||
|
}
|
||||||
|
if tableViewController.isBackPaginating != context.viewState.timelineViewState.isBackPaginating {
|
||||||
|
tableViewController.isBackPaginating = context.viewState.timelineViewState.isBackPaginating
|
||||||
|
}
|
||||||
|
if tableViewController.composerMode != context.viewState.composerMode {
|
||||||
|
tableViewController.composerMode = context.viewState.composerMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doesn't have an equatable conformance :(
|
||||||
|
tableViewController.contextMenuActionProvider = context.viewState.timelineItemMenuActionProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(viewAction: RoomScreenViewAction) {
|
||||||
|
context.send(viewAction: viewAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct UITimelineView_Previews: PreviewProvider {
|
||||||
|
static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
|
||||||
|
mediaProvider: MockMediaProvider(),
|
||||||
|
roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")),
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
analytics: ServiceLocator.shared.analytics,
|
||||||
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
NavigationStack {
|
||||||
|
RoomScreen(context: viewModel.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ import SwiftUIIntrospect
|
|||||||
|
|
||||||
struct TimelineView: View {
|
struct TimelineView: View {
|
||||||
let viewState: TimelineViewState
|
let viewState: TimelineViewState
|
||||||
|
@Binding var scrollToBottomButtonVisible: Bool
|
||||||
|
let paginationAction: () -> Void
|
||||||
|
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
@Environment(\.timelineStyle) private var timelineStyle
|
||||||
|
|
||||||
private let bottomID = "RoomTimelineBottomPinIdentifier"
|
private let bottomID = "RoomTimelineBottomPinIdentifier"
|
||||||
@@ -28,8 +31,6 @@ struct TimelineView: View {
|
|||||||
|
|
||||||
@State private var scrollViewAdapter = ScrollViewAdapter()
|
@State private var scrollViewAdapter = ScrollViewAdapter()
|
||||||
@State private var paginateBackwardsPublisher = PassthroughSubject<Void, Never>()
|
@State private var paginateBackwardsPublisher = PassthroughSubject<Void, Never>()
|
||||||
@State private var scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
|
||||||
@State private var scrollToBottomButtonVisible = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollViewReader { scrollView in
|
ScrollViewReader { scrollView in
|
||||||
@@ -49,9 +50,10 @@ struct TimelineView: View {
|
|||||||
|
|
||||||
// Allows the scroll to top to work properly
|
// Allows the scroll to top to work properly
|
||||||
uiScrollView.contentOffset.y -= 1
|
uiScrollView.contentOffset.y -= 1
|
||||||
|
paginateBackwardsPublisher.send()
|
||||||
}
|
}
|
||||||
.scaleEffect(x: 1, y: -1)
|
.scaleEffect(x: 1, y: -1)
|
||||||
.onReceive(scrollToBottomPublisher) { _ in
|
.onReceive(viewState.scrollToBottomPublisher) { _ in
|
||||||
withElementAnimation {
|
withElementAnimation {
|
||||||
scrollView.scrollTo(bottomID)
|
scrollView.scrollTo(bottomID)
|
||||||
}
|
}
|
||||||
@@ -59,7 +61,7 @@ struct TimelineView: View {
|
|||||||
.scrollDismissesKeyboard(.immediately)
|
.scrollDismissesKeyboard(.immediately)
|
||||||
}
|
}
|
||||||
.overlay(scrollToBottomButton, alignment: .bottomTrailing)
|
.overlay(scrollToBottomButton, alignment: .bottomTrailing)
|
||||||
.animation(.elementDefault, value: viewState.itemViewStates)
|
.animation(.elementDefault, value: viewState.timelineIDs)
|
||||||
.onReceive(scrollViewAdapter.didScroll) { _ in
|
.onReceive(scrollViewAdapter.didScroll) { _ in
|
||||||
guard let scrollView = scrollViewAdapter.scrollView else {
|
guard let scrollView = scrollViewAdapter.scrollView else {
|
||||||
return
|
return
|
||||||
@@ -120,7 +122,7 @@ struct TimelineView: View {
|
|||||||
|
|
||||||
private var scrollToBottomButton: some View {
|
private var scrollToBottomButton: some View {
|
||||||
Button {
|
Button {
|
||||||
scrollToBottomPublisher.send()
|
viewState.scrollToBottomPublisher.send()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.down")
|
Image(systemName: "chevron.down")
|
||||||
.font(.compound.bodyLG)
|
.font(.compound.bodyLG)
|
||||||
@@ -142,8 +144,7 @@ struct TimelineView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func paginateBackwardsIfNeeded() {
|
private func paginateBackwardsIfNeeded() {
|
||||||
guard let paginateAction = viewState.paginateAction,
|
guard let scrollView = scrollViewAdapter.scrollView,
|
||||||
let scrollView = scrollViewAdapter.scrollView,
|
|
||||||
viewState.canBackPaginate,
|
viewState.canBackPaginate,
|
||||||
!viewState.isBackPaginating else {
|
!viewState.isBackPaginating else {
|
||||||
return
|
return
|
||||||
@@ -158,20 +159,20 @@ struct TimelineView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
paginateAction()
|
paginationAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct TimelineTableView_Previews: PreviewProvider {
|
struct TimelineView_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
|
static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
|
||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")),
|
roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")),
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
RoomScreen(context: viewModel.context)
|
RoomScreen(context: viewModel.context)
|
||||||
|
|||||||
+1
@@ -47,6 +47,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
|||||||
var userSuggestionsEnabled: Bool { get set }
|
var userSuggestionsEnabled: Bool { get set }
|
||||||
var readReceiptsEnabled: Bool { get set }
|
var readReceiptsEnabled: Bool { get set }
|
||||||
var notificationSettingsEnabled: Bool { get set }
|
var notificationSettingsEnabled: Bool { get set }
|
||||||
|
var swiftUITimelineEnabled: Bool { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppSettings: DeveloperOptionsProtocol { }
|
extension AppSettings: DeveloperOptionsProtocol { }
|
||||||
|
|||||||
+5
@@ -31,6 +31,11 @@ struct DeveloperOptionsScreen: View {
|
|||||||
Text("Show read receipts")
|
Text("Show read receipts")
|
||||||
Text("Requires app reboot")
|
Text("Requires app reboot")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $context.swiftUITimelineEnabled) {
|
||||||
|
Text("SwiftUI Timeline")
|
||||||
|
Text("Resets on reboot")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Notifications") {
|
Section("Notifications") {
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ import SwiftUI
|
|||||||
|
|
||||||
struct RoomTimelineItemView: View {
|
struct RoomTimelineItemView: View {
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
let viewState: RoomTimelineItemViewState
|
@ObservedObject var viewState: RoomTimelineItemViewState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
timelineView
|
timelineView
|
||||||
|
.animation(.elementDefault, value: viewState.groupStyle)
|
||||||
|
.animation(.elementDefault, value: viewState.type)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
.environment(\.timelineGroupStyle, viewState.groupStyle)
|
.environment(\.timelineGroupStyle, viewState.groupStyle)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct RoomTimelineItemViewState: Identifiable, Equatable {
|
final class RoomTimelineItemViewState: Identifiable, Equatable, ObservableObject {
|
||||||
let type: RoomTimelineItemType
|
static func == (lhs: RoomTimelineItemViewState, rhs: RoomTimelineItemViewState) -> Bool {
|
||||||
let groupStyle: TimelineGroupStyle
|
lhs.type == rhs.type && lhs.groupStyle == rhs.groupStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var type: RoomTimelineItemType
|
||||||
|
@Published var groupStyle: TimelineGroupStyle
|
||||||
|
|
||||||
/// Contains all the identification info of the item, `timelineID`, `eventID` and `transactionID`
|
/// Contains all the identification info of the item, `timelineID`, `eventID` and `transactionID`
|
||||||
var identifier: TimelineItemIdentifier {
|
var identifier: TimelineItemIdentifier {
|
||||||
@@ -33,10 +37,13 @@ struct RoomTimelineItemViewState: Identifiable, Equatable {
|
|||||||
var isReactable: Bool {
|
var isReactable: Bool {
|
||||||
type.isReactable
|
type.isReactable
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension RoomTimelineItemViewState {
|
init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) {
|
||||||
init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
|
self.type = type
|
||||||
|
self.groupStyle = groupStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
|
||||||
self.init(type: .init(item: item), groupStyle: groupStyle)
|
self.init(type: .init(item: item), groupStyle: groupStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user