From afbb0a82609c0e30c30b0d89886f2ad77c4fe111 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:32:16 +0100 Subject: [PATCH] Replace FilePreviewScreen with InteractiveQuickLook. (#1187) * Replace FilePreviewScreen with InteractiveQuickLook. - Allows full screen presentation. - Allows dismiss gesture to work. - Presentation looks much better on macOS. --- ElementX.xcodeproj/project.pbxproj | 50 ++----- .../RoomFlowCoordinator.swift | 31 ----- .../FilePreviewScreenCoordinator.swift | 56 -------- .../FilePreviewScreenModels.swift | 30 ---- .../FilePreviewScreenViewModel.swift | 34 ----- .../FilePreviewScreenViewModelProtocol.swift | 23 ---- .../InteractiveQuickLook.swift | 130 ++++++++++++++++++ .../View/FilePreviewScreen.swift | 101 -------------- .../RoomScreen/RoomScreenCoordinator.swift | 3 - .../Screens/RoomScreen/RoomScreenModels.swift | 4 +- .../RoomScreen/RoomScreenViewModel.swift | 2 +- .../Screens/RoomScreen/View/RoomScreen.swift | 1 + .../Sources/FilePreviewViewModelTests.swift | 44 ------ changelog.d/pr-1187.change | 1 + 14 files changed, 147 insertions(+), 363 deletions(-) delete mode 100644 ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenCoordinator.swift delete mode 100644 ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenModels.swift delete mode 100644 ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModel.swift delete mode 100644 ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/FilePreviewScreen/InteractiveQuickLook.swift delete mode 100644 ElementX/Sources/Screens/FilePreviewScreen/View/FilePreviewScreen.swift delete mode 100644 UnitTests/Sources/FilePreviewViewModelTests.swift create mode 100644 changelog.d/pr-1187.change diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index c48db4466..91baf7a5e 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -78,7 +78,6 @@ 1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; }; 1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; }; 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; }; - 1EEF3580CC62E86CB04C9021 /* FilePreviewScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B45A6B13D32A131FCA4EFF /* FilePreviewScreenViewModelProtocol.swift */; }; 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; }; 1F3232BD368DF430AB433907 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5A56C4F47C368EBE5C5E870 /* DesignKit */; }; 1FE593ECEC40A43789105D80 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */; }; @@ -260,7 +259,7 @@ 654E802C127B84554042903E /* AnalyticsSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */; }; 659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; }; 65EDA77363BEDC40CDE43B43 /* InvitesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ADEA322D2089391E049535 /* InvitesScreen.swift */; }; - 661A664C6EDF856B05519206 /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F562E2CBA002E8E1B6545C38 /* FilePreviewScreen.swift */; }; + 661A664C6EDF856B05519206 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = F562E2CBA002E8E1B6545C38 /* InteractiveQuickLook.swift */; }; 663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; }; 6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */; }; 67160204A8D362BB7D4AD259 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693E16574C6F7F9FA1015A8C /* Search.swift */; }; @@ -393,7 +392,6 @@ 9408CE8B8865C0C8DD4C9869 /* NoticeRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */; }; 9462C62798F47E39DCC182D2 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA89A2DD51B6BBE1DA55E263 /* Application.swift */; }; 94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */; }; - 94BEFD4EC49644AD06A748D4 /* FilePreviewScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1964EE08550BEDBD0B0F5FD /* FilePreviewScreenViewModel.swift */; }; 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; }; 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; @@ -456,7 +454,6 @@ A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */; }; A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; - A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.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 */; }; A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; }; @@ -499,7 +496,6 @@ B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; B5479997ECC516C121E6625E /* LocationMarkerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFECCE59967018204876D0A5 /* LocationMarkerView.swift */; }; B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */; }; - B5BD05558DC2C3091905E14A /* FilePreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714977AF906461C8F6F16ABA /* FilePreviewScreenCoordinator.swift */; }; B5E455C9689EA600EDB3E9E0 /* NavigationRootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */; }; B6048166B4AA4CEFEA9B77A6 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; B64C9BCE61E77D578D40D689 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 232F7D3C19F1FEF0E0450110 /* MatrixRustSDK */; }; @@ -561,7 +557,6 @@ C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; }; C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; }; C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; }; - CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; }; CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; }; CB137BFB3E083C33E398A6CB /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; @@ -814,7 +809,7 @@ 127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = ""; }; 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -945,7 +940,7 @@ 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; - 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 = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -1054,7 +1049,6 @@ 7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = ""; }; 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = ""; }; 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModelProtocol.swift; sourceTree = ""; }; - 714977AF906461C8F6F16ABA /* FilePreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreenCoordinator.swift; sourceTree = ""; }; 71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineControllerFactory.swift; sourceTree = ""; }; 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreen.swift; sourceTree = ""; }; 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = ""; }; @@ -1085,7 +1079,6 @@ 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = ""; }; 81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModel.swift; sourceTree = ""; }; 81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreenModels.swift; sourceTree = ""; }; 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModel.swift; sourceTree = ""; }; 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = ""; }; 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = ""; }; @@ -1110,7 +1103,7 @@ 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = ""; }; @@ -1120,7 +1113,6 @@ 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; 923485F85E1D765EF9D20E88 /* UserProfileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileCell.swift; sourceTree = ""; }; 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = ""; }; - 92B45A6B13D32A131FCA4EFF /* FilePreviewScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreenViewModelProtocol.swift; sourceTree = ""; }; 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = ""; }; 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenModels.swift; sourceTree = ""; }; @@ -1215,7 +1207,7 @@ B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -1292,7 +1284,7 @@ CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; @@ -1324,7 +1316,6 @@ DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = ""; }; DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = ""; }; DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = ""; }; - DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelTests.swift; sourceTree = ""; }; DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = ""; }; E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = ""; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = ""; }; @@ -1361,7 +1352,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -1373,7 +1364,6 @@ F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelTests.swift; sourceTree = ""; }; F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; - F1964EE08550BEDBD0B0F5FD /* FilePreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreenViewModel.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; @@ -1383,7 +1373,7 @@ F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModel.swift; sourceTree = ""; }; - F562E2CBA002E8E1B6545C38 /* FilePreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreen.swift; sourceTree = ""; }; + F562E2CBA002E8E1B6545C38 /* InteractiveQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveQuickLook.swift; sourceTree = ""; }; F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorToastView.swift; sourceTree = ""; }; F72EFC8C634469F9262659C7 /* NSItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSItemProvider.swift; sourceTree = ""; }; F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = ""; }; @@ -1966,11 +1956,7 @@ 448435400B561C40E514BE1C /* FilePreviewScreen */ = { isa = PBXGroup; children = ( - 714977AF906461C8F6F16ABA /* FilePreviewScreenCoordinator.swift */, - 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */, - F1964EE08550BEDBD0B0F5FD /* FilePreviewScreenViewModel.swift */, - 92B45A6B13D32A131FCA4EFF /* FilePreviewScreenViewModelProtocol.swift */, - 5EC4A8482DA110602FE6DF42 /* View */, + F562E2CBA002E8E1B6545C38 /* InteractiveQuickLook.swift */, ); path = FilePreviewScreen; sourceTree = ""; @@ -2195,14 +2181,6 @@ path = View; sourceTree = ""; }; - 5EC4A8482DA110602FE6DF42 /* View */ = { - isa = PBXGroup; - children = ( - F562E2CBA002E8E1B6545C38 /* FilePreviewScreen.swift */, - ); - path = View; - sourceTree = ""; - }; 5F6CB68B44F6C587E463A934 /* View */ = { isa = PBXGroup; children = ( @@ -2367,7 +2345,6 @@ 9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */, 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */, 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */, - DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */, 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */, 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */, CC14E5209C262530E19BC4C1 /* InvitesScreenViewModelTests.swift */, @@ -3903,7 +3880,6 @@ 501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */, 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */, 71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */, - CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */, 07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */, F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */, A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */, @@ -4096,11 +4072,7 @@ 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, 63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */, 5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */, - 661A664C6EDF856B05519206 /* FilePreviewScreen.swift in Sources */, - B5BD05558DC2C3091905E14A /* FilePreviewScreenCoordinator.swift in Sources */, - A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */, - 94BEFD4EC49644AD06A748D4 /* FilePreviewScreenViewModel.swift in Sources */, - 1EEF3580CC62E86CB04C9021 /* FilePreviewScreenViewModelProtocol.swift in Sources */, + 661A664C6EDF856B05519206 /* InteractiveQuickLook.swift in Sources */, D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */, 37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */, 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 4af6b2004..6c23af6cf 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -119,11 +119,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case (.dismissRoomMemberDetails, .roomMemberDetails(let roomID, _)): return .room(roomID: roomID) - case (.presentMediaViewer(let file, let title), .room(let roomID)): - return .mediaViewer(roomID: roomID, file: file, title: title) - case (.dismissMediaViewer, .mediaViewer(let roomID, _, _)): - return .room(roomID: roomID) - case (.presentReportContent(let itemID, let senderID), .room(let roomID)): return .reportContent(roomID: roomID, itemID: itemID, senderID: senderID) case (.dismissReportContent, .reportContent(let roomID, _, _)): @@ -185,11 +180,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case (.roomDetails, .dismissRoom, .initial): dismissRoom(animated: animated) - case (.room, .presentMediaViewer, .mediaViewer(_, let file, let title)): - presentMediaViewer(file, title: title) - case (.mediaViewer, .dismissMediaViewer, .room): - break - case (.room, .presentReportContent, .reportContent(_, let itemID, let senderID)): presentReportContent(for: itemID, from: senderID) case (.reportContent, .dismissReportContent, .room): @@ -308,8 +298,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { switch action { case .presentRoomDetails: stateMachine.tryEvent(.presentRoomDetails(roomID: roomID)) - case .presentMediaViewer(let file, let title): - stateMachine.tryEvent(.presentMediaViewer(file: file, title: title)) case .presentReportContent(let itemID, let senderID): stateMachine.tryEvent(.presentReportContent(itemID: itemID, senderID: senderID)) case .presentMediaUploadPicker(let source): @@ -412,21 +400,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } - private func presentMediaViewer(_ file: MediaFileHandleProxy, title: String?) { - let params = FilePreviewScreenCoordinatorParameters(mediaFile: file, title: title) - let coordinator = FilePreviewScreenCoordinator(parameters: params) - coordinator.callback = { [weak self] action in - switch action { - case .cancel: - self?.navigationStackCoordinator.pop() - } - } - - navigationStackCoordinator.push(coordinator) { [weak self] in - self?.stateMachine.tryEvent(.dismissMediaViewer) - } - } - private func presentReportContent(for itemID: String, from senderID: String) { guard let roomProxy else { fatalError() @@ -640,7 +613,6 @@ private extension RoomFlowCoordinator { enum State: StateType { case initial case room(roomID: String) - case mediaViewer(roomID: String, file: MediaFileHandleProxy, title: String?) case reportContent(roomID: String, itemID: String, senderID: String) case roomDetails(roomID: String, isRoot: Bool) case mediaUploadPicker(roomID: String, source: MediaPickerScreenSource) @@ -660,9 +632,6 @@ private extension RoomFlowCoordinator { case presentRoom(roomID: String) case dismissRoom - case presentMediaViewer(file: MediaFileHandleProxy, title: String?) - case dismissMediaViewer - case presentReportContent(itemID: String, senderID: String) case dismissReportContent diff --git a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenCoordinator.swift b/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenCoordinator.swift deleted file mode 100644 index 07e4be9fe..000000000 --- a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenCoordinator.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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 - -struct FilePreviewScreenCoordinatorParameters { - let mediaFile: MediaFileHandleProxy - let title: String? -} - -enum FilePreviewScreenCoordinatorAction { - case cancel -} - -final class FilePreviewScreenCoordinator: CoordinatorProtocol { - private let parameters: FilePreviewScreenCoordinatorParameters - private var viewModel: FilePreviewScreenViewModelProtocol - - var callback: ((FilePreviewScreenCoordinatorAction) -> Void)? - - init(parameters: FilePreviewScreenCoordinatorParameters) { - self.parameters = parameters - - viewModel = FilePreviewScreenViewModel(mediaFile: parameters.mediaFile, title: parameters.title) - } - - // MARK: - Public - - func start() { - viewModel.callback = { [weak self] action in - guard let self else { return } - - switch action { - case .cancel: - self.callback?(.cancel) - } - } - } - - func toPresentable() -> AnyView { - AnyView(FilePreviewScreen(context: viewModel.context)) - } -} diff --git a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenModels.swift b/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenModels.swift deleted file mode 100644 index 8401b4d91..000000000 --- a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenModels.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// 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 Foundation - -enum FilePreviewScreenViewModelAction { - case cancel -} - -struct FilePreviewScreenViewState: BindableState { - let mediaFile: MediaFileHandleProxy - let title: String? -} - -enum FilePreviewScreenViewAction { - case cancel -} diff --git a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModel.swift b/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModel.swift deleted file mode 100644 index 530751dd2..000000000 --- a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModel.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// 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 - -typealias FilePreviewScreenViewModelType = StateStoreViewModel - -class FilePreviewScreenViewModel: FilePreviewScreenViewModelType, FilePreviewScreenViewModelProtocol { - var callback: ((FilePreviewScreenViewModelAction) -> Void)? - - init(mediaFile: MediaFileHandleProxy, title: String? = nil) { - super.init(initialViewState: FilePreviewScreenViewState(mediaFile: mediaFile, title: title)) - } - - override func process(viewAction: FilePreviewScreenViewAction) { - switch viewAction { - case .cancel: - callback?(.cancel) - } - } -} diff --git a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModelProtocol.swift b/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModelProtocol.swift deleted file mode 100644 index 4e7b3c03f..000000000 --- a/ElementX/Sources/Screens/FilePreviewScreen/FilePreviewScreenViewModelProtocol.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// 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 Foundation - -@MainActor -protocol FilePreviewScreenViewModelProtocol { - var callback: ((FilePreviewScreenViewModelAction) -> Void)? { get set } - var context: FilePreviewScreenViewModelType.Context { get } -} diff --git a/ElementX/Sources/Screens/FilePreviewScreen/InteractiveQuickLook.swift b/ElementX/Sources/Screens/FilePreviewScreen/InteractiveQuickLook.swift new file mode 100644 index 000000000..553ef9404 --- /dev/null +++ b/ElementX/Sources/Screens/FilePreviewScreen/InteractiveQuickLook.swift @@ -0,0 +1,130 @@ +// +// 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 QuickLook +import SwiftUI + +extension View { + /// Preview a media file using a QuickLook Preview Controller. The preview is interactive with + /// the dismiss gesture working as expected if it was presented from UIKit. + func interactiveQuickLook(item: Binding) -> some View { + modifier(InteractiveQuickLookModifier(item: item)) + } +} + +private struct InteractiveQuickLookModifier: ViewModifier { + @Binding var item: MediaPreviewItem? + + func body(content: Content) -> some View { + content.background { + if let item { + MediaPreviewViewController(previewItem: item) { self.item = nil } + } + } + } +} + +private struct MediaPreviewViewController: UIViewControllerRepresentable { + let previewItem: MediaPreviewItem + let onDismiss: () -> Void + + func makeUIViewController(context: Context) -> PreviewHostingController { + PreviewHostingController(previewItem: previewItem, onDismiss: onDismiss) + } + + func updateUIViewController(_ uiViewController: PreviewHostingController, context: Context) { } + + /// A view controller that hosts the QuickLook preview. + /// + /// This wrapper somehow allows the preview controller to do presentation/dismissal + /// animations and interactions which don't work if you represent it directly to SwiftUI 🤷‍♂️ + class PreviewHostingController: UIViewController, QLPreviewControllerDataSource, QLPreviewControllerDelegate { + let previewItem: MediaPreviewItem + let onDismiss: () -> Void + + var previewController: QLPreviewController? + + init(previewItem: MediaPreviewItem, onDismiss: @escaping () -> Void) { + self.previewItem = previewItem + self.onDismiss = onDismiss + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + view.backgroundColor = .clear + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + guard self.previewController == nil else { return } + + let previewController = QLPreviewController() + previewController.dataSource = self + previewController.delegate = self + present(previewController, animated: true) + + self.previewController = previewController + } + + // MARK: QLPreviewControllerDataSource + + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + 1 + } + + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + previewItem + } + + // MARK: QLPreviewControllerDelegate + + func previewControllerDidDismiss(_ controller: QLPreviewController) { + onDismiss() + } + } +} + +/// Wraps a media file and title to be previewed with QuickLook. +class MediaPreviewItem: NSObject, QLPreviewItem { + let file: MediaFileHandleProxy + + var previewItemURL: URL? { file.url } + let previewItemTitle: String? + + init(file: MediaFileHandleProxy, title: String?) { + self.file = file + previewItemTitle = title + } +} + +// MARK: - Previews + +struct PreviewView_Previews: PreviewProvider { + static let previewURL = URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf") + static let previewItem = MediaPreviewItem(file: .unmanaged(url: previewURL), + title: "Important Document") + + static var previews: some View { + MediaPreviewViewController(previewItem: previewItem) { } + } +} diff --git a/ElementX/Sources/Screens/FilePreviewScreen/View/FilePreviewScreen.swift b/ElementX/Sources/Screens/FilePreviewScreen/View/FilePreviewScreen.swift deleted file mode 100644 index f583b1a79..000000000 --- a/ElementX/Sources/Screens/FilePreviewScreen/View/FilePreviewScreen.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// 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 QuickLook -import SwiftUI -import UIKit - -struct FilePreviewScreen: View { - @ObservedObject var context: FilePreviewScreenViewModel.Context - - var body: some View { - ZStack(alignment: .leading) { - PreviewView(context: context, - fileURL: context.viewState.mediaFile.url, - title: context.viewState.title) - .ignoresSafeArea(edges: .bottom) - - // Drop a random view on top to make QLPreviewController stop - // swallowing all gestures and allow swiping backwards - Rectangle() - .frame(maxWidth: 20.0) - .foregroundColor(.red) - .opacity(0.005) - .ignoresSafeArea() - } - } -} - -private struct PreviewView: UIViewControllerRepresentable { - let context: FilePreviewScreenViewModel.Context - let fileURL: URL - let title: String? - - func makeUIViewController(context: Context) -> UINavigationController { - let controller = QLPreviewController() - controller.dataSource = context.coordinator - - return UINavigationController(rootViewController: controller) - } - - func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { } - - func makeCoordinator() -> Coordinator { - Coordinator(view: self) - } - - class Coordinator: NSObject, QLPreviewControllerDataSource { - let view: PreviewView - - init(view: PreviewView) { - self.view = view - } - - @objc func done() { - Task { await view.context.send(viewAction: .cancel) } - } - - // MARK: - QLPreviewControllerDataSource - - func numberOfPreviewItems(in controller: QLPreviewController) -> Int { - 1 - } - - func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { - PreviewItem(previewItemURL: view.fileURL, previewItemTitle: view.title) - } - } -} - -private class PreviewItem: NSObject, QLPreviewItem { - var previewItemURL: URL? - var previewItemTitle: String? - - init(previewItemURL: URL?, previewItemTitle: String?) { - self.previewItemURL = previewItemURL - self.previewItemTitle = previewItemTitle - } -} - -// MARK: - Previews - -struct FilePreviewScreen_Previews: PreviewProvider { - static let viewModel = FilePreviewScreenViewModel(mediaFile: .unmanaged(url: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"))) - - static var previews: some View { - FilePreviewScreen(context: viewModel.context) - } -} diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index 82aade9a8..bc16f2f16 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -25,7 +25,6 @@ struct RoomScreenCoordinatorParameters { } enum RoomScreenCoordinatorAction { - case presentMediaViewer(file: MediaFileHandleProxy, title: String?) case presentReportContent(itemID: String, senderID: String) case presentMediaUploadPicker(MediaPickerScreenSource) case presentMediaUploadPreviewScreen(URL) @@ -67,8 +66,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol { switch action { case .displayRoomDetails: actionsSubject.send(.presentRoomDetails) - case .displayMediaViewer(let file, let title): - actionsSubject.send(.presentMediaViewer(file: file, title: title)) case .displayEmojiPicker(let itemID): actionsSubject.send(.presentEmojiPicker(itemID: itemID)) case .displayReportContent(let itemID, let senderID): diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index ea76ae893..7434ef9b9 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -20,7 +20,6 @@ import UIKit enum RoomScreenViewModelAction { case displayRoomDetails - case displayMediaViewer(file: MediaFileHandleProxy, title: String?) case displayEmojiPicker(itemID: String) case displayReportContent(itemID: String, senderID: String) case displayCameraPicker @@ -115,6 +114,9 @@ struct RoomScreenViewStateBindings { } } + /// A media item that will be previewed with QuickLook. + var mediaPreviewItem: MediaPreviewItem? + /// Information describing the currently displayed alert. var alertInfo: AlertInfo? diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 5d4e09c02..72ff18d65 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -220,7 +220,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol switch action { case .displayMediaFile(let file, let title): - callback?(.displayMediaViewer(file: file, title: title)) + state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: title) case .none: break } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 52d0e539d..b265ff54b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -50,6 +50,7 @@ struct RoomScreen: View { .environmentObject(context) } } + .interactiveQuickLook(item: $context.mediaPreviewItem) .track(screen: .room) .task(id: context.viewState.roomId) { // Give a couple of seconds for items to load and to see them. diff --git a/UnitTests/Sources/FilePreviewViewModelTests.swift b/UnitTests/Sources/FilePreviewViewModelTests.swift deleted file mode 100644 index ef9fc20db..000000000 --- a/UnitTests/Sources/FilePreviewViewModelTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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 XCTest - -@testable import ElementX - -@MainActor -class FilePreviewScreenViewModelTests: XCTestCase { - var viewModel: FilePreviewScreenViewModelProtocol! - var context: FilePreviewScreenViewModelType.Context! - - override func setUpWithError() throws { - viewModel = FilePreviewScreenViewModel(mediaFile: .unmanaged(url: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"))) - context = viewModel.context - } - - func testCancel() async throws { - var correctResult = false - viewModel.callback = { result in - switch result { - case .cancel: - correctResult = true - } - } - - context.send(viewAction: .cancel) - await Task.yield() - XCTAssert(correctResult) - } -} diff --git a/changelog.d/pr-1187.change b/changelog.d/pr-1187.change new file mode 100644 index 000000000..20faedfea --- /dev/null +++ b/changelog.d/pr-1187.change @@ -0,0 +1 @@ +Improve media preview presentation and interaction in the timeline. \ No newline at end of file