diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 919fb273..67cd254c 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -454,6 +454,7 @@ D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C592B76FD5100C59298 /* LogoView.swift */; }; D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.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 */; }; D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; @@ -1373,6 +1374,7 @@ D7100C592B76FD5100C59298 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = ""; }; D7100C5B2B77016700C59298 /* IAPProductStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPProductStateView.swift; sourceTree = ""; }; D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleStoreKitManager.swift; sourceTree = ""; }; + D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityTracker.swift; sourceTree = ""; }; D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = ""; }; D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = ""; }; D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = ""; }; @@ -1978,6 +1980,7 @@ 4C75EFA227FA576C0006080F /* Views */ = { isa = PBXGroup; children = ( + D71AC4CA2BA8E3320076268E /* Extensions */, BA3759952ABCCF360018D73B /* Camera */, F71694E82A66221E001F4053 /* Onboarding */, 4C190F232A547D1700027FD5 /* NostrScript */, @@ -2706,6 +2709,14 @@ path = Detail; sourceTree = ""; }; + D71AC4CA2BA8E3320076268E /* Extensions */ = { + isa = PBXGroup; + children = ( + D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */, + ); + path = Extensions; + sourceTree = ""; + }; D72A2D032AD9C165002AFF62 /* Mocking */ = { isa = PBXGroup; children = ( @@ -3206,6 +3217,7 @@ F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */, 4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, + D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */, 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, 4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */, diff --git a/damus/Views/Extensions/VisibilityTracker.swift b/damus/Views/Extensions/VisibilityTracker.swift new file mode 100644 index 00000000..89950a9f --- /dev/null +++ b/damus/Views/Extensions/VisibilityTracker.swift @@ -0,0 +1,36 @@ +// +// VisibilityTracker.swift +// damus +// +// Created by Daniel D’Aquino 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) + } +} diff --git a/damus/Views/Images/ImageContainerView.swift b/damus/Views/Images/ImageContainerView.swift index 5c697e4d..153a61a0 100644 --- a/damus/Views/Images/ImageContainerView.swift +++ b/damus/Views/Images/ImageContainerView.swift @@ -46,7 +46,7 @@ struct ImageContainerView: View { case .image(let url): Img(url: url) case .video(let url): - DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller, style: .full) + DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller, style: .full, visibility_tracking_method: .generic) } } } diff --git a/damus/Views/Video/DamusVideoPlayer.swift b/damus/Views/Video/DamusVideoPlayer.swift index 502987e3..6beaa924 100644 --- a/damus/Views/Video/DamusVideoPlayer.swift +++ b/damus/Views/Video/DamusVideoPlayer.swift @@ -21,8 +21,10 @@ struct DamusVideoPlayer: View { @StateObject var model: DamusVideoPlayerViewModel @EnvironmentObject private var orientationTracker: OrientationTracker let style: Style + let visibility_tracking_method: VisibilityTrackingMethod + @State var isVisible: Bool = false - init(url: URL, video_size: Binding, controller: VideoController, style: Style) { + init(url: URL, video_size: Binding, controller: VideoController, style: Style, visibility_tracking_method: VisibilityTrackingMethod = .y_scroll) { self.url = url let mute: Bool? if case .full = style { @@ -32,6 +34,7 @@ struct DamusVideoPlayer: View { 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 } @@ -67,14 +70,25 @@ struct DamusVideoPlayer: View { } } .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 { - update_is_visible(centerY: centerY) + if case .y_scroll = visibility_tracking_method { + update_is_visible(centerY: centerY) + } } } .onDisappear { - model.view_did_disappear() + if case .y_scroll = visibility_tracking_method { + model.view_did_disappear() + } } } @@ -141,6 +155,13 @@ struct DamusVideoPlayer: View { /// 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 { static var previews: some View {