diff --git a/NativeYoutube.xcodeproj/project.pbxproj b/NativeYoutube.xcodeproj/project.pbxproj index 5635984..6e26e59 100644 --- a/NativeYoutube.xcodeproj/project.pbxproj +++ b/NativeYoutube.xcodeproj/project.pbxproj @@ -16,17 +16,16 @@ 133DCE5227ADF16600AE6F1F /* GeneralPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DCE5127ADF16600AE6F1F /* GeneralPreferenceView.swift */; }; 133DCE5627ADF29C00AE6F1F /* YoutubePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DCE5527ADF29C00AE6F1F /* YoutubePreferenceView.swift */; }; 138F4D1427ACFCBC00DF099D /* NativeYoutubeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138F4D1327ACFCBC00DF099D /* NativeYoutubeApp.swift */; }; - 139EF52F27BAC7950031BF1D /* URL+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139EF52E27BAC7950031BF1D /* URL+Extension.swift */; }; - 13E02D0A27ABB35E00B4A648 /* VideoPlayerControlsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E02D0927ABB35E00B4A648 /* VideoPlayerControlsViewModel.swift */; }; - 13E02D0C27ABD2FE00B4A648 /* String+Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E02D0B27ABD2FE00B4A648 /* String+Time.swift */; }; 13FA3CF127AF820D005555C3 /* VideoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FA3CF027AF820C005555C3 /* VideoRowView.swift */; }; 13FA3CF327AF82B2005555C3 /* VideoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FA3CF227AF82B2005555C3 /* VideoListView.swift */; }; - 13FA3CF527AF8B6B005555C3 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FA3CF427AF8B6B005555C3 /* Request.swift */; }; - C22D70B829557E71000DE71E /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = C22D70B729557E71000DE71E /* SDWebImageSwiftUI */; }; + 70A3843C2955B35E000941F5 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A3843B2955B35E000941F5 /* Enums.swift */; }; + 70A3843F2955D1A1000941F5 /* YouTubeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 70A3843E2955D1A1000941F5 /* YouTubeKit */; }; + 70A3844529561851000941F5 /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 70A3844429561851000941F5 /* SDWebImageSwiftUI */; }; + 70A3845E295644A2000941F5 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70A3845D295644A1000941F5 /* AVKit.framework */; }; + C21B533D2956630500DDCAD8 /* ThinBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21B533C2956630500DDCAD8 /* ThinBackground.swift */; }; C27DD350272C853E00B4DC16 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD34F272C853E00B4DC16 /* ContentView.swift */; }; C27DD352272C853F00B4DC16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C27DD351272C853F00B4DC16 /* Assets.xcassets */; }; C27DD355272C853F00B4DC16 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C27DD354272C853F00B4DC16 /* Preview Assets.xcassets */; }; - C27DD373272C8A8900B4DC16 /* Collections+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD372272C8A8900B4DC16 /* Collections+Extension.swift */; }; C27DD37C272C8CCF00B4DC16 /* PlayListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD37B272C8CCF00B4DC16 /* PlayListView.swift */; }; C27DD384272C8D9D00B4DC16 /* YoutubePreferenceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD383272C8D9D00B4DC16 /* YoutubePreferenceViewModel.swift */; }; C27DD386272C8DD100B4DC16 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD385272C8DD100B4DC16 /* Constants.swift */; }; @@ -38,15 +37,11 @@ C27DD3A8272CB48700B4DC16 /* CleanButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3A7272CB48700B4DC16 /* CleanButton.swift */; }; C27DD3AA272CB9FD00B4DC16 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3A9272CB9FD00B4DC16 /* View+Extension.swift */; }; C27DD3AC272CBA5800B4DC16 /* NSViewRepresentable+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3AB272CBA5800B4DC16 /* NSViewRepresentable+Extension.swift */; }; - C27DD3B1272CBBD500B4DC16 /* LogTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3B0272CBBD500B4DC16 /* LogTextView.swift */; }; + C27DD3B1272CBBD500B4DC16 /* LogPrefrenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3B0272CBBD500B4DC16 /* LogPrefrenceView.swift */; }; C27DD3B5272CE7A500B4DC16 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3B4272CE7A500B4DC16 /* WelcomeView.swift */; }; C27DD3B7272CE9DF00B4DC16 /* BottomBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27DD3B6272CE9DF00B4DC16 /* BottomBarView.swift */; }; - C2A7FC23274D78F0000D6D33 /* YouTubePlayerKit in Frameworks */ = {isa = PBXBuildFile; productRef = C2A7FC22274D78F0000D6D33 /* YouTubePlayerKit */; }; - C2A7FC25274D791D000D6D33 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A7FC24274D791D000D6D33 /* VideoPlayerView.swift */; }; - C2A7FC27274D79E6000D6D33 /* YoutubePlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A7FC26274D79E6000D6D33 /* YoutubePlayerViewModel.swift */; }; C2A7FC29274D808D000D6D33 /* VideoContextMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A7FC28274D808D000D6D33 /* VideoContextMenuView.swift */; }; C2A7FC2B274D8455000D6D33 /* PopupPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A7FC2A274D8455000D6D33 /* PopupPlayerView.swift */; }; - C2A7FC2D274D8572000D6D33 /* VideoPlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A7FC2C274D8572000D6D33 /* VideoPlayerControlsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -59,12 +54,11 @@ 133DCE5127ADF16600AE6F1F /* GeneralPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferenceView.swift; sourceTree = ""; }; 133DCE5527ADF29C00AE6F1F /* YoutubePreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubePreferenceView.swift; sourceTree = ""; }; 138F4D1327ACFCBC00DF099D /* NativeYoutubeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeYoutubeApp.swift; sourceTree = ""; }; - 139EF52E27BAC7950031BF1D /* URL+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extension.swift"; sourceTree = ""; }; - 13E02D0927ABB35E00B4A648 /* VideoPlayerControlsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerControlsViewModel.swift; sourceTree = ""; }; - 13E02D0B27ABD2FE00B4A648 /* String+Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Time.swift"; sourceTree = ""; }; 13FA3CF027AF820C005555C3 /* VideoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRowView.swift; sourceTree = ""; }; 13FA3CF227AF82B2005555C3 /* VideoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoListView.swift; sourceTree = ""; }; - 13FA3CF427AF8B6B005555C3 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 70A3843B2955B35E000941F5 /* Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; + 70A3845D295644A1000941F5 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; + C21B533C2956630500DDCAD8 /* ThinBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThinBackground.swift; sourceTree = ""; }; C27DD34A272C853E00B4DC16 /* NativeYoutube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeYoutube.app; sourceTree = BUILT_PRODUCTS_DIR; }; C27DD34F272C853E00B4DC16 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; C27DD351272C853F00B4DC16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -72,7 +66,6 @@ C27DD356272C853F00B4DC16 /* NativeYoutube.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NativeYoutube.entitlements; sourceTree = ""; }; C27DD36B272C899600B4DC16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; C27DD36F272C8A6200B4DC16 /* PlayListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListViewModel.swift; sourceTree = ""; }; - C27DD372272C8A8900B4DC16 /* Collections+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collections+Extension.swift"; sourceTree = ""; }; C27DD376272C8AB900B4DC16 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; C27DD37B272C8CCF00B4DC16 /* PlayListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayListView.swift; sourceTree = ""; }; C27DD37D272C8CD600B4DC16 /* SearchVideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchVideosView.swift; sourceTree = ""; }; @@ -82,14 +75,11 @@ C27DD3A7272CB48700B4DC16 /* CleanButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CleanButton.swift; sourceTree = ""; }; C27DD3A9272CB9FD00B4DC16 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; C27DD3AB272CBA5800B4DC16 /* NSViewRepresentable+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSViewRepresentable+Extension.swift"; sourceTree = ""; }; - C27DD3B0272CBBD500B4DC16 /* LogTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogTextView.swift; sourceTree = ""; }; + C27DD3B0272CBBD500B4DC16 /* LogPrefrenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogPrefrenceView.swift; sourceTree = ""; }; C27DD3B4272CE7A500B4DC16 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; C27DD3B6272CE9DF00B4DC16 /* BottomBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomBarView.swift; sourceTree = ""; }; - C2A7FC24274D791D000D6D33 /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; - C2A7FC26274D79E6000D6D33 /* YoutubePlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubePlayerViewModel.swift; sourceTree = ""; }; C2A7FC28274D808D000D6D33 /* VideoContextMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoContextMenuView.swift; sourceTree = ""; }; C2A7FC2A274D8455000D6D33 /* PopupPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupPlayerView.swift; sourceTree = ""; }; - C2A7FC2C274D8572000D6D33 /* VideoPlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerControlsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,9 +87,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C22D70B829557E71000DE71E /* SDWebImageSwiftUI in Frameworks */, - C2A7FC23274D78F0000D6D33 /* YouTubePlayerKit in Frameworks */, + 70A3845E295644A2000941F5 /* AVKit.framework in Frameworks */, + 70A3844529561851000941F5 /* SDWebImageSwiftUI in Frameworks */, C27DD38E272C93AD00B4DC16 /* SwiftyJSON in Frameworks */, + 70A3843F2955D1A1000941F5 /* YouTubeKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -111,7 +102,6 @@ children = ( 131A387027AF258600B67A1B /* SearchRequest.swift */, 131A387227AF259000B67A1B /* PlaylistsRequest.swift */, - 13FA3CF427AF8B6B005555C3 /* Request.swift */, ); path = API; sourceTree = ""; @@ -122,16 +112,33 @@ 133DCE4C27ADED6300AE6F1F /* PreferencesView.swift */, 133DCE5127ADF16600AE6F1F /* GeneralPreferenceView.swift */, 133DCE5527ADF29C00AE6F1F /* YoutubePreferenceView.swift */, - C27DD3B0272CBBD500B4DC16 /* LogTextView.swift */, + C27DD3B0272CBBD500B4DC16 /* LogPrefrenceView.swift */, ); path = PreferencesView; sourceTree = ""; }; + 70A3845C295644A1000941F5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 70A3845D295644A1000941F5 /* AVKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C21B533B2956593F00DDCAD8 /* ViewModifiers */ = { + isa = PBXGroup; + children = ( + C21B533C2956630500DDCAD8 /* ThinBackground.swift */, + ); + path = ViewModifiers; + sourceTree = ""; + }; C27DD341272C853E00B4DC16 = { isa = PBXGroup; children = ( C27DD34C272C853E00B4DC16 /* NativeYoutube */, C27DD34B272C853E00B4DC16 /* Products */, + 70A3845C295644A1000941F5 /* Frameworks */, ); sourceTree = ""; }; @@ -155,6 +162,7 @@ C27DD35D272C857300B4DC16 /* Helpers */, C27DD371272C8A7600B4DC16 /* Extensions */, C27DD351272C853F00B4DC16 /* Assets.xcassets */, + C21B533B2956593F00DDCAD8 /* ViewModifiers */, C27DD356272C853F00B4DC16 /* NativeYoutube.entitlements */, C27DD36B272C899600B4DC16 /* Info.plist */, C27DD353272C853F00B4DC16 /* Preview Content */, @@ -195,6 +203,7 @@ children = ( 131A386C27AF191200B67A1B /* VideoModel.swift */, C27DD385272C8DD100B4DC16 /* Constants.swift */, + 70A3843B2955B35E000941F5 /* Enums.swift */, ); path = Models; sourceTree = ""; @@ -202,11 +211,9 @@ C27DD35F272C857C00B4DC16 /* ViewModels */ = { isa = PBXGroup; children = ( - C2A7FC26274D79E6000D6D33 /* YoutubePlayerViewModel.swift */, C27DD36F272C8A6200B4DC16 /* PlayListViewModel.swift */, C27DD376272C8AB900B4DC16 /* SearchViewModel.swift */, C27DD383272C8D9D00B4DC16 /* YoutubePreferenceViewModel.swift */, - 13E02D0927ABB35E00B4A648 /* VideoPlayerControlsViewModel.swift */, 131A386A27AF02F400B67A1B /* AppStateViewModel.swift */, ); path = ViewModels; @@ -215,11 +222,8 @@ C27DD371272C8A7600B4DC16 /* Extensions */ = { isa = PBXGroup; children = ( - C27DD372272C8A8900B4DC16 /* Collections+Extension.swift */, C27DD3A9272CB9FD00B4DC16 /* View+Extension.swift */, C27DD3AB272CBA5800B4DC16 /* NSViewRepresentable+Extension.swift */, - 13E02D0B27ABD2FE00B4A648 /* String+Time.swift */, - 139EF52E27BAC7950031BF1D /* URL+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -230,12 +234,10 @@ C27DD3B4272CE7A500B4DC16 /* WelcomeView.swift */, C27DD3A7272CB48700B4DC16 /* CleanButton.swift */, C27DD3B6272CE9DF00B4DC16 /* BottomBarView.swift */, - C2A7FC24274D791D000D6D33 /* VideoPlayerView.swift */, C2A7FC28274D808D000D6D33 /* VideoContextMenuView.swift */, C2A7FC2A274D8455000D6D33 /* PopupPlayerView.swift */, - C2A7FC2C274D8572000D6D33 /* VideoPlayerControlsView.swift */, - 13FA3CF027AF820C005555C3 /* VideoRowView.swift */, 13FA3CF227AF82B2005555C3 /* VideoListView.swift */, + 13FA3CF027AF820C005555C3 /* VideoRowView.swift */, ); path = SharedViews; sourceTree = ""; @@ -274,8 +276,8 @@ name = NativeYoutube; packageProductDependencies = ( C27DD38D272C93AD00B4DC16 /* SwiftyJSON */, - C2A7FC22274D78F0000D6D33 /* YouTubePlayerKit */, - C22D70B729557E71000DE71E /* SDWebImageSwiftUI */, + 70A3843E2955D1A1000941F5 /* YouTubeKit */, + 70A3844429561851000941F5 /* SDWebImageSwiftUI */, ); productName = NativeYoutube; productReference = C27DD34A272C853E00B4DC16 /* NativeYoutube.app */; @@ -307,8 +309,8 @@ mainGroup = C27DD341272C853E00B4DC16; packageReferences = ( C27DD38C272C93AD00B4DC16 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, - C2A7FC21274D78F0000D6D33 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, - C22D70B629557E71000DE71E /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, + 70A3843D2955D1A1000941F5 /* XCRemoteSwiftPackageReference "YouTubeKit" */, + 70A3844329561850000941F5 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, ); productRefGroup = C27DD34B272C853E00B4DC16 /* Products */; projectDirPath = ""; @@ -338,10 +340,10 @@ files = ( 133DCE5227ADF16600AE6F1F /* GeneralPreferenceView.swift in Sources */, C27DD3A8272CB48700B4DC16 /* CleanButton.swift in Sources */, + C21B533D2956630500DDCAD8 /* ThinBackground.swift in Sources */, C27DD39D272CA96700B4DC16 /* DateConverter.swift in Sources */, C27DD3AC272CBA5800B4DC16 /* NSViewRepresentable+Extension.swift in Sources */, 131A387127AF258600B67A1B /* SearchRequest.swift in Sources */, - 13FA3CF527AF8B6B005555C3 /* Request.swift in Sources */, 133DCE4D27ADED6300AE6F1F /* PreferencesView.swift in Sources */, 132F7F5D27AE244E00D0F607 /* KeyWindow.swift in Sources */, C27DD39B272CA54A00B4DC16 /* SearchViewModel.swift in Sources */, @@ -352,24 +354,18 @@ C27DD39E272CAA1E00B4DC16 /* SearchVideosView.swift in Sources */, C27DD3B5272CE7A500B4DC16 /* WelcomeView.swift in Sources */, 13FA3CF327AF82B2005555C3 /* VideoListView.swift in Sources */, - C2A7FC27274D79E6000D6D33 /* YoutubePlayerViewModel.swift in Sources */, C27DD3B7272CE9DF00B4DC16 /* BottomBarView.swift in Sources */, C27DD350272C853E00B4DC16 /* ContentView.swift in Sources */, - C27DD373272C8A8900B4DC16 /* Collections+Extension.swift in Sources */, - 139EF52F27BAC7950031BF1D /* URL+Extension.swift in Sources */, C2A7FC29274D808D000D6D33 /* VideoContextMenuView.swift in Sources */, 138F4D1427ACFCBC00DF099D /* NativeYoutubeApp.swift in Sources */, C27DD386272C8DD100B4DC16 /* Constants.swift in Sources */, C27DD3AA272CB9FD00B4DC16 /* View+Extension.swift in Sources */, 131A387327AF259000B67A1B /* PlaylistsRequest.swift in Sources */, - 13E02D0A27ABB35E00B4A648 /* VideoPlayerControlsViewModel.swift in Sources */, - C27DD3B1272CBBD500B4DC16 /* LogTextView.swift in Sources */, + C27DD3B1272CBBD500B4DC16 /* LogPrefrenceView.swift in Sources */, C27DD384272C8D9D00B4DC16 /* YoutubePreferenceViewModel.swift in Sources */, - 13E02D0C27ABD2FE00B4A648 /* String+Time.swift in Sources */, - C2A7FC2D274D8572000D6D33 /* VideoPlayerControlsView.swift in Sources */, 133DCE5627ADF29C00AE6F1F /* YoutubePreferenceView.swift in Sources */, - C2A7FC25274D791D000D6D33 /* VideoPlayerView.swift in Sources */, 13FA3CF127AF820D005555C3 /* VideoRowView.swift in Sources */, + 70A3843C2955B35E000941F5 /* Enums.swift in Sources */, 131A386D27AF191200B67A1B /* VideoModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -506,7 +502,7 @@ CURRENT_PROJECT_VERSION = 10; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NativeYoutube/Preview Content\""; - DEVELOPMENT_TEAM = 4538W4A79B; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -534,12 +530,13 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = NativeYoutube/NativeYoutube.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 10; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NativeYoutube/Preview Content\""; - DEVELOPMENT_TEAM = 4538W4A79B; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -584,36 +581,41 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - C22D70B629557E71000DE71E /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { + 70A3843D2955D1A1000941F5 /* XCRemoteSwiftPackageReference "YouTubeKit" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI.git"; + repositoryURL = "https://github.com/alexeichhorn/YouTubeKit"; requirement = { - branch = master; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 0.1.6; }; }; - C27DD38C272C93AD00B4DC16 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { + 70A3844329561850000941F5 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; + repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.0.0; + minimumVersion = 2.0.0; }; }; - C2A7FC21274D78F0000D6D33 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */ = { + C27DD38C272C93AD00B4DC16 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SvenTiigi/YouTubePlayerKit.git"; + repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.1.9; + minimumVersion = 4.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - C22D70B729557E71000DE71E /* SDWebImageSwiftUI */ = { + 70A3843E2955D1A1000941F5 /* YouTubeKit */ = { + isa = XCSwiftPackageProductDependency; + package = 70A3843D2955D1A1000941F5 /* XCRemoteSwiftPackageReference "YouTubeKit" */; + productName = YouTubeKit; + }; + 70A3844429561851000941F5 /* SDWebImageSwiftUI */ = { isa = XCSwiftPackageProductDependency; - package = C22D70B629557E71000DE71E /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; + package = 70A3844329561850000941F5 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; productName = SDWebImageSwiftUI; }; C27DD38D272C93AD00B4DC16 /* SwiftyJSON */ = { @@ -621,11 +623,6 @@ package = C27DD38C272C93AD00B4DC16 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; - C2A7FC22274D78F0000D6D33 /* YouTubePlayerKit */ = { - isa = XCSwiftPackageProductDependency; - package = C2A7FC21274D78F0000D6D33 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */; - productName = YouTubePlayerKit; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = C27DD342272C853E00B4DC16 /* Project object */; diff --git a/NativeYoutube.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NativeYoutube.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c689bdc..e57652a 100644 --- a/NativeYoutube.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NativeYoutube.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -12,10 +12,10 @@ { "identity" : "sdwebimageswiftui", "kind" : "remoteSourceControl", - "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI.git", + "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI", "state" : { - "branch" : "master", - "revision" : "ed288667c909c89127ab1b690113a3e397af3098" + "revision" : "ed288667c909c89127ab1b690113a3e397af3098", + "version" : "2.2.1" } }, { @@ -28,12 +28,12 @@ } }, { - "identity" : "youtubeplayerkit", + "identity" : "youtubekit", "kind" : "remoteSourceControl", - "location" : "https://github.com/SvenTiigi/YouTubePlayerKit.git", + "location" : "https://github.com/alexeichhorn/YouTubeKit", "state" : { - "revision" : "1f78a2121f58b393bca54bc0a8da9e9b11cab198", - "version" : "1.1.9" + "revision" : "7dcbb57b39a58dd5c208c40665383de0934e1ce7", + "version" : "0.1.6" } } ], diff --git a/NativeYoutube.xcodeproj/project.xcworkspace/xcuserdata/aayushpokharel.xcuserdatad/UserInterfaceState.xcuserstate b/NativeYoutube.xcodeproj/project.xcworkspace/xcuserdata/aayushpokharel.xcuserdatad/UserInterfaceState.xcuserstate index c32f566..d2252de 100644 Binary files a/NativeYoutube.xcodeproj/project.xcworkspace/xcuserdata/aayushpokharel.xcuserdatad/UserInterfaceState.xcuserstate and b/NativeYoutube.xcodeproj/project.xcworkspace/xcuserdata/aayushpokharel.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/NativeYoutube/API/PlaylistsRequest.swift b/NativeYoutube/API/PlaylistsRequest.swift index b1ed134..2191b0d 100644 --- a/NativeYoutube/API/PlaylistsRequest.swift +++ b/NativeYoutube/API/PlaylistsRequest.swift @@ -8,7 +8,7 @@ import Foundation import SwiftyJSON -struct PlaylistsRequest: Request { +struct PlaylistsRequest { let apiKey: String let playListID: String let maxResults: String diff --git a/NativeYoutube/API/Request.swift b/NativeYoutube/API/Request.swift deleted file mode 100644 index 7894594..0000000 --- a/NativeYoutube/API/Request.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Request.swift -// NativeYoutube -// -// Created by Erik Bautista on 2/5/22. -// - -import Foundation -import SwiftyJSON - -protocol Request { - associatedtype T - - func createURL() -> URL? - static func parseJSON(json: JSON) -> [T] -} diff --git a/NativeYoutube/API/SearchRequest.swift b/NativeYoutube/API/SearchRequest.swift index 860287a..63931ff 100644 --- a/NativeYoutube/API/SearchRequest.swift +++ b/NativeYoutube/API/SearchRequest.swift @@ -8,7 +8,7 @@ import Foundation import SwiftyJSON -struct SearchRequest: Request { +struct SearchRequest { let apiKey: String let query: String let maxResults: String diff --git a/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/Contents.json b/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/Contents.json deleted file mode 100644 index 9718d69..0000000 --- a/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "StatusBarIcon.png", - "idiom" : "mac" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/StatusBarIcon.png b/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/StatusBarIcon.png deleted file mode 100644 index 94383f5..0000000 Binary files a/NativeYoutube/Assets.xcassets/StatusBarIcon.imageset/StatusBarIcon.png and /dev/null differ diff --git a/NativeYoutube/ContentView.swift b/NativeYoutube/ContentView.swift index 35bd996..5fa7f05 100644 --- a/NativeYoutube/ContentView.swift +++ b/NativeYoutube/ContentView.swift @@ -22,7 +22,7 @@ struct ContentView: View { } BottomBarView(currentPage: $currentPage) } - .frame(width: 380.0) + .frame(width: 360.0) } } diff --git a/NativeYoutube/Extensions/Collections+Extension.swift b/NativeYoutube/Extensions/Collections+Extension.swift deleted file mode 100644 index fb320e2..0000000 --- a/NativeYoutube/Extensions/Collections+Extension.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Collections+Extension.swift -// NativeYoutube -// -// Created by Aayush Pokharel on 2021-10-29. -// - -import Foundation - -extension Collection where Indices.Iterator.Element == Index { - subscript(safe index: Index) -> Iterator.Element? { - return indices.contains(index) ? self[index] : nil - } -} diff --git a/NativeYoutube/Extensions/String+Time.swift b/NativeYoutube/Extensions/String+Time.swift deleted file mode 100644 index 096d0c1..0000000 --- a/NativeYoutube/Extensions/String+Time.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// String+Time.swift -// NativeYoutube -// -// Created by Erik Bautista on 2/3/22. -// - -import Foundation - -extension String { - init(timeInterval: TimeInterval) { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .positional - formatter.zeroFormattingBehavior = .pad - - let hasHour = (timeInterval / (60 * 60)) > 1 - if hasHour { - formatter.allowedUnits = [.hour, .minute, .second] - } else { - formatter.allowedUnits = [.minute, .second] - } - - if let timeString = formatter.string(from: timeInterval) { - self.init(timeString) - } else { - self.init("0:00") - } - } -} diff --git a/NativeYoutube/Extensions/URL+Extension.swift b/NativeYoutube/Extensions/URL+Extension.swift deleted file mode 100644 index 11a9847..0000000 --- a/NativeYoutube/Extensions/URL+Extension.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// URL+Extension.swift -// NativeYoutube -// -// Created by Erik Bautista on 2/14/22. -// - -import Foundation - -extension URL { - var isDeeplink: Bool { - return scheme == "nativeyoutube" - } -} diff --git a/NativeYoutube/Extensions/View+Extension.swift b/NativeYoutube/Extensions/View+Extension.swift index 9d54783..f38fa58 100644 --- a/NativeYoutube/Extensions/View+Extension.swift +++ b/NativeYoutube/Extensions/View+Extension.swift @@ -9,36 +9,43 @@ import Cocoa import SwiftUI extension View { - private func newWindowInternal(with title: String, isTransparent: Bool = false) -> NSWindow { + private func newWindowInternal(with title: String, isTransparent: Bool = false, appState: AppStateViewModel? = nil) -> NSWindow { let window = KeyWindow( - contentRect: NSRect(x: 20, y: 20, width: 640, height: 360), + contentRect: NSRect(x: 20, y: 20, width: 480, height: 270), styleMask: [.titled, .closable, .resizable, .fullSizeContentView], backing: .buffered, defer: false ) + window.appState = appState window.makeKey() window.isReleasedWhenClosed = false window.title = title window.makeKeyAndOrderFront(self) window.level = .floating + window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary] // Failed attempt to make the window sticks at the right aspect ratio, keeping it in comment as it should work (see https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio) -// window.aspectRatio = NSMakeSize(16.0, 9.0) if isTransparent { window.backgroundColor = .clear window.isOpaque = false window.styleMask = [.hudWindow, .closable, .resizable] window.isMovableByWindowBackground = true - window.makeKeyAndOrderFront(self) } window.setIsVisible(true) return window } - func openNewWindow(with title: String = "New Window", isTransparent: Bool = false) { - let window = newWindowInternal(with: title, isTransparent: isTransparent) + func openNewWindow(with title: String = "New Window", isTransparent: Bool = false, appState: AppStateViewModel? = nil) { + let window = newWindowInternal(with: title, isTransparent: isTransparent, appState: appState) window.contentView = NSHostingView(rootView: self) NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(self) } } + +extension View { + func playVideo(url: URL, appState: AppStateViewModel) { + PopupPlayerView(appStateViewModel: appState, videoURL: url) + .openNewWindow(isTransparent: true, appState: appState) + } +} diff --git a/NativeYoutube/Helpers/KeyWindow.swift b/NativeYoutube/Helpers/KeyWindow.swift index 059d35f..4be63e7 100644 --- a/NativeYoutube/Helpers/KeyWindow.swift +++ b/NativeYoutube/Helpers/KeyWindow.swift @@ -8,6 +8,7 @@ import AppKit class KeyWindow: NSWindow { + var appState: AppStateViewModel? override var canBecomeKey: Bool { return true } @@ -18,6 +19,9 @@ class KeyWindow: NSWindow { extension KeyWindow { override func keyDown(with event: NSEvent) { if event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command && event.charactersIgnoringModifiers == "w" { + if self.appState != nil { + self.appState!.stopPlaying() + } close() return } else { diff --git a/NativeYoutube/Models/Constants.swift b/NativeYoutube/Models/Constants.swift index 77b1241..6bf034a 100644 --- a/NativeYoutube/Models/Constants.swift +++ b/NativeYoutube/Models/Constants.swift @@ -14,23 +14,9 @@ struct Constants { static let defaultPlaylistID: String = "PLFgquLnL59alKyN8i_z5Ofm_h0KthT072" } -enum StatusStates: String { - case starting = "Starting.." - case badOuath = "Incorrect Access Token" - case badClient = "Incorrect Client ID" - case badScopes = "BAD Scopes (Client ID / Access Token)" - case userValidating = "Validating User" - case userValidated = "User Validated" - case userLoading = "Loading User Data" - case userLoaded = "Got User Data" - case videoLoading = "Loading videos" - case videoLoaded = "View Has Been Loaded" -} - -enum AppStorageStrings: String { - case apiKey = "Api Key" - case playListID = "Playlist ID for your goto playlist" - -// iina plus works much better, this could potentially replace youtubeplayerkit - case useIINA = "Use IINA Plus" +enum AppStorageStrings { + static let apiKey = "Api Key" + static let playListID = "Playlist ID for your goto playlist" + static let videoClickBehaviour = "com.aayush.nativeyoutube.videoClickBehaviour" + static let useIINA = "com.aayush.nativeyoutube.useIINA" } diff --git a/NativeYoutube/Models/Enums.swift b/NativeYoutube/Models/Enums.swift new file mode 100644 index 0000000..2c133f5 --- /dev/null +++ b/NativeYoutube/Models/Enums.swift @@ -0,0 +1,18 @@ +// +// Enums.swift +// NativeYoutube +// +// Created by Adélaïde Sky on 23/12/2022. +// + +import Foundation + +// Didn't knew where to put it so i created this file lol + +// All behaviours available on double clicking a video element. +enum VideoClickBehaviour: String, CaseIterable { + case nothing = "Do Nothing" + case playVideo = "Play Video" + case openOnYoutube = "Open on Youtube" + case playInIINA = "Play Using IINA" +} diff --git a/NativeYoutube/Models/VideoModel.swift b/NativeYoutube/Models/VideoModel.swift index e99755a..6bda9a2 100644 --- a/NativeYoutube/Models/VideoModel.swift +++ b/NativeYoutube/Models/VideoModel.swift @@ -25,18 +25,6 @@ struct VideoModel { channelTitle: "OliviaRodrigoVEVO" ) - var cleanTitle: String { - var title = String(title.split(separator: "(")[0]) - - if title.split(separator: "-").count > 1 { - title = String(title.split(separator: "-")[1]) - } - if title.split(separator: ":").count > 1 { - title = String(title.split(separator: ":")[1]) - } - return title - } - enum VideoKind: String { case playlist = "youtube#playlistItem" case search = "youtube#searchResult" diff --git a/NativeYoutube/NativeYoutubeApp.swift b/NativeYoutube/NativeYoutubeApp.swift index c5c9e72..bf2c295 100644 --- a/NativeYoutube/NativeYoutubeApp.swift +++ b/NativeYoutube/NativeYoutubeApp.swift @@ -10,24 +10,14 @@ import SwiftUI @main struct NativeYoutubeApp: App { @StateObject private var appStateViewModel = AppStateViewModel() - @StateObject private var youtubePlayerViewModel = YoutubePlayerViewModel() @StateObject private var searchViewModel = SearchViewModel() var body: some Scene { - MenuBarExtra("Native Youtube", systemImage: "play.circle") { + MenuBarExtra("Native Youtube", systemImage: "play.rectangle.fill") { ContentView() .frame(width: 360, height: 512) .environmentObject(appStateViewModel) - .environmentObject(youtubePlayerViewModel) .environmentObject(searchViewModel) } .menuBarExtraStyle(WindowMenuBarExtraStyle()) - - Settings { - PreferencesView() - .padding(-12) - .frame(minWidth: 320, minHeight: 512) - .environmentObject(appStateViewModel) - } - .windowStyle(.hiddenTitleBar) } } diff --git a/NativeYoutube/ViewModels/AppStateViewModel.swift b/NativeYoutube/ViewModels/AppStateViewModel.swift index 4afe4fe..35a89e6 100644 --- a/NativeYoutube/ViewModels/AppStateViewModel.swift +++ b/NativeYoutube/ViewModels/AppStateViewModel.swift @@ -11,9 +11,10 @@ import SwiftUI class AppStateViewModel: ObservableObject { // MARK: Global values - @AppStorage(AppStorageStrings.apiKey.rawValue) var apiKey = Constants.defaultAPIKey - @AppStorage(AppStorageStrings.playListID.rawValue) var playListID = Constants.defaultPlaylistID - @AppStorage(AppStorageStrings.useIINA.rawValue) var useIINA: Bool = false + @AppStorage(AppStorageStrings.apiKey) var apiKey = Constants.defaultAPIKey + @AppStorage(AppStorageStrings.playListID) var playListID = Constants.defaultPlaylistID + @AppStorage(AppStorageStrings.useIINA) var useIINA: Bool = false + @AppStorage(AppStorageStrings.videoClickBehaviour) var vidClickBehaviour: VideoClickBehaviour = .playVideo // MARK: States @@ -21,24 +22,6 @@ class AppStateViewModel: ObservableObject { @Published var isPlaying: Bool = false @Published var currentlyPlaying: String = "" - func changePlayListID(for playlistURL: String) -> Bool { - // Using regex would be 100% better (this is a quick and dirty method) - var changed = false - let splitted = playlistURL.split(separator: "&") - for splitted in splitted { - if splitted.contains("list") { - let id = splitted.split(separator: "=") - if let idCount = id.last?.count { - if idCount > 6 { - playListID = String(id.last!) - changed = true - } - } - } - } - return changed - } - func addToLogs(for page: Pages, message: String) { logs.append("Log at: \(Date()), from \(page.rawValue), message => \(message)") } @@ -46,10 +29,12 @@ class AppStateViewModel: ObservableObject { func stopPlaying() { currentlyPlaying = "" isPlaying = false - Task { - let shellOutput = shell("killall IINA") - DispatchQueue.main.async { - self.logs.append(shellOutput) + if useIINA { + Task { + let shellOutput = shell("killall IINA") + DispatchQueue.main.async { + self.logs.append(shellOutput) + } } } } @@ -61,7 +46,7 @@ class AppStateViewModel: ObservableObject { } func playVideoIINA(url: URL, title: String) { -// togglePlaying(title) + togglePlaying(title) Task { DispatchQueue.main.async { self.logs.append(self.shell("open -a iina '\(url)'")) diff --git a/NativeYoutube/ViewModels/VideoPlayerControlsViewModel.swift b/NativeYoutube/ViewModels/VideoPlayerControlsViewModel.swift deleted file mode 100644 index 1443495..0000000 --- a/NativeYoutube/ViewModels/VideoPlayerControlsViewModel.swift +++ /dev/null @@ -1,276 +0,0 @@ -// -// VideoPlayerControlsViewModel.swift -// NativeYoutube -// -// Created by Erik Bautista on 2/3/22. -// - -import Combine -import Foundation -import YouTubePlayerKit - -class VideoPlayerControlsViewModel: ObservableObject { - enum Input { - case onAppear - case playVideo - case pauseVideo - case muteVideo - case unmuteVideo - case nextVideo - case prevVideo - case playbackRate(PlaybackRate) - case seeking(Bool) - case changingVolume(Bool) - } - - func apply(_ input: Input) { - switch input { - case .onAppear: - onAppearSubject.send() - case .playVideo: - playVideoSubject.send() - case .pauseVideo: - pauseVideoSubject.send() - case .muteVideo: - muteVideoSubject.send() - case .unmuteVideo: - umuteVideoSubject.send() - case .nextVideo: - nextVideoSubject.send() - case .prevVideo: - prevVideoSubject.send() - case .playbackRate(let rate): - playbackRateSubject.send(rate) - case .seeking(let seeking): - currentlySeeking = seeking - case .changingVolume(let changing): - currentlyChangingVolume = changing - } - } - - // Input Subjects - - private let onAppearSubject = PassthroughSubject() - private let playVideoSubject = PassthroughSubject() - private let pauseVideoSubject = PassthroughSubject() - private let muteVideoSubject = PassthroughSubject() - private let umuteVideoSubject = PassthroughSubject() - private let nextVideoSubject = PassthroughSubject() - private let prevVideoSubject = PassthroughSubject() - private let playbackRateSubject = PassthroughSubject() - - private var cancellables = Set() - - // Properties - - private let youtubePlayer: YouTubePlayer - private var currentlySeeking = false - private var currentlyChangingVolume = false - - @Published var playbackState: YouTubePlayer.PlaybackState = .unstarted - @Published var playbackRate: PlaybackRate = .normal - @Published var isMuted: Bool = false - @Published var volume: Double = 100 - @Published var seekbar: Double = 0 - @Published var duration: TimeInterval = 0.001 - - init(youtubePlayer: YouTubePlayer) { - self.youtubePlayer = youtubePlayer - bindInputs() - } -} - -// MARK: - View Model - -extension VideoPlayerControlsViewModel { - private func bindInputs() { - let appearSubject = onAppearSubject - .eraseToAnyPublisher() - .share() - - // Bind and observe playback state changes - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.playbackStatePublisher - } - .assign(to: \.playbackState, on: self) - .store(in: &cancellables) - - // Bind and observe playback rate changes - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.playbackRatePublisher - } - .map { $0 < 1.0 ? .slow : $0 == 1.0 ? .normal : .fast } - .assign(to: \.playbackRate, on: self) - .store(in: &cancellables) - - // Bind and observe mute states - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.isMutedPublisher() - } - .replaceError(with: false) - .assign(to: \.isMuted, on: self) - .store(in: &cancellables) - - // Bind and observe volume changes if user not changing it - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.volumePublisher() - } - .filter { [unowned self] _ in !self.currentlyChangingVolume } - .map { Double($0) } - .assign(to: \.volume, on: self) - .store(in: &cancellables) - - // Bind and observe duration changss - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.durationPublisher - } - .assign(to: \.duration, on: self) - .store(in: &cancellables) - - // Bind and observe seek changes if not seeking - - appearSubject - .flatMap { [youtubePlayer] in - youtubePlayer.currentTimePublisher() - } - .filter { [unowned self] _ in !self.currentlySeeking } - .assign(to: \.seekbar, on: self) - .store(in: &cancellables) - - // MARK: - User Input Changes - - // Bind and observe any user seek changes and handle the input - - appearSubject - .combineLatest($seekbar) - .map { $1 } - .filter { [unowned self] _ in self.currentlySeeking } - .removeDuplicates() - .sink(receiveValue: { [youtubePlayer] in youtubePlayer.seek(to: $0, allowSeekAhead: true) }) - .store(in: &cancellables) - - // Bind and observe any user volume changes and handle the input - - appearSubject - .flatMap { [unowned self] in self.$volume } - .filter { [unowned self] _ in self.currentlyChangingVolume } - .map { Int($0) } - .removeDuplicates() - .sink(receiveValue: { [youtubePlayer] in youtubePlayer.set(volume: $0) }) - .store(in: &cancellables) - - prevVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.previousVideo) - .store(in: &cancellables) - - playVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.play) - .store(in: &cancellables) - - pauseVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.pause) - .store(in: &cancellables) - - muteVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.mute) - .store(in: &cancellables) - - umuteVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.unmute) - .store(in: &cancellables) - - nextVideoSubject - .eraseToAnyPublisher() - .sink(receiveValue: youtubePlayer.nextVideo) - .store(in: &cancellables) - - playbackRateSubject - .eraseToAnyPublisher() - .removeDuplicates() - .map { ($0 == .slow) ? 0.5 : ($0 == .normal) ? 1.0 : 2.0 } - .sink(receiveValue: youtubePlayer.set(playbackRate:)) - .store(in: &cancellables) - } -} - -extension VideoPlayerControlsViewModel { - enum PlaybackRate: String { - case normal = "speedometer" - case fast = "hare" - case slow = "tortoise" - } -} - -extension YouTubePlayer { - // Allows to observe a value within a time frame. - func isMutedPublisher( - updateInterval: TimeInterval = 0.5 - ) -> AnyPublisher { - Just( - .init() - ) - .append( - Timer.publish( - every: updateInterval, - on: .main, - in: .common - ) - .autoconnect() - ) - .flatMap { _ in - Future { [weak self] promise in - self?.isMuted { result in - guard case .success(let muted) = result else { - return - } - promise(.success(muted)) - } - } - } - .removeDuplicates() - .eraseToAnyPublisher() - } - - func volumePublisher( - updateInterval: TimeInterval = 0.5 - ) -> AnyPublisher { - Just( - .init() - ) - .append( - Timer.publish( - every: updateInterval, - on: .main, - in: .common - ) - .autoconnect() - ) - .flatMap { _ in - Future { [weak self] promise in - self?.getVolume(completion: { result in - guard case .success(let volume) = result else { - return - } - promise(.success(volume)) - }) - } - } - .removeDuplicates() - .eraseToAnyPublisher() - } -} diff --git a/NativeYoutube/ViewModels/YoutubePlayerViewModel.swift b/NativeYoutube/ViewModels/YoutubePlayerViewModel.swift deleted file mode 100644 index f516213..0000000 --- a/NativeYoutube/ViewModels/YoutubePlayerViewModel.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// YoutubePlayerViewModel.swift -// NativeYoutube -// -// Created by Aayush Pokharel on 2021-11-23. -// - -import SwiftUI -import YouTubePlayerKit - -class YoutubePlayerViewModel: ObservableObject { - func playVideo(url: String) { - let videoPlayer = YouTubePlayer(source: YouTubePlayer.Source.url(url), configuration: YoutubePlayerViewModel.configuration) - PopupPlayerView(youtubePlayer: videoPlayer) - .openNewWindow(isTransparent: true) - } - - static let configuration = YouTubePlayer.Configuration( - isUserInteractionEnabled: false, - allowsPictureInPictureMediaPlayback: true, - autoPlay: true, - showControls: false, - keyboardControlsDisabled: false, - enableJavaScriptAPI: true, - loopEnabled: true, - showRelatedVideos: false - ) -} - -// MARK: - Example video data - -extension YoutubePlayerViewModel { - static let exampleVideo = YouTubePlayer( - source: .video(id: "LDU_Txk06tM"), - configuration: - YoutubePlayerViewModel.configuration - ) -} diff --git a/NativeYoutube/ViewModifiers/ThinBackground.swift b/NativeYoutube/ViewModifiers/ThinBackground.swift new file mode 100644 index 0000000..5733d13 --- /dev/null +++ b/NativeYoutube/ViewModifiers/ThinBackground.swift @@ -0,0 +1,27 @@ +// +// ThinBackground.swift +// NativeYoutube +// +// Created by Aayush Pokharel on 2022-12-23. +// + +import SwiftUI + +struct ThinRoundedBackground: ViewModifier { + let padding: CGFloat + let radius: CGFloat + let material: Material + + func body(content: Content) -> some View { + content + .padding(padding) + .background(material) + .cornerRadius(radius) + } +} + +extension View { + func thinRoundedBG(padding: CGFloat = 12, radius: CGFloat = 6, material: Material = .ultraThinMaterial) -> ModifiedContent { + return modifier(ThinRoundedBackground(padding: padding, radius: radius, material: material)) + } +} diff --git a/NativeYoutube/Views/PreferencesView/GeneralPreferenceView.swift b/NativeYoutube/Views/PreferencesView/GeneralPreferenceView.swift index a3ce82f..97e397b 100644 --- a/NativeYoutube/Views/PreferencesView/GeneralPreferenceView.swift +++ b/NativeYoutube/Views/PreferencesView/GeneralPreferenceView.swift @@ -6,28 +6,45 @@ // import SwiftUI +import YouTubeKit struct GeneralPreferenceView: View { @EnvironmentObject var appStateViewModel: AppStateViewModel var body: some View { VStack(alignment: .leading) { - Label("Custom Playlist ID", systemImage: "music.note.list") - .bold() + DisclosureGroup { + TextField("Playlist ID", text: $appStateViewModel.playListID) + .textFieldStyle(.plain) + .thinRoundedBG() + } label: { + Label("Custom Playlist ID", systemImage: "music.note.list") + .bold() + } + .thinRoundedBG() - TextField("Playlist ID", text: $appStateViewModel.playListID) - .textFieldStyle(.plain) - .padding(8) - .background(.ultraThinMaterial) - .cornerRadius(6) + Divider() + .opacity(0.5) - Toggle("Use IINA", isOn: $appStateViewModel.useIINA) - .toggleStyle(.switch) - .bold() + DisclosureGroup { + VStack { + SpacedToggle("Use IINA", $appStateViewModel.useIINA) + + Picker("Double Click to", selection: $appStateViewModel.vidClickBehaviour) { + ForEach(VideoClickBehaviour.allCases, id: \.self) { behaviour in + if behaviour != .playInIINA || appStateViewModel.useIINA { + Text(behaviour.rawValue).tag(behaviour) + } + } + } + } + .thinRoundedBG() + } label: { + Label("Player Settings", systemImage: "play.rectangle.on.rectangle.fill") + .bold() + } + .thinRoundedBG() } - .padding() - .background(.ultraThinMaterial) - .cornerRadius(6) } } @@ -36,3 +53,23 @@ struct GeneralView_Previews: PreviewProvider { GeneralPreferenceView() } } + +struct SpacedToggle: View { + let title: String + @Binding var binded: Bool + + init(_ title: String = "", _ isOn: Binding) { + self.title = title + self._binded = isOn + } + + var body: some View { + HStack { + Text(title) + Spacer() + Toggle("", isOn: $binded) + .toggleStyle(.switch) + .bold() + } + } +} diff --git a/NativeYoutube/Views/PreferencesView/LogPrefrenceView.swift b/NativeYoutube/Views/PreferencesView/LogPrefrenceView.swift new file mode 100644 index 0000000..32e3bb6 --- /dev/null +++ b/NativeYoutube/Views/PreferencesView/LogPrefrenceView.swift @@ -0,0 +1,79 @@ +// +// LogTextView.swift +// NativeYoutube +// +// Created by Aayush Pokharel on 2021-10-29. +// + +import SwiftUI + +struct LogPrefrenceView: View { + @EnvironmentObject var appStateViewModel: AppStateViewModel + @StateObject var youtubePreferenceViewModel: YoutubePreferenceViewModel = .init() + + var body: some View { + Group { + DisclosureGroup { + VStack(alignment: .leading) { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading) { + ForEach(appStateViewModel.logs, id: \.self) { log in + LogText(text: log, color: .gray) + } + } + } + } + } + label: { + HStack { + Label("Logs", systemImage: "newspaper.fill") + .bold() + .padding(.top, 5) + + Spacer() + + Label("Copy", systemImage: "clipboard.fill") + .labelStyle(.iconOnly) + .thinRoundedBG(padding: 8, material: .thinMaterial) + .clipShape(Circle()) + .onTapGesture { + youtubePreferenceViewModel.copyLogsToClipboard(redacted: true, appState: appStateViewModel) + } + .contextMenu { + VStack { + Button { + youtubePreferenceViewModel.copyLogsToClipboard(redacted: false, appState: appStateViewModel) + } label: { + Label("Copy Raw", systemImage: "key.radiowaves.forward.fill") + } + + Button { + appStateViewModel.logs = [] + } label: { + Label("Clear Logs", systemImage: "trash.fill") + } + } + } + } + } + } + .thinRoundedBG() + } +} + +struct LogText: View { + let text: String + var color: Color = .gray + + var body: some View { + Text(text) + .font(.caption2) + .foregroundColor(.gray) + } +} + +struct BoldTextView_Previews: PreviewProvider { + static var previews: some View { + LogText(text: "All streams are offline :(", color: .gray) + } +} diff --git a/NativeYoutube/Views/PreferencesView/LogTextView.swift b/NativeYoutube/Views/PreferencesView/LogTextView.swift deleted file mode 100644 index c6780ba..0000000 --- a/NativeYoutube/Views/PreferencesView/LogTextView.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// LogTextView.swift -// NativeYoutube -// -// Created by Aayush Pokharel on 2021-10-29. -// - -import SwiftUI - -struct LogText: View { - let text: String - var color: Color = .gray - - var body: some View { - Text(text) - .font(.caption2) - .foregroundColor(.gray) - } -} - -struct BoldTextView_Previews: PreviewProvider { - static var previews: some View { - LogText(text: "All streams are offline :(", color: .gray) - } -} diff --git a/NativeYoutube/Views/PreferencesView/PreferencesView.swift b/NativeYoutube/Views/PreferencesView/PreferencesView.swift index 0e0273c..4674c13 100644 --- a/NativeYoutube/Views/PreferencesView/PreferencesView.swift +++ b/NativeYoutube/Views/PreferencesView/PreferencesView.swift @@ -9,16 +9,19 @@ import SwiftUI struct PreferencesView: View { var body: some View { - VStack(alignment: .leading) { + ScrollView(.vertical, showsIndicators: false) { GeneralPreferenceView() Divider() - .opacity(0.25) + .opacity(0.5) YoutubePreferenceView() + + Divider() + .opacity(0.5) + + LogPrefrenceView() } - .padding(.horizontal) - .padding(.vertical, 6) } } diff --git a/NativeYoutube/Views/PreferencesView/YoutubePreferenceView.swift b/NativeYoutube/Views/PreferencesView/YoutubePreferenceView.swift index c11b842..c8c2afb 100644 --- a/NativeYoutube/Views/PreferencesView/YoutubePreferenceView.swift +++ b/NativeYoutube/Views/PreferencesView/YoutubePreferenceView.swift @@ -9,73 +9,34 @@ import SwiftUI struct YoutubePreferenceView: View { @EnvironmentObject var appStateViewModel: AppStateViewModel - @StateObject var youtubePreferenceViewModel = YoutubePreferenceViewModel() var body: some View { VStack(alignment: .leading) { - VStack(alignment: .leading) { - Label("Your Youtube API Key", systemImage: "person.badge.key.fill") - .bold() - - TextField("Your Google API Key", text: $appStateViewModel.apiKey) - .textFieldStyle(.plain) - .padding(8) - .background(.ultraThinMaterial) - .cornerRadius(6) - - Link( - destination: URL(string: Constants.demoYoutubeVideo)!, - label: { - HStack { - Spacer() - Label("How to get Google API Key?", systemImage: "globe") - Spacer() - } + Group { + DisclosureGroup { + VStack(alignment: .leading) { + TextField("Your Google API Key", text: $appStateViewModel.apiKey) + .textFieldStyle(.plain) + .thinRoundedBG() + + Link( + destination: URL(string: Constants.demoYoutubeVideo)!, + label: { + HStack { + Spacer() + Label("How to get Google API Key?", systemImage: "globe") + Spacer() + } + } + ) } - ) - } - .padding() - .background(.ultraThinMaterial) - .cornerRadius(6) - - Divider() - VStack(alignment: .leading) { - HStack { - Label("Logs", systemImage: "newspaper.fill") + } label: { + Label("Your Youtube API Key", systemImage: "person.badge.key.fill") .bold() - .padding(.top, 5) - - Spacer() - - Label("Copy", systemImage: "clipboard.fill") - .labelStyle(.iconOnly) - .padding(8) - .background(.thinMaterial) - .clipShape(Circle()) - .onTapGesture { - youtubePreferenceViewModel.copyLogsToClipboard(redacted: true, appState: appStateViewModel) - } - .contextMenu { - Button { - youtubePreferenceViewModel.copyLogsToClipboard(redacted: false, appState: appStateViewModel) - } label: { - Label("Copy Raw", systemImage: "key.radiowaves.forward.fill") - } - } - } - - ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading) { - ForEach(appStateViewModel.logs, id: \.self) { log in - LogText(text: log, color: .gray) - } - } } } - .padding() - .background(.ultraThinMaterial) - .cornerRadius(6) + .thinRoundedBG() } } } diff --git a/NativeYoutube/Views/SearchView/SearchVideosView.swift b/NativeYoutube/Views/SearchView/SearchVideosView.swift index 969deaa..73df350 100644 --- a/NativeYoutube/Views/SearchView/SearchVideosView.swift +++ b/NativeYoutube/Views/SearchView/SearchVideosView.swift @@ -8,7 +8,6 @@ import SwiftUI struct SearchView: View { - @EnvironmentObject var appStateViewModel: AppStateViewModel @EnvironmentObject var searchViewModel: SearchViewModel var body: some View { @@ -38,6 +37,6 @@ struct SearchView: View { struct SearchView_Previews: PreviewProvider { static var previews: some View { SearchView() - .environmentObject(AppStateViewModel()) + .environmentObject(SearchViewModel()) } } diff --git a/NativeYoutube/Views/SharedViews/BottomBarView.swift b/NativeYoutube/Views/SharedViews/BottomBarView.swift index c108983..f1444ba 100644 --- a/NativeYoutube/Views/SharedViews/BottomBarView.swift +++ b/NativeYoutube/Views/SharedViews/BottomBarView.swift @@ -15,67 +15,67 @@ struct BottomBarView: View { var body: some View { Group { HStack { - CleanButton( - page: .playlists, - image: "music.note.list", - binded: $currentPage - ) - CleanButton( - page: .search, - image: "magnifyingglass", - binded: $currentPage - ) + Group { + CleanButton( + page: .playlists, + image: "music.note.list", + binded: $currentPage + ) + CleanButton( + page: .search, + image: "magnifyingglass", + binded: $currentPage + ) + } + if currentPage == .search { - HStack { - TextField("Search..", text: $searchViewModel.searchQuery) - .textFieldStyle(.plain) - .padding(6) - .background(.ultraThinMaterial) - .cornerRadius(8) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(.gray.opacity(0.5), lineWidth: 1) - ) - .onSubmit { - searchViewModel.startSearch(apiKey: appStateViewModel.apiKey) - appStateViewModel.addToLogs(for: .search, message: "Searching for \($searchViewModel.searchQuery)") - } + Group { + HStack { + TextField("Search..", text: $searchViewModel.searchQuery) + .textFieldStyle(.plain) + .thinRoundedBG(padding: 6, radius: 6) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(.gray.opacity(0.5), lineWidth: 1) + ) + .onSubmit { + searchViewModel.startSearch(apiKey: appStateViewModel.apiKey) + appStateViewModel.addToLogs(for: .search, message: "Searching for \($searchViewModel.searchQuery)") + } + } + .transition(.offset(y: 120)) + .animation(.linear, value: currentPage == .search) } - .transition(.offset(y: 120)) - .animation(.linear, value: currentPage == .search) - } else { - if appStateViewModel.isPlaying { - ScrollView(.horizontal, showsIndicators: false) { - Text("\(appStateViewModel.currentlyPlaying)") - .font(.caption) - .foregroundStyle(.secondary) + Group { + if appStateViewModel.isPlaying { + ScrollView(.horizontal, showsIndicators: false) { + Text("\(appStateViewModel.currentlyPlaying)") + .font(.caption) + .foregroundStyle(.secondary) + } + .lineLimit(1) } - .lineLimit(1) - } - Spacer() - } - CleanButton( - page: .settings, - image: "gear", - binded: $currentPage - ) - .contextMenu { - Button { - NSApplication.shared.terminate(self) - } label: { - Label("Quit app", systemImage: "power") - .labelStyle(.titleAndIcon) + Spacer() + } + CleanButton( + page: .settings, + image: "gear", + binded: $currentPage + ) + .contextMenu { + Button { + NSApplication.shared.terminate(self) + } label: { + Label("Quit app", systemImage: "power") + .labelStyle(.titleAndIcon) + } } } } - .padding(.horizontal) - .padding(.vertical, 6) .labelStyle(.iconOnly) - .background(.ultraThinMaterial) - .cornerRadius(5) - .shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 5) + .thinRoundedBG(padding: 6, radius: 6) } } } diff --git a/NativeYoutube/Views/SharedViews/CleanButton.swift b/NativeYoutube/Views/SharedViews/CleanButton.swift index 0cc14a4..e64a5ab 100644 --- a/NativeYoutube/Views/SharedViews/CleanButton.swift +++ b/NativeYoutube/Views/SharedViews/CleanButton.swift @@ -24,9 +24,7 @@ struct CleanButton: View { .font(.callout) .foregroundColor(binded == page ? .red : .gray) } - .padding(6) - .background(.ultraThinMaterial) - .cornerRadius(8) + .thinRoundedBG(padding: 6, radius: 8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke( diff --git a/NativeYoutube/Views/SharedViews/PopupPlayerView.swift b/NativeYoutube/Views/SharedViews/PopupPlayerView.swift index dd796c1..ad616aa 100644 --- a/NativeYoutube/Views/SharedViews/PopupPlayerView.swift +++ b/NativeYoutube/Views/SharedViews/PopupPlayerView.swift @@ -5,35 +5,58 @@ // Created by Aayush Pokharel on 2021-11-23. // +import AVKit import SwiftUI -import YouTubePlayerKit +import YouTubeKit struct PopupPlayerView: View { - @StateObject var youtubePlayer: YouTubePlayer + @ObservedObject var appStateViewModel: AppStateViewModel + + let videoURL: URL + @State var player: AVPlayer? = nil @State var isHoveringOnPlayer = false var body: some View { ZStack(alignment: .topLeading) { VStack { - ZStack { - Rectangle() - .foregroundColor(.clear) - VideoPlayerView(youtubePlayer: youtubePlayer) - } - if isHoveringOnPlayer { - VideoPlayerControlsView(viewModel: .init(youtubePlayer: youtubePlayer)) - .padding(.horizontal) - .padding(.bottom, 5) + if player != nil { + VideoPlayer(player: player) + } else { + VStack { + ProgressView() + Text("\(appStateViewModel.currentlyPlaying)") + .frame(width: 420) + .multilineTextAlignment(.center) + .foregroundStyle(.tertiary) + .padding() + } + .padding() + .frame(width: 600, height: 400) + .background(.black) } } if isHoveringOnPlayer { PopUpPlayerCloseButton() .onTapGesture { + appStateViewModel.stopPlaying() NSApp.keyWindow?.close() } } } + .task { + let video = YouTube(url: videoURL) + do { + let streams = try await video.streams + let streamHQ = streams + .filter { $0.isProgressive } + .highestResolutionStream()?.url + if let streamHQ { + player = AVPlayer(url: streamHQ) + } + player?.play() + } catch {} + } .onHover { hovering in withAnimation { isHoveringOnPlayer = hovering @@ -41,7 +64,8 @@ struct PopupPlayerView: View { } .background(VisualEffectView(material: .popover, blendingMode: .behindWindow)) .cornerRadius(10) - .frame(minWidth: 480, minHeight: 270) + .frame(minWidth: 320, maxWidth: 1600, minHeight: 180, maxHeight: 900) + .aspectRatio(16/9, contentMode: .fit) } } @@ -60,9 +84,3 @@ struct PopUpPlayerCloseButton: View { .offset(x: 28/2, y: 28/2) } } - -struct PopupPlayerView_Previews: PreviewProvider { - static var previews: some View { - PopupPlayerView(youtubePlayer: YoutubePlayerViewModel.exampleVideo) - } -} diff --git a/NativeYoutube/Views/SharedViews/VideoContextMenuView.swift b/NativeYoutube/Views/SharedViews/VideoContextMenuView.swift index d6bdb87..7dfbd38 100644 --- a/NativeYoutube/Views/SharedViews/VideoContextMenuView.swift +++ b/NativeYoutube/Views/SharedViews/VideoContextMenuView.swift @@ -9,7 +9,6 @@ import SwiftUI struct VideoContextMenuView: View { @EnvironmentObject var appStateViewModel: AppStateViewModel - @EnvironmentObject var youtubePlayerViewModel: YoutubePlayerViewModel let video: VideoModel @@ -28,7 +27,8 @@ struct VideoContextMenuView: View { VStack { Button { appStateViewModel.togglePlaying(video.title) - youtubePlayerViewModel.playVideo(url: video.url.absoluteString) + playVideo(url: video.url, appState: appStateViewModel) + } label: { Label("Play Video", systemImage: "play.circle") } diff --git a/NativeYoutube/Views/SharedViews/VideoListView.swift b/NativeYoutube/Views/SharedViews/VideoListView.swift index dc7ca70..5621ffe 100644 --- a/NativeYoutube/Views/SharedViews/VideoListView.swift +++ b/NativeYoutube/Views/SharedViews/VideoListView.swift @@ -27,14 +27,26 @@ struct VideoListView: View { .foregroundStyle(.quaternary) } else { ScrollView(.vertical, showsIndicators: false) { - ForEach(videos, id: \.self.id) { vid in - VideoRowView(video: vid) + ForEach(videos, id: \.self.id) { video in + VideoRowView(video: video) .contextMenu(ContextMenu(menuItems: { - VideoContextMenuView(video: vid) + VideoContextMenuView(video: video) })) + .onTapGesture(count: 2) { + switch appStateViewModel.vidClickBehaviour { + case .nothing: + return + case .playVideo: + appStateViewModel.togglePlaying(video.title) + playVideo(url: video.url, appState: appStateViewModel) + case .openOnYoutube: + NSWorkspace.shared.open(video.url) + case .playInIINA: + appStateViewModel.playVideoIINA(url: video.url, title: video.title) + } + } } - .padding(.horizontal) - .padding(.top, 6) + .padding(6) } } } diff --git a/NativeYoutube/Views/SharedViews/VideoPlayerControlsView.swift b/NativeYoutube/Views/SharedViews/VideoPlayerControlsView.swift deleted file mode 100644 index 2d965c9..0000000 --- a/NativeYoutube/Views/SharedViews/VideoPlayerControlsView.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// VideoPlayerControlsView.swift -// NativeYoutube -// -// Created by Aayush Pokharel on 2021-11-23. -// - -import SwiftUI - -struct VideoPlayerControlsView: View { - @StateObject var viewModel: VideoPlayerControlsViewModel - - var body: some View { - HStack { - Group { - Button(action: { - viewModel.apply(.prevVideo) - }, label: { - Label("Previous video", systemImage: "backward.end") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - - switch viewModel.playbackState { - case .playing: - Button(action: { - viewModel.apply(.pauseVideo) - }, label: { - Label("Pause video", systemImage: "pause") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - - case .buffering: - ProgressView() - .controlSize(.small) - .padding(6) - - case .paused: - Button(action: { - viewModel.apply(.playVideo) - }, label: { - Label("Play video", systemImage: "play") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - - default: - Button(action: { - viewModel.apply(.playVideo) - }, label: { - Label("Play pause video", systemImage: "circle") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - } - - Button(action: { - viewModel.apply(.nextVideo) - }, label: { - Label("Next video", systemImage: "forward.end") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - } - - Spacer() - - Group { - Text(String(timeInterval: viewModel.seekbar)) - - Slider(value: $viewModel.seekbar, in: 0...viewModel.duration) { - viewModel.apply(.seeking($0)) - } - .controlSize(.mini) - - Text(String(timeInterval: viewModel.duration)) - } - - Spacer() - - Group { - Button(action: { - viewModel.isMuted ? viewModel.apply(.unmuteVideo) : viewModel.apply(.muteVideo) - }, label: { - Label("Toggle Mute", systemImage: viewModel.isMuted ? "speaker.slash" : "speaker.wave.3") - }) - .buttonStyle(VideoPlayerControlsButtonStyle()) - - if !viewModel.isMuted { - Slider(value: $viewModel.volume, in: 0...100) { - viewModel.apply(.changingVolume($0)) - } - .frame(width: 80) - .controlSize(.mini) - } - - Button(action: { - switch viewModel.playbackRate { - case .normal: - viewModel.apply(.playbackRate(.slow)) - case .fast: - viewModel.apply(.playbackRate(.normal)) - case .slow: - viewModel.apply(.playbackRate(.fast)) - } - }, label: { - Label("Change playback speed", systemImage: viewModel.playbackRate.rawValue) - }) - .frame(width: 26) - .buttonStyle(VideoPlayerControlsButtonStyle()) - } - } - .symbolVariant(.fill) - .labelStyle(.iconOnly) - .animation(.easeIn(duration: 0.25), value: viewModel.isMuted) - .onAppear { - viewModel.apply(.onAppear) - } - } -} - -struct VideoPlayerControlsView_Previews: PreviewProvider { - static var previews: some View { - VideoPlayerControlsView(viewModel: .init(youtubePlayer: YoutubePlayerViewModel.exampleVideo)) - } -} - -struct VideoPlayerControlsButtonStyle: ButtonStyle { - @State private var isHovering: Bool = false - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(6) - .background(isHovering ? .ultraThickMaterial : .ultraThinMaterial) - .cornerRadius(8) - .onHover { - isHovering = $0 - } - .animation(.easeIn(duration: 0.10), value: isHovering) - } -} diff --git a/NativeYoutube/Views/SharedViews/VideoPlayerView.swift b/NativeYoutube/Views/SharedViews/VideoPlayerView.swift deleted file mode 100644 index 1c60aec..0000000 --- a/NativeYoutube/Views/SharedViews/VideoPlayerView.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// VideoPlayerView.swift -// NativeYoutube -// -// Created by Aayush Pokharel on 2021-11-23. -// - -import SwiftUI -import YouTubePlayerKit - -struct VideoPlayerView: View { - let youtubePlayer: YouTubePlayer - var body: some View { - YouTubePlayerView(self.youtubePlayer) { state in - switch state { - case .idle: - ProgressView() - case .ready: - EmptyView() - case .error(let error): - Text(verbatim: "Youtube video player couldn't be loaded \(error)") - } - } - } -} - -struct VideoPlayerView_Previews: PreviewProvider { - static var previews: some View { - VideoPlayerView(youtubePlayer: YoutubePlayerViewModel.exampleVideo) - } -} diff --git a/NativeYoutube/Views/SharedViews/VideoRowView.swift b/NativeYoutube/Views/SharedViews/VideoRowView.swift index 6c7aa97..0f12415 100644 --- a/NativeYoutube/Views/SharedViews/VideoRowView.swift +++ b/NativeYoutube/Views/SharedViews/VideoRowView.swift @@ -33,7 +33,7 @@ struct VideoRowView: View { .shadow(radius: 6, x: 2) .padding(.leading, 4) .padding(.vertical, 2) - .transition(.offset(x: -128)) + .transition(.offset(x: -130)) } VStack(alignment: .leading, spacing: 2) { diff --git a/NativeYoutube/Views/SharedViews/WelcomeView.swift b/NativeYoutube/Views/SharedViews/WelcomeView.swift index d5b7c54..73068ae 100644 --- a/NativeYoutube/Views/SharedViews/WelcomeView.swift +++ b/NativeYoutube/Views/SharedViews/WelcomeView.swift @@ -31,9 +31,7 @@ struct WelcomeView: View { Text("Click Gear Icon") Image(systemName: "arrow.down") } - .padding(6) - .background(.ultraThinMaterial) - .cornerRadius(8) + .thinRoundedBG(padding: 4) .shadow(radius: 4) } .offset(y: jump ? -30 : -10)