From 730ba1ea2ee3984af0d51814d96185ca7370bf32 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 11 Nov 2022 18:50:13 +0100 Subject: [PATCH] Improve open videos layouts --- Model/NavigationModel.swift | 3 + Model/OpenVideosModel.swift | 18 ++- Shared/Home/HistoryView.swift | 38 +++-- Shared/Home/HomeView.swift | 43 ++++-- Shared/Navigation/AppSidebarNavigation.swift | 7 + Shared/Navigation/ContentView.swift | 26 ++++ Shared/Views/OpenVideosButton.swift | 39 ++++++ Shared/Views/OpenVideosView.swift | 139 +++++++++---------- Yattee.xcodeproj/project.pbxproj | 10 +- 9 files changed, 216 insertions(+), 107 deletions(-) create mode 100644 Shared/Views/OpenVideosButton.swift diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index 08213a64..05e4436c 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -81,10 +81,13 @@ final class NavigationModel: ObservableObject { @Published var alert = Alert(title: Text("Error")) @Published var presentingAlert = false + @Published var presentingAlertInOpenVideos = false #if os(macOS) @Published var presentingAlertInVideoPlayer = false #endif + @Published var presentingFileImporter = false + static func openChannel( _ channel: Channel, player: PlayerModel, diff --git a/Model/OpenVideosModel.swift b/Model/OpenVideosModel.swift index ad88195e..dc6f5be4 100644 --- a/Model/OpenVideosModel.swift +++ b/Model/OpenVideosModel.swift @@ -6,6 +6,7 @@ import Logging #if canImport(UIKit) import UIKit #endif +import SwiftUI struct OpenVideosModel { enum PlaybackMode: String, CaseIterable { @@ -62,11 +63,20 @@ struct OpenVideosModel { return [] } - func openURLsFromClipboard(removeQueueItems: Bool = false, playbackMode: OpenVideosModel.PlaybackMode) { - openURLs(urlsFromClipboard, removeQueueItems: removeQueueItems, playbackMode: playbackMode) + func openURLsFromClipboard(removeQueueItems: Bool = false, playbackMode: OpenVideosModel.PlaybackMode = .playNow) { + if urlsFromClipboard.isEmpty { + NavigationModel.shared.alert = Alert(title: Text("Could not find any links to open in your clipboard")) + if NavigationModel.shared.presentingOpenVideos { + NavigationModel.shared.presentingAlertInOpenVideos = true + } else { + NavigationModel.shared.presentingAlert = true + } + } else { + openURLs(urlsFromClipboard, removeQueueItems: removeQueueItems, playbackMode: playbackMode) + } } - func openURLs(_ urls: [URL], removeQueueItems: Bool, playbackMode: OpenVideosModel.PlaybackMode) { + func openURLs(_ urls: [URL], removeQueueItems: Bool = false, playbackMode: OpenVideosModel.PlaybackMode = .playNow) { guard !urls.isEmpty else { return } @@ -98,7 +108,6 @@ struct OpenVideosModel { ) if playbackMode == .playNow || playbackMode == .shuffleAll { - player.show() #if os(iOS) if player.presentingPlayer { player.advanceToNextItem() @@ -108,6 +117,7 @@ struct OpenVideosModel { #else player.advanceToNextItem() #endif + player.show() } } diff --git a/Shared/Home/HistoryView.swift b/Shared/Home/HistoryView.swift index 18816f09..1969ad32 100644 --- a/Shared/Home/HistoryView.swift +++ b/Shared/Home/HistoryView.swift @@ -10,24 +10,34 @@ struct HistoryView: View { var body: some View { LazyVStack { - ForEach(visibleWatches, id: \.videoID) { watch in - PlayerQueueRow( - item: PlayerQueueItem.from(watch, video: player.historyVideo(watch.videoID)), - history: true - ) - .onAppear { - player.loadHistoryVideoDetails(watch.videoID) + if visibleWatches.isEmpty { + VStack(alignment: .center, spacing: 20) { + HStack { + Image(systemName: "clock") + Text("Playback history is empty") + + }.foregroundColor(.secondary) } - .contextMenu { - VideoContextMenuView(video: player.historyVideo(watch.videoID) ?? watch.video) + } else { + ForEach(visibleWatches, id: \.videoID) { watch in + PlayerQueueRow( + item: PlayerQueueItem.from(watch, video: player.historyVideo(watch.videoID)), + history: true + ) + .onAppear { + player.loadHistoryVideoDetails(watch.videoID) + } + .contextMenu { + VideoContextMenuView(video: player.historyVideo(watch.videoID) ?? watch.video) + } } } - #if os(tvOS) - .padding(.horizontal, 40) - #else - .padding(.horizontal, 15) - #endif } + #if os(tvOS) + .padding(.horizontal, 40) + #else + .padding(.horizontal, 15) + #endif } private var visibleWatches: [Watch] { diff --git a/Shared/Home/HomeView.swift b/Shared/Home/HomeView.swift index 3179802c..5157371f 100644 --- a/Shared/Home/HomeView.swift +++ b/Shared/Home/HomeView.swift @@ -24,6 +24,36 @@ struct HomeView: View { var body: some View { BrowserPlayerControls { ScrollView(.vertical, showsIndicators: false) { + HStack { + #if os(tvOS) + OpenVideosButton(text: "Open Video", imageSystemName: "globe") { + NavigationModel.shared.presentingOpenVideos = true + } + .frame(maxWidth: 600) + #else + OpenVideosButton(text: "Files", imageSystemName: "folder") { + NavigationModel.shared.presentingFileImporter = true + } + OpenVideosButton(text: "Paste", imageSystemName: "doc.on.clipboard.fill") { + OpenVideosModel.shared.openURLsFromClipboard(playbackMode: .playNow) + } + OpenVideosButton(imageSystemName: "ellipsis") { + NavigationModel.shared.presentingOpenVideos = true + } + .frame(maxWidth: 40) + #endif + } + #if os(iOS) + .padding(.top, RefreshControl.navigationBarTitleDisplayMode == .inline ? 15 : 0) + #else + .padding(.top, 15) + #endif + #if os(tvOS) + .padding(.horizontal, 40) + #else + .padding(.horizontal, 15) + #endif + if !accounts.current.isNil { #if os(tvOS) ForEach(Defaults[.favorites]) { item in @@ -60,18 +90,7 @@ struct HomeView: View { HistoryView(limit: homeHistoryItems) } - #if os(tvOS) - HStack { - Button { - navigation.presentingOpenVideos = true - } label: { - Label("Open Videos...", systemImage: "folder") - .padding(.horizontal, 20) - .padding(.vertical, 10) - } - .buttonStyle(.plain) - } - #else + #if !os(tvOS) Color.clear.padding(.bottom, 60) #endif } diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index f25acec6..6185ed04 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -120,3 +120,10 @@ struct AppSidebarNavigation: View { #endif } } + +struct AppSidebarNavigation_Preview: PreviewProvider { + static var previews: some View { + AppSidebarNavigation() + .injectFixtureEnvironmentObjects() + } +} diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 04b79ee1..fbef6c77 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -85,6 +85,32 @@ struct ContentView: View { } ) #if !os(tvOS) + .fileImporter( + isPresented: $navigation.presentingFileImporter, + allowedContentTypes: [.audiovisualContent], + allowsMultipleSelection: true + ) { result in + do { + let selectedFiles = try result.get() + let urlsToOpen = selectedFiles.map { url in + if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(url) { + return bookmarkURL + } + + if url.startAccessingSecurityScopedResource() { + URLBookmarkModel.shared.saveBookmark(url) + } + + return url + } + + OpenVideosModel.shared.openURLs(urlsToOpen) + } catch { + NavigationModel.shared.presentAlert(title: "Could not open Files") + } + + NavigationModel.shared.presentingOpenVideos = false + } .onOpenURL { OpenURLHandler( accounts: accounts, diff --git a/Shared/Views/OpenVideosButton.swift b/Shared/Views/OpenVideosButton.swift new file mode 100644 index 00000000..7728401c --- /dev/null +++ b/Shared/Views/OpenVideosButton.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct OpenVideosButton: View { + var text: String? + var imageSystemName: String? + var action: () -> Void = {} + + var body: some View { + Button(action: action) { + HStack { + if let imageSystemName { + Image(systemName: imageSystemName) + } + if let text { + Text(text ?? "") + .fontWeight(.bold) + } + } + .padding(.vertical, 10) + .frame(minHeight: 45) + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + } + .foregroundColor(.accentColor) + .buttonStyle(.plain) + .background(buttonBackground) + } + + var buttonBackground: some View { + RoundedRectangle(cornerRadius: 4) + .foregroundColor(Color.accentColor.opacity(0.33)) + } +} + +struct OpenVideosButton_Previews: PreviewProvider { + static var previews: some View { + OpenVideosButton(text: "Open Videos", imageSystemName: "play.circle.fill") + } +} diff --git a/Shared/Views/OpenVideosView.swift b/Shared/Views/OpenVideosView.swift index da8343fe..4c639d03 100644 --- a/Shared/Views/OpenVideosView.swift +++ b/Shared/Views/OpenVideosView.swift @@ -18,7 +18,7 @@ struct OpenVideosView: View { var body: some View { #if os(macOS) openVideos - .frame(minWidth: 600, maxWidth: 800, minHeight: 250) + .frame(minWidth: 600, maxWidth: 800, minHeight: 350, maxHeight: 500) #else NavigationView { openVideos @@ -44,48 +44,45 @@ struct OpenVideosView: View { VStack(alignment: .leading) { ZStack(alignment: .topLeading) { #if os(tvOS) - TextField("URLs to Open", text: $urlsToOpenText) + TextField("URL to Open", text: $urlsToOpenText) #else TextEditor(text: $urlsToOpenText) .padding(2) .border(Color(white: 0.8), width: 1) - .frame(minHeight: 100, maxHeight: 200) + .frame(minHeight: 100, maxHeight: 250) #if !os(macOS) .keyboardType(.URL) #endif #endif } - Text("Enter or paste URLs to open, one per line") - .font(.caption2) - .foregroundColor(.secondary) - - Menu { - Picker("Playback Mode", selection: $playbackMode) { - ForEach(OpenVideosModel.PlaybackMode.allCases, id: \.rawValue) { mode in - Text(mode.description).tag(mode) - } - } - } label: { - Text(playbackMode.description) + Group { + #if os(tvOS) + Text("Enter link to open") + #else + Text("Enter links to open, one per line") + #endif } - .transaction { t in t.disablesAnimations = true } - .labelsHidden() - .padding(.bottom, 5) - .frame(maxWidth: .infinity, alignment: .center) + .font(.caption2) + .foregroundColor(.secondary) + + playbackModeControl Toggle(isOn: $removeQueueItems) { - Text("Clear queue before opening") + Text("Clear Queue before opening") } .disabled(!playbackMode.allowsRemovingQueueItems) .padding(.bottom) HStack { Group { + #if os(tvOS) + Spacer() + #endif openURLsButton + Spacer() #if !os(tvOS) - Spacer() openFromClipboardButton #endif @@ -100,6 +97,7 @@ struct OpenVideosView: View { Spacer() } .padding() + .alert(isPresented: $navigation.presentingAlertInOpenVideos) { navigation.alert } #if !os(tvOS) .fileImporter( isPresented: $presentingFileImporter, @@ -122,7 +120,8 @@ struct OpenVideosView: View { openURLs(selectedFiles) } catch { - NavigationModel.shared.presentAlert(title: "Could not open Files") + NavigationModel.shared.alert = Alert(title: Text("Could not open Files")) + NavigationModel.shared.presentingAlertInOpenVideos = true } presentationMode.wrappedValue.dismiss() @@ -130,77 +129,64 @@ struct OpenVideosView: View { #endif } - var openURLsButton: some View { - Button { - openURLs(urlsToOpenFromText) - } label: { - HStack { - Image(systemName: "network") - Text("Open") - .fontWeight(.bold) - .padding(.vertical, 10) - } - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .padding(.horizontal, 20) + var playbackModeControl: some View { + HStack { + #if !os(tvOS) + Text("Playback Mode") + Spacer() + #endif + #if os(iOS) + Menu { + playbackModePicker + } label: { + Text(playbackMode.description) + } + #else + playbackModePicker + #if !os(tvOS) + .frame(maxWidth: 200) + #endif + #endif + } + .transaction { t in t.animation = .none } + .padding(.bottom, 5) + .frame(maxWidth: .infinity, alignment: .center) + } + + var playbackModePicker: some View { + Picker("Playback Mode", selection: $playbackMode) { + ForEach(OpenVideosModel.PlaybackMode.allCases, id: \.rawValue) { mode in + Text(mode.description).tag(mode) + } + } + .labelsHidden() + } + + var openURLsButton: some View { + OpenVideosButton(text: "Open", imageSystemName: "network") { + openURLs(urlsToOpenFromText) } - .foregroundColor(.accentColor) - .buttonStyle(.plain) - .background(buttonBackground) .disabled(urlsToOpenFromText.isEmpty) - #if !os(tvOS) + #if os(tvOS) + .frame(maxWidth: 600) + #else .keyboardShortcut(.defaultAction) #endif } var openFromClipboardButton: some View { - Button { + OpenVideosButton(text: "Paste", imageSystemName: "doc.on.clipboard.fill") { OpenVideosModel.shared.openURLsFromClipboard( removeQueueItems: removeQueueItems, playbackMode: playbackMode ) - } label: { - HStack { - Image(systemName: "doc.on.clipboard.fill") - Text("Paste") - .fontWeight(.bold) - .padding(.vertical, 10) - } - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .padding(.horizontal, 20) } - .foregroundColor(.accentColor) - .buttonStyle(.plain) - .background(buttonBackground) - #if !os(tvOS) - .keyboardShortcut(.defaultAction) - #endif } var openFilesButton: some View { - Button { + OpenVideosButton(text: "Open Files", imageSystemName: "folder") { presentingFileImporter = true - } label: { - HStack { - Image(systemName: "folder") - Text("Open Files") - - .fontWeight(.bold) - .padding(.vertical, 10) - } - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .padding(.horizontal, 20) } - .foregroundColor(.accentColor) - .buttonStyle(.plain) - .background(buttonBackground) - } - - var buttonBackground: some View { - RoundedRectangle(cornerRadius: 4) - .foregroundColor(Color.accentColor.opacity(0.33)) } var urlsToOpenFromText: [URL] { @@ -217,6 +203,7 @@ struct OpenVideosView: View { struct OpenVideosView_Previews: PreviewProvider { static var previews: some View { OpenVideosView() + .injectFixtureEnvironmentObjects() #if os(iOS) .navigationViewStyle(.stack) #endif diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index f971fed7..79ec5fed 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -395,6 +395,9 @@ 3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; 3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; 3763495226DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; + 37635FE4291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37635FE3291EA6CF00C11E79 /* OpenVideosButton.swift */; }; + 37635FE5291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37635FE3291EA6CF00C11E79 /* OpenVideosButton.swift */; }; + 37635FE6291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37635FE3291EA6CF00C11E79 /* OpenVideosButton.swift */; }; 3763C989290C7A50004D3B5F /* OpenVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763C988290C7A50004D3B5F /* OpenVideosView.swift */; }; 3763C98A290C7A50004D3B5F /* OpenVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763C988290C7A50004D3B5F /* OpenVideosView.swift */; }; 3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763C988290C7A50004D3B5F /* OpenVideosView.swift */; }; @@ -1138,6 +1141,7 @@ 375F740F289DC35A00747050 /* PlayerBackendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBackendView.swift; sourceTree = ""; }; 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarRecents.swift; sourceTree = ""; }; + 37635FE3291EA6CF00C11E79 /* OpenVideosButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenVideosButton.swift; sourceTree = ""; }; 3763C988290C7A50004D3B5F /* OpenVideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenVideosView.swift; sourceTree = ""; }; 37648B68286CF5F1003D330B /* TVControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVControls.swift; sourceTree = ""; }; 376527BA285F60F700102284 /* PlayerTimeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeModel.swift; sourceTree = ""; }; @@ -1688,6 +1692,8 @@ 37152EE926EFEB95004FB96D /* LazyView.swift */, 37030FF627B0347C00ECDDAA /* MPVPlayerView.swift */, 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */, + 37635FE3291EA6CF00C11E79 /* OpenVideosButton.swift */, + 3763C988290C7A50004D3B5F /* OpenVideosView.swift */, 37FEF11227EFD8580033912F /* PlaceholderCell.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */, @@ -1698,7 +1704,6 @@ 37AAF29F26741C97007FC770 /* SubscriptionsView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */, - 3763C988290C7A50004D3B5F /* OpenVideosView.swift */, ); path = Views; sourceTree = ""; @@ -2937,6 +2942,7 @@ 374C053B2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, 37A9965A26D6F8CA006E3224 /* HorizontalCells.swift in Sources */, 37EBD8CA27AF26C200F1C24B /* MPVBackend.swift in Sources */, + 37635FE4291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */, 37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */, 37484C2526FC83E000287258 /* InstanceForm.swift in Sources */, 37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */, @@ -3111,6 +3117,7 @@ 37030FFC27B0398000ECDDAA /* MPVClient.swift in Sources */, 3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */, 3782B9532755667600990149 /* String+Format.swift in Sources */, + 37635FE5291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */, 37E2EEAC270656EC00170416 /* BrowserPlayerControls.swift in Sources */, 37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */, 3776ADD7287381240078EBC4 /* Captions.swift in Sources */, @@ -3434,6 +3441,7 @@ 37F7D82E289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */, 374AB3DD28BCAF7E00DF56FB /* SeekType.swift in Sources */, 374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */, + 37635FE6291EA6CF00C11E79 /* OpenVideosButton.swift in Sources */, 377ABC42286E4AD5009C986F /* InstancesManifest.swift in Sources */, 37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */, 37EBD8CC27AF26C200F1C24B /* MPVBackend.swift in Sources */,