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:
parent
88a2dc9742
commit
0f081d0716
31 changed files with 634 additions and 302 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
Ferrite/Extensions/UIDevice.swift
Normal file
18
Ferrite/Extensions/UIDevice.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
print("Plugin fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if underlying type is Source or Action
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
Ferrite/Views/CommonViews/FilterLabelView.swift
Normal file
27
Ferrite/Views/CommonViews/FilterLabelView.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
17
Ferrite/Views/CommonViews/LibraryHeaderView.swift
Normal file
17
Ferrite/Views/CommonViews/LibraryHeaderView.swift
Normal 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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
63
Ferrite/Views/CommonViews/Modifiers/SearchAppearance.swift
Normal file
63
Ferrite/Views/CommonViews/Modifiers/SearchAppearance.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Ferrite/Views/CommonViews/SearchableContent.swift
Normal file
37
Ferrite/Views/CommonViews/SearchableContent.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Ferrite/Views/CommonViews/SectionHeaderView.swift
Normal file
20
Ferrite/Views/CommonViews/SectionHeaderView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
75
Ferrite/Views/CommonViews/TestHostingView.swift
Normal file
75
Ferrite/Views/CommonViews/TestHostingView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
31
Ferrite/Views/ComponentViews/Library/LibraryPickerView.swift
Normal file
31
Ferrite/Views/ComponentViews/Library/LibraryPickerView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
27
Ferrite/Views/ComponentViews/Plugin/PluginPickerView.swift
Normal file
27
Ferrite/Views/ComponentViews/Plugin/PluginPickerView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ struct BackupsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.inlinedList()
|
||||
.inlinedList(inset: -20)
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ struct SettingsPluginListView: View {
|
|||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.inlinedList()
|
||||
.inlinedList(inset: -20)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $presentSourceSheet) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue