commit 1bc660c9cd1e7beb5110d97d22341b34abbac905
parent a56a59f81de5e3983f9fb19742eba20f08779a61
Author: Daniel D’Aquino <daniel@daquino.me>
Date: Fri, 1 Nov 2024 18:12:49 -0700
Improve full screen support
This commit introduces new mechanisms to solve some issues with full screen support:
1. Full screen covers disappear when its caller disappears (e.g. when it
is an event in a lazy stack, and an orientation change causes the
view to disappear along with the full screen cover)
2. There are no mechanisms for views to determine whether they are being
presented under a full screen cover or not, and whether the device is
in full screen mode or not.
The commit overcomes the above limitations through the following:
1. A full screen cover on `ContentView` that can be accessed by any view
when calling `present(fullScreenItem)`
2. A new `damus_full_screen_cover` view modifier that automatically
tracks whether a device is in full screen mode or not, and allows
any descendant view in its hierarchy to introspect on which view
layer it is being presented in.
This commit lays a foundation that will later become important for
improving video coordination.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Diffstat:
7 files changed, 253 insertions(+), 3 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -599,6 +599,8 @@
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
+ D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */; };
+ D734B1462CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */; };
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */; };
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA92B68A65A00F7783D /* PurpleAccountUpdateNotify.swift */; };
@@ -1121,6 +1123,8 @@
D7D68FFA2C9E01BE0015A515 /* KFClickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D68FF82C9E01B60015A515 /* KFClickable.swift */; };
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
+ D7EB00B02CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
+ D7EB00B12CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
D7EDED162B1177840018B19C /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; };
D7EDED172B1177960018B19C /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; };
@@ -1924,6 +1928,7 @@
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
+ D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusFullScreenCover.swift; sourceTree = "<group>"; };
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNewUserOnboardingView.swift; sourceTree = "<group>"; };
D7373BA92B68A65A00F7783D /* PurpleAccountUpdateNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleAccountUpdateNotify.swift; sourceTree = "<group>"; };
@@ -1972,6 +1977,7 @@
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
D7D68FF82C9E01B60015A515 /* KFClickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFClickable.swift; sourceTree = "<group>"; };
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
+ D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentFullScreenItemNotify.swift; sourceTree = "<group>"; };
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
D7EDED202B117DCA0018B19C /* SequenceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceUtils.swift; sourceTree = "<group>"; };
@@ -2810,6 +2816,7 @@
4CA3529C2A76AE47003BB08B /* Notify */ = {
isa = PBXGroup;
children = (
+ D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */,
4C86F7C52A76C51100EC0817 /* AttachedWalletNotify.swift */,
4C9D6D152B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift */,
4C1253552A76C8C60004F4B8 /* BroadcastNotify.swift */,
@@ -3326,6 +3333,7 @@
isa = PBXGroup;
children = (
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */,
+ D734B1442CCC19B1000B5C97 /* DamusFullScreenCover.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -3966,6 +3974,7 @@
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */,
4C9D6D1B2B1D35D7004E5CD9 /* PullDownSearch.swift in Sources */,
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
+ D7EB00B12CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */,
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */,
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
4C363A94282704FA006E126D /* Post.swift in Sources */,
@@ -3983,6 +3992,7 @@
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
+ D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
4C4E137D2A76D63600BDD832 /* UnmuteThreadNotify.swift in Sources */,
4CE4F0F829DB7399005914DB /* ThiccDivider.swift in Sources */,
4CFF8F5929C9FD1E008DB934 /* DamusPurpleView.swift in Sources */,
@@ -4491,6 +4501,7 @@
D73E5EFC2C6A97F4007EB227 /* DamusAppNotificationView.swift in Sources */,
D73E5EFD2C6A97F4007EB227 /* InnerTimelineView.swift in Sources */,
D73E5EFE2C6A97F4007EB227 /* (null) in Sources */,
+ D7EB00B02CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */,
D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */,
D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */,
D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */,
@@ -4607,6 +4618,7 @@
D73E5F692C6A97F5007EB227 /* ReactionsView.swift in Sources */,
D73E5F6A2C6A97F5007EB227 /* ReportView.swift in Sources */,
D73E5F6C2C6A97F5007EB227 /* RepostsView.swift in Sources */,
+ D734B1462CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
D73E5F6D2C6A97F5007EB227 /* Launch.storyboard in Sources */,
D73E5F6F2C6A97F5007EB227 /* RelayFilterView.swift in Sources */,
D703D78A2C670C8A00A400EA /* LibreTranslateServer.swift in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -57,6 +57,41 @@ enum Sheets: Identifiable {
}
}
+/// An item to be presented full screen in a mechanism that is more robust for timeline views.
+///
+/// ## Implementation notes
+///
+/// This is part of the `present(full_screen_item: FullScreenItem)` interface that allows views in a timeline to show something full-screen without the lazy stack issues
+/// Full screen cover modifiers are not suitable in those cases because device orientation changes or programmatic scroll commands will cause the view to be unloaded along with the cover,
+/// causing the user to lose the full screen view randomly.
+///
+/// The `ContentView` is responsible for handling these objects
+///
+/// New items can be added as needed.
+///
+enum FullScreenItem: Identifiable, Equatable {
+ /// A full screen media carousel for images and videos.
+ case full_screen_carousel(urls: [MediaUrl], selectedIndex: Binding<Int>)
+
+ var id: String {
+ switch self {
+ case .full_screen_carousel(let urls, _): return "full_screen_carousel:\(urls.map(\.url))"
+ }
+ }
+
+ static func == (lhs: FullScreenItem, rhs: FullScreenItem) -> Bool {
+ return lhs.id == rhs.id
+ }
+
+ /// The view to display the item
+ func view(damus_state: DamusState) -> some View {
+ switch self {
+ case .full_screen_carousel(let urls, let selectedIndex):
+ return FullScreenCarouselView<AnyView>(video_coordinator: damus_state.video, urls: urls, settings: damus_state.settings, selectedIndex: selectedIndex)
+ }
+ }
+}
+
func present_sheet(_ sheet: Sheets) {
notify(.present_sheet(sheet))
}
@@ -78,6 +113,7 @@ struct ContentView: View {
@Environment(\.scenePhase) var scenePhase
@State var active_sheet: Sheets? = nil
+ @State var active_full_screen_item: FullScreenItem? = nil
@State var damus_state: DamusState!
@State var menu_subtitle: String? = nil
@SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home {
@@ -245,6 +281,9 @@ struct ContentView: View {
}
}
.navigationViewStyle(.stack)
+ .damus_full_screen_cover($active_full_screen_item, damus_state: damus, content: { item in
+ return item.view(damus_state: damus)
+ })
.overlay(alignment: .bottom) {
if !hide_bar {
if !isSideBarOpened {
@@ -421,6 +460,9 @@ struct ContentView: View {
.onReceive(handle_notify(.present_sheet)) { sheet in
self.active_sheet = sheet
}
+ .onReceive(handle_notify(.present_full_screen_item)) { item in
+ self.active_full_screen_item = item
+ }
.onReceive(handle_notify(.zapping)) { zap_ev in
guard !zap_ev.is_custom else {
return
diff --git a/damus/Notify/PresentFullScreenItemNotify.swift b/damus/Notify/PresentFullScreenItemNotify.swift
@@ -0,0 +1,42 @@
+//
+// PresentFullScreenItemNotify.swift
+// damus
+//
+// Created by Daniel D’Aquino on 2024-11-01.
+//
+
+struct PresentFullScreenItemNotify: Notify {
+ typealias Payload = FullScreenItem
+ var payload: Payload
+}
+
+extension NotifyHandler {
+ static var present_full_screen_item: NotifyHandler<PresentFullScreenItemNotify> {
+ .init()
+ }
+}
+
+extension Notifications {
+ static func present_full_screen_item(_ item: FullScreenItem) -> Notifications<PresentFullScreenItemNotify> {
+ .init(.init(payload: item))
+ }
+}
+
+/// Tell the app to present an item in full screen. Use this when presenting items coming from a timeline or any lazy stack.
+///
+/// ## Usage notes
+///
+/// Use this instead of `.damus_full_screen_cover` when the source view is on a lazy stack or timeline.
+///
+/// The reason is that when using a full screen modifier in those scenarios, the full screen view may abruptly disappear.
+/// One example is when showing videos from the timeline in full screen, where changing the orientation of the device (landscape/portrait)
+/// can cause the source view to be unloaded by the lazy stack, making your full screen overlay to simply disappear, causing a feeling of flakiness to the app
+///
+/// ## Implementation notes
+///
+/// The requests from this function will be received and handled at the top level app view (`ContentView`), which contains a `.damus_full_screen_cover`.
+///
+func present(full_screen_item: FullScreenItem) {
+ notify(.present_full_screen_item(full_screen_item))
+}
+
diff --git a/damus/Views/Extensions/DamusFullScreenCover.swift b/damus/Views/Extensions/DamusFullScreenCover.swift
@@ -0,0 +1,140 @@
+//
+// DamusFullScreenCover.swift
+// damus
+//
+// Created by Daniel D’Aquino on 2024-10-25.
+//
+
+import SwiftUI
+
+
+// MARK: - Private view modifier implementations of DamusFullScreenCover
+
+/// This implements a full screen cover made for use in Damus.
+/// This was created as a way to facilitate video coordination throughout the app, by handling the necessary logic — without requiring any special handling in the usages of video player views.
+///
+/// In the future this could be used to faciliate other full screen logic as well.
+///
+/// # Features
+///
+/// This has the following features:
+/// 1. It automatically tells the video coordinator about full screen mode changes, so that the video coordinator always knows if the app is in normal mode or in full screen mode for video coordination
+/// 2. It automatically sets the `view_layer_context`, which is consumed by video player views, allowing those views to communicate about their layer position to the video coordinator
+fileprivate struct DamusFullScreenCover<FullScreenContent: View, T: Identifiable & Equatable>: ViewModifier {
+ /// The `damus_state`, where we can access the video coordinator
+ let damus_state: DamusState
+ /// The item to be presented full screen
+ @Binding var item: T?
+ /// The view to be presented full screen
+ let full_screen_content: (T) -> FullScreenContent
+
+ func body(content: Content) -> some View {
+ content
+ .environment(\.view_layer_context, .normal_layer) // Let the views under content know they are NOT in a full screen environment
+ .onChange(of: item, perform: { newValue in
+ // Inform the video coordinator whether we are in full screen mode or not.
+ damus_state.video.set_full_screen_mode(newValue != nil)
+ })
+ .fullScreenCover(item: $item, content: { item in
+ full_screen_content(item)
+ .environment(\.view_layer_context, .full_screen_layer) // Let the views under full screen content know they are in a full screen environment
+ // Another observer for full screen presentation is needed here because in some cases the underlying view (`body::content`) may have been deinitialized and no longer listen to changes
+ // One such example is when the underlying navigation stack navigates away from a source view at the same time it opens the full screen view
+ // Therefore, when the full screen view is dismissed, this content will disappear, and we should notify the video coordinator.
+ .onDisappear {
+ damus_state.video.set_full_screen_mode(false)
+ }
+ })
+ }
+}
+
+/// A convenience view modifier that provides a different interface than `DamusFullScreenCover`, but is otherwise identical to it.
+fileprivate struct DamusFullScreenCoverWithoutItem<FullScreenContent: View>: ViewModifier {
+ let damus_state: DamusState
+ @Binding var is_presented: Bool
+ let full_screen_content: () -> FullScreenContent
+ private let fake_item: FakeItem = FakeItem()
+ private var binding_item: Binding<FakeItem?> {
+ return Binding(
+ get: { is_presented ? self.fake_item : nil },
+ set: { is_presented = $0 != nil ? true : false }
+ )
+ }
+
+ func body(content: Content) -> some View {
+ content
+ .damus_full_screen_cover(self.binding_item, damus_state: damus_state, content: { _ in full_screen_content() })
+ }
+
+ private struct FakeItem: Identifiable, Equatable {
+ let id: Int = 1
+ }
+}
+
+
+// MARK: - Environment variable definitions
+
+extension EnvironmentValues {
+ @Entry var view_layer_context: ViewLayerContext? = nil
+}
+
+
+/// Context about the layer a view finds itself in
+/// This communicates to a view (e.g. a video player) context about whether it is being displayed inside a full screen layer, or a normal layer
+enum ViewLayerContext {
+ /// This is used for items placed in a scroll view, such as on a timeline or a thread view.
+ case normal_layer
+ /// This is used for video players being displayed full screen
+ case full_screen_layer
+}
+
+
+// MARK: - View extension interfaces to access Damus' full screen cover
+
+extension View {
+
+ /// A full screen cover to be used throughout Damus, containing extra functionality that helps with app coordination, and is meant to replace `.fullScreenCover`
+ ///
+ /// ## Usage notes
+ ///
+ /// This is the preferred method of doing a full screen cover. This is preferred over `.fullScreenCover` because it helps with certain coordination elements:
+ ///
+ /// 1. It automatically informs the video coordinator if the app is in full screen or not
+ /// 2. It provides contextual information that any child view can pickup to introspect whether or not they are in a full screen layer. This can be picked up via the `\.view_layer_context` environment variable
+ ///
+ /// **CAUTION:**
+ /// If you are planning to use this from a view that is presented on a timeline or lazy stack, please use `present(full_screen_item: FullScreenItem)` instead to avoid your full screen view to abruptly disappear.
+ /// Please read the documentation for `present(full_screen_item: FullScreenItem)` for more details.
+ ///
+ /// - Parameters:
+ /// - is_presented: whether to show the full screen cover
+ /// - damus_state: The state of the app
+ /// - content: The view to show full screen
+ /// - Returns: the modified view
+ func damus_full_screen_cover<Content: View>(_ is_presented: Binding<Bool>, damus_state: DamusState, @ViewBuilder content: @escaping () -> Content) -> some View {
+ return self.modifier(DamusFullScreenCoverWithoutItem(damus_state: damus_state, is_presented: is_presented, full_screen_content: content))
+ }
+
+ /// A full screen cover to be used throughout Damus, containing extra functionality that helps with app coordination, and is meant to replace `.fullScreenCover`
+ ///
+ /// ## Usage notes
+ ///
+ /// This is the preferred method of doing a full screen cover. This is preferred over `.fullScreenCover` because it helps with certain coordination elements:
+ ///
+ /// 1. It automatically informs the video coordinator if the app is in full screen or not
+ /// 2. It provides contextual information that any child view can pickup to introspect whether or not they are in a full screen layer. This can be picked up via the `\.view_layer_context` environment variable
+ ///
+ /// **CAUTION:**
+ /// If you are planning to use this from a view that is presented on a timeline or lazy stack, please use `present(full_screen_item: FullScreenItem)` instead to avoid your full screen view to abruptly disappear.
+ /// Please read the documentation for `present(full_screen_item: FullScreenItem)` for more details.
+ ///
+ ///
+ /// - Parameters:
+ /// - item: The item to be displayed full screen, or `nil` if full screen should be dismissed.
+ /// - damus_state: The state of the app
+ /// - content: The view to render `item`
+ /// - Returns: the modified view
+ func damus_full_screen_cover<Content: View, T: Identifiable & Equatable>(_ item: Binding<T?>, damus_state: DamusState, @ViewBuilder content: @escaping (T) -> Content) -> some View {
+ return self.modifier(DamusFullScreenCover(damus_state: damus_state, item: item, full_screen_content: content))
+ }
+}
diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift
@@ -297,7 +297,7 @@ struct ProfileView: View {
.onTapGesture {
is_zoomed.toggle()
}
- .fullScreenCover(isPresented: $is_zoomed) {
+ .damus_full_screen_cover($is_zoomed, damus_state: damus_state) {
ProfilePicImageView(pubkey: profile.pubkey, profiles: damus_state.profiles, settings: damus_state.settings, nav: damus_state.nav, shouldShowEditButton: damus_state.pubkey == profile.pubkey)
}
@@ -478,7 +478,7 @@ struct ProfileView: View {
let url = URL(string: "https://damus.io/" + profile.pubkey.npub)!
ShareSheet(activityItems: [url])
}
- .fullScreenCover(isPresented: $show_qr_code) {
+ .damus_full_screen_cover($show_qr_code, damus_state: damus_state) {
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
}
diff --git a/damus/Views/SideMenuView.swift b/damus/Views/SideMenuView.swift
@@ -135,7 +135,7 @@ struct SideMenuView: View {
Circle()
.foregroundColor(DamusColors.neutral3)
}
- }).fullScreenCover(isPresented: $showQRCode) {
+ }).damus_full_screen_cover($showQRCode, damus_state: damus_state) {
QRCodeView(damus_state: damus_state, pubkey: damus_state.pubkey)
}
}
diff --git a/damus/Views/Video/DamusVideoCoordinator.swift b/damus/Views/Video/DamusVideoCoordinator.swift
@@ -26,6 +26,14 @@ final class DamusVideoCoordinator: ObservableObject {
private var mute_states: [URL: Bool] = [:]
private var metadatas: [URL: VideoMetadata] = [:]
+ // MARK: Coordinator state
+ // Members representing the state of the coordinator itself
+
+ private var full_screen_mode: Bool = false {
+ didSet {
+ }
+ }
+
@Published var focused_model_id: UUID?
func toggle_should_mute_video(url: URL) {
@@ -46,6 +54,12 @@ final class DamusVideoCoordinator: ObservableObject {
func metadata(for url: URL) -> VideoMetadata? {
metadatas[url]
}
+
+ // MARK: - Additional interface to help with video coordination
+
+ func set_full_screen_mode(_ is_full_screen: Bool) {
+ full_screen_mode = is_full_screen
+ }
func size_for_url(_ url: URL) -> CGSize? {
metadatas[url]?.size