From 287fa4cad5e562a90328357593926f002c8d3b10 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Wed, 16 Nov 2022 14:34:37 +0300 Subject: [PATCH] File messages on timeline (#311) * Create media player screen * Introduce `FileCache` to cache message attachments * Add file loading functionality into the media provider * Process tap action on timeline items * Pass item taps to view model * Navigate to media player on view model callback * Commit project file * Add changelog * Add file messages into the timeline * Create file preview screen * Display files in the preview screen * Commit project file * Update Rust SDK to 1.0.19-alpha * Add changelog * Bump the RustSDK to `v1.0.20-alpha` * Configure audio session on video playback Co-authored-by: Stefan Ceriu --- ElementX.xcodeproj/project.pbxproj | 190 +++++++++++++----- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../Sources/Application/AppCoordinator.swift | 4 +- .../FilePreview/FilePreviewCoordinator.swift | 95 +++++++++ .../FilePreview/FilePreviewModels.swift | 36 ++++ .../FilePreview/FilePreviewViewModel.swift | 44 ++++ .../FilePreviewViewModelProtocol.swift | 23 +++ .../FilePreview/View/FilePreviewScreen.swift | 95 +++++++++ .../MediaPlayer/MediaPlayerCoordinator.swift | 94 +++++++++ .../MediaPlayer/MediaPlayerModels.swift | 36 ++++ .../MediaPlayer/MediaPlayerViewModel.swift | 45 +++++ .../MediaPlayerViewModelProtocol.swift | 23 +++ .../MediaPlayer/View/MediaPlayerScreen.swift | 52 +++++ .../RoomScreen/RoomScreenCoordinator.swift | 18 ++ .../Screens/RoomScreen/RoomScreenModels.swift | 1 + .../RoomScreen/RoomScreenViewModel.swift | 3 +- .../View/Timeline/FileRoomTimelineView.swift | 81 ++++++++ .../VideoPlayer/VideoPlayerCoordinator.swift | 15 ++ .../Sources/Services/Room/RoomProxy.swift | 2 +- .../Timeline/RoomTimelineController.swift | 38 ++++ .../RoomTimelineControllerProtocol.swift | 1 + .../MessageTimelineItem.swift | 18 +- .../Items/FileRoomTimelineItem.swift | 37 ++++ .../RoomTimelineItemFactory.swift | 23 +++ .../RoomTimelineViewFactory.swift | 2 + .../RoomTimelineViewProvider.swift | 5 + .../Sources/FilePreviewViewModelTests.swift | 44 ++++ .../Sources/MediaPlayerViewModelTests.swift | 44 ++++ changelog.d/310.feature | 1 + project.yml | 4 +- 30 files changed, 1019 insertions(+), 61 deletions(-) create mode 100644 ElementX/Sources/Screens/FilePreview/FilePreviewCoordinator.swift create mode 100644 ElementX/Sources/Screens/FilePreview/FilePreviewModels.swift create mode 100644 ElementX/Sources/Screens/FilePreview/FilePreviewViewModel.swift create mode 100644 ElementX/Sources/Screens/FilePreview/FilePreviewViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/FilePreview/View/FilePreviewScreen.swift create mode 100644 ElementX/Sources/Screens/MediaPlayer/MediaPlayerCoordinator.swift create mode 100644 ElementX/Sources/Screens/MediaPlayer/MediaPlayerModels.swift create mode 100644 ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModel.swift create mode 100644 ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/MediaPlayer/View/MediaPlayerScreen.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift create mode 100644 ElementX/Sources/Services/Timeline/TimelineItems/Items/FileRoomTimelineItem.swift create mode 100644 UnitTests/Sources/FilePreviewViewModelTests.swift create mode 100644 UnitTests/Sources/MediaPlayerViewModelTests.swift create mode 100644 changelog.d/310.feature diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index f15d597992..58e6c3c2e7 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -26,6 +26,7 @@ 09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; }; 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; }; 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; }; + 0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */; }; 0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; }; 0C601923A872A87C775B889A /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3689E6F87850DD65DAA45428 /* KeychainControllerProtocol.swift */; }; 0E8C480700870BB34A2A360F /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 4346F63D53A346271577FD9C /* AppAuth */; }; @@ -41,6 +42,7 @@ 13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */; }; 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; }; 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; }; + 1504CE9A609A348D90B69E47 /* VideoPlayerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */; }; 152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */; }; 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; }; 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; }; @@ -53,10 +55,12 @@ 191161FE9E0DA89704301F37 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; }; 19839F3526CE8C35AAF241AD /* ServerSelectionViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F52BF30D12BA3BD3D3DBB8F /* ServerSelectionViewModelProtocol.swift */; }; + 19ED6CF7FDBB1158692D101C /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D783758EAE6A88C93564EB /* VideoPlayerViewModel.swift */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; 1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */; }; 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; }; + 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; }; 1F3232BD368DF430AB433907 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5A56C4F47C368EBE5C5E870 /* DesignKit */; }; 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */; }; 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */; }; @@ -87,16 +91,16 @@ 30122AB3484AC6C3A7F6A717 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B64F3A3D0DF86ED5A241AB05 /* ActivityIndicatorView.xib */; }; 308BD9343B95657FAA583FB7 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = AD2AC190E55B2BD4D0F1D4A7 /* SwiftyBeaver */; }; 3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; }; + 3274219F7F26A5C6C2C55630 /* FilePreviewViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */; }; 32BA37B01B05261FCF2D4B45 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090CA61A835C151CEDF8F372 /* WeakDictionaryKeyReference.swift */; }; 33CAC1226DFB8B5D8447D286 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; }; + 33D630461FC4562CC767EE9F /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B0B1226DA8DB55918B34CD /* FileCache.swift */; }; 344AF4CBB6D8786214878642 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9D5F812E5AD6DC786DBC9B /* NavigationRouterStoreProtocol.swift */; }; 34966D4C1C2C6D37FE3F7F50 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD2D50A7EAA4FC78417730E /* SettingsCoordinator.swift */; }; - 34C258B9CDFC07F7D9BD00E8 /* VideoPlayerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8482665982D664BDDA644F /* VideoPlayerViewModelTests.swift */; }; 352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; }; 3588F34D05B4D731A73214C6 /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DED59F9EFF273BFA2055FFDF /* BugReportScreen.swift */; }; 35C57543D245E82CBFE15DF0 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; - 3603946B7A65EAE18FF5AB63 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 749C42DE5951C0695312EBFD /* FileCache.swift */; }; 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; 36C10EDEDC0466E3A9D63132 /* VideoRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5B19A10D3F7C2BC5315DF /* VideoRoomTimelineItem.swift */; }; @@ -117,12 +121,14 @@ 43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD460ED7ED1E03B85DEA25C /* TemplateCoordinator.swift */; }; 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; 447E8580A0A2569E32529E17 /* MockRoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */; }; + 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */; }; 457465EC436703E8C76133A4 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7955B20E2E6DA68E5BC0AB9 /* WeakDictionaryReference.swift */; }; 462813B93C39DF93B1249403 /* RoundedToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFABDF2E19D349DAAAC18C65 /* RoundedToastView.swift */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; }; - 46F8817A235DC41228128BE7 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B7BF5D0705F3CB70E7B2D7 /* VideoPlayerViewModel.swift */; }; - 485A7A97076C7D19104BDC1D /* VideoPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCBE603A7EB2C93E81BA6415 /* VideoPlayerModels.swift */; }; + 46F8817A235DC41228128BE7 /* MediaPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B7BF5D0705F3CB70E7B2D7 /* MediaPlayerViewModel.swift */; }; + 483507026FDCA2E16E5197A6 /* MediaPlayerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C444092DB0E4AB393067AC36 /* MediaPlayerViewModelTests.swift */; }; + 485A7A97076C7D19104BDC1D /* MediaPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCBE603A7EB2C93E81BA6415 /* MediaPlayerModels.swift */; }; 490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; }; 492274DA6691EE985C2FCCAA /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; }; @@ -150,16 +156,18 @@ 5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; }; 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; }; 5E1FCC43B738941D5A5F1794 /* SplashScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */; }; + 5E540CAEF764D7FBD8D80776 /* VideoPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3FC45B7643298BF361CEB1 /* VideoPlayerModels.swift */; }; 5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */; }; 5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; }; 60ED66E63A169E47489348A8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 2B788C81F6369D164ADEB917 /* GZIP */; }; 617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; }; 6298AB0906DDD3525CD78C6B /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; }; 62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8210612D17A39369480FC183 /* MediaSource.swift */; }; + 630E89EBB0F791208EEE6D11 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00A7110B937C6AE2EF5D7D6 /* FileRoomTimelineItem.swift */; }; 63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB10E673916D2B8D21FD197 /* TemplateModels.swift */; }; 64F43D7390DA2A0AFD6BA911 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */; }; 64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; }; - 656427D3C59554E03ECD898E /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C2348F84A80F682E3A68D0 /* VideoPlayerCoordinator.swift */; }; + 656427D3C59554E03ECD898E /* MediaPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C2348F84A80F682E3A68D0 /* MediaPlayerCoordinator.swift */; }; 663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; }; 6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */; }; 67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; @@ -172,6 +180,8 @@ 6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; }; 6A367F3D7A437A79B7D9A31C /* FullscreenLoadingViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */; }; 6AC1DC1EAD9F7568360DA1BA /* ServerSelectionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */; }; + 6C67774E8387D44426718BD9 /* FilePreviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */; }; + 6C9F6C7F2B35288C4230EF3F /* FilePreviewModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */; }; 6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; }; 6DF37000571B1BC6D134CC9E /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304FFD608DB6E612075AB1B4 /* WeakDictionary.swift */; }; 6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */; }; @@ -199,6 +209,7 @@ 7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; }; 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; }; 7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0950733DD4BA83EEE752E259 /* PlaceholderAvatarImage.swift */; }; + 7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */; }; 7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */; }; 7F08F4BC1312075E2B5EAEFA /* AuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */; }; 7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF47564C584F614B7287F3EB /* RootRouter.swift */; }; @@ -206,7 +217,7 @@ 7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */; }; 7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C483956FA3D665E3842E319A /* SettingsScreen.swift */; }; 8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; }; - 80997E933A5B2C0868D80B45 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6410F8C03DC4AA46991A6B02 /* VideoPlayerViewModelProtocol.swift */; }; + 80997E933A5B2C0868D80B45 /* MediaPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */; }; 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; }; 80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */; }; 83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; }; @@ -223,12 +234,13 @@ 8B807DC963D1D4155A241BCC /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9E67AAB66638C69626866C /* UserSessionFlowCoordinator.swift */; }; 8BBD3AA589DEE02A1B0923B2 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; - 8D332A24CD23B4216E33EC5C /* VideoPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447A6399BC5EDE7AF7713267 /* VideoPlayerScreen.swift */; }; + 8D332A24CD23B4216E33EC5C /* MediaPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447A6399BC5EDE7AF7713267 /* MediaPlayerScreen.swift */; }; 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; }; 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; }; 8F2FAA98457750D9D664136F /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; + 91DFCB641FBA03EE2DA0189E /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB27E1BE894F9F9F0134372 /* FilePreviewScreen.swift */; }; 9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; }; 93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; }; 93BA4A81B6D893271101F9F0 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; }; @@ -314,10 +326,13 @@ C7B251DC896C0867C51B616D /* AnalyticsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */; }; C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; }; C8E82786DE1B6A400DA9BA25 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */; }; + C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */; }; + CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; }; CA9558C0B40C1EE2AD00124A /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; }; CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; }; + CBF64DE774298D773DBD5354 /* VideoPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */; }; CC736DA1AA8F8B9FD8785009 /* ScreenshotDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */; }; CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */; }; CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; @@ -327,6 +342,7 @@ D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */; }; D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; }; D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; }; + D3E603A5E9D529CF293E1BF9 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1651A532305027D3F605E2B /* VideoPlayerCoordinator.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; }; D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; }; @@ -356,8 +372,6 @@ EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; }; EC280623A42904341363EAAF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; }; EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */; }; - ECDDA9CD291E6F4100FC93E6 /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDDA9CC291E6F4100FC93E6 /* FileCacheTests.swift */; }; - ECDDA9D3292233B600FC93E6 /* Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDDA9D2292233B600FC93E6 /* Swipe.swift */; }; EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; }; EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; }; EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; }; @@ -427,6 +441,7 @@ 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = ""; }; 0C13A92C1E9C79F055B8133D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerScreen.swift; sourceTree = ""; }; 0DD16CE9A66C9040B066AD60 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = ""; }; 0E7062F88E9D5F79C8A80524 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = ""; }; 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; @@ -454,6 +469,7 @@ 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorViewPresentable.swift; sourceTree = ""; }; 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = ""; }; 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = ""; }; + 1A3FC45B7643298BF361CEB1 /* VideoPlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerModels.swift; sourceTree = ""; }; 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = ""; }; 1C429043E986008B97736636 /* ab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ab; path = ab.lproj/Localizable.strings; sourceTree = ""; }; 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = ""; }; @@ -491,7 +507,6 @@ 2CF9FE7E0CF9F40D1509E63A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = bg; path = bg.lproj/Localizable.stringsdict; sourceTree = ""; }; 2D256FEE2F1AF1E51D39B622 /* LoginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTests.swift; sourceTree = ""; }; 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemProxy.swift; sourceTree = ""; }; - 2D8482665982D664BDDA644F /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelTests.swift; sourceTree = ""; }; 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertInfo.swift; sourceTree = ""; }; 2F1B28C596DE541DA0AFD16C /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lo; path = lo.lproj/Localizable.stringsdict; sourceTree = ""; }; 304FFD608DB6E612075AB1B4 /* WeakDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = ""; }; @@ -530,11 +545,11 @@ 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = ""; }; 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenLoadingViewPresenter.swift; sourceTree = ""; }; - 41C2348F84A80F682E3A68D0 /* VideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerCoordinator.swift; sourceTree = ""; }; + 41C2348F84A80F682E3A68D0 /* MediaPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerCoordinator.swift; sourceTree = ""; }; 422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = ""; }; 434522ED2BDED08759048077 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewPresenter.swift; sourceTree = ""; }; - 447A6399BC5EDE7AF7713267 /* VideoPlayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerScreen.swift; sourceTree = ""; }; + 447A6399BC5EDE7AF7713267 /* MediaPlayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerScreen.swift; sourceTree = ""; }; 4488F5F92A64A137665C96CD /* pa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pa; path = pa.lproj/Localizable.strings; sourceTree = ""; }; 44AEEE13AC1BF303AE48CBF8 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = ""; }; 44D8C8431416EB8DFEC7E235 /* ApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTests.swift; sourceTree = ""; }; @@ -568,6 +583,7 @@ 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = ""; }; 51DF91C374901E94D93276F1 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-MX"; path = "es-MX.lproj/Localizable.stringsdict"; sourceTree = ""; }; 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreen.swift; sourceTree = ""; }; + 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelProtocol.swift; sourceTree = ""; }; 529513218340CC8419273165 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; 534A5C8FCDE2CBC50266B9F2 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = gl; path = gl.lproj/Localizable.stringsdict; sourceTree = ""; }; 536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; @@ -576,6 +592,7 @@ 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = ""; }; 55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = ""; }; 55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = ""; }; + 55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewModels.swift; sourceTree = ""; }; 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = ""; }; 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedRoomTimelineView.swift; sourceTree = ""; }; 56F01DD1BBD4450E18115916 /* LabelledActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelledActivityIndicatorView.swift; sourceTree = ""; }; @@ -599,8 +616,9 @@ 61B73D5E21F524A9BE44448D /* UserIndicatorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorRequest.swift; sourceTree = ""; }; 6235E1CE00A6D989D7DB6D47 /* RectangleToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleToastView.swift; sourceTree = ""; }; 624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModel.swift; sourceTree = ""; }; 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; - 6410F8C03DC4AA46991A6B02 /* VideoPlayerViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelProtocol.swift; sourceTree = ""; }; + 6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModelProtocol.swift; sourceTree = ""; }; 653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = ""; }; 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = ""; }; 6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -630,7 +648,6 @@ 72D03D36422177EF01905D20 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; 73FC861755C6388F62B9280A /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; - 749C42DE5951C0695312EBFD /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = ""; }; 752DEC02D93AFF46BC13313A /* NavigationRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterType.swift; sourceTree = ""; }; 799A3A11C434296ED28F87C8 /* iw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = iw; path = iw.lproj/Localizable.strings; sourceTree = ""; }; 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelTests.swift; sourceTree = ""; }; @@ -644,6 +661,7 @@ 7DDBF99755A9008CF8C8499E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 7E154FEA1E6FE964D3DF7859 /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fy; path = fy.lproj/Localizable.strings; sourceTree = ""; }; 7E532D95330139D118A9BF88 /* BugReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModel.swift; sourceTree = ""; }; + 7FB27E1BE894F9F9F0134372 /* FilePreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreen.swift; sourceTree = ""; }; 804F9B0FABE093C7284CD09B /* TimelineItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemList.swift; sourceTree = ""; }; 8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 8166F121C79C7B62BF01D508 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = pt.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -665,18 +683,18 @@ 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; 8A9AE4967817E9608E22EB44 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 8AC1A01C3A745BDF1D3697D3 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = ""; }; - 8B9A55AC2FB0FE0AEAA3DF1F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 8B9A55AC2FB0FE0AEAA3DF1F /* LICENSE */ = {isa = PBXFileReference; path = LICENSE; sourceTree = ""; }; 8C0AA893D6F8A2F563E01BB9 /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = in; path = in.lproj/Localizable.stringsdict; sourceTree = ""; }; 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; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; 8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = ""; }; 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; 9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; 9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugScreen.swift; sourceTree = ""; }; - 92B7BF5D0705F3CB70E7B2D7 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; + 92B7BF5D0705F3CB70E7B2D7 /* MediaPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModel.swift; sourceTree = ""; }; 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = ""; }; 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = ""; }; 938BD1FCD9E6FF3FCFA7AB4C /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -709,12 +727,14 @@ A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = ""; }; A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = ""; }; + A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelTests.swift; sourceTree = ""; }; A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = ""; }; A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = ""; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserSession.swift; sourceTree = ""; }; A4B5B19A10D3F7C2BC5315DF /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; + A5B0B1226DA8DB55918B34CD /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = ""; }; A64F0DB78E0AC23C91AD89EF /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = mk.lproj/Localizable.strings; sourceTree = ""; }; A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; @@ -723,6 +743,7 @@ A8F48EB9B52E70285A4BCB07 /* ur */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ur; path = ur.lproj/Localizable.strings; sourceTree = ""; }; A9873374E72AA53260AE90A2 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; + A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swipe.swift; sourceTree = ""; }; AA19C32BD97F45847724E09A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Untranslated.strings; sourceTree = ""; }; AA8BA82CF99D843FEF680E91 /* AnalyticsPromptModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptModels.swift; sourceTree = ""; }; AAC9344689121887B74877AF /* UnitTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -732,6 +753,7 @@ ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = ""; }; AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportCoordinator.swift; sourceTree = ""; }; + ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewCoordinator.swift; sourceTree = ""; }; ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; AE225C66978648AA4AF37B45 /* te */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = te; path = te.lproj/Localizable.strings; sourceTree = ""; }; AE5DDBEBBA17973ED4638823 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -764,15 +786,18 @@ BA7B2E9CC5DC3B76ADC35A43 /* AnalyticsPromptCheckmarkItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptCheckmarkItem.swift; sourceTree = ""; }; BB3073CCD77D906B330BC1D6 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; BC9B05D6B293A039EB963CA7 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Localizable.strings; sourceTree = ""; }; - BCBE603A7EB2C93E81BA6415 /* VideoPlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerModels.swift; sourceTree = ""; }; + BCBE603A7EB2C93E81BA6415 /* MediaPlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerModels.swift; sourceTree = ""; }; BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = ""; }; BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = ""; }; BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = ""; }; + C00A7110B937C6AE2EF5D7D6 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = ""; }; C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = ""; }; C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = ""; }; C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = ""; }; C2DE30233B57761F8AFEB415 /* ReversedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversedScrollView.swift; sourceTree = ""; }; + C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = ""; }; + C444092DB0E4AB393067AC36 /* MediaPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModelTests.swift; sourceTree = ""; }; C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = ""; }; C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = ""; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = ""; }; @@ -806,8 +831,10 @@ D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; D0ADFDC712027931F2216668 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = ""; }; + D1651A532305027D3F605E2B /* VideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerCoordinator.swift; sourceTree = ""; }; D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionVerificationControllerProxy.swift; sourceTree = ""; }; D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = ""; }; + D2D783758EAE6A88C93564EB /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; D31DC8105C6233E5FFD9B84C /* element-x-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "element-x-ios"; path = .; sourceTree = SOURCE_ROOT; }; D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; @@ -825,6 +852,7 @@ DD73FAAA4A76CE4A1F3014D9 /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = ""; }; DED59F9EFF273BFA2055FFDF /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.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 = ""; }; E077F76026C85ED96FEBB810 /* UserIndicatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorPresenter.swift; sourceTree = ""; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = ""; }; E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -835,6 +863,7 @@ E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = ""; }; E45C57120F28F8D619150219 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/Localizable.strings; sourceTree = ""; }; E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationModels.swift; sourceTree = ""; }; + E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = ""; }; E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; E579A0DA01F488C97B771EF6 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lv; path = lv.lproj/Localizable.stringsdict; sourceTree = ""; }; E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = ""; }; @@ -844,8 +873,6 @@ E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = ""; }; EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = ""; }; EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; - ECDDA9CC291E6F4100FC93E6 /* FileCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacheTests.swift; sourceTree = ""; }; - ECDDA9D2292233B600FC93E6 /* Swipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swipe.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 = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; @@ -864,6 +891,7 @@ F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = ""; }; F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; + F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacheTests.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = ""; }; F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; @@ -973,11 +1001,11 @@ 0787F81684E503024BD0C051 /* Services */ = { isa = PBXGroup; children = ( - ECDDA9C9291E5EE800FC93E6 /* Cache */, 4BF8D11D9ED15CFC373D0119 /* Analytics */, AAFDD509929A0CCF8BCE51EB /* Authentication */, EBBEB5471737E9D116DF4738 /* Background */, 0ED3F5C21537519389C07644 /* BugReport */, + 2D6DC9871FD7173E51D67C73 /* Cache */, 8039515BAA53B7C3275AC64A /* Client */, 79E560F5113ED25D172E550C /* Media */, 40E6246F03D1FE377BC5D963 /* Room */, @@ -1070,6 +1098,26 @@ path = Resources; sourceTree = ""; }; + 285079C24A5189C48284CC47 /* VideoPlayer */ = { + isa = PBXGroup; + children = ( + D1651A532305027D3F605E2B /* VideoPlayerCoordinator.swift */, + 1A3FC45B7643298BF361CEB1 /* VideoPlayerModels.swift */, + D2D783758EAE6A88C93564EB /* VideoPlayerViewModel.swift */, + 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */, + 5E01022071DDDC48EF453374 /* View */, + ); + path = VideoPlayer; + sourceTree = ""; + }; + 2D6DC9871FD7173E51D67C73 /* Cache */ = { + isa = PBXGroup; + children = ( + A5B0B1226DA8DB55918B34CD /* FileCache.swift */, + ); + path = Cache; + sourceTree = ""; + }; 2ECFF6B05DAA37EB10DBF7E8 /* UITests */ = { isa = PBXGroup; children = ( @@ -1171,10 +1219,10 @@ 4E2245243369B99216C7D84E /* ImageCache.swift */, 2AFEF3AC64B1358083F76B8B /* List.swift */, 40B21E611DADDEF00307E7AC /* String.swift */, + A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */, A40C19719687984FD9478FBE /* Task.swift */, 287FC98AF2664EAD79C0D902 /* UIDevice.swift */, 227AC5D71A4CE43512062243 /* URL.swift */, - ECDDA9D2292233B600FC93E6 /* Swipe.swift */, ); path = Extensions; sourceTree = ""; @@ -1300,6 +1348,14 @@ path = TimeLineItemContent; sourceTree = ""; }; + 5E01022071DDDC48EF453374 /* View */ = { + isa = PBXGroup; + children = ( + 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 605F8221E52991786397FCC9 /* View */ = { isa = PBXGroup; children = ( @@ -1381,6 +1437,8 @@ EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */, 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */, DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */, + F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */, + DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */, 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */, 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */, FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, @@ -1388,6 +1446,7 @@ 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, A05707BF550D770168A406DB /* LoginViewModelTests.swift */, F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */, + C444092DB0E4AB393067AC36 /* MediaPlayerViewModelTests.swift */, 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */, 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */, F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */, @@ -1399,8 +1458,7 @@ 7B5CF94E124616FD89424B73 /* SplashScreenViewModelTests.swift */, 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */, EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */, - 2D8482665982D664BDDA644F /* VideoPlayerViewModelTests.swift */, - ECDDA9CC291E6F4100FC93E6 /* FileCacheTests.swift */, + A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */, AF552BB969DC98A4BB8CF8D5 /* UserIndicators */, ); path = Sources; @@ -1411,6 +1469,7 @@ children = ( F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */, B3FA7C8D4EF2B1873C180ED7 /* EncryptedRoomTimelineItem.swift */, + C00A7110B937C6AE2EF5D7D6 /* FileRoomTimelineItem.swift */, 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */, 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */, 9B577F829C693B8DFB7014FD /* RedactedRoomTimelineItem.swift */, @@ -1633,7 +1692,7 @@ A253B36CAD2059B6D8C130CD /* View */ = { isa = PBXGroup; children = ( - 447A6399BC5EDE7AF7713267 /* VideoPlayerScreen.swift */, + 447A6399BC5EDE7AF7713267 /* MediaPlayerScreen.swift */, ); path = View; sourceTree = ""; @@ -1711,6 +1770,18 @@ path = SplashScreen; sourceTree = ""; }; + B442FCF47E0A6F28D7D50A4D /* FilePreview */ = { + isa = PBXGroup; + children = ( + ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */, + 55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */, + 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */, + C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */, + DBF3259D9A7092A49E0FE642 /* View */, + ); + path = FilePreview; + sourceTree = ""; + }; B53CA9BECD3F97805E1432D0 /* HomeScreen */ = { isa = PBXGroup; children = ( @@ -1728,6 +1799,7 @@ children = ( 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */, 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */, + E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */, F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */, D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */, B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */, @@ -1837,16 +1909,16 @@ path = Vendor; sourceTree = ""; }; - D3E07C2F92EC8C5659601744 /* VideoPlayer */ = { + D3E07C2F92EC8C5659601744 /* MediaPlayer */ = { isa = PBXGroup; children = ( - 41C2348F84A80F682E3A68D0 /* VideoPlayerCoordinator.swift */, - BCBE603A7EB2C93E81BA6415 /* VideoPlayerModels.swift */, - 92B7BF5D0705F3CB70E7B2D7 /* VideoPlayerViewModel.swift */, - 6410F8C03DC4AA46991A6B02 /* VideoPlayerViewModelProtocol.swift */, + 41C2348F84A80F682E3A68D0 /* MediaPlayerCoordinator.swift */, + BCBE603A7EB2C93E81BA6415 /* MediaPlayerModels.swift */, + 92B7BF5D0705F3CB70E7B2D7 /* MediaPlayerViewModel.swift */, + 6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */, A253B36CAD2059B6D8C130CD /* View */, ); - path = VideoPlayer; + path = MediaPlayer; sourceTree = ""; }; D958761758AA1110476DE6A3 /* SessionVerification */ = { @@ -1862,6 +1934,14 @@ path = SessionVerification; sourceTree = ""; }; + DBF3259D9A7092A49E0FE642 /* View */ = { + isa = PBXGroup; + children = ( + 7FB27E1BE894F9F9F0134372 /* FilePreviewScreen.swift */, + ); + path = View; + sourceTree = ""; + }; E0EEBB2F7AA1BB36FC08F606 /* AnalyticsPrompt */ = { isa = PBXGroup; children = ( @@ -1888,13 +1968,15 @@ E0EEBB2F7AA1BB36FC08F606 /* AnalyticsPrompt */, E74CD7681375AD2EAA34D66B /* Authentication */, 4009BE2E791C16AC6EE39A7E /* BugReport */, + B442FCF47E0A6F28D7D50A4D /* FilePreview */, B53CA9BECD3F97805E1432D0 /* HomeScreen */, - D3E07C2F92EC8C5659601744 /* VideoPlayer */, + D3E07C2F92EC8C5659601744 /* MediaPlayer */, 679E9837ECA8D6776079D16E /* RoomScreen */, D958761758AA1110476DE6A3 /* SessionVerification */, 70B74A432C241E56A7ACE610 /* Settings */, 02175C9269C4632DB6D12C25 /* Splash */, B1A847595434E3DD177F5143 /* SplashScreen */, + 285079C24A5189C48284CC47 /* VideoPlayer */, ); path = Screens; sourceTree = ""; @@ -1946,14 +2028,6 @@ path = Background; sourceTree = ""; }; - ECDDA9C9291E5EE800FC93E6 /* Cache */ = { - isa = PBXGroup; - children = ( - 749C42DE5951C0695312EBFD /* FileCache.swift */, - ); - path = Cache; - sourceTree = ""; - }; F8474EB69289112888B65518 /* UserIndicators */ = { isa = PBXGroup; children = ( @@ -2403,15 +2477,16 @@ 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */, C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */, 9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */, + 7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */, + CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */, F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */, 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */, - ECDDA9CD291E6F4100FC93E6 /* FileCacheTests.swift in Sources */, EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */, 2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */, - 34C258B9CDFC07F7D9BD00E8 /* VideoPlayerViewModelTests.swift in Sources */, + 483507026FDCA2E16E5197A6 /* MediaPlayerViewModelTests.swift in Sources */, 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */, 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */, EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */, @@ -2427,6 +2502,7 @@ 1151DCC5EC2C6585826545EC /* UserIndicatorPresenterSpy.swift in Sources */, 4B8A2C45FF906ADBB1F5C3B4 /* UserIndicatorQueueTests.swift in Sources */, BEEC06EFD30BFCA02F0FD559 /* UserIndicatorTests.swift in Sources */, + 1504CE9A609A348D90B69E47 /* VideoPlayerViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2495,7 +2571,15 @@ F6E860FF7B18B81DF43B30B8 /* EncryptedRoomTimelineItem.swift in Sources */, B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, + 33D630461FC4562CC767EE9F /* FileCache.swift in Sources */, FD4706DC752744A0C91ED6FE /* FileManager.swift in Sources */, + 6C67774E8387D44426718BD9 /* FilePreviewCoordinator.swift in Sources */, + 6C9F6C7F2B35288C4230EF3F /* FilePreviewModels.swift in Sources */, + 91DFCB641FBA03EE2DA0189E /* FilePreviewScreen.swift in Sources */, + 0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */, + 3274219F7F26A5C6C2C55630 /* FilePreviewViewModelProtocol.swift in Sources */, + 630E89EBB0F791208EEE6D11 /* FileRoomTimelineItem.swift in Sources */, + 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */, A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */, 85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */, 6A367F3D7A437A79B7D9A31C /* FullscreenLoadingViewPresenter.swift in Sources */, @@ -2524,17 +2608,15 @@ BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */, 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */, 2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */, - ECDDA9D3292233B600FC93E6 /* Swipe.swift in Sources */, B94368839BDB69172E28E245 /* MXLog.swift in Sources */, 2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */, BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */, 67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */, - 3603946B7A65EAE18FF5AB63 /* FileCache.swift in Sources */, - 656427D3C59554E03ECD898E /* VideoPlayerCoordinator.swift in Sources */, - 485A7A97076C7D19104BDC1D /* VideoPlayerModels.swift in Sources */, - 8D332A24CD23B4216E33EC5C /* VideoPlayerScreen.swift in Sources */, - 46F8817A235DC41228128BE7 /* VideoPlayerViewModel.swift in Sources */, - 80997E933A5B2C0868D80B45 /* VideoPlayerViewModelProtocol.swift in Sources */, + 656427D3C59554E03ECD898E /* MediaPlayerCoordinator.swift in Sources */, + 485A7A97076C7D19104BDC1D /* MediaPlayerModels.swift in Sources */, + 8D332A24CD23B4216E33EC5C /* MediaPlayerScreen.swift in Sources */, + 46F8817A235DC41228128BE7 /* MediaPlayerViewModel.swift in Sources */, + 80997E933A5B2C0868D80B45 /* MediaPlayerViewModelProtocol.swift in Sources */, EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */, 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */, 62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */, @@ -2639,6 +2721,7 @@ 2F94054F50E312AF30BE07F3 /* String.swift in Sources */, A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */, 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */, + 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */, E290C78E7F09F47FD2662986 /* Task.swift in Sources */, 43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */, 63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */, @@ -2685,6 +2768,11 @@ 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */, 7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */, AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */, + D3E603A5E9D529CF293E1BF9 /* VideoPlayerCoordinator.swift in Sources */, + 5E540CAEF764D7FBD8D80776 /* VideoPlayerModels.swift in Sources */, + CBF64DE774298D773DBD5354 /* VideoPlayerScreen.swift in Sources */, + 19ED6CF7FDBB1158692D101C /* VideoPlayerViewModel.swift in Sources */, + C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */, 36C10EDEDC0466E3A9D63132 /* VideoRoomTimelineItem.swift in Sources */, 64F43D7390DA2A0AFD6BA911 /* VideoRoomTimelineView.swift in Sources */, 6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */, @@ -3316,7 +3404,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.18-alpha"; + version = "1.0.20-alpha"; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a224545706..685f6638f9 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "c8494d858cfb60eb6167d76790b819c9dc15e822", - "version" : "1.0.18-alpha" + "revision" : "f4ff25f54c7e854fa1498f6dfd7bedca8c0ed260", + "version" : "1.0.20-alpha" } }, { @@ -129,7 +129,7 @@ { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", "version" : "0.1.4" diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 6130cfe10c..a6e4ce1606 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -116,11 +116,11 @@ class AppCoordinator: Coordinator { // This exposes the full Rust side tracing subscriber filter for more flexibility. // We can filter by level, crate and even file. See more details here: // https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples - setupTracing(configuration: "warn,hyper=warn,sled=warn,matrix_sdk_sled=warn") + setupTracing(filter: "warn,hyper=warn,sled=warn,matrix_sdk_sled=warn") loggerConfiguration.logLevel = .debug #else - setupTracing(configuration: "info,hyper=warn,sled=warn,matrix_sdk_sled=warn") + setupTracing(filter: "info,hyper=warn,sled=warn,matrix_sdk_sled=warn") loggerConfiguration.logLevel = .info #endif diff --git a/ElementX/Sources/Screens/FilePreview/FilePreviewCoordinator.swift b/ElementX/Sources/Screens/FilePreview/FilePreviewCoordinator.swift new file mode 100644 index 0000000000..cc8f053d1c --- /dev/null +++ b/ElementX/Sources/Screens/FilePreview/FilePreviewCoordinator.swift @@ -0,0 +1,95 @@ +// +// 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 FilePreviewCoordinatorParameters { + let fileURL: URL + let title: String? +} + +enum FilePreviewCoordinatorAction { + case cancel +} + +final class FilePreviewCoordinator: Coordinator, Presentable { + // MARK: - Properties + + // MARK: Private + + private let parameters: FilePreviewCoordinatorParameters + private let filePreviewHostingController: UIViewController + private var filePreviewViewModel: FilePreviewViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var activityIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var callback: ((FilePreviewCoordinatorAction) -> Void)? + + // MARK: - Setup + + init(parameters: FilePreviewCoordinatorParameters) { + self.parameters = parameters + + let viewModel = FilePreviewViewModel(fileURL: parameters.fileURL, title: parameters.title) + let view = FilePreviewScreen(context: viewModel.context) + filePreviewViewModel = viewModel + filePreviewHostingController = UIHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: filePreviewHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("Did start.") + filePreviewViewModel.callback = { [weak self] action in + guard let self else { return } + MXLog.debug("FilePreviewViewModel did complete with result: \(action).") + switch action { + case .cancel: + self.callback?(.cancel) + } + } + } + + func toPresentable() -> UIViewController { + filePreviewHostingController + } + + func stop() { + stopLoading() + } + + // MARK: - Private + + /// Show an activity indicator whilst loading. + /// - Parameters: + /// - label: The label to show on the indicator. + /// - isInteractionBlocking: Whether the indicator should block any user interaction. + private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) { + activityIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking)) + } + + /// Hide the currently displayed activity indicator. + private func stopLoading() { + activityIndicator = nil + } +} diff --git a/ElementX/Sources/Screens/FilePreview/FilePreviewModels.swift b/ElementX/Sources/Screens/FilePreview/FilePreviewModels.swift new file mode 100644 index 0000000000..0b07aa2800 --- /dev/null +++ b/ElementX/Sources/Screens/FilePreview/FilePreviewModels.swift @@ -0,0 +1,36 @@ +// +// 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 + +// MARK: - Coordinator + +// MARK: View model + +enum FilePreviewViewModelAction { + case cancel +} + +// MARK: View + +struct FilePreviewViewState: BindableState { + let fileURL: URL + let title: String? +} + +enum FilePreviewViewAction { + case cancel +} diff --git a/ElementX/Sources/Screens/FilePreview/FilePreviewViewModel.swift b/ElementX/Sources/Screens/FilePreview/FilePreviewViewModel.swift new file mode 100644 index 0000000000..0bf5d684e7 --- /dev/null +++ b/ElementX/Sources/Screens/FilePreview/FilePreviewViewModel.swift @@ -0,0 +1,44 @@ +// +// 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 FilePreviewViewModelType = StateStoreViewModel + +class FilePreviewViewModel: FilePreviewViewModelType, FilePreviewViewModelProtocol { + // MARK: - Properties + + // MARK: Private + + // MARK: Public + + var callback: ((FilePreviewViewModelAction) -> Void)? + + // MARK: - Setup + + init(fileURL: URL, title: String? = nil) { + super.init(initialViewState: FilePreviewViewState(fileURL: fileURL, title: title)) + } + + // MARK: - Public + + override func process(viewAction: FilePreviewViewAction) async { + switch viewAction { + case .cancel: + callback?(.cancel) + } + } +} diff --git a/ElementX/Sources/Screens/FilePreview/FilePreviewViewModelProtocol.swift b/ElementX/Sources/Screens/FilePreview/FilePreviewViewModelProtocol.swift new file mode 100644 index 0000000000..2bcef048a9 --- /dev/null +++ b/ElementX/Sources/Screens/FilePreview/FilePreviewViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// 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 FilePreviewViewModelProtocol { + var callback: ((FilePreviewViewModelAction) -> Void)? { get set } + var context: FilePreviewViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/FilePreview/View/FilePreviewScreen.swift b/ElementX/Sources/Screens/FilePreview/View/FilePreviewScreen.swift new file mode 100644 index 0000000000..a71ce938b9 --- /dev/null +++ b/ElementX/Sources/Screens/FilePreview/View/FilePreviewScreen.swift @@ -0,0 +1,95 @@ +// +// 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 { + // MARK: Private + + @Environment(\.colorScheme) private var colorScheme + + var counterColor: Color { + colorScheme == .light ? .element.secondaryContent : .element.tertiaryContent + } + + // MARK: Public + + @ObservedObject var context: FilePreviewViewModel.Context + + // MARK: Views + + var body: some View { + PreviewController(fileURL: context.viewState.fileURL, title: context.viewState.title) + } +} + +private struct PreviewController: UIViewControllerRepresentable { + 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(parent: self) + } + + class Coordinator: QLPreviewControllerDataSource { + let parent: PreviewController + + init(parent: PreviewController) { + self.parent = parent + } + + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + 1 + } + + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + PreviewItem(previewItemURL: parent.fileURL, previewItemTitle: parent.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 FilePreview_Previews: PreviewProvider { + static var previews: some View { + Group { + let upgradeViewModel = FilePreviewViewModel(fileURL: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")) + FilePreviewScreen(context: upgradeViewModel.context) + } + .tint(.element.accent) + } +} diff --git a/ElementX/Sources/Screens/MediaPlayer/MediaPlayerCoordinator.swift b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerCoordinator.swift new file mode 100644 index 0000000000..425ae9443a --- /dev/null +++ b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerCoordinator.swift @@ -0,0 +1,94 @@ +// +// 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 MediaPlayerCoordinatorParameters { + let mediaURL: URL +} + +enum MediaPlayerCoordinatorAction { + case cancel +} + +final class MediaPlayerCoordinator: Coordinator, Presentable { + // MARK: - Properties + + // MARK: Private + + private let parameters: MediaPlayerCoordinatorParameters + private let mediaPlayerHostingController: UIViewController + private var mediaPlayerViewModel: MediaPlayerViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var activityIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var callback: ((MediaPlayerCoordinatorAction) -> Void)? + + // MARK: - Setup + + init(parameters: MediaPlayerCoordinatorParameters) { + self.parameters = parameters + + let viewModel = MediaPlayerViewModel(mediaURL: parameters.mediaURL) + let view = MediaPlayerScreen(context: viewModel.context) + mediaPlayerViewModel = viewModel + mediaPlayerHostingController = UIHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: mediaPlayerHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("Did start.") + mediaPlayerViewModel.callback = { [weak self] action in + guard let self else { return } + MXLog.debug("MediaPlayerViewModel did complete with result: \(action).") + switch action { + case .cancel: + self.callback?(.cancel) + } + } + } + + func toPresentable() -> UIViewController { + mediaPlayerHostingController + } + + func stop() { + stopLoading() + } + + // MARK: - Private + + /// Show an activity indicator whilst loading. + /// - Parameters: + /// - label: The label to show on the indicator. + /// - isInteractionBlocking: Whether the indicator should block any user interaction. + private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) { + activityIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking)) + } + + /// Hide the currently displayed activity indicator. + private func stopLoading() { + activityIndicator = nil + } +} diff --git a/ElementX/Sources/Screens/MediaPlayer/MediaPlayerModels.swift b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerModels.swift new file mode 100644 index 0000000000..1fe720b207 --- /dev/null +++ b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerModels.swift @@ -0,0 +1,36 @@ +// +// 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 + +// MARK: - Coordinator + +// MARK: View model + +enum MediaPlayerViewModelAction { + case cancel +} + +// MARK: View + +struct MediaPlayerViewState: BindableState { + let mediaURL: URL + let autoplay: Bool +} + +enum MediaPlayerViewAction { + case cancel +} diff --git a/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModel.swift b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModel.swift new file mode 100644 index 0000000000..696f3a25fa --- /dev/null +++ b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModel.swift @@ -0,0 +1,45 @@ +// +// 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 MediaPlayerViewModelType = StateStoreViewModel + +class MediaPlayerViewModel: MediaPlayerViewModelType, MediaPlayerViewModelProtocol { + // MARK: - Properties + + // MARK: Private + + // MARK: Public + + var callback: ((MediaPlayerViewModelAction) -> Void)? + + // MARK: - Setup + + init(mediaURL: URL, autoplay: Bool = true) { + super.init(initialViewState: MediaPlayerViewState(mediaURL: mediaURL, + autoplay: autoplay)) + } + + // MARK: - Public + + override func process(viewAction: MediaPlayerViewAction) async { + switch viewAction { + case .cancel: + callback?(.cancel) + } + } +} diff --git a/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModelProtocol.swift b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModelProtocol.swift new file mode 100644 index 0000000000..99911ff412 --- /dev/null +++ b/ElementX/Sources/Screens/MediaPlayer/MediaPlayerViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// 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 MediaPlayerViewModelProtocol { + var callback: ((MediaPlayerViewModelAction) -> Void)? { get set } + var context: MediaPlayerViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/MediaPlayer/View/MediaPlayerScreen.swift b/ElementX/Sources/Screens/MediaPlayer/View/MediaPlayerScreen.swift new file mode 100644 index 0000000000..6d35957c23 --- /dev/null +++ b/ElementX/Sources/Screens/MediaPlayer/View/MediaPlayerScreen.swift @@ -0,0 +1,52 @@ +// +// 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 AVKit +import SwiftUI + +struct MediaPlayerScreen: View { + // MARK: Public + + @ObservedObject var context: MediaPlayerViewModel.Context + + // MARK: Views + + var body: some View { + VideoPlayer(player: player()) + .ignoresSafeArea() + } + + private func player() -> AVPlayer { + let player = AVPlayer(url: context.viewState.mediaURL) + if context.viewState.autoplay { + player.play() + } + return player + } +} + +// MARK: - Previews + +struct MediaPlayer_Previews: PreviewProvider { + static var previews: some View { + Group { + let viewModel = MediaPlayerViewModel(mediaURL: URL(staticString: "https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"), + autoplay: false) + MediaPlayerScreen(context: viewModel.context) + } + .tint(.element.accent) + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index f80751371f..2046c3e5be 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -65,6 +65,8 @@ final class RoomScreenCoordinator: Coordinator, Presentable { switch result { case .displayVideo(let videoURL): self.displayVideo(for: videoURL) + case .displayFile(let fileURL, let title): + self.displayFile(for: fileURL, with: title) } } } @@ -94,4 +96,20 @@ final class RoomScreenCoordinator: Coordinator, Presentable { self?.remove(childCoordinator: coordinator) } } + + private func displayFile(for fileURL: URL, with title: String?) { + let params = FilePreviewCoordinatorParameters(fileURL: fileURL, title: title) + let coordinator = FilePreviewCoordinator(parameters: params) + coordinator.callback = { [weak self, weak coordinator] _ in + guard let self, let coordinator = coordinator else { return } + self.navigationRouter.popModule(animated: true) + self.remove(childCoordinator: coordinator) + } + + add(childCoordinator: coordinator) + coordinator.start() + navigationRouter.push(coordinator) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index ef20147aff..2795e437e1 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -19,6 +19,7 @@ import UIKit enum RoomScreenViewModelAction { case displayVideo(videoURL: URL) + case displayFile(fileURL: URL, title: String?) } enum RoomScreenComposerMode: Equatable { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 0e92c97da8..aa17346bad 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -126,10 +126,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol switch action { case .displayVideo(let videoURL): callback?(.displayVideo(videoURL: videoURL)) + case .displayFile(let fileURL, let title): + callback?(.displayFile(fileURL: fileURL, title: title)) case .none: break } - state.showLoading = false } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift new file mode 100644 index 0000000000..5b1b2c660f --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift @@ -0,0 +1,81 @@ +// +// 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 +import SwiftUI + +struct FileRoomTimelineView: View { + let timelineItem: FileRoomTimelineItem + + var body: some View { + TimelineStyler(timelineItem: timelineItem) { + HStack { + Image(systemName: "doc.text.fill") + .foregroundColor(.element.primaryContent) + FormattedBodyText(text: timelineItem.text) + } + .padding(.vertical, 12) + .padding(.horizontal, 6) + } + .id(timelineItem.id) + } +} + +struct FileRoomTimelineView_Previews: PreviewProvider { + static var previews: some View { + body.preferredColorScheme(.light) + body.preferredColorScheme(.dark) + body.preferredColorScheme(.light) + .timelineStyle(.plain) + body.preferredColorScheme(.dark) + .timelineStyle(.plain) + } + + @ViewBuilder + static var body: some View { + VStack(spacing: 20.0) { + FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString, + text: "document.pdf", + timestamp: "Now", + inGroupState: .single, + isOutgoing: false, + isEditable: false, + senderId: "Bob", + source: nil, + thumbnailSource: nil)) + + FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString, + text: "document.docx", + timestamp: "Now", + inGroupState: .single, + isOutgoing: false, + isEditable: false, + senderId: "Bob", + source: nil, + thumbnailSource: nil)) + + FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString, + text: "document.txt", + timestamp: "Now", + inGroupState: .single, + isOutgoing: false, + isEditable: false, + senderId: "Bob", + source: nil, + thumbnailSource: nil)) + } + } +} diff --git a/ElementX/Sources/Screens/VideoPlayer/VideoPlayerCoordinator.swift b/ElementX/Sources/Screens/VideoPlayer/VideoPlayerCoordinator.swift index f6adba40f3..17aeb0ae59 100644 --- a/ElementX/Sources/Screens/VideoPlayer/VideoPlayerCoordinator.swift +++ b/ElementX/Sources/Screens/VideoPlayer/VideoPlayerCoordinator.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import AVKit import SwiftUI struct VideoPlayerCoordinatorParameters { @@ -59,6 +60,9 @@ final class VideoPlayerCoordinator: Coordinator, Presentable { func start() { MXLog.debug("Did start.") + + configureAudioSession(.sharedInstance()) + videoPlayerViewModel.callback = { [weak self] action in guard let self else { return } MXLog.debug("VideoPlayerViewModel did complete with result: \(action).") @@ -78,6 +82,17 @@ final class VideoPlayerCoordinator: Coordinator, Presentable { } // MARK: - Private + + private func configureAudioSession(_ session: AVAudioSession) { + do { + try session.setCategory(.playback, + mode: .default, + options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP]) + try session.setActive(true) + } catch { + MXLog.debug("Configure audio session failed: \(error)") + } + } /// Show an activity indicator whilst loading. /// - Parameters: diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 60f777c522..daefc838d3 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -75,7 +75,7 @@ class RoomProxy: RoomProxyProtocol { } var isEncrypted: Bool { - room.isEncrypted() + (try? room.isEncrypted()) ?? false } var isTombstoned: Bool { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index d6b3229869..f1676bff42 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -124,6 +124,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return .displayVideo(videoURL: videoURL) } return .none + case let item as FileRoomTimelineItem: + await loadFileForTimelineItem(item) + guard let index = timelineItems.firstIndex(where: { $0.id == itemId }), + let item = timelineItems[index] as? FileRoomTimelineItem else { + return .none + } + if let fileURL = item.cachedFileURL { + return .displayFile(fileURL: fileURL, title: item.text) + } + return .none default: return .none } @@ -338,6 +348,34 @@ class RoomTimelineController: RoomTimelineControllerProtocol { break } } + + private func loadFileForTimelineItem(_ timelineItem: FileRoomTimelineItem) async { + if timelineItem.cachedFileURL != nil { + // already cached + return + } + + guard let source = timelineItem.source else { + return + } + + // This is not great. We could better estimate file extension from the mimetype. + guard let fileExtension = timelineItem.text.split(separator: ".").last else { + return + } + switch await mediaProvider.loadFileFromSource(source, fileExtension: String(fileExtension)) { + case .success(let fileURL): + guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), + var item = timelineItems[index] as? FileRoomTimelineItem else { + return + } + + item.cachedFileURL = fileURL + timelineItems[index] = item + case .failure: + break + } + } private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async { if timelineItem.shouldShowSenderDetails == false { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift index e0b5b90512..c0f9b76f1a 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift @@ -26,6 +26,7 @@ enum RoomTimelineControllerCallback { enum RoomTimelineControllerAction { case displayVideo(videoURL: URL) + case displayFile(fileURL: URL, title: String?) case none } diff --git a/ElementX/Sources/Services/Timeline/TimeLineItemContent/MessageTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimeLineItemContent/MessageTimelineItem.swift index 3ff225298c..3e627a66b0 100644 --- a/ElementX/Sources/Services/Timeline/TimeLineItemContent/MessageTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimeLineItemContent/MessageTimelineItem.swift @@ -116,7 +116,7 @@ extension MessageTimelineItem where Content == MatrixRustSDK.ImageMessageContent extension MatrixRustSDK.VideoMessageContent: MessageContentProtocol { } -/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.image`. +/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.video`. extension MessageTimelineItem where Content == MatrixRustSDK.VideoMessageContent { var source: MediaSource { MediaSource(source: content.source) @@ -145,3 +145,19 @@ extension MessageTimelineItem where Content == MatrixRustSDK.VideoMessageContent content.info?.blurhash } } + +extension MatrixRustSDK.FileMessageContent: MessageContentProtocol { } + +/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.file`. +extension MessageTimelineItem where Content == MatrixRustSDK.FileMessageContent { + var source: MediaSource { + MediaSource(source: content.source) + } + + var thumbnailSource: MediaSource? { + guard let src = content.info?.thumbnailSource else { + return nil + } + return MediaSource(source: src) + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/FileRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/FileRoomTimelineItem.swift new file mode 100644 index 0000000000..e5e4f74d5b --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/FileRoomTimelineItem.swift @@ -0,0 +1,37 @@ +// +// 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 +import UIKit + +struct FileRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { + let id: String + let text: String + let timestamp: String + let inGroupState: TimelineItemInGroupState + let isOutgoing: Bool + let isEditable: Bool + + let senderId: String + var senderDisplayName: String? + var senderAvatar: UIImage? + + let source: MediaSource? + let thumbnailSource: MediaSource? + var cachedFileURL: URL? + + var properties = RoomTimelineItemProperties() +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index ba21e89cb5..e432bdda38 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -62,6 +62,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { case .video(let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) return buildVideoTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage) + case .file(let content): + let message = MessageTimelineItem(item: eventItemProxy.item, content: content) + return buildFileTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage) case .notice(content: let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) return buildNoticeTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage) @@ -227,6 +230,26 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: message.isEdited, reactions: aggregateReactions(message.reactions))) } + + private func buildFileTimelineItemFromMessage(_ message: MessageTimelineItem, + _ isOutgoing: Bool, + _ inGroupState: TimelineItemInGroupState, + _ displayName: String?, + _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + FileRoomTimelineItem(id: message.id, + text: message.body, + timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), + inGroupState: inGroupState, + isOutgoing: isOutgoing, + isEditable: message.isEditable, + senderId: message.sender, + senderDisplayName: displayName, + senderAvatar: avatarImage, + source: message.source, + thumbnailSource: message.thumbnailSource, + properties: RoomTimelineItemProperties(isEdited: message.isEdited, + reactions: aggregateReactions(message.reactions))) + } private func buildNoticeTimelineItemFromMessage(_ message: MessageTimelineItem, _ isOutgoing: Bool, diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift index 0f1295faa3..b889d1fc86 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift @@ -25,6 +25,8 @@ struct RoomTimelineViewFactory: RoomTimelineViewFactoryProtocol { return .image(item) case let item as VideoRoomTimelineItem: return .video(item) + case let item as FileRoomTimelineItem: + return .file(item) case let item as SeparatorRoomTimelineItem: return .separator(item) case let item as NoticeRoomTimelineItem: diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewProvider.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewProvider.swift index 5d8107c2fc..5a3aa349db 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewProvider.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewProvider.swift @@ -22,6 +22,7 @@ enum RoomTimelineViewProvider: Identifiable, Equatable { case separator(SeparatorRoomTimelineItem) case image(ImageRoomTimelineItem) case video(VideoRoomTimelineItem) + case file(FileRoomTimelineItem) case emote(EmoteRoomTimelineItem) case notice(NoticeRoomTimelineItem) case redacted(RedactedRoomTimelineItem) @@ -37,6 +38,8 @@ enum RoomTimelineViewProvider: Identifiable, Equatable { return item.id case .video(let item): return item.id + case .file(let item): + return item.id case .emote(let item): return item.id case .notice(let item): @@ -60,6 +63,8 @@ extension RoomTimelineViewProvider: View { ImageRoomTimelineView(timelineItem: item) case .video(let item): VideoRoomTimelineView(timelineItem: item) + case .file(let item): + FileRoomTimelineView(timelineItem: item) case .emote(let item): EmoteRoomTimelineView(timelineItem: item) case .notice(let item): diff --git a/UnitTests/Sources/FilePreviewViewModelTests.swift b/UnitTests/Sources/FilePreviewViewModelTests.swift new file mode 100644 index 0000000000..336f8dfdc6 --- /dev/null +++ b/UnitTests/Sources/FilePreviewViewModelTests.swift @@ -0,0 +1,44 @@ +// +// 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: FilePreviewViewModelProtocol! + var context: FilePreviewViewModelType.Context! + + @MainActor override func setUpWithError() throws { + viewModel = FilePreviewViewModel(fileURL: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")) + context = viewModel.context + } + + @MainActor 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/UnitTests/Sources/MediaPlayerViewModelTests.swift b/UnitTests/Sources/MediaPlayerViewModelTests.swift new file mode 100644 index 0000000000..a0926d8d98 --- /dev/null +++ b/UnitTests/Sources/MediaPlayerViewModelTests.swift @@ -0,0 +1,44 @@ +// +// 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 MediaPlayerScreenViewModelTests: XCTestCase { + var viewModel: MediaPlayerViewModelProtocol! + var context: MediaPlayerViewModelType.Context! + + @MainActor override func setUpWithError() throws { + viewModel = MediaPlayerViewModel(mediaURL: URL(staticString: "https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"), autoplay: true) + context = viewModel.context + } + + @MainActor 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/310.feature b/changelog.d/310.feature new file mode 100644 index 0000000000..ce463f3ce2 --- /dev/null +++ b/changelog.d/310.feature @@ -0,0 +1 @@ +Timeline: Display file messages and preview them when tapped. diff --git a/project.yml b/project.yml index f18f42f490..949e28a46b 100644 --- a/project.yml +++ b/project.yml @@ -35,8 +35,8 @@ include: packages: MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.18-alpha - # path: ../matrix-rust-components-swift + exactVersion: 1.0.20-alpha +# path: ../matrix-rust-components-swift DesignKit: path: ./ AnalyticsEvents: