Merge branch 'video-controls'

This commit is contained in:
William Casarin
2024-03-20 09:57:10 +00:00
13 changed files with 394 additions and 134 deletions

View File

@@ -375,7 +375,7 @@
4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; }; 4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; };
4CFF8F5929C9FD1E008DB934 /* DamusPurpleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */; }; 4CFF8F5929C9FD1E008DB934 /* DamusPurpleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */; };
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; }; 4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; };
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; }; 4CFF8F6729CC9E3A008DB934 /* FullScreenCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* FullScreenCarouselView.swift */; };
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; }; 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; };
4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; }; 4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; };
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; }; 4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
@@ -386,7 +386,7 @@
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; }; 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; };
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; }; 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; };
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; }; 5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; };
50A16FFB2AA6C06600DFEC1F /* AVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */; }; 50A16FFB2AA6C06600DFEC1F /* DamusAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFA2AA6C06600DFEC1F /* DamusAVPlayerView.swift */; };
50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */; }; 50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */; };
50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */; }; 50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */; };
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
@@ -431,8 +431,6 @@
B59CAD4D2B688D1000677E8B /* MutelistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533694D2B66D791008A805E /* MutelistManager.swift */; }; B59CAD4D2B688D1000677E8B /* MutelistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533694D2B66D791008A805E /* MutelistManager.swift */; };
B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A75C292B546D94007AFBC0 /* MuteItemTests.swift */; }; B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A75C292B546D94007AFBC0 /* MuteItemTests.swift */; };
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B4D1422B37D47600844320 /* NdbExtensions.swift */; }; B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B4D1422B37D47600844320 /* NdbExtensions.swift */; };
BA0F0A6F2B36207E001641B2 /* CameraMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0F0A6E2B36207E001641B2 /* CameraMediaView.swift */; };
BA10192F2B449556009C57DA /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA10192E2B449556009C57DA /* CameraPreview.swift */; };
B5C60C202B530D5100C5ECA7 /* MuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */; }; B5C60C202B530D5100C5ECA7 /* MuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */; };
B5C60C212B530D5600C5ECA7 /* MuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */; }; B5C60C212B530D5600C5ECA7 /* MuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */; };
B5C60C232B532A8700C5ECA7 /* DamusDuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C222B532A8700C5ECA7 /* DamusDuration.swift */; }; B5C60C232B532A8700C5ECA7 /* DamusDuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C60C222B532A8700C5ECA7 /* DamusDuration.swift */; };
@@ -454,6 +452,7 @@
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C592B76FD5100C59298 /* LogoView.swift */; }; D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C592B76FD5100C59298 /* LogoView.swift */; };
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.swift */; }; D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.swift */; };
D7100C5E2B7709ED00C59298 /* PurpleStoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */; }; D7100C5E2B7709ED00C59298 /* PurpleStoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */; };
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */; };
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; }; D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
@@ -1296,7 +1295,7 @@
4CFD502E2A2DA45800A229DB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; }; 4CFD502E2A2DA45800A229DB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleView.swift; sourceTree = "<group>"; }; 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleView.swift; sourceTree = "<group>"; };
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; }; 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; };
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; }; 4CFF8F6629CC9E3A008DB934 /* FullScreenCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCarouselView.swift; sourceTree = "<group>"; };
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; }; 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; };
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedEvent.swift; sourceTree = "<group>"; }; 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedEvent.swift; sourceTree = "<group>"; };
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; }; 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
@@ -1307,7 +1306,7 @@
504323A62A34915F006AE6DC /* RelayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModel.swift; sourceTree = "<group>"; }; 504323A62A34915F006AE6DC /* RelayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModel.swift; sourceTree = "<group>"; };
504323A82A3495B6006AE6DC /* RelayModelCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModelCache.swift; sourceTree = "<group>"; }; 504323A82A3495B6006AE6DC /* RelayModelCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModelCache.swift; sourceTree = "<group>"; };
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; }; 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; };
50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerView.swift; sourceTree = "<group>"; }; 50A16FFA2AA6C06600DFEC1F /* DamusAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusAVPlayerView.swift; sourceTree = "<group>"; };
50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoPlayerViewModel.swift; sourceTree = "<group>"; }; 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoPlayerViewModel.swift; sourceTree = "<group>"; };
50A16FFE2AA76A0900DFEC1F /* VideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoController.swift; sourceTree = "<group>"; }; 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoController.swift; sourceTree = "<group>"; };
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
@@ -1351,8 +1350,6 @@
B57B4C652B312C3700A232C0 /* NostrAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NostrAuth.swift; sourceTree = "<group>"; }; B57B4C652B312C3700A232C0 /* NostrAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NostrAuth.swift; sourceTree = "<group>"; };
B5A75C292B546D94007AFBC0 /* MuteItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteItemTests.swift; sourceTree = "<group>"; usesTabs = 0; }; B5A75C292B546D94007AFBC0 /* MuteItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteItemTests.swift; sourceTree = "<group>"; usesTabs = 0; };
B5B4D1422B37D47600844320 /* NdbExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbExtensions.swift; sourceTree = "<group>"; usesTabs = 0; }; B5B4D1422B37D47600844320 /* NdbExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbExtensions.swift; sourceTree = "<group>"; usesTabs = 0; };
BA0F0A6E2B36207E001641B2 /* CameraMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraMediaView.swift; sourceTree = "<group>"; };
BA10192E2B449556009C57DA /* CameraPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = "<group>"; };
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteItem.swift; sourceTree = "<group>"; usesTabs = 0; }; B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteItem.swift; sourceTree = "<group>"; usesTabs = 0; };
B5C60C222B532A8700C5ECA7 /* DamusDuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusDuration.swift; sourceTree = "<group>"; usesTabs = 0; }; B5C60C222B532A8700C5ECA7 /* DamusDuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusDuration.swift; sourceTree = "<group>"; usesTabs = 0; };
BA3759892ABCCDE30018D73B /* ImageResizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizer.swift; sourceTree = "<group>"; }; BA3759892ABCCDE30018D73B /* ImageResizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizer.swift; sourceTree = "<group>"; };
@@ -1373,6 +1370,7 @@
D7100C592B76FD5100C59298 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; }; D7100C592B76FD5100C59298 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
D7100C5B2B77016700C59298 /* IAPProductStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPProductStateView.swift; sourceTree = "<group>"; }; D7100C5B2B77016700C59298 /* IAPProductStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPProductStateView.swift; sourceTree = "<group>"; };
D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleStoreKitManager.swift; sourceTree = "<group>"; }; D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleStoreKitManager.swift; sourceTree = "<group>"; };
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityTracker.swift; sourceTree = "<group>"; };
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; }; D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = "<group>"; }; D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = "<group>"; };
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; }; D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
@@ -1728,7 +1726,7 @@
4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */, 4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */,
50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */, 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */,
50A16FFE2AA76A0900DFEC1F /* VideoController.swift */, 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */,
50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */, 50A16FFA2AA6C06600DFEC1F /* DamusAVPlayerView.swift */,
); );
path = Video; path = Video;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1977,6 +1975,7 @@
4C75EFA227FA576C0006080F /* Views */ = { 4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D71AC4CA2BA8E3320076268E /* Extensions */,
BA3759952ABCCF360018D73B /* Camera */, BA3759952ABCCF360018D73B /* Camera */,
F71694E82A66221E001F4053 /* Onboarding */, F71694E82A66221E001F4053 /* Onboarding */,
4C190F232A547D1700027FD5 /* NostrScript */, 4C190F232A547D1700027FD5 /* NostrScript */,
@@ -2653,7 +2652,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */, 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */,
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */, 4CFF8F6629CC9E3A008DB934 /* FullScreenCarouselView.swift */,
6439E013296790CF0020672B /* ProfilePicImageView.swift */, 6439E013296790CF0020672B /* ProfilePicImageView.swift */,
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */, 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */,
4CFD502E2A2DA45800A229DB /* MediaView.swift */, 4CFD502E2A2DA45800A229DB /* MediaView.swift */,
@@ -2704,6 +2703,14 @@
path = Detail; path = Detail;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D71AC4CA2BA8E3320076268E /* Extensions */ = {
isa = PBXGroup;
children = (
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D72A2D032AD9C165002AFF62 /* Mocking */ = { D72A2D032AD9C165002AFF62 /* Mocking */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3171,7 +3178,7 @@
4C363A9A28283854006E126D /* Reply.swift in Sources */, 4C363A9A28283854006E126D /* Reply.swift in Sources */,
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */, BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
D7ADD3E02B538D4200F104C4 /* DamusPurpleURLSheetView.swift in Sources */, D7ADD3E02B538D4200F104C4 /* DamusPurpleURLSheetView.swift in Sources */,
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */, 4CFF8F6729CC9E3A008DB934 /* FullScreenCarouselView.swift in Sources */,
4CA927632A290EB10098A105 /* EventTop.swift in Sources */, 4CA927632A290EB10098A105 /* EventTop.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */, 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */, 4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
@@ -3203,6 +3210,7 @@
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */, F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */, 4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */,
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */, 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */, 4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
@@ -3409,7 +3417,7 @@
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */, 4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */, 4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */,
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */, 7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
50A16FFB2AA6C06600DFEC1F /* AVPlayerView.swift in Sources */, 50A16FFB2AA6C06600DFEC1F /* DamusAVPlayerView.swift in Sources */,
4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */, 4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */,
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */, 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
7527271E2A93FF0100214108 /* Block.swift in Sources */, 7527271E2A93FF0100214108 /* Block.swift in Sources */,

View File

@@ -77,13 +77,14 @@ class CarouselModel: ObservableObject {
// MARK: - Image Carousel // MARK: - Image Carousel
@MainActor @MainActor
struct ImageCarousel: View { struct ImageCarousel<Content: View>: View {
var urls: [MediaUrl] var urls: [MediaUrl]
let evid: NoteId let evid: NoteId
let state: DamusState let state: DamusState
@ObservedObject var model: CarouselModel @ObservedObject var model: CarouselModel
let content: ((_ dismiss: @escaping (() -> Void)) -> Content)?
init(state: DamusState, evid: NoteId, urls: [MediaUrl]) { init(state: DamusState, evid: NoteId, urls: [MediaUrl]) {
self.urls = urls self.urls = urls
@@ -91,6 +92,16 @@ struct ImageCarousel: View {
self.state = state self.state = state
let media_model = state.events.get_cache_data(evid).media_metadata_model let media_model = state.events.get_cache_data(evid).media_metadata_model
self._model = ObservedObject(initialValue: CarouselModel(image_fill: media_model.fill)) self._model = ObservedObject(initialValue: CarouselModel(image_fill: media_model.fill))
self.content = nil
}
init(state: DamusState, evid: NoteId, urls: [MediaUrl], @ViewBuilder content: @escaping (_ dismiss: @escaping (() -> Void)) -> Content) {
self.urls = urls
self.evid = evid
self.state = state
let media_model = state.events.get_cache_data(evid).media_metadata_model
self._model = ObservedObject(initialValue: CarouselModel(image_fill: media_model.fill))
self.content = content
} }
var filling: Bool { var filling: Bool {
@@ -132,7 +143,7 @@ struct ImageCarousel: View {
model.open_sheet = true model.open_sheet = true
} }
case .video(let url): case .video(let url):
DamusVideoPlayer(url: url, video_size: $model.video_size, controller: state.video) DamusVideoPlayer(url: url, video_size: $model.video_size, controller: state.video, style: .preview(on_tap: { model.open_sheet = true }))
.onChange(of: model.video_size) { size in .onChange(of: model.video_size) { size in
guard let size else { return } guard let size else { return }
@@ -201,7 +212,16 @@ struct ImageCarousel: View {
} }
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.fullScreenCover(isPresented: $model.open_sheet) { .fullScreenCover(isPresented: $model.open_sheet) {
ImageView(video_controller: state.video, urls: urls, settings: state.settings, selectedIndex: $model.selectedIndex) if let content {
FullScreenCarouselView<Content>(video_controller: state.video, urls: urls, settings: state.settings, selectedIndex: $model.selectedIndex) {
content({ // Dismiss closure
model.open_sheet = false
})
}
}
else {
FullScreenCarouselView<AnyView>(video_controller: state.video, urls: urls, settings: state.settings, selectedIndex: $model.selectedIndex)
}
} }
.frame(height: height) .frame(height: height)
.onChange(of: model.selectedIndex) { value in .onChange(of: model.selectedIndex) { value in
@@ -296,7 +316,9 @@ public struct ImageFill {
struct ImageCarousel_Previews: PreviewProvider { struct ImageCarousel_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!) let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
ImageCarousel(state: test_damus_state, evid: test_note.id, urls: [url, url]) let test_video_url: MediaUrl = .video(URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!)
ImageCarousel<AnyView>(state: test_damus_state, evid: test_note.id, urls: [test_video_url, url])
.environmentObject(OrientationTracker())
} }
} }

View File

@@ -9,7 +9,12 @@ import SwiftUI
struct TruncatedText: View { struct TruncatedText: View {
let text: CompatibleText let text: CompatibleText
let maxChars: Int = 280 let maxChars: Int
init(text: CompatibleText, maxChars: Int = 280) {
self.text = text
self.maxChars = maxChars
}
var body: some View { var body: some View {
let truncatedAttributedString: AttributedString? = text.attributed.truncateOrNil(maxLength: maxChars) let truncatedAttributedString: AttributedString? = text.attributed.truncateOrNil(maxLength: maxChars)

View File

@@ -19,8 +19,11 @@ struct EventViewOptions: OptionSet {
static let nested = EventViewOptions(rawValue: 1 << 7) static let nested = EventViewOptions(rawValue: 1 << 7)
static let top_zap = EventViewOptions(rawValue: 1 << 8) static let top_zap = EventViewOptions(rawValue: 1 << 8)
static let no_mentions = EventViewOptions(rawValue: 1 << 9) static let no_mentions = EventViewOptions(rawValue: 1 << 9)
static let no_media = EventViewOptions(rawValue: 1 << 10)
static let truncate_content_very_short = EventViewOptions(rawValue: 1 << 11)
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested] static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
static let embedded_text_only: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested, .no_media, .truncate_content_very_short]
} }
struct TextEvent: View { struct TextEvent: View {

View File

@@ -0,0 +1,36 @@
//
// VisibilityTracker.swift
// damus
//
// Created by Daniel DAquino on 2024-03-18.
//
// Based on code examples shown in this article: https://medium.com/@jackvanderpump/how-to-detect-is-an-element-is-visible-in-swiftui-9ff58ca72339
import Foundation
import SwiftUI
extension View {
func on_visibility_change(perform visibility_change_notifier: @escaping (Bool) -> Void, edge: Alignment = .center) -> some View {
self.modifier(VisibilityTracker(visibility_change_notifier: visibility_change_notifier, edge: edge))
}
}
struct VisibilityTracker: ViewModifier {
let visibility_change_notifier: (Bool) -> Void
let edge: Alignment
func body(content: Content) -> some View {
content
.overlay(
LazyVStack {
Color.clear
.onAppear {
visibility_change_notifier(true)
}
.onDisappear {
visibility_change_notifier(false)
}
},
alignment: edge)
}
}

View File

@@ -0,0 +1,171 @@
//
// FullScreenCarouselView.swift
// damus
//
// Created by William Casarin on 2023-03-23.
//
import SwiftUI
struct FullScreenCarouselView<Content: View>: View {
let video_controller: VideoController
let urls: [MediaUrl]
@Environment(\.presentationMode) var presentationMode
@State var showMenu = true
let settings: UserSettingsStore
@Binding var selectedIndex: Int
let content: (() -> Content)?
init(video_controller: VideoController, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>, @ViewBuilder content: @escaping () -> Content) {
self.video_controller = video_controller
self.urls = urls
self._showMenu = State(initialValue: showMenu)
self.settings = settings
_selectedIndex = selectedIndex
self.content = content
}
init(video_controller: VideoController, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>) {
self.video_controller = video_controller
self.urls = urls
self._showMenu = State(initialValue: showMenu)
self.settings = settings
_selectedIndex = selectedIndex
self.content = nil
}
var tabViewIndicator: some View {
HStack(spacing: 10) {
ForEach(urls.indices, id: \.self) { index in
Capsule()
.fill(index == selectedIndex ? Color.white : Color.damusMediumGrey)
.frame(width: 7, height: 7)
.onTapGesture {
selectedIndex = index
}
}
}
.padding()
.clipShape(Capsule())
}
var background: some ShapeStyle {
if case .video = urls[safe: selectedIndex] {
return AnyShapeStyle(Color.black)
}
else {
return AnyShapeStyle(.regularMaterial)
}
}
var background_color: UIColor {
return .black
}
var body: some View {
ZStack {
Color(self.background_color)
.ignoresSafeArea()
TabView(selection: $selectedIndex) {
ForEach(urls.indices, id: \.self) { index in
VStack {
if case .video = urls[safe: index] {
ImageContainerView(video_controller: video_controller, url: urls[index], settings: settings)
.clipped() // SwiftUI hack from https://stackoverflow.com/a/74401288 to make playback controls show up within the TabView
.aspectRatio(contentMode: .fit)
.padding(.top, Theme.safeAreaInsets?.top)
.padding(.bottom, Theme.safeAreaInsets?.bottom)
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
.ignoresSafeArea()
}
else {
ZoomableScrollView {
ImageContainerView(video_controller: video_controller, url: urls[index], settings: settings)
.aspectRatio(contentMode: .fit)
.padding(.top, Theme.safeAreaInsets?.top)
.padding(.bottom, Theme.safeAreaInsets?.bottom)
}
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
.ignoresSafeArea()
}
}.tag(index)
}
}
.ignoresSafeArea()
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.gesture(TapGesture(count: 2).onEnded {
// Prevents menu from hiding on double tap
})
.gesture(TapGesture(count: 1).onEnded {
showMenu.toggle()
})
.overlay(
GeometryReader { geo in
VStack {
if showMenu {
NavDismissBarView(showBackgroundCircle: false)
.foregroundColor(.white)
Spacer()
if (urls.count > 1) {
tabViewIndicator
}
self.content?()
}
}
.animation(.easeInOut, value: showMenu)
.padding(.bottom, geo.safeAreaInsets.bottom == 0 ? 12 : 0)
}
)
}
}
}
fileprivate struct FullScreenCarouselPreviewView<Content: View>: View {
@State var selectedIndex: Int = 0
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
let test_video_url: MediaUrl = .video(URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!)
let custom_content: (() -> Content)?
init(content: (() -> Content)? = nil) {
self.custom_content = content
}
var body: some View {
FullScreenCarouselView(video_controller: test_damus_state.video, urls: [test_video_url, url], settings: test_damus_state.settings, selectedIndex: $selectedIndex) {
self.custom_content?()
}
.environmentObject(OrientationTracker())
}
}
struct FullScreenCarouselView_Previews: PreviewProvider {
static var previews: some View {
Group {
FullScreenCarouselPreviewView<AnyView>()
.previewDisplayName("No custom content on overlay")
FullScreenCarouselPreviewView(content: {
HStack {
Spacer()
Text("Some content")
.padding()
.foregroundColor(.white)
Spacer()
}.background(.ultraThinMaterial)
})
.previewDisplayName("Custom content on overlay")
}
}
}

View File

@@ -43,19 +43,26 @@ struct ImageContainerView: View {
var body: some View { var body: some View {
Group { Group {
switch url { switch url {
case .image(let url): case .image(let url):
Img(url: url) Img(url: url)
case .video(let url): case .video(let url):
DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller) DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller, style: .full, visibility_tracking_method: .generic)
} }
} }
} }
} }
let test_image_url = URL(string: "https://jb55.com/red-me.jpg")! let test_image_url = URL(string: "https://jb55.com/red-me.jpg")!
fileprivate let test_video_url = URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!
struct ImageContainerView_Previews: PreviewProvider { struct ImageContainerView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ImageContainerView(video_controller: test_damus_state.video, url: .image(test_image_url), settings: test_damus_state.settings) Group {
ImageContainerView(video_controller: test_damus_state.video, url: .image(test_image_url), settings: test_damus_state.settings)
.previewDisplayName("Image")
ImageContainerView(video_controller: test_damus_state.video, url: .video(test_video_url), settings: test_damus_state.settings)
.previewDisplayName("Video")
}
.environmentObject(OrientationTracker())
} }
} }

View File

@@ -1,90 +0,0 @@
//
// ImageView.swift
// damus
//
// Created by William Casarin on 2023-03-23.
//
import SwiftUI
struct ImageView: View {
let video_controller: VideoController
let urls: [MediaUrl]
@Environment(\.presentationMode) var presentationMode
@State var showMenu = true
let settings: UserSettingsStore
@Binding var selectedIndex: Int
var tabViewIndicator: some View {
HStack(spacing: 10) {
ForEach(urls.indices, id: \.self) { index in
Capsule()
.fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary)
.frame(width: 7, height: 7)
.onTapGesture {
selectedIndex = index
}
}
}
.padding()
.background(.regularMaterial)
.clipShape(Capsule())
}
var body: some View {
ZStack {
Color(.systemBackground)
.ignoresSafeArea()
TabView(selection: $selectedIndex) {
ForEach(urls.indices, id: \.self) { index in
ZoomableScrollView {
ImageContainerView(video_controller: video_controller, url: urls[index], settings: settings)
.aspectRatio(contentMode: .fit)
.padding(.top, Theme.safeAreaInsets?.top)
.padding(.bottom, Theme.safeAreaInsets?.bottom)
}
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
.ignoresSafeArea()
.tag(index)
}
}
.ignoresSafeArea()
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.gesture(TapGesture(count: 2).onEnded {
// Prevents menu from hiding on double tap
})
.gesture(TapGesture(count: 1).onEnded {
showMenu.toggle()
})
.overlay(
GeometryReader { geo in
VStack {
if showMenu {
NavDismissBarView()
Spacer()
if (urls.count > 1) {
tabViewIndicator
}
}
}
.animation(.easeInOut, value: showMenu)
.padding(.bottom, geo.safeAreaInsets.bottom == 0 ? 12 : 0)
}
)
}
}
}
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
ImageView(video_controller: test_damus_state.video, urls: [url], settings: test_damus_state.settings, selectedIndex: Binding.constant(0))
}
}

View File

@@ -42,16 +42,27 @@ struct ProfileImageContainerView: View {
struct NavDismissBarView: View { struct NavDismissBarView: View {
@Environment(\.presentationMode) var presentationMode @Environment(\.presentationMode) var presentationMode
let showBackgroundCircle: Bool
init(showBackgroundCircle: Bool = true) {
self.showBackgroundCircle = showBackgroundCircle
}
var body: some View { var body: some View {
HStack { HStack {
Button(action: { Button(action: {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
}, label: { }, label: {
Image("close") if showBackgroundCircle {
.frame(width: 33, height: 33) Image("close")
.background(.regularMaterial) .frame(width: 33, height: 33)
.clipShape(Circle()) .background(.regularMaterial)
.clipShape(Circle())
}
else {
Image("close")
.frame(width: 33, height: 33)
}
}) })
Spacer() Spacer()

View File

@@ -57,6 +57,10 @@ struct NoteContentView: View {
return options.contains(.truncate_content) return options.contains(.truncate_content)
} }
var truncate_very_short: Bool {
return options.contains(.truncate_content_very_short)
}
var with_padding: Bool { var with_padding: Bool {
return options.contains(.wide) return options.contains(.wide)
} }
@@ -73,7 +77,11 @@ struct NoteContentView: View {
func truncatedText(content: CompatibleText) -> some View { func truncatedText(content: CompatibleText) -> some View {
Group { Group {
if truncate { if truncate_very_short {
TruncatedText(text: content, maxChars: 140)
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
}
else if truncate {
TruncatedText(text: content) TruncatedText(text: content)
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size)) .font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
} else { } else {
@@ -107,6 +115,19 @@ struct NoteContentView: View {
} }
} }
func fullscreen_preview(dismiss: @escaping () -> Void) -> some View {
VStack {
EventView(damus: damus_state, event: self.event, options: .embedded_text_only)
.padding(.top)
}
.background(.thinMaterial)
.preferredColorScheme(.dark)
.onTapGesture(perform: {
damus_state.nav.push(route: Route.Thread(thread: .init(event: self.event, damus_state: damus_state)))
dismiss()
})
}
func MainContent(artifacts: NoteArtifactsSeparated) -> some View { func MainContent(artifacts: NoteArtifactsSeparated) -> some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if size == .selected { if size == .selected {
@@ -135,13 +156,19 @@ struct NoteContentView: View {
} }
if artifacts.media.count > 0 { if artifacts.media.count > 0 {
if !damus_state.settings.media_previews && !load_media { if (self.options.contains(.no_media)) {
EmptyView()
} else if !damus_state.settings.media_previews && !load_media {
loadMediaButton(artifacts: artifacts) loadMediaButton(artifacts: artifacts)
} else if !blur_images || (!blur_images && !damus_state.settings.media_previews && load_media) { } else if !blur_images || (!blur_images && !damus_state.settings.media_previews && load_media) {
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media) ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media) { dismiss in
fullscreen_preview(dismiss: dismiss)
}
} else if blur_images || (blur_images && !damus_state.settings.media_previews && load_media) { } else if blur_images || (blur_images && !damus_state.settings.media_previews && load_media) {
ZStack { ZStack {
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media) ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media) { dismiss in
fullscreen_preview(dismiss: dismiss)
}
Blur() Blur()
.onTapGesture { .onTapGesture {
blur_images = false blur_images = false

View File

@@ -9,12 +9,15 @@ import Foundation
import AVKit import AVKit
import SwiftUI import SwiftUI
struct AVPlayerView: UIViewControllerRepresentable { struct DamusAVPlayerView: UIViewControllerRepresentable {
let player: AVPlayer let player: AVPlayer
var controller: AVPlayerViewController
let show_playback_controls: Bool
func makeUIViewController(context: Context) -> AVPlayerViewController { func makeUIViewController(context: Context) -> AVPlayerViewController {
AVPlayerViewController() self.controller.showsPlaybackControls = show_playback_controls
return self.controller
} }
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {

View File

@@ -20,10 +20,22 @@ struct DamusVideoPlayer: View {
let url: URL let url: URL
@StateObject var model: DamusVideoPlayerViewModel @StateObject var model: DamusVideoPlayerViewModel
@EnvironmentObject private var orientationTracker: OrientationTracker @EnvironmentObject private var orientationTracker: OrientationTracker
let style: Style
let visibility_tracking_method: VisibilityTrackingMethod
@State var isVisible: Bool = false
init(url: URL, video_size: Binding<CGSize?>, controller: VideoController) { init(url: URL, video_size: Binding<CGSize?>, controller: VideoController, style: Style, visibility_tracking_method: VisibilityTrackingMethod = .y_scroll) {
self.url = url self.url = url
_model = StateObject(wrappedValue: DamusVideoPlayerViewModel(url: url, video_size: video_size, controller: controller)) let mute: Bool?
if case .full = style {
mute = false
}
else {
mute = nil
}
_model = StateObject(wrappedValue: DamusVideoPlayerViewModel(url: url, video_size: video_size, controller: controller, mute: mute))
self.visibility_tracking_method = visibility_tracking_method
self.style = style
} }
var body: some View { var body: some View {
@@ -31,7 +43,15 @@ struct DamusVideoPlayer: View {
let localFrame = geo.frame(in: .local) let localFrame = geo.frame(in: .local)
let centerY = globalCoordinate(localX: 0, localY: localFrame.midY, localGeometry: geo).y let centerY = globalCoordinate(localX: 0, localY: localFrame.midY, localGeometry: geo).y
ZStack { ZStack {
AVPlayerView(player: model.player) if case .full = self.style {
DamusAVPlayerView(player: model.player, controller: model.player_view_controller, show_playback_controls: true)
}
if case .preview(let on_tap) = self.style {
DamusAVPlayerView(player: model.player, controller: model.player_view_controller, show_playback_controls: false)
.simultaneousGesture(TapGesture().onEnded({
on_tap?()
}))
}
if model.is_loading { if model.is_loading {
ProgressView() ProgressView()
@@ -40,22 +60,35 @@ struct DamusVideoPlayer: View {
.scaleEffect(CGSize(width: 1.5, height: 1.5)) .scaleEffect(CGSize(width: 1.5, height: 1.5))
} }
if model.has_audio { if case .preview = self.style {
mute_button if model.has_audio {
mute_button
}
} }
if model.is_live { if model.is_live {
live_indicator live_indicator
} }
} }
.onChange(of: centerY) { _ in .onChange(of: centerY) { _ in
update_is_visible(centerY: centerY) if case .y_scroll = visibility_tracking_method {
update_is_visible(centerY: centerY)
}
} }
.on_visibility_change(perform: { new_visibility in
if case .generic = visibility_tracking_method {
model.set_view_is_visible(new_visibility)
}
})
.onAppear { .onAppear {
update_is_visible(centerY: centerY) if case .y_scroll = visibility_tracking_method {
update_is_visible(centerY: centerY)
}
} }
} }
.onDisappear { .onDisappear {
model.view_did_disappear() if case .y_scroll = visibility_tracking_method {
model.view_did_disappear()
}
} }
} }
@@ -115,9 +148,31 @@ struct DamusVideoPlayer: View {
Spacer() Spacer()
} }
} }
enum Style {
/// A full video player with playback controls
case full
/// A style suitable for muted, auto-playing videos on a feed
case preview(on_tap: (() -> Void)?)
}
enum VisibilityTrackingMethod {
/// Detects visibility based on its Y position relative to viewport. Ideal for long feeds
case y_scroll
/// Detects visibility based whether the view intersects with the viewport
case generic
}
} }
struct DamusVideoPlayer_Previews: PreviewProvider { struct DamusVideoPlayer_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
DamusVideoPlayer(url: URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!, video_size: .constant(nil), controller: VideoController()) Group {
DamusVideoPlayer(url: URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!, video_size: .constant(nil), controller: VideoController(), style: .full)
.environmentObject(OrientationTracker())
.previewDisplayName("Full video player")
DamusVideoPlayer(url: URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!, video_size: .constant(nil), controller: VideoController(), style: .preview(on_tap: nil))
.environmentObject(OrientationTracker())
.previewDisplayName("Preview video player")
}
} }
} }

View File

@@ -6,6 +6,7 @@
// //
import AVFoundation import AVFoundation
import AVKit
import Combine import Combine
import Foundation import Foundation
import SwiftUI import SwiftUI
@@ -27,7 +28,8 @@ final class DamusVideoPlayerViewModel: ObservableObject {
private let url: URL private let url: URL
private let player_item: AVPlayerItem private let player_item: AVPlayerItem
let player: AVPlayer let player: AVPlayer
private let controller: VideoController fileprivate let controller: VideoController
let player_view_controller = AVPlayerViewController()
let id = UUID() let id = UUID()
@Published var has_audio = false @Published var has_audio = false
@@ -55,7 +57,7 @@ final class DamusVideoPlayerViewModel: ObservableObject {
} }
} }
init(url: URL, video_size: Binding<CGSize?>, controller: VideoController) { init(url: URL, video_size: Binding<CGSize?>, controller: VideoController, mute: Bool? = nil) {
self.url = url self.url = url
player_item = AVPlayerItem(url: url) player_item = AVPlayerItem(url: url)
player = AVPlayer(playerItem: player_item) player = AVPlayer(playerItem: player_item)
@@ -66,7 +68,7 @@ final class DamusVideoPlayerViewModel: ObservableObject {
await load() await load()
} }
is_muted = controller.should_mute_video(url: url) is_muted = mute ?? controller.should_mute_video(url: url)
player.isMuted = is_muted player.isMuted = is_muted
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(