Image viewer (#338)

* Resume other app's music when video playback finished

* Remove old media player

* Add `isModallyPresented` into the video player screen

* Create image viewer screen

* Add test screen identifier

* Display image viewer when message tapped

* Fix template script unit test path

* Tweaks on scaling

* Commit project file

* Add changelog

* Ignore safe areas on the file preview screen

* Display images in preview

* Remove image viewer screen

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
ismailgulek 2022-11-28 14:19:40 +03:00 committed by GitHub
parent 9589c689c3
commit 232430ac95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 143 additions and 284 deletions

View File

@ -25,6 +25,7 @@
086C2FA7750378EB2BFD0BEE /* UITestsRootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */; };
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; };
098CE03C6CC71A31F263FA33 /* ActivityCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9D14D6F914324865C7DB9F /* ActivityCoordinator.swift */; };
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 */; };
@ -132,9 +133,6 @@
44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */; };
457465EC436703E8C76133A4 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7955B20E2E6DA68E5BC0AB9 /* WeakDictionaryReference.swift */; };
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.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 */; };
@ -181,7 +179,6 @@
64F43D7390DA2A0AFD6BA911 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */; };
64FF5CB4E35971255872E1BB /* AuthenticationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CB536D1C3CC15AA740CC6 /* AuthenticationServiceProxyProtocol.swift */; };
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.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 */; };
@ -234,7 +231,6 @@
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */; };
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C483956FA3D665E3842E319A /* SettingsScreen.swift */; };
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
80997E933A5B2C0868D80B45 /* MediaPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */; };
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */; };
834DD9E41FC42A509BAD52E3 /* NavigationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 109361C96BFFBE2FD89BF15C /* NavigationControllerTests.swift */; };
@ -254,7 +250,6 @@
8BBD3AA589DEE02A1B0923B2 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */; };
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.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 */; };
@ -613,11 +608,9 @@
3FDFF4C1153D263BAB93C1F3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = "<group>"; };
40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
41C2348F84A80F682E3A68D0 /* MediaPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerCoordinator.swift; sourceTree = "<group>"; };
41F3B445BD6EF1C751806B22 /* SlidingSyncViewProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingSyncViewProxy.swift; sourceTree = "<group>"; };
422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; };
434522ED2BDED08759048077 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
447A6399BC5EDE7AF7713267 /* MediaPlayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerScreen.swift; sourceTree = "<group>"; };
4488F5F92A64A137665C96CD /* pa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pa; path = pa.lproj/Localizable.strings; sourceTree = "<group>"; };
44AEEE13AC1BF303AE48CBF8 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
44D8C8431416EB8DFEC7E235 /* ApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTests.swift; sourceTree = "<group>"; };
@ -683,7 +676,6 @@
624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModel.swift; sourceTree = "<group>"; };
6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModelProtocol.swift; sourceTree = "<group>"; };
649759084B0C9FE1F8DF8D17 /* UserNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationPresenter.swift; sourceTree = "<group>"; };
653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = "<group>"; };
@ -753,7 +745,6 @@
9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = "<group>"; };
9080CDD3881D0D1B2F280A7C /* MockUserNotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserNotificationController.swift; sourceTree = "<group>"; };
9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugScreen.swift; sourceTree = "<group>"; };
92B7BF5D0705F3CB70E7B2D7 /* MediaPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModel.swift; sourceTree = "<group>"; };
92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = "<group>"; };
9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = "<group>"; };
938BD1FCD9E6FF3FCFA7AB4C /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
@ -846,7 +837,6 @@
BB3073CCD77D906B330BC1D6 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
BB33A751BFDA223BDD106EC0 /* OnboardingModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingModels.swift; sourceTree = "<group>"; };
BC9B05D6B293A039EB963CA7 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Localizable.strings; sourceTree = "<group>"; };
BCBE603A7EB2C93E81BA6415 /* MediaPlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerModels.swift; sourceTree = "<group>"; };
BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; };
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = "<group>"; };
BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -857,7 +847,6 @@
C1198B925F4A88DA74083662 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = "<group>"; };
C444092DB0E4AB393067AC36 /* MediaPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerViewModelTests.swift; sourceTree = "<group>"; };
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
@ -871,6 +860,7 @@
C95ADE8D9527523572532219 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hu; path = hu.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
C9A86C95340248A8B7BA9A43 /* AnalyticsPromptViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptViewModelProtocol.swift; sourceTree = "<group>"; };
CA89A2DD51B6BBE1DA55E263 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
CA9D14D6F914324865C7DB9F /* ActivityCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityCoordinator.swift; sourceTree = "<group>"; };
CAAE4A709C0A2144C103AA0F /* ang */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ang; path = ang.lproj/Localizable.strings; sourceTree = "<group>"; };
CACA846B3E3E9A521D98B178 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
CBA95E52C4C6EE8769A63E57 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eo; path = eo.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1571,7 +1561,6 @@
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
A05707BF550D770168A406DB /* LoginViewModelTests.swift */,
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */,
C444092DB0E4AB393067AC36 /* MediaPlayerViewModelTests.swift */,
109361C96BFFBE2FD89BF15C /* NavigationControllerTests.swift */,
00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */,
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */,
@ -1848,14 +1837,6 @@
path = Localizations;
sourceTree = "<group>";
};
A253B36CAD2059B6D8C130CD /* View */ = {
isa = PBXGroup;
children = (
447A6399BC5EDE7AF7713267 /* MediaPlayerScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
A312471EA62EFB0FD94E60DC /* Style */ = {
isa = PBXGroup;
children = (
@ -1870,6 +1851,7 @@
A448A3A8F764174C60CD0CA1 /* Other */ = {
isa = PBXGroup;
children = (
CA9D14D6F914324865C7DB9F /* ActivityCoordinator.swift */,
1F7AB0A148FCCAC28681C190 /* InviteFriendsCoordinator.swift */,
854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */,
4A57A4AFA6A068668AFBD070 /* UIActivityViewControllerWrapper.swift */,
@ -1931,6 +1913,14 @@
path = NSE;
sourceTree = "<group>";
};
B3E78735F63FA93FAAAF700A /* MockUserNotificationController.swift~refs */ = {
isa = PBXGroup;
children = (
F798CDE87F83A94B8BC2E18A /* remotes */,
);
path = "MockUserNotificationController.swift~refs";
sourceTree = "<group>";
};
B442FCF47E0A6F28D7D50A4D /* FilePreview */ = {
isa = PBXGroup;
children = (
@ -2018,6 +2008,13 @@
path = UITests;
sourceTree = "<group>";
};
C5A8A8B1C16BBFEA4B9D5988 /* origin */ = {
isa = PBXGroup;
children = (
);
path = origin;
sourceTree = "<group>";
};
CA555F7C7CA382ACACF0D82B /* Keychain */ = {
isa = PBXGroup;
children = (
@ -2075,16 +2072,11 @@
path = Vendor;
sourceTree = "<group>";
};
D3E07C2F92EC8C5659601744 /* MediaPlayer */ = {
D14F980E72A97D6169A499E8 /* ImageViewer */ = {
isa = PBXGroup;
children = (
41C2348F84A80F682E3A68D0 /* MediaPlayerCoordinator.swift */,
BCBE603A7EB2C93E81BA6415 /* MediaPlayerModels.swift */,
92B7BF5D0705F3CB70E7B2D7 /* MediaPlayerViewModel.swift */,
6410F8C03DC4AA46991A6B02 /* MediaPlayerViewModelProtocol.swift */,
A253B36CAD2059B6D8C130CD /* View */,
);
path = MediaPlayer;
path = ImageViewer;
sourceTree = "<group>";
};
D958761758AA1110476DE6A3 /* SessionVerification */ = {
@ -2110,6 +2102,7 @@
CD80F22830C2360F3F39DDCE /* UserNotificationModalView.swift */,
649759084B0C9FE1F8DF8D17 /* UserNotificationPresenter.swift */,
F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */,
B3E78735F63FA93FAAAF700A /* MockUserNotificationController.swift~refs */,
);
path = UserNotifications;
sourceTree = "<group>";
@ -2150,7 +2143,7 @@
4009BE2E791C16AC6EE39A7E /* BugReport */,
B442FCF47E0A6F28D7D50A4D /* FilePreview */,
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
D3E07C2F92EC8C5659601744 /* MediaPlayer */,
D14F980E72A97D6169A499E8 /* ImageViewer */,
3F38EAC92E2281990E65DAF2 /* OnboardingScreen */,
A448A3A8F764174C60CD0CA1 /* Other */,
679E9837ECA8D6776079D16E /* RoomScreen */,
@ -2208,6 +2201,14 @@
path = Background;
sourceTree = "<group>";
};
F798CDE87F83A94B8BC2E18A /* remotes */ = {
isa = PBXGroup;
children = (
C5A8A8B1C16BBFEA4B9D5988 /* origin */,
);
path = remotes;
sourceTree = "<group>";
};
FCDF06BDB123505F0334B4F9 /* Timeline */ = {
isa = PBXGroup;
children = (
@ -2688,7 +2689,6 @@
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */,
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */,
2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */,
483507026FDCA2E16E5197A6 /* MediaPlayerViewModelTests.swift in Sources */,
834DD9E41FC42A509BAD52E3 /* NavigationControllerTests.swift in Sources */,
F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */,
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
@ -2712,6 +2712,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
098CE03C6CC71A31F263FA33 /* ActivityCoordinator.swift in Sources */,
7096FA3AC218D914E88BFB70 /* AggregratedReaction.swift in Sources */,
A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */,
39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */,
@ -2810,11 +2811,6 @@
B94368839BDB69172E28E245 /* MXLog.swift in Sources */,
B66757D0254843162595B25D /* MXLogger.swift in Sources */,
67C05C50AD734283374605E3 /* MatrixEntityRegex.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 */,
FBCD77D557AACBE9B445133A /* MediaProxy.swift in Sources */,

View File

@ -19,16 +19,11 @@ import SwiftUI
import UIKit
struct FilePreviewScreen: View {
@Environment(\.colorScheme) private var colorScheme
var counterColor: Color {
colorScheme == .light ? .element.secondaryContent : .element.tertiaryContent
}
@ObservedObject var context: FilePreviewViewModel.Context
var body: some View {
PreviewController(fileURL: context.viewState.fileURL, title: context.viewState.title)
.ignoresSafeArea()
}
}

View File

@ -1,56 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
struct MediaPlayerCoordinatorParameters {
let mediaURL: URL
}
enum MediaPlayerCoordinatorAction {
case cancel
}
final class MediaPlayerCoordinator: CoordinatorProtocol {
private let parameters: MediaPlayerCoordinatorParameters
private var viewModel: MediaPlayerViewModelProtocol
var callback: ((MediaPlayerCoordinatorAction) -> Void)?
init(parameters: MediaPlayerCoordinatorParameters) {
self.parameters = parameters
viewModel = MediaPlayerViewModel(mediaURL: parameters.mediaURL)
}
// MARK: - Public
func start() {
MXLog.debug("Did start.")
viewModel.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() -> AnyView {
AnyView(MediaPlayerScreen(context: viewModel.context))
}
}

View File

@ -1,35 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
typealias MediaPlayerViewModelType = StateStoreViewModel<MediaPlayerViewState, MediaPlayerViewAction>
class MediaPlayerViewModel: MediaPlayerViewModelType, MediaPlayerViewModelProtocol {
var callback: ((MediaPlayerViewModelAction) -> Void)?
init(mediaURL: URL, autoplay: Bool = true) {
super.init(initialViewState: MediaPlayerViewState(mediaURL: mediaURL,
autoplay: autoplay))
}
override func process(viewAction: MediaPlayerViewAction) async {
switch viewAction {
case .cancel:
callback?(.cancel)
}
}
}

View File

@ -1,23 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@MainActor
protocol MediaPlayerViewModelProtocol {
var callback: ((MediaPlayerViewModelAction) -> Void)? { get set }
var context: MediaPlayerViewModelType.Context { get }
}

View File

@ -1,48 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AVKit
import SwiftUI
struct MediaPlayerScreen: View {
@ObservedObject var context: MediaPlayerViewModel.Context
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)
}
}

View File

@ -14,17 +14,14 @@
// limitations under the License.
//
import Foundation
import SwiftUI
enum MediaPlayerViewModelAction {
case cancel
}
struct MediaPlayerViewState: BindableState {
let mediaURL: URL
let autoplay: Bool
}
enum MediaPlayerViewAction {
case cancel
struct ActivityCoordinator: CoordinatorProtocol {
let items: [Any]
func toPresentable() -> AnyView {
return AnyView(UIActivityViewControllerWrapper(activityItems: items)
.presentationDetents([.medium])
.ignoresSafeArea())
}
}

View File

@ -66,13 +66,25 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
// MARK: - Private
private func displayVideo(for videoURL: URL) {
let params = VideoPlayerCoordinatorParameters(videoURL: videoURL)
let params = VideoPlayerCoordinatorParameters(videoURL: videoURL, isModallyPresented: false)
let coordinator = VideoPlayerCoordinator(parameters: params)
coordinator.callback = { [weak self] _ in
self?.navigationController.pop()
}
navigationController.push(coordinator)
if params.isModallyPresented {
coordinator.callback = { [weak self] _ in
self?.navigationController.dismissSheet()
}
let controller = NavigationController()
controller.setRootCoordinator(coordinator)
navigationController.presentSheet(controller)
} else {
coordinator.callback = { [weak self] _ in
self?.navigationController.pop()
}
navigationController.push(coordinator)
}
}
private func displayFile(for fileURL: URL, with title: String?) {

View File

@ -19,6 +19,7 @@ import SwiftUI
struct VideoPlayerCoordinatorParameters {
let videoURL: URL
let isModallyPresented: Bool
}
enum VideoPlayerCoordinatorAction {
@ -34,7 +35,8 @@ final class VideoPlayerCoordinator: CoordinatorProtocol {
init(parameters: VideoPlayerCoordinatorParameters) {
self.parameters = parameters
viewModel = VideoPlayerViewModel(videoURL: parameters.videoURL)
viewModel = VideoPlayerViewModel(videoURL: parameters.videoURL,
isModallyPresented: parameters.isModallyPresented)
}
// MARK: - Public
@ -51,6 +53,10 @@ final class VideoPlayerCoordinator: CoordinatorProtocol {
}
}
}
func stop() {
deconfigureAudioSession(.sharedInstance())
}
func toPresentable() -> AnyView {
AnyView(VideoPlayerScreen(context: viewModel.context))
@ -62,10 +68,18 @@ final class VideoPlayerCoordinator: CoordinatorProtocol {
do {
try session.setCategory(.playback,
mode: .default,
options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])
options: [.allowBluetooth, .allowBluetoothA2DP])
try session.setActive(true)
} catch {
MXLog.debug("Configure audio session failed: \(error)")
}
}
private func deconfigureAudioSession(_ session: AVAudioSession) {
do {
try session.setActive(false, options: .notifyOthersOnDeactivation)
} catch {
MXLog.debug("Deconfigure audio session failed: \(error)")
}
}
}

View File

@ -21,8 +21,9 @@ enum VideoPlayerViewModelAction {
}
struct VideoPlayerViewState: BindableState {
var videoURL: URL
var autoplay: Bool
let videoURL: URL
let autoplay: Bool
let isModallyPresented: Bool
}
enum VideoPlayerViewAction {

View File

@ -21,9 +21,10 @@ typealias VideoPlayerViewModelType = StateStoreViewModel<VideoPlayerViewState, V
class VideoPlayerViewModel: VideoPlayerViewModelType, VideoPlayerViewModelProtocol {
var callback: ((VideoPlayerViewModelAction) -> Void)?
init(videoURL: URL, autoplay: Bool = true) {
init(videoURL: URL, autoplay: Bool = true, isModallyPresented: Bool = true) {
super.init(initialViewState: VideoPlayerViewState(videoURL: videoURL,
autoplay: autoplay))
autoplay: autoplay,
isModallyPresented: isModallyPresented))
}
override func process(viewAction: VideoPlayerViewAction) async {

View File

@ -22,25 +22,33 @@ struct VideoPlayerScreen: View {
var body: some View {
VideoPlayer(player: player())
.ignoresSafeArea()
.background(Color.black.ignoresSafeArea())
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
context.send(viewAction: .cancel)
} label: {
Image(systemName: "chevron.backward")
.foregroundColor(.white)
.fontWeight(.semibold)
}
.toolbar { toolbar }
.onSwipeGesture(minimumDistance: 3.0, down: {
if context.viewState.isModallyPresented {
context.send(viewAction: .cancel)
}
}, right: {
if !context.viewState.isModallyPresented {
context.send(viewAction: .cancel)
}
}
.onSwipeGesture(minimumDistance: 3.0, right: {
context.send(viewAction: .cancel)
})
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button { context.send(viewAction: .cancel) } label: {
Image(systemName: context.viewState.isModallyPresented ? "xmark" : "chevron.backward")
.foregroundColor(.white)
.fontWeight(.semibold)
}
.accessibilityIdentifier("dismissButton")
}
}
private func player() -> AVPlayer {
let player = AVPlayer(url: context.viewState.videoURL)
if context.viewState.autoplay {

View File

@ -114,6 +114,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
switch timelineItem {
case let item as ImageRoomTimelineItem:
await loadFileForImageTimelineItem(item)
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
let item = timelineItems[index] as? ImageRoomTimelineItem else {
return .none
}
if let fileURL = item.cachedFileURL {
return .displayFile(fileURL: fileURL, title: item.text)
}
return .none
case let item as VideoRoomTimelineItem:
await loadVideoForTimelineItem(item)
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
@ -349,6 +359,34 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
private func loadFileForImageTimelineItem(_ timelineItem: ImageRoomTimelineItem) 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? ImageRoomTimelineItem else {
return
}
item.cachedFileURL = fileURL
timelineItems[index] = item
case .failure:
break
}
}
private func loadFileForTimelineItem(_ timelineItem: FileRoomTimelineItem) async {
if timelineItem.cachedFileURL != nil {
// already cached

View File

@ -16,6 +16,7 @@
import Combine
import Foundation
import UIKit
enum RoomTimelineControllerCallback {
case updatedTimelineItems

View File

@ -31,6 +31,7 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
let source: MediaSourceProxy?
var image: UIImage?
var cachedFileURL: URL?
var width: CGFloat?
var height: CGFloat?

View File

@ -26,7 +26,7 @@ echo "Copying tests"
cp -R "Templates/SimpleScreenExample/Tests/UI/" $UI_TESTS_DIR/
cp -R "Templates/SimpleScreenExample/Tests/Unit" $UNIT_TESTS_DIR/
cp -R "Templates/SimpleScreenExample/Tests/Unit/" $UNIT_TESTS_DIR/
SCREEN_NAME=$2
SCREEN_VAR_NAME=`echo $SCREEN_NAME | awk '{ print tolower(substr($0, 1, 1)) substr($0, 2) }'`

View File

@ -1,44 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import XCTest
@testable import ElementX
@MainActor
class 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)
}
}

1
changelog.d/244.feature Normal file
View File

@ -0,0 +1 @@
Timeline: Display images fullscreen when tapped.