Ferrite: Modify UI

The overall UI of Ferrite has been changed to make animations smoother
and streamline the experiences.

A new search filter interface has been added for all iOS versions,
but iOS 15 and up have smooth UI applied due to bugs with searchbars
in iOS 14 (which shouldn't even have a searchbar in the first place).

Also fix the plugin fetching logic to not listen to a combine publisher
and instead use a notification that is easier to control.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-02-10 00:40:36 -05:00
parent 88a2dc9742
commit 0f081d0716
31 changed files with 634 additions and 302 deletions

View file

@ -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 = "<group>"; };
0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeWrapper.swift; sourceTree = "<group>"; };
0C422E7F293542F300486D65 /* PremiumizeModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeModels.swift; sourceTree = "<group>"; };
0C42B5952932F2D5008057A0 /* DebridChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridChoiceView.swift; sourceTree = "<group>"; };
0C42B5952932F2D5008057A0 /* DebridPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridPickerView.swift; sourceTree = "<group>"; };
0C42B5972932F6DD008057A0 /* Set.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Set.swift; sourceTree = "<group>"; };
0C445C61293F9A0B0060744D /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
0C44E2A728D4DDDC007711AE /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
@ -167,6 +173,7 @@
0C5005512992B6750064606A /* PluginTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginTagsView.swift; sourceTree = "<group>"; };
0C5005582992BA6A0064606A /* PluginTag+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginTag+CoreDataClass.swift"; sourceTree = "<group>"; };
0C5005592992BA6A0064606A /* PluginTag+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginTag+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = "<group>"; };
0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = "<group>"; };
@ -197,6 +204,7 @@
0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataClass.swift"; sourceTree = "<group>"; };
0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = "<group>"; };
0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultActionsPickerViews.swift; sourceTree = "<group>"; };
0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
0CA05456288EE58200850554 /* SettingsPluginListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPluginListView.swift; sourceTree = "<group>"; };
@ -241,10 +249,14 @@
0CC6E4D428A45BA000AF2BCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryActionsView.swift; sourceTree = "<group>"; };
0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledAppearance.swift; sourceTree = "<group>"; };
0CD5F1FA299BEFBE00476DDB /* SearchAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchAppearance.swift; sourceTree = "<group>"; };
0CD5F1FC299C083B00476DDB /* PluginPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginPickerView.swift; sourceTree = "<group>"; };
0CD72E16293D9928001A7EA4 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; sourceTree = "<group>"; };
0CE1C4172981E8D700418F20 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = "<group>"; };
0CE66B3928E640D200F69346 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFilterHeaderView.swift; sourceTree = "<group>"; };
0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPickerView.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -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 = "<group>";
@ -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 = "<group>";
@ -556,6 +573,7 @@
0CA3B23628C2660700616D3A /* HistoryView.swift */,
0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */,
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */,
0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */,
);
path = Library;
sourceTree = "<group>";
@ -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 */

View file

@ -18,6 +18,16 @@ public class RealDebrid {
var authTask: Task<Void, Error>?
@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") {

View file

@ -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")
}
}

View file

@ -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
}
}

View file

@ -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: View>(_ content: Content) -> some View {
modifier(SearchAppearance(hostingContent: content))
}
func searchAppearance<Content: View>(_ content: @escaping () -> Content) -> some View {
modifier(SearchAppearance(hostingContent: content()))
}
}

View file

@ -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")
}
}
*/
}
}

View file

@ -87,7 +87,7 @@ public class PluginManager: ObservableObject {
}
print("Plugin fetch error: \(error)")
}
}
}
// Check if underlying type is Source or Action

View file

@ -6,6 +6,7 @@
//
import SwiftUI
import Introspect
public struct Backport<Content> {
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)
}
}
}
}
}

View file

@ -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))
}
}

View file

@ -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 {
}
}

View file

@ -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
}
}
}

View file

@ -0,0 +1,63 @@
//
// SearchAppearance.swift
// Ferrite
//
// Created by Brian Dashore on 2/14/23.
//
import SwiftUI
import Introspect
struct SearchAppearance<V: View>: ViewModifier {
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true
let hostingContent: V
let hostingController: UIHostingController<V>
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
}
}
}
}

View file

@ -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<Content: View>: 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)
}
}
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -7,15 +7,17 @@
import SwiftUI
struct DebridChoiceView: View {
struct DebridPickerView<Content: View>: 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()
}
}

View file

@ -26,34 +26,33 @@ struct BookmarksView: View {
sortDescriptors: [NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true)]
) { (bookmarks: FetchedResults<Bookmark>) 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 {

View file

@ -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)
}
}

View file

@ -54,6 +54,7 @@ struct InstalledPluginButtonView<P: Plugin>: 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<P: Plugin>: View {
} else {
Button {
PersistenceController.shared.delete(installedPlugin, context: backgroundContext)
NotificationCenter.default.post(name: .didDeletePlugin, object: nil)
} label: {
Text("Remove")
Image(systemName: "trash")

View file

@ -57,6 +57,7 @@ struct PluginListView<P: Plugin, PJ: PluginJson>: 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<P: Plugin, PJ: PluginJson>: 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)
}

View file

@ -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)
}
}

View file

@ -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<Source>
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)
}
}
}

View file

@ -44,7 +44,7 @@ struct BackupsView: View {
}
}
}
.inlinedList()
.inlinedList(inset: -20)
.listStyle(.insetGrouped)
}
}

View file

@ -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)
}

View file

@ -73,7 +73,7 @@ struct SettingsPluginListView: View {
}
}
.listStyle(.insetGrouped)
.inlinedList()
.inlinedList(inset: -20)
}
}
.sheet(isPresented: $presentSourceSheet) {

View file

@ -14,118 +14,56 @@ struct ContentView: View {
@EnvironmentObject var navModel: NavigationViewModel
@EnvironmentObject var pluginManager: PluginManager
@FetchRequest(
entity: Source.entity(),
sortDescriptors: []
) var sources: FetchedResults<Source>
@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)
}
}
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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<Source, SourceJson>(searchText: $searchText)
case .actions:
PluginListView<Action, ActionJson>(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()
}
}
}

View file

@ -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 {

View file

@ -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()