diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index ca19997..94d1b5e 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -32,7 +32,7 @@ 0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */; }; 0C422E7E293542EA00486D65 /* PremiumizeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */; }; 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C422E7F293542F300486D65 /* PremiumizeModels.swift */; }; - 0C42B5962932F2D5008057A0 /* DebridChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C42B5952932F2D5008057A0 /* DebridChoiceView.swift */; }; + 0C42B5962932F2D5008057A0 /* DebridPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C42B5952932F2D5008057A0 /* DebridPickerView.swift */; }; 0C42B5982932F6DD008057A0 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C42B5972932F6DD008057A0 /* Set.swift */; }; 0C445C62293F9A0B0060744D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C445C61293F9A0B0060744D /* Bundle.swift */; }; 0C44E2A828D4DDDC007711AE /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44E2A728D4DDDC007711AE /* Application.swift */; }; @@ -44,6 +44,7 @@ 0C5005522992B6750064606A /* PluginTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5005512992B6750064606A /* PluginTagsView.swift */; }; 0C50055A2992BA6A0064606A /* PluginTag+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5005582992BA6A0064606A /* PluginTag+CoreDataClass.swift */; }; 0C50055B2992BA6A0064606A /* PluginTag+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5005592992BA6A0064606A /* PluginTag+CoreDataProperties.swift */; }; + 0C50B7D0299DF63C00A9FA3C /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */; }; 0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */; }; 0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */; }; 0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */; }; @@ -78,6 +79,7 @@ 0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */; }; 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */; }; 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */; }; + 0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.swift */; }; 0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */; }; 0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */; }; 0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsPluginListView.swift */; }; @@ -112,7 +114,6 @@ 0CAF9319296399190050812A /* PremiumizeCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAF9318296399190050812A /* PremiumizeCloudView.swift */; }; 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; }; 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; }; - 0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CB6516728C5A5EC00DCA721 /* Introspect */; }; 0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */; }; 0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */; }; 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; }; @@ -122,11 +123,16 @@ 0CC389542970AD900066D06F /* Action+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC389522970AD900066D06F /* Action+CoreDataProperties.swift */; }; 0CD4CAC628C980EB0046E1DC /* HistoryActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */; }; 0CD5E78928CD932B001BF684 /* DisabledAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */; }; + 0CD5F1FB299BEFBE00476DDB /* SearchAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD5F1FA299BEFBE00476DDB /* SearchAppearance.swift */; }; + 0CD5F1FD299C083B00476DDB /* PluginPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD5F1FC299C083B00476DDB /* PluginPickerView.swift */; }; 0CD72E17293D9928001A7EA4 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD72E16293D9928001A7EA4 /* Array.swift */; }; 0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */; }; 0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */ = {isa = PBXBuildFile; productRef = 0CDDDE042935235E006810B1 /* BetterSafariView */; }; 0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE1C4172981E8D700418F20 /* Plugin.swift */; }; 0CE66B3A28E640D200F69346 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE66B3928E640D200F69346 /* Backport.swift */; }; + 0CEC8AAC299B03E5007BFE8F /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CEC8AAB299B03E5007BFE8F /* Introspect */; }; + 0CEC8AAE299B31B6007BFE8F /* SearchFilterHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */; }; + 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -156,7 +162,7 @@ 0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModels.swift; sourceTree = ""; }; 0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeWrapper.swift; sourceTree = ""; }; 0C422E7F293542F300486D65 /* PremiumizeModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeModels.swift; sourceTree = ""; }; - 0C42B5952932F2D5008057A0 /* DebridChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridChoiceView.swift; sourceTree = ""; }; + 0C42B5952932F2D5008057A0 /* DebridPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridPickerView.swift; sourceTree = ""; }; 0C42B5972932F6DD008057A0 /* Set.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Set.swift; sourceTree = ""; }; 0C445C61293F9A0B0060744D /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 0C44E2A728D4DDDC007711AE /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; @@ -167,6 +173,7 @@ 0C5005512992B6750064606A /* PluginTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginTagsView.swift; sourceTree = ""; }; 0C5005582992BA6A0064606A /* PluginTag+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginTag+CoreDataClass.swift"; sourceTree = ""; }; 0C5005592992BA6A0064606A /* PluginTag+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginTag+CoreDataProperties.swift"; sourceTree = ""; }; + 0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = ""; }; 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = ""; }; 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = ""; }; @@ -197,6 +204,7 @@ 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataProperties.swift"; sourceTree = ""; }; 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataClass.swift"; sourceTree = ""; }; 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = ""; }; + 0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = ""; }; 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultActionsPickerViews.swift; sourceTree = ""; }; 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = ""; }; 0CA05456288EE58200850554 /* SettingsPluginListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPluginListView.swift; sourceTree = ""; }; @@ -241,10 +249,14 @@ 0CC6E4D428A45BA000AF2BCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryActionsView.swift; sourceTree = ""; }; 0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledAppearance.swift; sourceTree = ""; }; + 0CD5F1FA299BEFBE00476DDB /* SearchAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchAppearance.swift; sourceTree = ""; }; + 0CD5F1FC299C083B00476DDB /* PluginPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginPickerView.swift; sourceTree = ""; }; 0CD72E16293D9928001A7EA4 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; sourceTree = ""; }; 0CE1C4172981E8D700418F20 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; 0CE66B3928E640D200F69346 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; + 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFilterHeaderView.swift; sourceTree = ""; }; + 0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPickerView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -256,8 +268,8 @@ 0C64A4B4288903680079976D /* Base32 in Frameworks */, 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, 0C7376F028A97D1400D60918 /* SwiftUIX in Frameworks */, - 0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, + 0CEC8AAC299B03E5007BFE8F /* Introspect in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, 0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */, ); @@ -290,7 +302,7 @@ 0C0755C42934245800ECA142 /* Debrid */ = { isa = PBXGroup; children = ( - 0C42B5952932F2D5008057A0 /* DebridChoiceView.swift */, + 0C42B5952932F2D5008057A0 /* DebridPickerView.swift */, 0C0755C5293424A200ECA142 /* DebridLabelView.swift */, ); path = Debrid; @@ -360,6 +372,7 @@ 0C794B65289DAC9F00DD1CC8 /* Source */, 0C0D50E6288DFF850035ECC8 /* PluginListView.swift */, 0C5005512992B6750064606A /* PluginTagsView.swift */, + 0CD5F1FC299C083B00476DDB /* PluginPickerView.swift */, ); path = Plugin; sourceTree = ""; @@ -380,6 +393,7 @@ 0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */, 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */, 0CB6516428C5A5D700DCA721 /* InlinedList.swift */, + 0CD5F1FA299BEFBE00476DDB /* SearchAppearance.swift */, 0C572D4D299403B7003EEC05 /* ViewDidAppearModifier.swift */, ); path = Modifiers; @@ -399,6 +413,7 @@ children = ( 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */, 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */, + 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */, ); path = SearchResult; sourceTree = ""; @@ -459,6 +474,7 @@ 0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */, 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */, 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */, + 0C871BDE29994D9D005279AC /* FilterLabelView.swift */, 0CA148C1288903F000DE2211 /* NavView.swift */, 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */, 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */, @@ -491,6 +507,7 @@ 0C7ED14228D65518009E29AD /* FileManager.swift */, 0C42B5972932F6DD008057A0 /* Set.swift */, 0C7C128528DAA3CD00381CD1 /* URL.swift */, + 0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */, ); path = Extensions; sourceTree = ""; @@ -556,6 +573,7 @@ 0CA3B23628C2660700616D3A /* HistoryView.swift */, 0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */, 0C12D43C28CC332A000195BF /* HistoryButtonView.swift */, + 0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */, ); path = Library; sourceTree = ""; @@ -610,8 +628,8 @@ 0C4CFC452897030D00AD9FAD /* Regex */, 0C7376EF28A97D1400D60918 /* SwiftUIX */, 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */, - 0CB6516728C5A5EC00DCA721 /* Introspect */, 0CDDDE042935235E006810B1 /* BetterSafariView */, + 0CEC8AAB299B03E5007BFE8F /* Introspect */, ); productName = Torrenter; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; @@ -648,8 +666,8 @@ 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */, 0C7376EE28A97D1400D60918 /* XCRemoteSwiftPackageReference "SwiftUIX" */, 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, - 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */, + 0CEC8AAA299B03E5007BFE8F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; projectDirPath = ""; @@ -725,7 +743,7 @@ 0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */, 0C5005522992B6750064606A /* PluginTagsView.swift in Sources */, 0CE66B3A28E640D200F69346 /* Backport.swift in Sources */, - 0C42B5962932F2D5008057A0 /* DebridChoiceView.swift in Sources */, + 0C42B5962932F2D5008057A0 /* DebridPickerView.swift in Sources */, 0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */, 0C794B6B289DACF100DD1CC8 /* PluginCatalogButtonView.swift in Sources */, 0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */, @@ -735,9 +753,11 @@ 0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */, 0CA3B23728C2660700616D3A /* HistoryView.swift in Sources */, 0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */, + 0C50B7D0299DF63C00A9FA3C /* UIDevice.swift in Sources */, 0C0D50E7288DFF850035ECC8 /* PluginListView.swift in Sources */, 0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */, 0CD72E17293D9928001A7EA4 /* Array.swift in Sources */, + 0CD5F1FB299BEFBE00476DDB /* SearchAppearance.swift in Sources */, 0C44E2A828D4DDDC007711AE /* Application.swift in Sources */, 0CC389542970AD900066D06F /* Action+CoreDataProperties.swift in Sources */, 0C7ED14128D61BBA009E29AD /* BackupModels.swift in Sources */, @@ -789,6 +809,7 @@ 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */, 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, 0C50055B2992BA6A0064606A /* PluginTag+CoreDataProperties.swift in Sources */, + 0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */, 0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */, 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */, 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */, @@ -800,12 +821,15 @@ 0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */, 0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */, 0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */, + 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */, 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */, 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */, + 0CEC8AAE299B31B6007BFE8F /* SearchFilterHeaderView.swift in Sources */, 0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */, 0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */, 0C7C128628DAA3CD00381CD1 /* URL.swift in Sources */, 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */, + 0CD5F1FD299C083B00476DDB /* PluginPickerView.swift in Sources */, 0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */, 0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */, 0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */, @@ -1072,14 +1096,6 @@ minimumVersion = 2.0.0; }; }; - 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; - requirement = { - branch = master; - kind = branch; - }; - }; 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/stleamist/BetterSafariView"; @@ -1088,6 +1104,14 @@ kind = branch; }; }; + 0CEC8AAA299B03E5007BFE8F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1121,16 +1145,16 @@ package = 0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; - 0CB6516728C5A5EC00DCA721 /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = 0CB6516628C5A5EC00DCA721 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; 0CDDDE042935235E006810B1 /* BetterSafariView */ = { isa = XCSwiftPackageProductDependency; package = 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */; productName = BetterSafariView; }; + 0CEC8AAB299B03E5007BFE8F /* Introspect */ = { + isa = XCSwiftPackageProductDependency; + package = 0CEC8AAA299B03E5007BFE8F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = Introspect; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 9b92a58..a81f69c 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -18,6 +18,16 @@ public class RealDebrid { var authTask: Task? + @MainActor + func setUserDefaultsValue(_ value: Any, forKey: String) { + UserDefaults.standard.set(value, forKey: forKey) + } + + @MainActor + func removeUserDefaultsValue(forKey: String) { + UserDefaults.standard.removeObject(forKey: forKey) + } + // Fetches the device code from RD public func getVerificationInfo() async throws -> DeviceCodeResponse { var urlComponents = URLComponents(string: "\(baseAuthUrl)/device/code")! @@ -72,7 +82,7 @@ public class RealDebrid { // If there's a client ID from the response, end the task successfully if let clientId = rawResponse?.clientID, let clientSecret = rawResponse?.clientSecret { - UserDefaults.standard.set(clientId, forKey: "RealDebrid.ClientId") + await setUserDefaultsValue(clientId, forKey: "RealDebrid.ClientId") keychain.set(clientSecret, forKey: "RealDebrid.ClientSecret") try await getTokens(deviceCode: deviceCode) @@ -124,7 +134,7 @@ public class RealDebrid { keychain.set(rawResponse.refreshToken, forKey: "RealDebrid.RefreshToken") let accessTimestamp = Date().timeIntervalSince1970 + Double(rawResponse.expiresIn) - UserDefaults.standard.set(accessTimestamp, forKey: "RealDebrid.AccessTokenStamp") + await setUserDefaultsValue(accessTimestamp, forKey: "RealDebrid.AccessTokenStamp") } public func fetchToken() async -> String? { @@ -147,8 +157,8 @@ public class RealDebrid { public func deleteTokens() async throws { keychain.delete("RealDebrid.RefreshToken") keychain.delete("RealDebrid.ClientSecret") - UserDefaults.standard.removeObject(forKey: "RealDebrid.ClientId") - UserDefaults.standard.removeObject(forKey: "RealDebrid.AccessTokenStamp") + await removeUserDefaultsValue(forKey: "RealDebrid.ClientId") + await removeUserDefaultsValue(forKey: "RealDebrid.AccessTokenStamp") // Run the request, doesn't matter if it fails if let token = keychain.get("RealDebrid.AccessToken") { diff --git a/Ferrite/Extensions/NotificationCenter.swift b/Ferrite/Extensions/NotificationCenter.swift index 7bc0db5..63599a9 100644 --- a/Ferrite/Extensions/NotificationCenter.swift +++ b/Ferrite/Extensions/NotificationCenter.swift @@ -11,4 +11,8 @@ extension Notification.Name { static var didDeleteBookmark: Notification.Name { Notification.Name("Deleted bookmark") } + + static var didDeletePlugin: Notification.Name { + Notification.Name("Deleted plugin") + } } diff --git a/Ferrite/Extensions/UIDevice.swift b/Ferrite/Extensions/UIDevice.swift new file mode 100644 index 0000000..f2ae7e6 --- /dev/null +++ b/Ferrite/Extensions/UIDevice.swift @@ -0,0 +1,18 @@ +// +// UIDevice.swift +// Ferrite +// +// Created by Brian Dashore on 2/16/23. +// + +import SwiftUI + +extension UIDevice { + var hasNotch: Bool { + if #available(iOS 11.0, *) { + let keyWindow = UIApplication.shared.windows.filter(\.isKeyWindow).first + return keyWindow?.safeAreaInsets.bottom ?? 0 > 0 + } + return false + } +} diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index b7910aa..f92064c 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -9,30 +9,6 @@ import Introspect import SwiftUI extension View { - // MARK: Custom introspect functions - - func introspectCollectionView(customize: @escaping (UICollectionView) -> Void) -> some View { - inject(UIKitIntrospectionView( - selector: { introspectionView in - guard let viewHost = Introspect.findViewHost(from: introspectionView) else { - return nil - } - return Introspect.previousSibling(containing: UICollectionView.self, from: viewHost) - }, - customize: customize - )) - } - - // From https://github.com/siteline/SwiftUI-Introspect/pull/129 - public func introspectSearchController(customize: @escaping (UISearchController) -> Void) -> some View { - introspectNavigationController { navigationController in - let navigationBar = navigationController.navigationBar - if let searchController = navigationBar.topItem?.searchController { - customize(searchController) - } - } - } - // MARK: Modifiers func conditionalContextMenu(id: some Hashable, @@ -53,11 +29,19 @@ extension View { modifier(DisableInteraction(disabled: disabled)) } - func inlinedList() -> some View { - modifier(InlinedList()) + func inlinedList(inset: CGFloat) -> some View { + modifier(InlinedList(inset: inset)) } func viewDidAppear(_ callback: @escaping () -> Void) -> some View { modifier(ViewDidAppearModifier(callback: callback)) } + + func searchAppearance(_ content: Content) -> some View { + modifier(SearchAppearance(hostingContent: content)) + } + + func searchAppearance(_ content: @escaping () -> Content) -> some View { + modifier(SearchAppearance(hostingContent: content())) + } } diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index 41e6e4f..d3460e2 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -7,13 +7,6 @@ import SwiftUI -enum ViewTab { - case search - case plugins - case settings - case library -} - @MainActor class NavigationViewModel: ObservableObject { var toastModel: ToastViewModel? @@ -29,6 +22,24 @@ class NavigationViewModel: ObservableObject { case activity } + enum ViewTab { + case search + case plugins + case settings + case library + } + + enum LibraryPickerSegment { + case bookmarks + case history + case debridCloud + } + + enum PluginPickerSegment { + case sources + case actions + } + @Published var isEditingSearch: Bool = false @Published var isSearching: Bool = false @@ -57,10 +68,16 @@ class NavigationViewModel: ObservableObject { @Published var showSourceListEditor: Bool = false + @Published var libraryPickerSelection: LibraryPickerSegment = .bookmarks + @Published var pluginPickerSelection: PluginPickerSegment = .sources + @AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none @AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none + // TODO: Fix for new Actions API public func runDebridAction(urlString: String, _ action: DefaultDebridActionType? = nil) { + currentChoiceSheet = .magnet + /* let selectedAction = action ?? defaultDebridAction switch selectedAction { @@ -92,10 +109,14 @@ class NavigationViewModel: ObservableObject { toastModel?.updateToastDescription("Could not create object for sharing") } } + */ } + // TODO: Fix for new Actions API public func runMagnetAction(magnet: Magnet?, _ action: DefaultMagnetActionType? = nil) { + currentChoiceSheet = .magnet // Fall back to selected magnet if the provided magnet is nil + /* let magnet = magnet ?? selectedMagnet guard let magnetLink = magnet?.link else { toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.") @@ -125,5 +146,6 @@ class NavigationViewModel: ObservableObject { toastModel?.updateToastDescription("Could not create object for sharing") } } + */ } } diff --git a/Ferrite/ViewModels/PluginManager.swift b/Ferrite/ViewModels/PluginManager.swift index 02629c1..2bac4d6 100644 --- a/Ferrite/ViewModels/PluginManager.swift +++ b/Ferrite/ViewModels/PluginManager.swift @@ -87,7 +87,7 @@ public class PluginManager: ObservableObject { } print("Plugin fetch error: \(error)") - } + } } // Check if underlying type is Source or Action diff --git a/Ferrite/Views/CommonViews/Backport.swift b/Ferrite/Views/CommonViews/Backport.swift index 139897b..2c7221d 100644 --- a/Ferrite/Views/CommonViews/Backport.swift +++ b/Ferrite/Views/CommonViews/Backport.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Introspect public struct Backport { public let content: Content @@ -123,4 +124,17 @@ extension Backport where Content: View { } } } + + @ViewBuilder func introspectSearchController(customize: @escaping (UISearchController) -> ()) -> some View { + if #available(iOS 15, *) { + content.introspectSearchController(customize: customize) + } else { + content.introspectNavigationController { navigationController in + let navigationBar = navigationController.navigationBar + if let searchController = navigationBar.topItem?.searchController { + customize(searchController) + } + } + } + } } diff --git a/Ferrite/Views/CommonViews/FilterLabelView.swift b/Ferrite/Views/CommonViews/FilterLabelView.swift new file mode 100644 index 0000000..deb36a2 --- /dev/null +++ b/Ferrite/Views/CommonViews/FilterLabelView.swift @@ -0,0 +1,27 @@ +// +// FilterLabelView.swift +// Ferrite +// +// Created by Brian Dashore on 2/12/23. +// + +import SwiftUI + +struct FilterLabelView: View { + var name: String + + var body: some View { + HStack(spacing: 4) { + Text(name) + .opacity(0.6) + .foregroundColor(.primary) + + Image(systemName: "chevron.down") + .foregroundColor(.tertiaryLabel) + } + .padding(.horizontal, 9) + .padding(.vertical, 7) + .font(.caption, weight: .medium) + .background(Capsule().foregroundColor(.secondarySystemFill)) + } +} diff --git a/Ferrite/Views/CommonViews/LibraryHeaderView.swift b/Ferrite/Views/CommonViews/LibraryHeaderView.swift new file mode 100644 index 0000000..e6009e1 --- /dev/null +++ b/Ferrite/Views/CommonViews/LibraryHeaderView.swift @@ -0,0 +1,17 @@ +// +// LibraryHeaderView.swift +// Ferrite +// +// Created by Brian Dashore on 2/12/23. +// + +import SwiftUI + +struct LibraryHeaderView: View { + @EnvironmentObject var debridManager: DebridManager + + @Binding var selectedSegment: LibraryPickerSegment + var body: some View { + + } +} diff --git a/Ferrite/Views/CommonViews/Modifiers/InlinedList.swift b/Ferrite/Views/CommonViews/Modifiers/InlinedList.swift index 3e2cac2..4c0b2dd 100644 --- a/Ferrite/Views/CommonViews/Modifiers/InlinedList.swift +++ b/Ferrite/Views/CommonViews/Modifiers/InlinedList.swift @@ -12,16 +12,18 @@ import Introspect import SwiftUI struct InlinedList: ViewModifier { + let inset: CGFloat + func body(content: Content) -> some View { if #available(iOS 16, *) { content .introspectCollectionView { collectionView in - collectionView.contentInset.top = -20 + collectionView.contentInset.top = inset } } else { content .introspectTableView { tableView in - tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 20)) + tableView.contentInset.top = inset } } } diff --git a/Ferrite/Views/CommonViews/Modifiers/SearchAppearance.swift b/Ferrite/Views/CommonViews/Modifiers/SearchAppearance.swift new file mode 100644 index 0000000..22d232e --- /dev/null +++ b/Ferrite/Views/CommonViews/Modifiers/SearchAppearance.swift @@ -0,0 +1,63 @@ +// +// SearchAppearance.swift +// Ferrite +// +// Created by Brian Dashore on 2/14/23. +// + +import SwiftUI +import Introspect + +struct SearchAppearance: ViewModifier { + @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true + + let hostingContent: V + let hostingController: UIHostingController + + init(hostingContent: V) { + self.hostingContent = hostingContent + hostingController = UIHostingController(rootView: hostingContent) + } + + func body(content: Content) -> some View { + if #available(iOS 15, *) { + content + .backport.introspectSearchController { searchController in + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no + searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none + searchController.searchBar.showsScopeBar = true + searchController.searchBar.scopeButtonTitles = [""] + (searchController.searchBar.value(forKey: "_scopeBar") as? UIView)?.isHidden = true + + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + hostingController.view.backgroundColor = .clear + + guard let containerView = searchController.searchBar.value(forKey: "_scopeBarContainerView") as? UIView else { + return + } + containerView.addSubview(hostingController.view) + + NSLayoutConstraint.activate([ + hostingController.view.widthAnchor.constraint(equalTo: containerView.widthAnchor), + hostingController.view.topAnchor.constraint(equalTo: containerView.topAnchor), + hostingController.view.heightAnchor.constraint(equalTo: containerView.heightAnchor) + ]) + } + .introspectNavigationController { navigationController in + navigationController.navigationBar.prefersLargeTitles = true + navigationController.navigationBar.sizeToFit() + } + } else { + VStack { + hostingContent + content + Spacer() + } + .backport.introspectSearchController { searchController in + searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no + searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none + } + } + } +} diff --git a/Ferrite/Views/CommonViews/SearchableContent.swift b/Ferrite/Views/CommonViews/SearchableContent.swift new file mode 100644 index 0000000..754fa3a --- /dev/null +++ b/Ferrite/Views/CommonViews/SearchableContent.swift @@ -0,0 +1,37 @@ +// +// SearchableContent.swift +// Ferrite +// +// Created by Brian Dashore on 2/11/23. +// +// View to link animations together with searchbar +// Passes through geometry proxy and last height vars for any comparison +// + +import SwiftUI + +struct SearchableContent: View { + @Binding var searching: Bool + + @State private var lastHeight: CGFloat = 0.0 + + @ViewBuilder var content: (Bool) -> Content + + var body: some View { + GeometryReader { geom in + // Return if the height has changed as a closure variable for child transactions + content(geom.size.height != lastHeight) + .backport.onAppear { + lastHeight = geom.size.height + } + .onChange(of: geom.size.height) { newHeight in + lastHeight = newHeight + } + .transaction { + if geom.size.height != lastHeight && searching { + $0.animation = .default.speed(2) + } + } + } + } +} diff --git a/Ferrite/Views/CommonViews/SectionHeaderView.swift b/Ferrite/Views/CommonViews/SectionHeaderView.swift new file mode 100644 index 0000000..c46f60e --- /dev/null +++ b/Ferrite/Views/CommonViews/SectionHeaderView.swift @@ -0,0 +1,20 @@ +// +// SectionHeaderView.swift +// Ferrite +// +// Created by Brian Dashore on 2/15/23. +// + +import SwiftUI + +struct SectionHeaderView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct SectionHeaderView_Previews: PreviewProvider { + static var previews: some View { + SectionHeaderView() + } +} diff --git a/Ferrite/Views/CommonViews/TestHostingView.swift b/Ferrite/Views/CommonViews/TestHostingView.swift new file mode 100644 index 0000000..ebf4ae8 --- /dev/null +++ b/Ferrite/Views/CommonViews/TestHostingView.swift @@ -0,0 +1,75 @@ +// +// TestHostingView.swift +// Ferrite +// +// Created by Brian Dashore on 2/13/23. +// + +import SwiftUI + +struct TestHostingView: View { + @State private var textName = "First" + @State private var secondTextName = "First" + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + Menu { + Picker("", selection: $textName) { + Text("First").tag("First") + Text("Second").tag("Second") + Text("Third").tag("Third") + } + } label: { + HStack(spacing: 2) { + Text(textName) + .opacity(0.6) + .foregroundColor(.primary) + + Image(systemName: "chevron.down") + .foregroundColor(.tertiaryLabel) + } + .padding(.horizontal, 9) + .padding(.vertical, 7) + .font(.caption, weight: .bold) + .background(Capsule().foregroundColor(.secondarySystemFill)) + } + .id(textName) + .transaction { + $0.animation = .none + } + + Menu { + Picker("", selection: $secondTextName) { + Text("First").tag("First") + Text("Second").tag("Second") + Text("Third").tag("Third") + } + } label: { + HStack(spacing: 2) { + Text(secondTextName) + .opacity(0.6) + .foregroundColor(.primary) + + Image(systemName: "chevron.down") + .foregroundColor(.tertiaryLabel) + } + .padding(.horizontal, 9) + .padding(.vertical, 7) + .font(.caption, weight: .bold) + .background(Capsule().foregroundColor(.secondarySystemFill)) + } + .id(secondTextName) + .transaction { + $0.animation = .none + } + } + .padding(.horizontal, 18) + } + } +} + +struct TestHostingView_Previews: PreviewProvider { + static var previews: some View { + TestHostingView() + } +} diff --git a/Ferrite/Views/ComponentViews/Debrid/DebridChoiceView.swift b/Ferrite/Views/ComponentViews/Debrid/DebridPickerView.swift similarity index 72% rename from Ferrite/Views/ComponentViews/Debrid/DebridChoiceView.swift rename to Ferrite/Views/ComponentViews/Debrid/DebridPickerView.swift index f38ddf5..6aaf7f8 100644 --- a/Ferrite/Views/ComponentViews/Debrid/DebridChoiceView.swift +++ b/Ferrite/Views/ComponentViews/Debrid/DebridPickerView.swift @@ -7,15 +7,17 @@ import SwiftUI -struct DebridChoiceView: View { +struct DebridPickerView: View { @EnvironmentObject var debridManager: DebridManager + @ViewBuilder var label: Content + var body: some View { Menu { Picker("", selection: $debridManager.selectedDebridType) { Text("None") .tag(nil as DebridType?) - + ForEach(DebridType.allCases, id: \.self) { (debridType: DebridType) in if debridManager.enabledDebrids.contains(debridType) { Text(debridType.toString()) @@ -24,14 +26,7 @@ struct DebridChoiceView: View { } } } label: { - Text(debridManager.selectedDebridType?.toString(abbreviated: true) ?? "Debrid") + label } - .animation(.none) - } -} - -struct DebridChoiceView_Previews: PreviewProvider { - static var previews: some View { - DebridChoiceView() } } diff --git a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift index fdf8690..7668409 100644 --- a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift +++ b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift @@ -26,34 +26,33 @@ struct BookmarksView: View { sortDescriptors: [NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true)] ) { (bookmarks: FetchedResults) in List { - if !bookmarks.isEmpty { - ForEach(bookmarks, id: \.self) { bookmark in - SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark) - } - .onDelete { offsets in - for index in offsets { - if let bookmark = bookmarks[safe: index] { - PersistenceController.shared.delete(bookmark, context: backgroundContext) - - NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark) + if !bookmarks.isEmpty { + ForEach(bookmarks, id: \.self) { bookmark in + SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark) + } + .onDelete { offsets in + for index in offsets { + if let bookmark = bookmarks[safe: index] { + PersistenceController.shared.delete(bookmark, context: backgroundContext) + NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark) + } } } - } - .onMove { source, destination in - var changedBookmarks = bookmarks.map { $0 } + .onMove { source, destination in + var changedBookmarks = bookmarks.map { $0 } - changedBookmarks.move(fromOffsets: source, toOffset: destination) + changedBookmarks.move(fromOffsets: source, toOffset: destination) - for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) { - changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex) + for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) { + changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex) + } + + PersistenceController.shared.save() } - - PersistenceController.shared.save() } - } } - .inlinedList() .listStyle(.insetGrouped) + .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 15 : -25) .backport.onAppear { if debridManager.enabledDebrids.count > 0 { viewTask = Task { diff --git a/Ferrite/Views/ComponentViews/Library/LibraryPickerView.swift b/Ferrite/Views/ComponentViews/Library/LibraryPickerView.swift new file mode 100644 index 0000000..8c97e58 --- /dev/null +++ b/Ferrite/Views/ComponentViews/Library/LibraryPickerView.swift @@ -0,0 +1,31 @@ +// +// LibraryPickerView.swift +// Ferrite +// +// Created by Brian Dashore on 2/13/23. +// + +import SwiftUI + +struct LibraryPickerView: View { + @Environment(\.verticalSizeClass) var verticalSizeClass + + @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var navModel: NavigationViewModel + + var body: some View { + HStack { + Picker("Segments", selection: $navModel.libraryPickerSelection) { + Text("Bookmarks").tag(NavigationViewModel.LibraryPickerSegment.bookmarks) + Text("History").tag(NavigationViewModel.LibraryPickerSegment.history) + + if !debridManager.enabledDebrids.isEmpty { + Text("Cloud").tag(NavigationViewModel.LibraryPickerSegment.debridCloud) + } + } + } + .pickerStyle(.segmented) + .padding(.horizontal, verticalSizeClass == .compact && UIDevice.current.hasNotch ? 65 : 18) + .padding(.vertical, 5) + } +} diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift index b99061d..0efdce4 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift @@ -54,6 +54,7 @@ struct InstalledPluginButtonView: View { if #available(iOS 15.0, *) { Button(role: .destructive) { PersistenceController.shared.delete(installedPlugin, context: backgroundContext) + NotificationCenter.default.post(name: .didDeletePlugin, object: nil) } label: { Text("Remove") Image(systemName: "trash") @@ -61,6 +62,7 @@ struct InstalledPluginButtonView: View { } else { Button { PersistenceController.shared.delete(installedPlugin, context: backgroundContext) + NotificationCenter.default.post(name: .didDeletePlugin, object: nil) } label: { Text("Remove") Image(systemName: "trash") diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift index ad515b8..5b18abb 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginListView.swift @@ -57,6 +57,7 @@ struct PluginListView: View { } } } + .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 0 : -25) .listStyle(.insetGrouped) .sheet(isPresented: $navModel.showSourceSettings) { if String(describing: P.self) == "Source" { @@ -70,8 +71,10 @@ struct PluginListView: View { } .onChange(of: searchText) { _ in sourcePredicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText) + filteredAvailablePlugins = pluginManager.fetchFilteredPlugins(installedPlugins: installedPlugins, searchText: searchText) + filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins(installedPlugins: installedPlugins, searchText: searchText) } - .onReceive(installedPlugins.publisher.count()) { _ in + .onReceive(NotificationCenter.default.publisher(for: .didDeletePlugin)) { _ in filteredAvailablePlugins = pluginManager.fetchFilteredPlugins(installedPlugins: installedPlugins, searchText: searchText) filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins(installedPlugins: installedPlugins, searchText: searchText) } diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginPickerView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginPickerView.swift new file mode 100644 index 0000000..bb02771 --- /dev/null +++ b/Ferrite/Views/ComponentViews/Plugin/PluginPickerView.swift @@ -0,0 +1,27 @@ +// +// PluginPickerView.swift +// Ferrite +// +// Created by Brian Dashore on 2/14/23. +// + +import SwiftUI + +struct PluginPickerView: View { + @Environment(\.verticalSizeClass) var verticalSizeClass + + @EnvironmentObject var navModel: NavigationViewModel + + @State private var textName = "" + @State private var secondTextName = "" + + var body: some View { + Picker("Segments", selection: $navModel.pluginPickerSelection) { + Text("Sources").tag(NavigationViewModel.PluginPickerSegment.sources) + Text("Actions").tag(NavigationViewModel.PluginPickerSegment.actions) + } + .pickerStyle(.segmented) + .padding(.horizontal, verticalSizeClass == .compact && UIDevice.current.hasNotch ? 65 : 18) + .padding(.vertical, 5) + } +} diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift new file mode 100644 index 0000000..8834060 --- /dev/null +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift @@ -0,0 +1,49 @@ +// +// SearchFilterHeaderView.swift +// Ferrite +// +// Created by Brian Dashore on 2/13/23. +// + +import SwiftUI + +struct SearchFilterHeaderView: View { + @Environment(\.verticalSizeClass) var verticalSizeClass + + @EnvironmentObject var scrapingModel: ScrapingViewModel + @EnvironmentObject var debridManager: DebridManager + + @FetchRequest( + entity: Source.entity(), + sortDescriptors: [] + ) var sources: FetchedResults + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 6) { + Menu { + Picker("", selection: $scrapingModel.filteredSource) { + Text("None").tag(nil as Source?) + + ForEach(sources, id: \.self) { source in + if let name = source.name, source.enabled { + Text(name) + .tag(Source?.some(source)) + } + } + } + } label: { + FilterLabelView(name: scrapingModel.filteredSource?.name ?? "Source") + } + .id(scrapingModel.filteredSource) + + DebridPickerView() { + FilterLabelView(name: debridManager.selectedDebridType?.toString() ?? "Debrid") + } + .id(debridManager.selectedDebridType) + } + .padding(.horizontal, verticalSizeClass == .compact ? (Application.shared.osVersion.majorVersion > 14 ? 65 : 18) : 18) + .padding(.top, Application.shared.osVersion.majorVersion > 14 ? 0 : 10) + } + } +} diff --git a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift index 8142845..b6488b7 100644 --- a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift +++ b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift @@ -44,7 +44,7 @@ struct BackupsView: View { } } } - .inlinedList() + .inlinedList(inset: -20) .listStyle(.insetGrouped) } } diff --git a/Ferrite/Views/ComponentViews/Settings/DefaultActionsPickerViews.swift b/Ferrite/Views/ComponentViews/Settings/DefaultActionsPickerViews.swift index c9c069c..ab7b52c 100644 --- a/Ferrite/Views/ComponentViews/Settings/DefaultActionsPickerViews.swift +++ b/Ferrite/Views/ComponentViews/Settings/DefaultActionsPickerViews.swift @@ -29,7 +29,7 @@ struct MagnetActionPickerView: View { } } .listStyle(.insetGrouped) - .inlinedList() + .inlinedList(inset: -20) .navigationTitle("Default magnet action") .navigationBarTitleDisplayMode(.inline) } @@ -68,7 +68,7 @@ struct DebridActionPickerView: View { } } .listStyle(.insetGrouped) - .inlinedList() + .inlinedList(inset: -20) .navigationTitle("Default debrid action") .navigationBarTitleDisplayMode(.inline) } diff --git a/Ferrite/Views/ComponentViews/Settings/SettingsPluginListView.swift b/Ferrite/Views/ComponentViews/Settings/SettingsPluginListView.swift index 4abd6ed..1f23b57 100644 --- a/Ferrite/Views/ComponentViews/Settings/SettingsPluginListView.swift +++ b/Ferrite/Views/ComponentViews/Settings/SettingsPluginListView.swift @@ -73,7 +73,7 @@ struct SettingsPluginListView: View { } } .listStyle(.insetGrouped) - .inlinedList() + .inlinedList(inset: -20) } } .sheet(isPresented: $presentSourceSheet) { diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 88e1ba8..56ea9e6 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -14,118 +14,56 @@ struct ContentView: View { @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var pluginManager: PluginManager - @FetchRequest( - entity: Source.entity(), - sortDescriptors: [] - ) var sources: FetchedResults - - @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true - - @State private var selectedSource: Source? { - didSet { - scrapingModel.filteredSource = selectedSource - } - } - var body: some View { NavView { - VStack(spacing: 10) { - HStack(spacing: 6) { - Text("Filter") - .foregroundColor(.secondary) + SearchResultsView() + .listStyle(.insetGrouped) + .navigationTitle("Search") + .navigationSearchBar { + SearchBar("Search", + text: $scrapingModel.searchText, + isEditing: $navModel.isEditingSearch, + onCommit: { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask = Task { + navModel.isSearching = true + navModel.showSearchProgress = true - Menu { - Button { - selectedSource = nil - } label: { - Text("None") + let sources = pluginManager.fetchInstalledSources() + await scrapingModel.scanSources(sources: sources) - if selectedSource == nil { - Image(systemName: "checkmark") - } - } + if debridManager.enabledDebrids.count > 0, !scrapingModel.searchResults.isEmpty { + debridManager.clearIAValues() - ForEach(sources, id: \.self) { source in - if let name = source.name, source.enabled { - Button { - selectedSource = source - } label: { - if selectedSource == source { - Label(name, systemImage: "checkmark") - } else { - Text(name) - } - } - } - } - } label: { - Text(selectedSource?.name ?? "Source") - .padding(.trailing, -3) - Image(systemName: "chevron.down") - } - .foregroundColor(.primary) - .animation(.none) - - Spacer() - } - .padding(.vertical, 5) - .padding(.horizontal, 20) - - SearchResultsView() - } - .navigationTitle("Search") - .navigationBarTitleDisplayMode( - navModel.isSearching && Application.shared.osVersion.majorVersion > 14 ? .inline : .large - ) - .navigationSearchBar { - SearchBar("Search", - text: $scrapingModel.searchText, - isEditing: $navModel.isEditingSearch, - onCommit: { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask = Task { - navModel.isSearching = true - navModel.showSearchProgress = true - - let sources = pluginManager.fetchInstalledSources() - await scrapingModel.scanSources(sources: sources) - - if debridManager.enabledDebrids.count > 0, !scrapingModel.searchResults.isEmpty { - debridManager.clearIAValues() - - // Remove magnets that don't have a hash - let magnets = scrapingModel.searchResults.compactMap { - if let magnetHash = $0.magnet.hash { - return Magnet(hash: magnetHash, link: $0.magnet.link) - } else { - return nil + // Remove magnets that don't have a hash + let magnets = scrapingModel.searchResults.compactMap { + if let magnetHash = $0.magnet.hash { + return Magnet(hash: magnetHash, link: $0.magnet.link) + } else { + return nil + } } + await debridManager.populateDebridIA(magnets) } - await debridManager.populateDebridIA(magnets) - } - navModel.showSearchProgress = false + navModel.showSearchProgress = false + } + }) + .showsCancelButton(navModel.isEditingSearch || navModel.isSearching) + .onCancel { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask?.cancel() + scrapingModel.runningSearchTask = nil + navModel.isSearching = false + scrapingModel.searchText = "" } - }) - .showsCancelButton(navModel.isEditingSearch || navModel.isSearching) - .onCancel { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask?.cancel() - scrapingModel.runningSearchTask = nil - navModel.isSearching = false - scrapingModel.searchText = "" - } - } - .introspectSearchController { searchController in - searchController.hidesNavigationBarDuringPresentation = false - searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no - searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none - } - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - DebridChoiceView() } - } + .navigationSearchBarHiddenWhenScrolling(false) + .searchAppearance { + SearchFilterHeaderView() + .environmentObject(scrapingModel) + .environmentObject(debridManager) + } } } } diff --git a/Ferrite/Views/LibraryView.swift b/Ferrite/Views/LibraryView.swift index 3c494c1..eaa027f 100644 --- a/Ferrite/Views/LibraryView.swift +++ b/Ferrite/Views/LibraryView.swift @@ -9,14 +9,8 @@ import SwiftUI import SwiftUIX struct LibraryView: View { - enum LibraryPickerSegment { - case bookmarks - case history - case debridCloud - } - - @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var navModel: NavigationViewModel @FetchRequest( entity: Bookmark.entity(), @@ -32,7 +26,6 @@ struct LibraryView: View { @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true - @State private var selectedSegment: LibraryPickerSegment = .bookmarks @State private var editMode: EditMode = .inactive @State private var searchText: String = "" @@ -41,20 +34,8 @@ struct LibraryView: View { var body: some View { NavView { - VStack { - Picker("Segments", selection: $selectedSegment) { - Text("Bookmarks").tag(LibraryPickerSegment.bookmarks) - Text("History").tag(LibraryPickerSegment.history) - - if !debridManager.enabledDebrids.isEmpty { - Text("Cloud").tag(LibraryPickerSegment.debridCloud) - } - } - .pickerStyle(.segmented) - .padding(.horizontal) - .padding(.vertical, 5) - - switch selectedSegment { + ZStack { + switch navModel.libraryPickerSelection { case .bookmarks: BookmarksView(searchText: $searchText) case .history: @@ -62,8 +43,27 @@ struct LibraryView: View { case .debridCloud: DebridCloudView(searchText: $searchText) } + } + .navigationTitle("Library") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack(spacing: Application.shared.osVersion.majorVersion > 14 ? 10 : 18) { + Spacer() + EditButton() - Spacer() + switch navModel.libraryPickerSelection { + case .bookmarks, .debridCloud: + DebridPickerView() { + Text(debridManager.selectedDebridType?.toString(abbreviated: true) ?? "Debrid") + } + .transaction { + $0.animation = .none + } + case .history: + HistoryActionsView() + } + } + } } .navigationSearchBar { SearchBar("Search", text: $searchText, isEditing: $isEditingSearch, onCommit: { @@ -75,46 +75,31 @@ struct LibraryView: View { isSearching = false } } - .introspectSearchController { searchController in - searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no - searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none - } - .overlay { - switch selectedSegment { - case .bookmarks: - if bookmarks.isEmpty { - EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results") - } - case .history: - if history.isEmpty { - EmptyInstructionView(title: "No History", message: "Start watching to build history") - } - case .debridCloud: - if debridManager.selectedDebridType == nil { - EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service") - } - } - } - .navigationTitle("Library") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - HStack(spacing: Application.shared.osVersion.majorVersion > 14 ? 10 : 18) { - Spacer() - EditButton() - - switch selectedSegment { - case .bookmarks, .debridCloud: - DebridChoiceView() - case .history: - HistoryActionsView() - } - } - .animation(.none) - } + .navigationSearchBarHiddenWhenScrolling(false) + .searchAppearance { + LibraryPickerView() + .environmentObject(debridManager) + .environmentObject(navModel) } .environment(\.editMode, $editMode) } - .onChange(of: selectedSegment) { _ in + .overlay { + switch navModel.libraryPickerSelection { + case .bookmarks: + if bookmarks.isEmpty { + EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results") + } + case .history: + if history.isEmpty { + EmptyInstructionView(title: "No History", message: "Start watching to build history") + } + case .debridCloud: + if debridManager.selectedDebridType == nil { + EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service") + } + } + } + .onChange(of: navModel.libraryPickerSelection) { _ in editMode = .inactive } .onDisappear { diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index 003f75b..69b2bd7 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -29,25 +29,25 @@ struct MainView: View { .tabItem { Label("Search", systemImage: "magnifyingglass") } - .tag(ViewTab.search) + .tag(NavigationViewModel.ViewTab.search) LibraryView() .tabItem { Label("Library", systemImage: "book.closed") } - .tag(ViewTab.library) + .tag(NavigationViewModel.ViewTab.library) PluginsView() .tabItem { Label("Plugins", systemImage: "doc.text") } - .tag(ViewTab.plugins) + .tag(NavigationViewModel.ViewTab.plugins) SettingsView() .tabItem { Label("Settings", systemImage: "gear") } - .tag(ViewTab.settings) + .tag(NavigationViewModel.ViewTab.settings) } .sheet(item: $navModel.currentChoiceSheet) { item in switch item { diff --git a/Ferrite/Views/PluginsView.swift b/Ferrite/Views/PluginsView.swift index 20f76d0..7f3eb7f 100644 --- a/Ferrite/Views/PluginsView.swift +++ b/Ferrite/Views/PluginsView.swift @@ -9,12 +9,8 @@ import SwiftUI import SwiftUIX struct PluginsView: View { - enum PluginPickerSegment { - case sources - case actions - } - @EnvironmentObject var pluginManager: PluginManager + @EnvironmentObject var navModel: NavigationViewModel @FetchRequest( entity: Source.entity(), @@ -28,7 +24,6 @@ struct PluginsView: View { @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true - @State private var selectedSegment: PluginPickerSegment = .sources @State private var checkedForPlugins = false @State private var isEditingSearch = false @@ -39,41 +34,15 @@ struct PluginsView: View { var body: some View { NavView { - VStack { - Picker("Segments", selection: $selectedSegment) { - Text("Sources").tag(PluginPickerSegment.sources) - Text("Actions").tag(PluginPickerSegment.actions) - } - .pickerStyle(.segmented) - .padding(.horizontal) - .padding(.vertical, 5) - + ZStack { if checkedForPlugins { - switch selectedSegment { + switch navModel.pluginPickerSelection { case .sources: PluginListView(searchText: $searchText) case .actions: PluginListView(searchText: $searchText) } } - - Spacer() - } - .overlay { - if checkedForPlugins { - switch selectedSegment { - case .sources: - if sources.isEmpty && pluginManager.availableSources.isEmpty { - EmptyInstructionView(title: "No Sources", message: "Add a plugin list in Settings") - } - case .actions: - if actions.isEmpty && pluginManager.availableActions.isEmpty { - EmptyInstructionView(title: "No Actions", message: "Add a plugin list in Settings") - } - } - } else { - ProgressView() - } } .backport.onAppear { viewTask = Task { @@ -96,9 +65,26 @@ struct PluginsView: View { isSearching = false } } - .introspectSearchController { searchController in - searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no - searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none + .navigationSearchBarHiddenWhenScrolling(false) + .searchAppearance { + PluginPickerView() + .environmentObject(navModel) + } + } + .overlay { + if checkedForPlugins { + switch navModel.pluginPickerSelection { + case .sources: + if sources.isEmpty && pluginManager.availableSources.isEmpty { + EmptyInstructionView(title: "No Sources", message: "Add a plugin list in Settings") + } + case .actions: + if actions.isEmpty && pluginManager.availableActions.isEmpty { + EmptyInstructionView(title: "No Actions", message: "Add a plugin list in Settings") + } + } + } else { + ProgressView() } } } diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index df775a7..dc2b699 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -21,7 +21,7 @@ struct SearchResultsView: View { } } .listStyle(.insetGrouped) - .inlinedList() + .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 20 : -20) .overlay { if scrapingModel.searchResults.isEmpty { if navModel.showSearchProgress { diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index 783a35e..b413b43 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -48,7 +48,7 @@ struct BatchChoiceView: View { } .backport.tint(.primary) .listStyle(.insetGrouped) - .inlinedList() + .inlinedList(inset: -20) .navigationTitle("Select a file") .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -82,7 +82,7 @@ struct BatchChoiceView: View { PersistenceController.shared.createHistory(selectedHistoryInfo, performSave: true) } - navModel.runDebridAction(urlString: debridManager.downloadUrl) + navModel.runDebridAction(urlString: debridManager.downloadUrl, nil) } debridManager.clearSelectedDebridItems()