diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index cf06677..6f2e599 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -22,13 +22,10 @@ 0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; }; 0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; }; 0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.swift */; }; - 0C2DD4CF29A6D47400293FC3 /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */; }; 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; }; 0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; }; 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; }; 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.swift */; }; - 0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */; }; - 0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */; }; 0C3DD43F29B6968D006429DB /* KodiEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3DD43E29B6968D006429DB /* KodiEditorView.swift */; }; 0C3DD44229B6ACD9006429DB /* KodiServer+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3DD44029B6ACD9006429DB /* KodiServer+CoreDataClass.swift */; }; 0C3DD44329B6ACD9006429DB /* KodiServer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3DD44129B6ACD9006429DB /* KodiServer+CoreDataProperties.swift */; }; @@ -46,6 +43,7 @@ 0C44E2A828D4DDDC007711AE /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44E2A728D4DDDC007711AE /* Application.swift */; }; 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44E2AC28D51C63007711AE /* BackupManager.swift */; }; 0C44E2AF28D52E8A007711AE /* BackupsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44E2AE28D52E8A007711AE /* BackupsView.swift */; }; + 0C45E6A529D4B2FE00F047D2 /* SearchListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C45E6A429D4B2FE00F047D2 /* SearchListener.swift */; }; 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = 0C4CFC452897030D00AD9FAD /* Regex */; }; 0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; }; 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; }; @@ -56,8 +54,6 @@ 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 */; }; 0C5708EB29B8F89300BE07F9 /* SettingsLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5708EA29B8F89300BE07F9 /* SettingsLogView.swift */; }; - 0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */; }; - 0C572D4E299403B7003EEC05 /* ViewDidAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4D299403B7003EEC05 /* ViewDidAppear.swift */; }; 0C57D4CC289032ED008534E8 /* SearchResultInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */; }; 0C5FCB05296744F300849E87 /* AllDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5FCB04296744F300849E87 /* AllDebridCloudView.swift */; }; 0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; }; @@ -71,6 +67,8 @@ 0C68135228BC1A7C00FAD890 /* GithubModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C68135128BC1A7C00FAD890 /* GithubModels.swift */; }; 0C6C7C9B2931521B002DF910 /* AllDebridWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C6C7C9A2931521B002DF910 /* AllDebridWrapper.swift */; }; 0C6C7C9D29315292002DF910 /* AllDebridModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C6C7C9C29315292002DF910 /* AllDebridModels.swift */; }; + 0C7075E429D374C50093DB2D /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7075E329D374C50093DB2D /* Color.swift */; }; + 0C7075E629D3845D0093DB2D /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7075E529D3845D0093DB2D /* ShareSheet.swift */; }; 0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */; }; 0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; }; 0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; }; @@ -127,6 +125,7 @@ 0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA429F728C5098D000D0610 /* DateFormatter.swift */; }; 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; }; 0CAF9319296399190050812A /* PremiumizeCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAF9318296399190050812A /* PremiumizeCloudView.swift */; }; + 0CB0115B29D36D9E009AFEDE /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB0115A29D36D9E009AFEDE /* SearchResultsView.swift */; }; 0CB0AB5F29BD2A200015422C /* KodiServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB0AB5E29BD2A200015422C /* KodiServerView.swift */; }; 0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; }; 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; }; @@ -145,9 +144,9 @@ 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 */; }; 0CEC8AAE299B31B6007BFE8F /* SearchFilterHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */; }; 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */; }; + 0CF2C0A529D1EBD400E716DD /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF2C0A429D1EBD400E716DD /* UIApplication.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -170,8 +169,6 @@ 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = ""; }; 0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = ""; }; - 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFetchRequest.swift; sourceTree = ""; }; - 0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; 0C3DD43E29B6968D006429DB /* KodiEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KodiEditorView.swift; sourceTree = ""; }; 0C3DD44029B6ACD9006429DB /* KodiServer+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KodiServer+CoreDataClass.swift"; sourceTree = ""; }; 0C3DD44129B6ACD9006429DB /* KodiServer+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KodiServer+CoreDataProperties.swift"; sourceTree = ""; }; @@ -189,6 +186,7 @@ 0C44E2A728D4DDDC007711AE /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 0C44E2AC28D51C63007711AE /* BackupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupManager.swift; sourceTree = ""; }; 0C44E2AE28D52E8A007711AE /* BackupsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupsView.swift; sourceTree = ""; }; + 0C45E6A429D4B2FE00F047D2 /* SearchListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchListener.swift; sourceTree = ""; }; 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = ""; }; 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = ""; }; 0C5005512992B6750064606A /* PluginTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginTagsView.swift; sourceTree = ""; }; @@ -198,8 +196,6 @@ 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = ""; }; 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = ""; }; 0C5708EA29B8F89300BE07F9 /* SettingsLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLogView.swift; sourceTree = ""; }; - 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = ""; }; - 0C572D4D299403B7003EEC05 /* ViewDidAppear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppear.swift; sourceTree = ""; }; 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultInfoView.swift; sourceTree = ""; }; 0C5FCB04296744F300849E87 /* AllDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridCloudView.swift; sourceTree = ""; }; 0C6771F329B3B4FD005D38D2 /* KodiWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KodiWrapper.swift; sourceTree = ""; }; @@ -211,6 +207,8 @@ 0C68135128BC1A7C00FAD890 /* GithubModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubModels.swift; sourceTree = ""; }; 0C6C7C9A2931521B002DF910 /* AllDebridWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridWrapper.swift; sourceTree = ""; }; 0C6C7C9C29315292002DF910 /* AllDebridModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridModels.swift; sourceTree = ""; }; + 0C7075E329D374C50093DB2D /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; + 0C7075E529D3845D0093DB2D /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalContextMenu.swift; sourceTree = ""; }; 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = ""; }; 0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = ""; }; @@ -266,6 +264,7 @@ 0CA429F728C5098D000D0610 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; }; 0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0CAF9318296399190050812A /* PremiumizeCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeCloudView.swift; sourceTree = ""; }; + 0CB0115A29D36D9E009AFEDE /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = ""; }; 0CB0AB5E29BD2A200015422C /* KodiServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KodiServerView.swift; sourceTree = ""; }; 0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = ""; }; 0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = ""; }; @@ -284,9 +283,9 @@ 0CD72E16293D9928001A7EA4 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyInstructionView.swift; sourceTree = ""; }; 0CE1C4172981E8D700418F20 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; - 0CE66B3928E640D200F69346 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFilterHeaderView.swift; sourceTree = ""; }; 0CEC8AB1299B3B57007BFE8F /* LibraryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPickerView.swift; sourceTree = ""; }; + 0CF2C0A429D1EBD400E716DD /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -297,7 +296,6 @@ 0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */, 0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */, 0C64A4B4288903680079976D /* Base32 in Frameworks */, - 0C2DD4CF29A6D47400293FC3 /* SwiftUIX in Frameworks */, 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, @@ -448,7 +446,7 @@ 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */, 0CB6516428C5A5D700DCA721 /* InlinedList.swift */, 0CD5F1FA299BEFBE00476DDB /* CustomScopeBar.swift */, - 0C572D4D299403B7003EEC05 /* ViewDidAppear.swift */, + 0C45E6A429D4B2FE00F047D2 /* SearchListener.swift */, ); path = Modifiers; sourceTree = ""; @@ -468,6 +466,7 @@ 0C41BC6228C2AD0F00B47DD6 /* SearchResultButtonView.swift */, 0C57D4CB289032ED008534E8 /* SearchResultInfoView.swift */, 0CEC8AAD299B31B6007BFE8F /* SearchFilterHeaderView.swift */, + 0CB0115A29D36D9E009AFEDE /* SearchResultsView.swift */, ); path = SearchResult; sourceTree = ""; @@ -529,9 +528,6 @@ isa = PBXGroup; children = ( 0C44E2A928D4DFC4007711AE /* Modifiers */, - 0CE66B3928E640D200F69346 /* Backport.swift */, - 0C391ECA28CAA44B009F1CA1 /* AlertButton.swift */, - 0C360C5B28C7DF1400884ED3 /* DynamicFetchRequest.swift */, 0CDCB91728C662640098B513 /* EmptyInstructionView.swift */, 0C871BDE29994D9D005279AC /* FilterLabelView.swift */, 0CA148C1288903F000DE2211 /* NavView.swift */, @@ -558,6 +554,7 @@ 0CD72E16293D9928001A7EA4 /* Array.swift */, 0C445C61293F9A0B0060744D /* Bundle.swift */, 0CA148C9288903F000DE2211 /* Collection.swift */, + 0C7075E329D374C50093DB2D /* Color.swift */, 0CA148CA288903F000DE2211 /* Data.swift */, 0CA429F728C5098D000D0610 /* DateFormatter.swift */, 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */, @@ -569,6 +566,7 @@ 0C42B5972932F6DD008057A0 /* Set.swift */, 0C7C128528DAA3CD00381CD1 /* URL.swift */, 0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */, + 0CF2C0A429D1EBD400E716DD /* UIApplication.swift */, ); path = Extensions; sourceTree = ""; @@ -608,7 +606,7 @@ isa = PBXGroup; children = ( 0CA148CE288903F000DE2211 /* WebView.swift */, - 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */, + 0C7075E529D3845D0093DB2D /* ShareSheet.swift */, ); path = RepresentableViews; sourceTree = ""; @@ -690,7 +688,6 @@ 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */, 0CDDDE042935235E006810B1 /* BetterSafariView */, 0C448BE829A135F100F4E266 /* Introspect-Static */, - 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */, ); productName = Torrenter; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; @@ -728,7 +725,6 @@ 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */, 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, - 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */, ); productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; projectDirPath = ""; @@ -803,11 +799,10 @@ 0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */, 0CA0545B288EEA4E00850554 /* PluginListEditorView.swift in Sources */, 0C3E00D8296F5B9A00ECECB2 /* PluginModels.swift in Sources */, - 0C360C5C28C7DF1400884ED3 /* DynamicFetchRequest.swift in Sources */, 0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */, 0C5005522992B6750064606A /* PluginTagsView.swift in Sources */, 0CB0AB5F29BD2A200015422C /* KodiServerView.swift in Sources */, - 0CE66B3A28E640D200F69346 /* Backport.swift in Sources */, + 0CF2C0A529D1EBD400E716DD /* UIApplication.swift in Sources */, 0C42B5962932F2D5008057A0 /* DebridPickerView.swift in Sources */, 0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */, 0C3DD44229B6ACD9006429DB /* KodiServer+CoreDataClass.swift in Sources */, @@ -831,7 +826,6 @@ 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, 0CC389532970AD900066D06F /* Action+CoreDataClass.swift in Sources */, 0C03EB72296F619900162E9A /* PluginList+CoreDataProperties.swift in Sources */, - 0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */, 0C95D8D828A55B03005E22B3 /* DefaultActionPickerView.swift in Sources */, 0C44E2AF28D52E8A007711AE /* BackupsView.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, @@ -877,12 +871,15 @@ 0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */, 0C78041D28BFB3EA001E8CA3 /* String.swift in Sources */, 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */, + 0C45E6A529D4B2FE00F047D2 /* SearchListener.swift in Sources */, 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, 0C50055B2992BA6A0064606A /* PluginTag+CoreDataProperties.swift in Sources */, + 0C7075E629D3845D0093DB2D /* ShareSheet.swift in Sources */, 0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */, 0C6771F429B3B4FD005D38D2 /* KodiWrapper.swift in Sources */, 0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */, 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */, + 0C7075E429D374C50093DB2D /* Color.swift in Sources */, 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */, 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */, 0C8DC35629CE2ABF008A83AD /* SourceSettingsApiView.swift in Sources */, @@ -890,10 +887,10 @@ 0C0974B029CCAAAF006DE7A3 /* OperatingSystemVersion.swift in Sources */, 0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */, 0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */, - 0C572D4E299403B7003EEC05 /* ViewDidAppear.swift in Sources */, 0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */, 0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */, 0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */, + 0CB0115B29D36D9E009AFEDE /* SearchResultsView.swift in Sources */, 0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */, 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */, 0C6771F629B3B602005D38D2 /* SettingsKodiView.swift in Sources */, @@ -902,7 +899,6 @@ 0CEC8AAE299B31B6007BFE8F /* SearchFilterHeaderView.swift in Sources */, 0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */, 0C3DD43F29B6968D006429DB /* KodiEditorView.swift in Sources */, - 0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */, 0C3DD44329B6ACD9006429DB /* KodiServer+CoreDataProperties.swift in Sources */, 0C7C128628DAA3CD00381CD1 /* URL.swift in Sources */, 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */, @@ -1124,14 +1120,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SwiftUIX/SwiftUIX"; - requirement = { - branch = master; - kind = branch; - }; - }; 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/"; @@ -1191,11 +1179,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 0C2DD4CE29A6D47400293FC3 /* SwiftUIX */ = { - isa = XCSwiftPackageProductDependency; - package = 0C2DD4CD29A6D47400293FC3 /* XCRemoteSwiftPackageReference "SwiftUIX" */; - productName = SwiftUIX; - }; 0C448BE829A135F100F4E266 /* Introspect-Static */ = { isa = XCSwiftPackageProductDependency; package = 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; diff --git a/Ferrite/Extensions/Color.swift b/Ferrite/Extensions/Color.swift new file mode 100644 index 0000000..bd3b14e --- /dev/null +++ b/Ferrite/Extensions/Color.swift @@ -0,0 +1,35 @@ +// +// Color.swift +// Ferrite +// +// Created by Brian Dashore on 3/28/23. +// + +import SwiftUI + +extension Color { + public init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} diff --git a/Ferrite/Extensions/UIApplication.swift b/Ferrite/Extensions/UIApplication.swift new file mode 100644 index 0000000..167b8dc --- /dev/null +++ b/Ferrite/Extensions/UIApplication.swift @@ -0,0 +1,15 @@ +// +// UIApplication.swift +// Ferrite +// +// Created by Brian Dashore on 3/27/23. +// + +import UIKit + +extension UIApplication { + // From https://stackoverflow.com/questions/69650504/how-to-get-rid-of-message-windows-was-deprecated-in-ios-15-0-use-uiwindowsc + var currentUIWindow: UIWindow? { + return UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow } + } +} diff --git a/Ferrite/Extensions/UIDevice.swift b/Ferrite/Extensions/UIDevice.swift index f2ae7e6..3c416ed 100644 --- a/Ferrite/Extensions/UIDevice.swift +++ b/Ferrite/Extensions/UIDevice.swift @@ -5,14 +5,14 @@ // Created by Brian Dashore on 2/16/23. // -import SwiftUI +import UIKit 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 UIApplication.shared.currentUIWindow?.safeAreaInsets.bottom ?? 0 > 0 + } else { + return false } - return false } } diff --git a/Ferrite/Extensions/View.swift b/Ferrite/Extensions/View.swift index ac67c54..181536a 100644 --- a/Ferrite/Extensions/View.swift +++ b/Ferrite/Extensions/View.swift @@ -33,11 +33,11 @@ extension View { modifier(InlinedListModifier(inset: inset)) } - func viewDidAppear(_ callback: @escaping () -> Void) -> some View { - modifier(ViewDidAppearModifier(callback: callback)) - } - func customScopeBar(_ content: @escaping () -> some View) -> some View { modifier(CustomScopeBarModifier(scopeBarContent: content())) } + + func searchListener(isSearching: Binding) -> some View { + modifier(SearchListenerModifier(isSearching: isSearching)) + } } diff --git a/Ferrite/FerriteApp.swift b/Ferrite/FerriteApp.swift index 0562d3c..c608020 100644 --- a/Ferrite/FerriteApp.swift +++ b/Ferrite/FerriteApp.swift @@ -21,7 +21,7 @@ struct FerriteApp: App { var body: some Scene { WindowGroup { MainView() - .backport.onAppear { + .onAppear { scrapingModel.logManager = logManager debridManager.logManager = logManager pluginManager.logManager = logManager diff --git a/Ferrite/ViewModels/BackupManager.swift b/Ferrite/ViewModels/BackupManager.swift index 72662f2..588c1ac 100644 --- a/Ferrite/ViewModels/BackupManager.swift +++ b/Ferrite/ViewModels/BackupManager.swift @@ -186,14 +186,7 @@ public class BackupManager: ObservableObject { PersistenceController.shared.save(backgroundContext) - // if iOS 14 is available, sleep to prevent any issues with alerts - if #available(iOS 15, *) { - await toggleRestoreCompletedAlert() - } else { - try? await Task.sleep(seconds: 0.1) - - await toggleRestoreCompletedAlert() - } + await toggleRestoreCompletedAlert() } catch { await logManager?.error( "Backup restore: \(error)", diff --git a/Ferrite/Views/AboutView.swift b/Ferrite/Views/AboutView.swift index 35b2b78..4af0aaf 100644 --- a/Ferrite/Views/AboutView.swift +++ b/Ferrite/Views/AboutView.swift @@ -31,7 +31,7 @@ struct AboutView: View { Text("Ferrite is a free and open source application developed by kingbri under the GNU-GPLv3 license.") .textCase(.none) - .foregroundColor(.label) + .foregroundColor(.init(uiColor: .label)) .font(.body) .padding(.top, 8) .padding(.bottom, 20) diff --git a/Ferrite/Views/CommonViews/AlertButton.swift b/Ferrite/Views/CommonViews/AlertButton.swift deleted file mode 100644 index e572879..0000000 --- a/Ferrite/Views/CommonViews/AlertButton.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// AlertButton.swift -// Ferrite -// -// Created by Brian Dashore on 9/8/22. -// -// Universal alert button for dynamic alert views -// - -import SwiftUI - -struct AlertButton: Identifiable { - enum Role { - case destructive - case cancel - } - - let id: UUID - let label: String - let action: () -> Void - let role: Role? - - // Used for all buttons - init(_ label: String, role: Role? = nil, action: @escaping () -> Void) { - id = UUID() - self.label = label - self.action = action - self.role = role - } - - // Used for buttons with no action - init(_ label: String? = nil, role: Role? = nil) { - id = UUID() - self.label = label ?? (role == .cancel ? "Cancel" : "OK") - action = {} - self.role = role - } - - func toActionButton() -> Alert.Button { - if let role { - switch role { - case .cancel: - return .cancel(Text(label)) - case .destructive: - return .destructive(Text(label), action: action) - } - } else { - return .default(Text(label), action: action) - } - } - - @available(iOS 15.0, *) - @ViewBuilder - func toButtonView() -> some View { - Button(label, role: toButtonRole(role), action: action) - } - - @available(iOS 15.0, *) - func toButtonRole(_ role: Role?) -> ButtonRole? { - if let role { - switch role { - case .destructive: - return .destructive - case .cancel: - return .cancel - } - } else { - return nil - } - } -} diff --git a/Ferrite/Views/CommonViews/Backport.swift b/Ferrite/Views/CommonViews/Backport.swift deleted file mode 100644 index 90c4799..0000000 --- a/Ferrite/Views/CommonViews/Backport.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// Backport.swift -// Ferrite -// -// Created by Brian Dashore on 9/29/22. -// - -import Introspect -import SwiftUI - -public struct Backport { - public let content: Content - - public init(_ content: Content) { - self.content = content - } -} - -extension View { - var backport: Backport { Backport(self) } -} - -extension Backport where Content: View { - @ViewBuilder func alert(isPresented: Binding, - title: String, - message: String?, - buttons: [AlertButton] = []) -> some View - { - if #available(iOS 15, *) { - content - .alert( - title, - isPresented: isPresented, - actions: { - ForEach(buttons) { button in - button.toButtonView() - } - }, - message: { - if let message { - Text(message) - } - } - ) - } else { - content - .background { - Color.clear - .alert(isPresented: isPresented) { - if let primaryButton = buttons[safe: 0], - let secondaryButton = buttons[safe: 1] - { - return Alert( - title: Text(title), - message: message.map { Text($0) } ?? nil, - primaryButton: primaryButton.toActionButton(), - secondaryButton: secondaryButton.toActionButton() - ) - } else { - return Alert( - title: Text(title), - message: message.map { Text($0) } ?? nil, - dismissButton: buttons[safe: 0].map { $0.toActionButton() } ?? .cancel() - ) - } - } - } - } - } - - @ViewBuilder func confirmationDialog(isPresented: Binding, - title: String, message: String?, - buttons: [AlertButton]) -> some View - { - if #available(iOS 15, *) { - content - .confirmationDialog( - title, - isPresented: isPresented, - titleVisibility: .visible - ) { - ForEach(buttons) { button in - button.toButtonView() - } - } message: { - if let message { - Text(message) - } - } - } else { - content - .actionSheet(isPresented: isPresented) { - ActionSheet( - title: Text(title), - message: message.map { Text($0) } ?? nil, - buttons: [buttons.map { $0.toActionButton() }, [.cancel()]].flatMap { $0 } - ) - } - } - } - - @ViewBuilder func tint(_ color: Color) -> some View { - if #available(iOS 15, *) { - content - .tint(color) - } else { - content - .accentColor(color) - } - } - - @ViewBuilder func onAppear(callback: @escaping () -> Void) -> some View { - if #available(iOS 15, *) { - content - .onAppear { - callback() - } - } else { - content - .viewDidAppear { - callback() - } - } - } - - @ViewBuilder func introspectSearchController(customize: @escaping (UISearchController) -> Void) -> some View { - if #available(iOS 15, *) { - content.introspectSearchController(customize: customize) - } else { - content.introspectNavigationController { navigationController in - let navigationBar = navigationController.navigationBar - if let searchController = navigationBar.topItem?.searchController { - customize(searchController) - } - } - } - } -} diff --git a/Ferrite/Views/CommonViews/DynamicFetchRequest.swift b/Ferrite/Views/CommonViews/DynamicFetchRequest.swift deleted file mode 100644 index dafbbfc..0000000 --- a/Ferrite/Views/CommonViews/DynamicFetchRequest.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// DynamicFetchRequest.swift -// Ferrite -// -// Created by Brian Dashore on 9/6/22. -// -// Used for FetchRequests with a dynamic predicate -// iOS 14 compatible view -// - -import CoreData -import SwiftUI - -struct DynamicFetchRequest: View { - @FetchRequest var fetchRequest: FetchedResults - - let content: (FetchedResults) -> Content - - var body: some View { - content(fetchRequest) - } - - init(predicate: NSPredicate?, - sortDescriptors: [NSSortDescriptor] = [], - @ViewBuilder content: @escaping (FetchedResults) -> Content) - { - _fetchRequest = FetchRequest(entity: T.entity(), sortDescriptors: sortDescriptors, predicate: predicate) - self.content = content - } -} diff --git a/Ferrite/Views/CommonViews/EmptyInstructionView.swift b/Ferrite/Views/CommonViews/EmptyInstructionView.swift index 8cd0055..9c0a3a3 100644 --- a/Ferrite/Views/CommonViews/EmptyInstructionView.swift +++ b/Ferrite/Views/CommonViews/EmptyInstructionView.swift @@ -20,7 +20,7 @@ struct EmptyInstructionView: View { .padding(.horizontal, 50) } .multilineTextAlignment(.center) - .foregroundColor(.secondaryLabel) + .foregroundColor(.init(uiColor: .secondaryLabel)) .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea() } diff --git a/Ferrite/Views/CommonViews/FilterLabelView.swift b/Ferrite/Views/CommonViews/FilterLabelView.swift index deb36a2..39171b4 100644 --- a/Ferrite/Views/CommonViews/FilterLabelView.swift +++ b/Ferrite/Views/CommonViews/FilterLabelView.swift @@ -17,11 +17,14 @@ struct FilterLabelView: View { .foregroundColor(.primary) Image(systemName: "chevron.down") - .foregroundColor(.tertiaryLabel) + .foregroundColor(.init(uiColor: .tertiaryLabel)) } .padding(.horizontal, 9) .padding(.vertical, 7) - .font(.caption, weight: .medium) - .background(Capsule().foregroundColor(.secondarySystemFill)) + .font( + .caption + .weight(.medium) + ) + .background(Capsule().foregroundColor(.init(uiColor: .secondarySystemFill))) } } diff --git a/Ferrite/Views/CommonViews/IndeterminateProgressView.swift b/Ferrite/Views/CommonViews/IndeterminateProgressView.swift index 2ce666f..c9f6e33 100644 --- a/Ferrite/Views/CommonViews/IndeterminateProgressView.swift +++ b/Ferrite/Views/CommonViews/IndeterminateProgressView.swift @@ -25,7 +25,7 @@ struct IndeterminateProgressView: View { .offset(x: -reader.size.width * 0.6, y: 0) .offset(x: reader.size.width * 1.2 * self.offset, y: 0) .animation(.default.repeatForever().speed(0.5), value: self.offset) - .backport.onAppear { + .onAppear { withAnimation { self.offset = 1 } diff --git a/Ferrite/Views/CommonViews/InlineHeader.swift b/Ferrite/Views/CommonViews/InlineHeader.swift index faff7b7..491e36d 100644 --- a/Ferrite/Views/CommonViews/InlineHeader.swift +++ b/Ferrite/Views/CommonViews/InlineHeader.swift @@ -19,11 +19,9 @@ struct InlineHeader: View { var body: some View { if #available(iOS 16, *) { Text(title) - } else if #available(iOS 15, *) { - Text(title) - .listRowInsets(EdgeInsets(top: 10, leading: 15, bottom: 0, trailing: 0)) } else { Text(title) + .listRowInsets(EdgeInsets(top: 10, leading: 15, bottom: 0, trailing: 0)) } } } diff --git a/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift b/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift index f2c4cf6..12569bf 100644 --- a/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift +++ b/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift @@ -13,50 +13,42 @@ struct CustomScopeBarModifier: ViewModifier { @State private var hostingController: UIHostingController? func body(content: Content) -> some View { - if #available(iOS 15, *) { - content - .backport.introspectSearchController { searchController in + content + .introspectSearchController { searchController in - // MARK: One-time setup + // MARK: One-time setup - guard hostingController == nil else { return } + guard hostingController == nil else { return } - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchBar.showsScopeBar = true - searchController.searchBar.scopeButtonTitles = [""] - (searchController.searchBar.value(forKey: "_scopeBar") as? UIView)?.isHidden = true + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchBar.showsScopeBar = true + searchController.searchBar.scopeButtonTitles = [""] + (searchController.searchBar.value(forKey: "_scopeBar") as? UIView)?.isHidden = true - let hostingController = UIHostingController(rootView: scopeBarContent) - hostingController.view.translatesAutoresizingMaskIntoConstraints = false - hostingController.view.backgroundColor = .clear + let hostingController = UIHostingController(rootView: scopeBarContent) + 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) - ]) - - self.hostingController = hostingController + guard let containerView = searchController.searchBar.value(forKey: "_scopeBarContainerView") as? UIView else { + return } - .introspectNavigationController { navigationController in - if #available(iOS 16, *) { - navigationController.viewControllers.first?.navigationItem.preferredSearchBarPlacement = .stacked - } + containerView.addSubview(hostingController.view) - navigationController.navigationBar.prefersLargeTitles = true - navigationController.navigationBar.sizeToFit() - } - } else { - VStack { - scopeBarContent - content - Spacer() + NSLayoutConstraint.activate([ + hostingController.view.widthAnchor.constraint(equalTo: containerView.widthAnchor), + hostingController.view.topAnchor.constraint(equalTo: containerView.topAnchor), + hostingController.view.heightAnchor.constraint(equalTo: containerView.heightAnchor) + ]) + + self.hostingController = hostingController + } + .introspectNavigationController { navigationController in + if #available(iOS 16, *) { + navigationController.viewControllers.first?.navigationItem.preferredSearchBarPlacement = .stacked + } + + navigationController.navigationBar.prefersLargeTitles = true + navigationController.navigationBar.sizeToFit() } - } } } diff --git a/Ferrite/Views/CommonViews/Modifiers/SearchListener.swift b/Ferrite/Views/CommonViews/Modifiers/SearchListener.swift new file mode 100644 index 0000000..ab3374d --- /dev/null +++ b/Ferrite/Views/CommonViews/Modifiers/SearchListener.swift @@ -0,0 +1,26 @@ +// +// SearchListener.swift +// Ferrite +// +// Created by Brian Dashore on 3/29/23. +// +// Communicate isSearching back to the parent view +// + +import SwiftUI + +struct SearchListenerModifier: ViewModifier { + @Environment(\.isSearching) var isSearchingEnvironment + + @Binding var isSearching: Bool + + func body(content: Content) -> some View { + content + .background { + EmptyView() + .onChange(of: isSearchingEnvironment) { newValue in + isSearching = newValue + } + } + } +} diff --git a/Ferrite/Views/CommonViews/Modifiers/ViewDidAppear.swift b/Ferrite/Views/CommonViews/Modifiers/ViewDidAppear.swift deleted file mode 100644 index 80524c8..0000000 --- a/Ferrite/Views/CommonViews/Modifiers/ViewDidAppear.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ViewDidAppearModifier.swift -// Ferrite -// -// Created by Brian Dashore on 2/8/23. -// - -import SwiftUI - -struct ViewDidAppearModifier: ViewModifier { - let callback: () -> Void - - func body(content: Content) -> some View { - content - .background(ViewDidAppearHandler(callback: callback)) - } -} diff --git a/Ferrite/Views/CommonViews/Tag.swift b/Ferrite/Views/CommonViews/Tag.swift index 7d3b218..a2a077e 100644 --- a/Ferrite/Views/CommonViews/Tag.swift +++ b/Ferrite/Views/CommonViews/Tag.swift @@ -21,7 +21,7 @@ struct Tag: View { .padding(.vertical, verticalPadding) .background( RoundedRectangle(cornerRadius: 5) - .foregroundColor(color.map { $0 } ?? .tertiaryLabel) + .foregroundColor(color.map { $0 } ?? .init(uiColor: .tertiaryLabel)) .opacity(0.3) ) } diff --git a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift index f49cbf7..f765ec3 100644 --- a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift +++ b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift @@ -8,84 +8,65 @@ import SwiftUI struct BookmarksView: View { - @Environment(\.verticalSizeClass) var verticalSizeClass - @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var debridManager: DebridManager let backgroundContext = PersistenceController.shared.backgroundContext @Binding var searchText: String - @Binding var bookmarksEmpty: Bool - @State private var viewTask: Task? - @State private var bookmarkPredicate: NSPredicate? + var bookmarks: FetchedResults var body: some View { - DynamicFetchRequest( - predicate: bookmarkPredicate, - sortDescriptors: [NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true)] - ) { (bookmarks: FetchedResults) in - List { - if !bookmarks.isEmpty { - ForEach(bookmarks, id: \.self) { bookmark in - SearchResultButtonView(result: bookmark.toSearchResult(), existingBookmark: bookmark) - } - .onDelete { offsets in - for index in offsets { - if let bookmark = bookmarks[safe: index] { - PersistenceController.shared.delete(bookmark, context: backgroundContext) - NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark) - } + 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) } } - .onMove { source, destination in - var changedBookmarks = bookmarks.map { $0 } - - changedBookmarks.move(fromOffsets: source, toOffset: destination) - - for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) { - changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex) - } - - PersistenceController.shared.save() - } } - } - .listStyle(.insetGrouped) - .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 15 : -25) - .backport.onAppear { - bookmarksEmpty = bookmarks.isEmpty + .onMove { source, destination in + var changedBookmarks = bookmarks.map { $0 } - if debridManager.enabledDebrids.count > 0 { - viewTask = Task { - let magnets = bookmarks.compactMap { - if let magnetHash = $0.magnetHash { - return Magnet(hash: magnetHash, link: $0.magnetLink) - } else { - return nil - } - } - await debridManager.populateDebridIA(magnets) + changedBookmarks.move(fromOffsets: source, toOffset: destination) + + for reverseIndex in stride(from: changedBookmarks.count - 1, through: 0, by: -1) { + changedBookmarks[reverseIndex].orderNum = Int16(reverseIndex) } + + PersistenceController.shared.save() } } - .onDisappear { - viewTask?.cancel() - } - .onChange(of: bookmarks.count) { newCount in - bookmarksEmpty = newCount == 0 - } } - .backport.onAppear { - applyPredicate() + .onAppear { + fetchPredicate() } .onChange(of: searchText) { _ in - applyPredicate() + fetchPredicate() + } + .listStyle(.insetGrouped) + .inlinedList(inset: 15) + .task { + if debridManager.enabledDebrids.count > 0 { + let magnets = bookmarks.compactMap { + if let magnetHash = $0.magnetHash { + return Magnet(hash: magnetHash, link: $0.magnetLink) + } else { + return nil + } + } + await debridManager.populateDebridIA(magnets) + } } } - func applyPredicate() { - bookmarkPredicate = searchText.isEmpty ? nil : NSPredicate(format: "title CONTAINS[cd] %@", searchText) + func fetchPredicate() { + bookmarks.nsPredicate = searchText.isEmpty ? nil : NSPredicate(format: "title CONTAINS[cd] %@", searchText) } } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift index 4bc1119..f871e34 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift @@ -14,8 +14,6 @@ struct AllDebridCloudView: View { @Binding var searchText: String - @State private var viewTask: Task? - var body: some View { DisclosureGroup("Magnets") { ForEach(debridManager.allDebridCloudMagnets.filter { @@ -72,7 +70,7 @@ struct AllDebridCloudView: View { } } .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) - .backport.tint(.black) + .tint(.black) } .onDelete { offsets in for index in offsets { diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift index 3b424dd..380d959 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftUIX struct PremiumizeCloudView: View { @EnvironmentObject var debridManager: DebridManager @@ -15,8 +14,6 @@ struct PremiumizeCloudView: View { @Binding var searchText: String - @State private var viewTask: Task? - var body: some View { DisclosureGroup("Items") { ForEach(debridManager.premiumizeCloudItems.filter { @@ -47,7 +44,7 @@ struct PremiumizeCloudView: View { } } .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) - .backport.tint(.black) + .tint(.black) } .onDelete { offsets in for index in offsets { diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift index 7380112..11059e9 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift @@ -14,8 +14,6 @@ struct RealDebridCloudView: View { @Binding var searchText: String - @State private var viewTask: Task? - var body: some View { Group { DisclosureGroup("Downloads") { @@ -41,7 +39,7 @@ struct RealDebridCloudView: View { navModel: navModel ) } - .backport.tint(.primary) + .tint(.primary) } .onDelete { offsets in for index in offsets { @@ -111,7 +109,7 @@ struct RealDebridCloudView: View { } } .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) - .backport.tint(.primary) + .tint(.primary) } .onDelete { offsets in for index in offsets { diff --git a/Ferrite/Views/ComponentViews/Library/DebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/DebridCloudView.swift index 09efc10..f7f4213 100644 --- a/Ferrite/Views/ComponentViews/Library/DebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/DebridCloudView.swift @@ -12,8 +12,6 @@ struct DebridCloudView: View { @Binding var searchText: String - @State private var viewTask: Task? - var body: some View { List { switch debridManager.selectedDebridType { @@ -28,19 +26,12 @@ struct DebridCloudView: View { } } .listStyle(.plain) - .backport.onAppear { - viewTask = Task { - await debridManager.fetchDebridCloud() - } - } - .onDisappear { - viewTask?.cancel() + .task { + await debridManager.fetchDebridCloud() } .onChange(of: debridManager.selectedDebridType) { newType in - viewTask?.cancel() - if newType != nil { - viewTask = Task { + Task { await debridManager.fetchDebridCloud() } } diff --git a/Ferrite/Views/ComponentViews/Library/HistoryActionsView.swift b/Ferrite/Views/ComponentViews/Library/HistoryActionsView.swift index a9aa115..a52d6f0 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryActionsView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryActionsView.swift @@ -16,26 +16,27 @@ struct HistoryActionsView: View { Button("Clear") { showActionSheet.toggle() } - .backport.tint(.red) - .backport.confirmationDialog( + .tint(.red) + .confirmationDialog( + "Clear watch history", isPresented: $showActionSheet, - title: "Clear watch history", - message: "This is an irreversible action!", - buttons: [ - AlertButton("Past day", role: .destructive) { - deleteHistory(.day) - }, - AlertButton("Past week", role: .destructive) { - deleteHistory(.week) - }, - AlertButton("Past month", role: .destructive) { - deleteHistory(.month) - }, - AlertButton("All time", role: .destructive) { - deleteHistory(.allTime) - } - ] - ) + titleVisibility: .visible + ) { + Button("Past day", role: .destructive) { + deleteHistory(.day) + } + Button("Past week", role: .destructive) { + deleteHistory(.week) + } + Button("Past month", role: .destructive) { + deleteHistory(.month) + } + Button("All time", role: .destructive) { + deleteHistory(.allTime) + } + } message: { + Text("This is an irreversible action!") + } } func deleteHistory(_ deleteRange: HistoryDeleteRange) { diff --git a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift index 7f9d10e..e8c6769 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift @@ -84,7 +84,7 @@ struct HistoryButtonView: View { } .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) } - .backport.tint(.primary) + .tint(.primary) .disableInteraction(navModel.currentChoiceSheet != nil) } diff --git a/Ferrite/Views/ComponentViews/Library/HistoryView.swift b/Ferrite/Views/ComponentViews/Library/HistoryView.swift index cc2a086..8670c80 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryView.swift @@ -17,29 +17,24 @@ struct HistoryView: View { ] ) var history: FetchedResults + var allHistoryEntries: FetchedResults + @Binding var searchText: String - @Binding var historyEmpty: Bool @State private var historyPredicate: NSPredicate? var body: some View { - DynamicFetchRequest(predicate: historyPredicate) { (allEntries: FetchedResults) in - List { - if !history.isEmpty { - ForEach(groupedHistory(history), id: \.self) { historyGroup in - HistorySectionView(allEntries: allEntries, historyGroup: historyGroup) - } + List { + if !history.isEmpty { + ForEach(groupedHistory(history), id: \.self) { historyGroup in + HistorySectionView(allEntries: allHistoryEntries, historyGroup: historyGroup) } } - .listStyle(.insetGrouped) } - .backport.onAppear { - historyEmpty = history.isEmpty + .listStyle(.insetGrouped) + .onAppear { applyPredicate() } - .onChange(of: history.count) { newCount in - historyEmpty = newCount == 0 - } .onChange(of: searchText) { _ in applyPredicate() } @@ -47,11 +42,11 @@ struct HistoryView: View { func applyPredicate() { if searchText.isEmpty { - historyPredicate = nil + allHistoryEntries.nsPredicate = nil } else { let namePredicate = NSPredicate(format: "name CONTAINS[cd] %@", searchText.lowercased()) let subNamePredicate = NSPredicate(format: "subName CONTAINS[cd] %@", searchText.lowercased()) - historyPredicate = NSCompoundPredicate(type: .or, subpredicates: [namePredicate, subNamePredicate]) + allHistoryEntries.nsPredicate = NSCompoundPredicate(type: .or, subpredicates: [namePredicate, subNamePredicate]) } } diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift index 30f6be8..8a8fa00 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/InstalledPluginButtonView.swift @@ -51,20 +51,11 @@ struct InstalledPluginButtonView: View { Image(systemName: "gear") } - if #available(iOS 15.0, *) { - Button(role: .destructive) { - PersistenceController.shared.delete(installedPlugin, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } - } else { - Button { - PersistenceController.shared.delete(installedPlugin, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } + Button(role: .destructive) { + PersistenceController.shared.delete(installedPlugin, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") } } } diff --git a/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift b/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift index b22f530..d4fb4c6 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Buttons/PluginCatalogButtonView.swift @@ -57,7 +57,7 @@ struct PluginCatalogButtonView: View { ) .padding(.horizontal, 7) .padding(.vertical, 5) - .background(.tertiarySystemBackground) + .background(Color.init(uiColor: .tertiarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 10)) } .buttonStyle(.borderless) diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift index 2f4284e..3041257 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift @@ -17,10 +17,11 @@ struct PluginAggregateView: View { sortDescriptors: [] ) var pluginLists: FetchedResults + var installedPlugins: FetchedResults

+ @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true @Binding var searchText: String - @Binding var pluginsEmpty: Bool @State private var isEditingSearch = false @State private var isSearching = false @@ -31,67 +32,63 @@ struct PluginAggregateView: View { @State private var selectedPlugin: P? var body: some View { - ZStack { - DynamicFetchRequest(predicate: sourcePredicate) { (installedPlugins: FetchedResults

) in - List { - if - let filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins( - forType: PJ.self, - installedPlugins: installedPlugins, - searchText: searchText - ), - !filteredUpdatedPlugins.isEmpty - { - Section(header: InlineHeader("Updates")) { - ForEach(filteredUpdatedPlugins, id: \.self) { (updatedPlugin: PJ) in - PluginCatalogButtonView(availablePlugin: updatedPlugin, needsUpdate: true) - } - } + List { + if + let filteredUpdatedPlugins = pluginManager.fetchUpdatedPlugins( + forType: PJ.self, + installedPlugins: installedPlugins, + searchText: searchText + ), + !filteredUpdatedPlugins.isEmpty + { + Section(header: InlineHeader("Updates")) { + ForEach(filteredUpdatedPlugins, id: \.self) { (updatedPlugin: PJ) in + PluginCatalogButtonView(availablePlugin: updatedPlugin, needsUpdate: true) } - - if !installedPlugins.isEmpty { - Section(header: InlineHeader("Installed")) { - ForEach(installedPlugins, id: \.self) { installedPlugin in - InstalledPluginButtonView( - installedPlugin: installedPlugin, - showPluginOptions: $showPluginOptions, - selectedPlugin: $selectedPlugin - ) - } - } - } - - if - let filteredAvailablePlugins = pluginManager.fetchFilteredPlugins( - forType: PJ.self, - installedPlugins: installedPlugins, - searchText: searchText - ), - !filteredAvailablePlugins.isEmpty - { - Section(header: InlineHeader("Catalog")) { - ForEach(filteredAvailablePlugins, id: \.self) { availablePlugin in - PluginCatalogButtonView(availablePlugin: availablePlugin, needsUpdate: false) - } - } - } - } - .inlinedList(inset: 0) - .listStyle(.insetGrouped) - .id(UUID()) - .backport.onAppear { - pluginsEmpty = installedPlugins.isEmpty - } - .onChange(of: searchText) { _ in - sourcePredicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText) - } - .onChange(of: installedPlugins.count) { newCount in - pluginsEmpty = newCount == 0 } } + + if !installedPlugins.isEmpty { + Section(header: InlineHeader("Installed")) { + ForEach(installedPlugins, id: \.self) { installedPlugin in + InstalledPluginButtonView( + installedPlugin: installedPlugin, + showPluginOptions: $showPluginOptions, + selectedPlugin: $selectedPlugin + ) + } + } + } + + if + let filteredAvailablePlugins = pluginManager.fetchFilteredPlugins( + forType: PJ.self, + installedPlugins: installedPlugins, + searchText: searchText + ), + !filteredAvailablePlugins.isEmpty + { + Section(header: InlineHeader("Catalog")) { + ForEach(filteredAvailablePlugins, id: \.self) { availablePlugin in + PluginCatalogButtonView(availablePlugin: availablePlugin, needsUpdate: false) + } + } + } + } + .inlinedList(inset: 0) + .listStyle(.insetGrouped) + .onAppear { + fetchPredicate() + } + .onChange(of: searchText) { _ in + fetchPredicate() } .sheet(isPresented: $showPluginOptions) { PluginInfoView(selectedPlugin: $selectedPlugin) } } + + func fetchPredicate() { + installedPlugins.nsPredicate = searchText.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", searchText) + } } diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift index 0d5b5c5..4d10e73 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginInfoView.swift @@ -8,7 +8,7 @@ import SwiftUI struct PluginInfoView: View { - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @Binding var selectedPlugin: P? @@ -69,7 +69,7 @@ struct PluginInfoView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { - presentationMode.wrappedValue.dismiss() + dismiss() } } } diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginTagsView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginTagsView.swift index 568d873..e70fe74 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginTagsView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginTagsView.swift @@ -14,7 +14,7 @@ struct PluginTagsView: View { ScrollView(.horizontal, showsIndicators: false) { HStack { ForEach(tags, id: \.self) { tag in - Tag(name: tag.name, color: tag.colorHex.map { Color(hexadecimal: $0) }) + Tag(name: tag.name, color: tag.colorHex.map { Color(hex: $0) }) } } } diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift index 30ada8b..b730c98 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsApiView.swift @@ -31,7 +31,7 @@ struct SourceSettingsApiView: View { }) .autocorrectionDisabled(true) .autocapitalization(.none) - .backport.onAppear { + .onAppear { tempClientId = clientId.value ?? "" } } @@ -45,7 +45,7 @@ struct SourceSettingsApiView: View { }) .autocorrectionDisabled(true) .autocapitalization(.none) - .backport.onAppear { + .onAppear { tempClientSecret = clientSecret.value ?? "" } } diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift index dadcc97..5918871 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsBaseUrlView.swift @@ -28,7 +28,7 @@ struct SourceSettingsBaseUrlView: View { .keyboardType(.URL) .autocorrectionDisabled(true) .autocapitalization(.none) - .backport.onAppear { + .onAppear { tempBaseUrl = selectedSource.baseUrl ?? "" } } diff --git a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift index ca64b7a..b7e27aa 100644 --- a/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/Source/SourceSettingsMethodView.swift @@ -57,6 +57,6 @@ struct SourceSettingsMethodView: View { } } } - .backport.tint(.primary) + .tint(.primary) } } diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift index 736fdfc..7bde5ba 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchFilterHeaderView.swift @@ -42,8 +42,7 @@ struct SearchFilterHeaderView: View { } .id(debridManager.selectedDebridType) } - .padding(.horizontal, verticalSizeClass == .compact ? (Application.shared.osVersion.majorVersion > 14 ? 65 : 18) : 18) - .padding(.top, Application.shared.osVersion.majorVersion > 14 ? 0 : 10) + .padding(.horizontal, verticalSizeClass == .compact ? 65 : 18) } } } diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift index 89361a4..2f408b7 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift @@ -90,7 +90,7 @@ struct SearchResultButtonView: View { .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) } .disableInteraction(navModel.currentChoiceSheet != nil) - .backport.tint(.primary) + .tint(.primary) .conditionalContextMenu(id: existingBookmark) { ZStack { if let bookmark = existingBookmark { @@ -123,19 +123,19 @@ struct SearchResultButtonView: View { } } } - .backport.alert( - isPresented: $debridManager.showDeleteAlert, - title: "Caching file", - message: "RealDebrid is currently caching this file. Would you like to delete it? \n\nProgress can be checked on the RealDebrid website.", - buttons: [ - AlertButton("Yes", role: .destructive) { - Task { - await debridManager.deleteRdTorrent() - } - }, - AlertButton(role: .cancel) - ] - ) + .alert("Caching file", isPresented: $debridManager.showDeleteAlert) { + Button("Yes", role: .destructive) { + Task { + await debridManager.deleteRdTorrent() + } + } + Button("Cancel", role: .cancel) {} + } message: { + Text( + "RealDebrid is currently caching this file. Would you like to delete it? \n\n" + + "Progress can be checked on the RealDebrid website." + ) + } .onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { notification in // If the instance contains the deleted bookmark, remove it. if let deletedBookmark = notification.object as? Bookmark, @@ -145,7 +145,7 @@ struct SearchResultButtonView: View { existingBookmark = nil } } - .backport.onAppear { + .onAppear { // Only run a exists request if a bookmark isn't passed to the view if existingBookmark == nil, !runOnce { let bookmarkRequest = Bookmark.fetchRequest() diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultsView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultsView.swift new file mode 100644 index 0000000..a101209 --- /dev/null +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultsView.swift @@ -0,0 +1,95 @@ +// +// SearchResultsView.swift +// Ferrite +// +// Created by Brian Dashore on 3/28/23. +// + +import SwiftUI + +struct SearchResultsView: View { + @Environment(\.isSearching) var isSearching + @Environment(\.dismissSearch) var dismissSearch + + @EnvironmentObject var scrapingModel: ScrapingViewModel + @EnvironmentObject var navModel: NavigationViewModel + + @AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false + + @Binding var searchText: String + + @Binding var searchPrompt: String + @State private var lastSearchPromptIndex: Int = -1 + let searchBarTextArray: [String] = [ + "What's on your mind?", + "Discover something interesting", + "Find an engaging show", + "Feeling adventurous?", + "Look for something new", + "The classics are a good idea" + ] + + var body: some View { + ForEach(scrapingModel.searchResults, id: \.self) { result in + if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil { + SearchResultButtonView(result: result) + } + } + .onAppear { + searchPrompt = getSearchPrompt() + } + .onChange(of: searchText) { newText in + if newText.isEmpty, isSearching { + searchPrompt = getSearchPrompt() + } + } + .onChange(of: navModel.selectedTab) { tab in + // Cancel the search if tab is switched while search is in progress + if tab != .search, scrapingModel.runningSearchTask != nil { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask?.cancel() + scrapingModel.runningSearchTask = nil + dismissSearch() + } + } + .onChange(of: scrapingModel.searchResults) { _ in + // Cleans up any leftover search results in the event of an abrupt cancellation + if !isSearching { + scrapingModel.searchResults = [] + } + } + .onChange(of: isSearching) { newValue in + if !newValue { + scrapingModel.searchResults = [] + scrapingModel.runningSearchTask?.cancel() + scrapingModel.runningSearchTask = nil + } + } + .overlay { + if + scrapingModel.searchResults.isEmpty, + isSearching, + scrapingModel.runningSearchTask == nil + { + Text("No results found") + } + } + } + + // Fetches random searchbar text if enabled, otherwise deinit the last case value + func getSearchPrompt() -> String { + if usesRandomSearchText { + let num = Int.random(in: 0 ..< searchBarTextArray.count - 1) + if num == lastSearchPromptIndex { + lastSearchPromptIndex = num + 1 + return searchBarTextArray[safe: num + 1] ?? "Search" + } else { + lastSearchPromptIndex = num + return searchBarTextArray[safe: num] ?? "Search" + } + } else { + lastSearchPromptIndex = -1 + return "Search" + } + } +} diff --git a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift index b6488b7..7083e04 100644 --- a/Ferrite/Views/ComponentViews/Settings/BackupsView.swift +++ b/Ferrite/Views/ComponentViews/Settings/BackupsView.swift @@ -34,7 +34,7 @@ struct BackupsView: View { Label("Export", systemImage: "square.and.arrow.up") } } - .backport.tint(.primary) + .tint(.primary) } .onDelete { offsets in for index in offsets { @@ -48,7 +48,7 @@ struct BackupsView: View { .listStyle(.insetGrouped) } } - .backport.onAppear { + .onAppear { backupManager.backupUrls = FileManager.default.appDirectory .appendingPathComponent("Backups", isDirectory: true).contentsByDateAdded } diff --git a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift index b5fbe44..749d960 100644 --- a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift +++ b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift @@ -40,46 +40,11 @@ struct DefaultActionPickerView: View { // Handle custom here ForEach(actions.filter { $0.requires.contains(actionRequirement.rawValue) }, id: \.id) { action in - Button { - if let actionListId = action.listId?.uuidString { - defaultAction = .custom(name: action.name, listId: actionListId) - } else { - logManager.error( - "Default action: This action doesn't have a corresponding plugin list! Please uninstall the action" - ) - } - } label: { - HStack { - VStack(alignment: .leading, spacing: 5) { - Text(action.name) - - Group { - if let pluginList = pluginLists.first(where: { $0.id == action.listId }) { - Text("List: \(pluginList.name)") - - Text(pluginList.id.uuidString) - .font(.caption) - } else { - Text("No plugin list found") - .font(.caption) - } - } - .foregroundColor(.secondary) - .lineLimit(1) - } - Spacer() - - if - case let .custom(name, listId) = defaultAction, - action.listId?.uuidString == listId, - action.name == name - { - Image(systemName: "checkmark") - .foregroundColor(.blue) - } - } - } - .backport.tint(.primary) + CustomChoiceButton( + action: action, + defaultAction: $defaultAction, + associatedPluginList: pluginLists.first(where: { $0.id == action.listId }) + ) } } .listStyle(.insetGrouped) @@ -89,6 +54,59 @@ struct DefaultActionPickerView: View { } } +private struct CustomChoiceButton: View { + @EnvironmentObject var logManager: LoggingManager + + @ObservedObject var action: Action + + @Binding var defaultAction: DefaultAction + + var associatedPluginList: PluginList? + + var body: some View { + Button { + if let actionListId = action.listId?.uuidString { + defaultAction = .custom(name: action.name, listId: actionListId) + } else { + logManager.error( + "Default action: This action doesn't have a corresponding plugin list! Please uninstall the action" + ) + } + } label: { + HStack { + VStack(alignment: .leading, spacing: 5) { + Text(action.name) + + Group { + if let associatedPluginList { + Text("List: \(associatedPluginList.name)") + + Text(associatedPluginList.id.uuidString) + .font(.caption) + } else { + Text("No plugin list found") + .font(.caption) + } + } + .foregroundColor(.secondary) + .lineLimit(1) + } + Spacer() + + if + case let .custom(name, listId) = defaultAction, + action.listId?.uuidString == listId, + action.name == name + { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } + .tint(.primary) + } +} + private struct DefaultChoiceButton: View { @Binding var defaultAction: DefaultAction let selectedOption: DefaultAction @@ -107,7 +125,7 @@ private struct DefaultChoiceButton: View { } } } - .backport.tint(.primary) + .tint(.primary) } func fetchButtonName() -> String { diff --git a/Ferrite/Views/ComponentViews/Settings/Kodi/KodiEditorView.swift b/Ferrite/Views/ComponentViews/Settings/Kodi/KodiEditorView.swift index 86e1411..de33bb0 100644 --- a/Ferrite/Views/ComponentViews/Settings/Kodi/KodiEditorView.swift +++ b/Ferrite/Views/ComponentViews/Settings/Kodi/KodiEditorView.swift @@ -8,7 +8,7 @@ import SwiftUI struct KodiEditorView: View { - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var pluginManager: PluginManager @@ -56,7 +56,7 @@ struct KodiEditorView: View { .autocapitalization(.none) .id(loadedSelectedServer) } - .backport.onAppear { + .onAppear { if let selectedKodiServer = navModel.selectedKodiServer { serverUrl = selectedKodiServer.urlString friendlyName = selectedKodiServer.name @@ -66,17 +66,17 @@ struct KodiEditorView: View { loadedSelectedServer.toggle() } } - .backport.alert( - isPresented: $showErrorAlert, - title: "Error", - message: errorAlertText - ) + .alert("Error", isPresented: $showErrorAlert) { + Button("OK", role: .cancel) {} + } message: { + Text(errorAlertText) + } .navigationTitle("Editing Kodi Server") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { - presentationMode.wrappedValue.dismiss() + dismiss() } } @@ -91,7 +91,7 @@ struct KodiEditorView: View { existingServer: navModel.selectedKodiServer ) - presentationMode.wrappedValue.dismiss() + dismiss() } catch { logManager.error("Editing Kodi server: \(error)", showToast: false) errorAlertText = error.localizedDescription diff --git a/Ferrite/Views/ComponentViews/Settings/Kodi/KodiServerView.swift b/Ferrite/Views/ComponentViews/Settings/Kodi/KodiServerView.swift index 8a02425..4623a4b 100644 --- a/Ferrite/Views/ComponentViews/Settings/Kodi/KodiServerView.swift +++ b/Ferrite/Views/ComponentViews/Settings/Kodi/KodiServerView.swift @@ -15,7 +15,6 @@ struct KodiServerView: View { @State private var isActive = false @State private var pingInProgress = false - @State private var viewTask: Task? var body: some View { HStack { @@ -30,23 +29,18 @@ struct KodiServerView: View { .frame(width: 10, height: 10) } } - .backport.onAppear { - viewTask = Task { - pingInProgress = true + .task { + pingInProgress = true - do { - try await pluginManager.kodi.ping(server: server) - isActive = true - } catch { - logManager.error("Kodi server \(server.name): \(error)", showToast: false) - isActive = false - } - - pingInProgress = false + do { + try await pluginManager.kodi.ping(server: server) + isActive = true + } catch { + logManager.error("Kodi server \(server.name): \(error)", showToast: false) + isActive = false } - } - .onDisappear { - viewTask?.cancel() + + pingInProgress = false } } } diff --git a/Ferrite/Views/ComponentViews/Settings/Kodi/SettingsKodiView.swift b/Ferrite/Views/ComponentViews/Settings/Kodi/SettingsKodiView.swift index ddf0693..ef074ab 100644 --- a/Ferrite/Views/ComponentViews/Settings/Kodi/SettingsKodiView.swift +++ b/Ferrite/Views/ComponentViews/Settings/Kodi/SettingsKodiView.swift @@ -12,11 +12,7 @@ struct SettingsKodiView: View { @EnvironmentObject var navModel: NavigationViewModel - // TODO: Change to var in v0.7 - @FetchRequest( - entity: KodiServer.entity(), - sortDescriptors: [] - ) var kodiServers: FetchedResults + var kodiServers: FetchedResults @State private var presentEditSheet = false @@ -48,20 +44,11 @@ struct SettingsKodiView: View { Image(systemName: "pencil") } - if #available(iOS 15.0, *) { - Button(role: .destructive) { - PersistenceController.shared.delete(server, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } - } else { - Button { - PersistenceController.shared.delete(server, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } + Button(role: .destructive) { + PersistenceController.shared.delete(server, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") } } } @@ -78,7 +65,6 @@ struct SettingsKodiView: View { .listStyle(.insetGrouped) .sheet(isPresented: $presentEditSheet) { KodiEditorView() - .environmentObject(navModel) } .navigationTitle("Kodi") .navigationBarTitleDisplayMode(.inline) diff --git a/Ferrite/Views/ComponentViews/Settings/PluginList/PluginListEditorView.swift b/Ferrite/Views/ComponentViews/Settings/PluginList/PluginListEditorView.swift index ae439e9..ceb8fcd 100644 --- a/Ferrite/Views/ComponentViews/Settings/PluginList/PluginListEditorView.swift +++ b/Ferrite/Views/ComponentViews/Settings/PluginList/PluginListEditorView.swift @@ -8,7 +8,7 @@ import SwiftUI struct PluginListEditorView: View { - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @EnvironmentObject var navModel: NavigationViewModel @EnvironmentObject var pluginManager: PluginManager @@ -33,23 +33,23 @@ struct PluginListEditorView: View { .autocapitalization(.none) .id(loadedSelectedList) } - .backport.onAppear { + .onAppear { if let selectedList = navModel.selectedPluginList { pluginListUrl = selectedList.urlString loadedSelectedList.toggle() } } - .backport.alert( - isPresented: $showUrlErrorAlert, - title: "Error", - message: urlErrorAlertText - ) + .alert("Error", isPresented: $showUrlErrorAlert) { + Button("OK", role: .cancel) {} + } message: { + Text(urlErrorAlertText) + } .navigationTitle("Editing Plugin List") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { - presentationMode.wrappedValue.dismiss() + dismiss() } } @@ -62,7 +62,7 @@ struct PluginListEditorView: View { existingPluginList: navModel.selectedPluginList ) - presentationMode.wrappedValue.dismiss() + dismiss() } catch { logManager.error("Editing plugin list: \(error)", showToast: false) urlErrorAlertText = error.localizedDescription diff --git a/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift b/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift index 779036d..7391944 100644 --- a/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift +++ b/Ferrite/Views/ComponentViews/Settings/PluginList/SettingsPluginListView.swift @@ -48,20 +48,11 @@ struct SettingsPluginListView: View { Image(systemName: "pencil") } - if #available(iOS 15.0, *) { - Button(role: .destructive) { - PersistenceController.shared.delete(pluginList, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } - } else { - Button { - PersistenceController.shared.delete(pluginList, context: backgroundContext) - } label: { - Text("Remove") - Image(systemName: "trash") - } + Button(role: .destructive) { + PersistenceController.shared.delete(pluginList, context: backgroundContext) + } label: { + Text("Remove") + Image(systemName: "trash") } } } @@ -83,7 +74,6 @@ struct SettingsPluginListView: View { .presentationDetents([.medium]) } else { PluginListEditorView() - .environmentObject(navModel) } } .navigationTitle("Plugin Lists") diff --git a/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift b/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift index 1e595f6..5e31365 100644 --- a/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift +++ b/Ferrite/Views/ComponentViews/Settings/SettingsAppVersionView.swift @@ -10,7 +10,6 @@ import SwiftUI struct SettingsAppVersionView: View { @EnvironmentObject var logManager: LoggingManager - @State private var viewTask: Task? @State private var releases: [Github.Release] = [] @State private var loadedReleases = false @@ -30,25 +29,20 @@ struct SettingsAppVersionView: View { .listStyle(.insetGrouped) } } - .backport.onAppear { - viewTask = Task { - do { - if let fetchedReleases = try await Github().fetchReleases() { - releases = fetchedReleases - } else { - logManager.error("Github: No releases found") - } - } catch { - logManager.error("Github: \(error)") - } - - withAnimation { - loadedReleases = true + .task { + do { + if let fetchedReleases = try await Github().fetchReleases() { + releases = fetchedReleases + } else { + logManager.error("Github: No releases found") } + } catch { + logManager.error("Github: \(error)") + } + + withAnimation { + loadedReleases = true } - } - .onDisappear { - viewTask?.cancel() } .navigationTitle("Version History") .navigationBarTitleDisplayMode(.inline) diff --git a/Ferrite/Views/ComponentViews/Settings/SettingsLogView.swift b/Ferrite/Views/ComponentViews/Settings/SettingsLogView.swift index 255fe34..d3235ff 100644 --- a/Ferrite/Views/ComponentViews/Settings/SettingsLogView.swift +++ b/Ferrite/Views/ComponentViews/Settings/SettingsLogView.swift @@ -23,11 +23,11 @@ struct SettingsLogView: View { } } .listStyle(.plain) - .backport.alert( - isPresented: $logManager.showLogExportedAlert, - title: "Success", - message: "Log successfully exported in Ferrite's logs folder" - ) + .alert("Success", isPresented: $logManager.showLogExportedAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Log successfully exported in Ferrite's logs folder") + } .navigationTitle("Logs") .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -39,18 +39,10 @@ struct SettingsLogView: View { Label("Export", systemImage: "square.and.arrow.up") } - if #available(iOS 15, *) { - Button(role: .destructive) { - logManager.messageArray = [] - } label: { - Label("Clear session logs", systemImage: "trash") - } - } else { - Button { - logManager.messageArray = [] - } label: { - Label("Clear session logs", systemImage: "trash") - } + Button(role: .destructive) { + logManager.messageArray = [] + } label: { + Label("Clear session logs", systemImage: "trash") } } label: { Image(systemName: "ellipsis") diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index c56c508..a6fc8c6 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftUIX struct ContentView: View { @EnvironmentObject var scrapingModel: ScrapingViewModel @@ -16,133 +15,41 @@ struct ContentView: View { @EnvironmentObject var logManager: LoggingManager @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch: Bool = false - @AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false - @State private var isEditingSearch = false - @State private var isSearching = false @State private var searchText: String = "" - - @State private var lastSearchTextIndex: Int = -1 - @State private var searchBarText: String = "Search" - let searchBarTextArray: [String] = [ - "What's on your mind?", - "Discover something interesting", - "Find an engaging show", - "Feeling adventurous?", - "Look for something new", - "The classics are a good idea" - ] + @State private var searchPrompt: String = "Search" var body: some View { NavView { List { - ForEach(scrapingModel.searchResults, id: \.self) { result in - if result.source == scrapingModel.filteredSource?.name || scrapingModel.filteredSource == nil { - SearchResultButtonView(result: result) - } - } + SearchResultsView(searchText: $searchText, searchPrompt: $searchPrompt) } .listStyle(.insetGrouped) - .inlinedList(inset: Application.shared.osVersion.majorVersion > 14 ? 20 : -20) - .overlay { - if - scrapingModel.searchResults.isEmpty, - isSearching, - scrapingModel.runningSearchTask == nil - { - Text("No results found") - } - } - .onChange(of: searchText) { newText in - if newText.isEmpty, isSearching { - searchBarText = getSearchBarText() - } - } - .onChange(of: scrapingModel.searchResults) { _ in - // Cleans up any leftover search results in the event of an abrupt cancellation - if !isSearching { - scrapingModel.searchResults = [] - } - } - .onChange(of: navModel.selectedTab) { tab in - // Cancel the search if tab is switched while search is in progress - if tab != .search, scrapingModel.runningSearchTask != nil { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask?.cancel() - scrapingModel.runningSearchTask = nil - isSearching = false - searchText = "" - } - } + .inlinedList(inset: 20) .navigationTitle("Search") - .navigationSearchBar { - SearchBar( - searchBarText, - text: $searchText, - isEditing: $isEditingSearch, - onCommit: { - if let runningSearchTask = scrapingModel.runningSearchTask, runningSearchTask.isCancelled { - scrapingModel.runningSearchTask = nil - return - } - - scrapingModel.runningSearchTask = Task { - isSearching = true - - let sources = pluginManager.fetchInstalledSources() - await scrapingModel.scanSources( - sources: sources, - searchText: searchText, - debridManager: debridManager - ) - - logManager.hideIndeterminateToast() - scrapingModel.runningSearchTask = nil - } - } - ) - .showsCancelButton(isEditingSearch || isSearching) - .onCancel { - scrapingModel.searchResults = [] - scrapingModel.runningSearchTask?.cancel() + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: Text(searchPrompt)) + .onSubmit(of: .search) { + if let runningSearchTask = scrapingModel.runningSearchTask, runningSearchTask.isCancelled { + scrapingModel.runningSearchTask = nil + return + } + + scrapingModel.runningSearchTask = Task { + let sources = pluginManager.fetchInstalledSources() + await scrapingModel.scanSources( + sources: sources, + searchText: searchText, + debridManager: debridManager + ) + + logManager.hideIndeterminateToast() scrapingModel.runningSearchTask = nil - isSearching = false - searchText = "" - searchBarText = getSearchBarText() } } - .autocorrectionDisabled(!autocorrectSearch) - .backport.introspectSearchController { searchController in - // TODO: Replace with SwiftUI autocapitalization modifier - searchController.searchBar.autocapitalizationType = .none - } - .navigationSearchBarHiddenWhenScrolling(false) .customScopeBar { SearchFilterHeaderView() - .environmentObject(scrapingModel) - .environmentObject(debridManager) } } - .backport.onAppear { - searchBarText = getSearchBarText() - } - } - - // Fetches random searchbar text if enabled, otherwise deinit the last case value - func getSearchBarText() -> String { - if usesRandomSearchText { - let num = Int.random(in: 0 ..< searchBarTextArray.count - 1) - if num == lastSearchTextIndex { - lastSearchTextIndex = num + 1 - return searchBarTextArray[safe: num + 1] ?? "Search" - } else { - lastSearchTextIndex = num - return searchBarTextArray[safe: num] ?? "Search" - } - } else { - lastSearchTextIndex = -1 - return "Search" - } } } diff --git a/Ferrite/Views/LibraryView.swift b/Ferrite/Views/LibraryView.swift index 7cce682..afa8c1b 100644 --- a/Ferrite/Views/LibraryView.swift +++ b/Ferrite/Views/LibraryView.swift @@ -6,55 +6,64 @@ // import SwiftUI -import SwiftUIX struct LibraryView: View { @EnvironmentObject var debridManager: DebridManager @EnvironmentObject var navModel: NavigationViewModel - @State private var bookmarksEmpty = false - @State private var historyEmpty = false + @FetchRequest( + entity: Bookmark.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \Bookmark.orderNum, ascending: true)] + ) var bookmarks: FetchedResults + + @FetchRequest( + entity: HistoryEntry.entity(), + sortDescriptors: [] + ) var allHistoryEntries: FetchedResults @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true @State private var editMode: EditMode = .inactive - @State private var searchText: String = "" - @State private var isEditingSearch = false + // Bound to the isSearching environment var @State private var isSearching = false + @State private var searchText: String = "" var body: some View { NavView { ZStack { switch navModel.libraryPickerSelection { case .bookmarks: - BookmarksView(searchText: $searchText, bookmarksEmpty: $bookmarksEmpty) + BookmarksView(searchText: $searchText, bookmarks: bookmarks) case .history: - HistoryView(searchText: $searchText, historyEmpty: $historyEmpty) + HistoryView(allHistoryEntries: allHistoryEntries, searchText: $searchText) case .debridCloud: DebridCloudView(searchText: $searchText) } } + .searchListener(isSearching: $isSearching) .overlay { - switch navModel.libraryPickerSelection { - case .bookmarks: - if bookmarksEmpty { - EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results") - } - case .history: - if historyEmpty { - 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") + if !isSearching { + switch navModel.libraryPickerSelection { + case .bookmarks: + if bookmarks.isEmpty { + EmptyInstructionView(title: "No Bookmarks", message: "Add a bookmark from search results") + } + case .history: + if allHistoryEntries.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) { + HStack { Spacer() EditButton() @@ -72,21 +81,9 @@ struct LibraryView: View { } } } - .navigationSearchBar { - SearchBar("Search", text: $searchText, isEditing: $isEditingSearch, onCommit: { - isSearching = true - }) - .showsCancelButton(isEditingSearch || isSearching) - .onCancel { - searchText = "" - isSearching = false - } - } - .navigationSearchBarHiddenWhenScrolling(false) + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) .customScopeBar { LibraryPickerView() - .environmentObject(debridManager) - .environmentObject(navModel) } .environment(\.editMode, $editMode) } diff --git a/Ferrite/Views/LoginWebView.swift b/Ferrite/Views/LoginWebView.swift index 4a8009f..c54ea07 100644 --- a/Ferrite/Views/LoginWebView.swift +++ b/Ferrite/Views/LoginWebView.swift @@ -8,7 +8,7 @@ import SwiftUI struct LoginWebView: View { - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss var url: URL var body: some View { @@ -19,7 +19,7 @@ struct LoginWebView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { - presentationMode.wrappedValue.dismiss() + dismiss() } } } diff --git a/Ferrite/Views/MainView.swift b/Ferrite/Views/MainView.swift index faea558..98a2e96 100644 --- a/Ferrite/Views/MainView.swift +++ b/Ferrite/Views/MainView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftUIX struct MainView: View { @EnvironmentObject var navModel: NavigationViewModel @@ -21,7 +20,6 @@ struct MainView: View { @State private var showUpdateAlert = false @State private var releaseVersionString: String = "" @State private var releaseUrlString: String = "" - @State private var viewTask: Task? var body: some View { TabView(selection: $navModel.selectedTab) { @@ -53,78 +51,67 @@ struct MainView: View { switch item { case .action: ActionChoiceView() - .environmentObject(debridManager) - .environmentObject(scrapingModel) - .environmentObject(navModel) - .environmentObject(pluginManager) - .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext) case .batch: BatchChoiceView() - .environmentObject(debridManager) - .environmentObject(scrapingModel) - .environmentObject(navModel) case .activity: + EmptyView() + // TODO: Fix share sheet if #available(iOS 16, *) { - AppActivityView(activityItems: navModel.activityItems) + ShareSheet(activityItems: navModel.activityItems) .presentationDetents([.medium, .large]) } else { - AppActivityView(activityItems: navModel.activityItems) + ShareSheet(activityItems: navModel.activityItems) } } } - .backport.onAppear { + .onAppear { + logManager.info("Ferrite started") + } + .task { if autoUpdateNotifs, Application.shared.osVersion.toString() >= Application.shared.minVersion { // MARK: If scope bar duplication happens, this may be the problem + // Sleep for 2 seconds to allow for view layout and app init + try? await Task.sleep(seconds: 2) - logManager.info("Ferrite started") - - viewTask = Task { - // Sleep for 2 seconds to allow for view layout and app init - try? await Task.sleep(seconds: 2) - - do { - guard let latestRelease = try await Github().fetchLatestRelease() else { - logManager.error( - "Github: No releases found", - description: "Github error: No releases found" - ) - return - } - - let releaseVersion = String(latestRelease.tagName.dropFirst()) - if releaseVersion > Application.shared.appVersion { - releaseVersionString = latestRelease.tagName - releaseUrlString = latestRelease.htmlUrl - - logManager.info("Update available to \(releaseVersionString)") - showUpdateAlert.toggle() - } - } catch { - let error = error as NSError - - if error.code == -1009 { - logManager.info( - "Github: The connection is offline", - description: "The connection is offline" - ) - } else { - logManager.error( - "Github: \(error)", - description: "A Github error was logged" - ) - } + do { + guard let latestRelease = try await Github().fetchLatestRelease() else { + logManager.error( + "Github: No releases found", + description: "Github error: No releases found" + ) + return } - logManager.info("Github release updates checked") + let releaseVersion = String(latestRelease.tagName.dropFirst()) + if releaseVersion > Application.shared.appVersion { + releaseVersionString = latestRelease.tagName + releaseUrlString = latestRelease.htmlUrl + + logManager.info("Update available to \(releaseVersionString)") + showUpdateAlert.toggle() + } + } catch { + let error = error as NSError + + if error.code == -1009 { + logManager.info( + "Github: The connection is offline", + description: "The connection is offline" + ) + } else { + logManager.error( + "Github: \(error)", + description: "A Github error was logged" + ) + } } + + logManager.info("Github release updates checked") } } - .onDisappear { - viewTask?.cancel() - } .onOpenURL { url in if url.scheme == "file" { // Attempt to copy to backups directory if backup doesn't exist @@ -134,54 +121,51 @@ struct MainView: View { } } // Global alerts and dialogs for backups - .backport.confirmationDialog( + .confirmationDialog( + "Restore backup?", isPresented: $backupManager.showRestoreAlert, - title: "Restore backup?", - message: - "Merge (preferred): Will merge your current data with the backup \n\n" + - "Overwrite: Will delete and replace all your data \n\n" + - "If Merge causes app instability, uninstall Ferrite and use the Overwrite option.", - buttons: [ - .init("Merge", role: .destructive) { - Task { - await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: false) - } - }, - .init("Overwrite", role: .destructive) { - Task { - await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: true) - } + titleVisibility: .visible + ) { + Button("Merge", role: .destructive) { + Task { + await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: false) } - ] - ) - .backport.alert( - isPresented: $backupManager.showRestoreCompletedAlert, - title: "Backup restored", - message: backupManager.restoreCompletedMessage.joined(separator: " \n\n"), - buttons: [ - .init("OK") { - backupManager.restoreCompletedMessage = [] + } + Button("Overwrite", role: .destructive) { + Task { + await backupManager.restoreBackup(pluginManager: pluginManager, doOverwrite: true) } - ] - ) + } + } message: { + Text( + "Merge (preferred): Will merge your current data with the backup \n\n" + + "Overwrite: Will delete and replace all your data \n\n" + + "If Merge causes app instability, uninstall Ferrite and use the Overwrite option." + ) + } + .alert("Backup restored", isPresented: $backupManager.showRestoreCompletedAlert) { + Button("OK", role: .cancel) { + backupManager.restoreCompletedMessage = [] + } + } message: { + Text(backupManager.restoreCompletedMessage.joined(separator: " \n\n")) + } // Updater alert - .backport.alert( - isPresented: $showUpdateAlert, - title: "Update available", - message: - "Ferrite \(releaseVersionString) can be downloaded. \n\n" + - "This alert can be disabled in Settings.", - buttons: [ - .init("Download") { - guard let releaseUrl = URL(string: releaseUrlString) else { - return - } + .alert("Update available", isPresented: $showUpdateAlert) { + Button("Download") { + guard let releaseUrl = URL(string: releaseUrlString) else { + return + } - UIApplication.shared.open(releaseUrl) - }, - .init(role: .cancel) - ] - ) + UIApplication.shared.open(releaseUrl) + } + Button("Cancel", role: .cancel) {} + } message: { + Text( + "Ferrite \(releaseVersionString) can be downloaded. \n\n" + + "This alert can be disabled in Settings." + ) + } .overlay { VStack { Spacer() @@ -198,9 +182,7 @@ struct MainView: View { } .padding(12) .font(.caption) - .background { - VisualEffectBlurView(blurStyle: .systemThinMaterial) - } + .background(.thinMaterial) .cornerRadius(10) } @@ -222,9 +204,7 @@ struct MainView: View { } .padding(12) .font(.caption) - .background { - VisualEffectBlurView(blurStyle: .systemThinMaterial) - } + .background(.thinMaterial) .cornerRadius(10) .frame(width: 200) } diff --git a/Ferrite/Views/PluginsView.swift b/Ferrite/Views/PluginsView.swift index a763b45..1f356c1 100644 --- a/Ferrite/Views/PluginsView.swift +++ b/Ferrite/Views/PluginsView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftUIX struct PluginsView: View { @EnvironmentObject var pluginManager: PluginManager @@ -14,16 +13,22 @@ struct PluginsView: View { @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch = true - @State private var installedSourcesEmpty = false - @State private var installedActionsEmpty = false + @FetchRequest( + entity: Source.entity(), + sortDescriptors: [] + ) var installedSources: FetchedResults + + @FetchRequest( + entity: Action.entity(), + sortDescriptors: [] + ) var installedActions: FetchedResults + @State private var checkedForPlugins = false - @State private var isEditingSearch = false + // Bound to the isSearching environment var @State private var isSearching = false @State private var searchText: String = "" - @State private var viewTask: Task? - var body: some View { NavView { ZStack { @@ -31,58 +36,47 @@ struct PluginsView: View { switch navModel.pluginPickerSelection { case .sources: PluginAggregateView( - searchText: $searchText, - pluginsEmpty: $installedSourcesEmpty + installedPlugins: installedSources, + searchText: $searchText ) case .actions: PluginAggregateView( - searchText: $searchText, - pluginsEmpty: $installedActionsEmpty + installedPlugins: installedActions, + searchText: $searchText ) } } } + .searchListener(isSearching: $isSearching) .overlay { - if checkedForPlugins { - switch navModel.pluginPickerSelection { - case .sources: - if installedSourcesEmpty, pluginManager.availableSources.isEmpty { - EmptyInstructionView(title: "No Sources", message: "Add a plugin list in Settings") - } - case .actions: - if installedActionsEmpty, pluginManager.availableActions.isEmpty { - EmptyInstructionView(title: "No Actions", message: "Add a plugin list in Settings") + if !isSearching { + if checkedForPlugins { + switch navModel.pluginPickerSelection { + case .sources: + if installedSources.isEmpty, pluginManager.availableSources.isEmpty { + EmptyInstructionView(title: "No Sources", message: "Add a plugin list in Settings") + } + case .actions: + if installedActions.isEmpty, pluginManager.availableActions.isEmpty { + EmptyInstructionView(title: "No Actions", message: "Add a plugin list in Settings") + } } + } else { + ProgressView() } - } else { - ProgressView() } } - .backport.onAppear { - viewTask = Task { - await pluginManager.fetchPluginsFromUrl() - checkedForPlugins = true - } + .task { + await pluginManager.fetchPluginsFromUrl() + checkedForPlugins = true } .onDisappear { - viewTask?.cancel() checkedForPlugins = false } .navigationTitle("Plugins") - .navigationSearchBar { - SearchBar("Search", text: $searchText, isEditing: $isEditingSearch, onCommit: { - isSearching = true - }) - .showsCancelButton(isEditingSearch || isSearching) - .onCancel { - searchText = "" - isSearching = false - } - } - .navigationSearchBarHiddenWhenScrolling(false) + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) .customScopeBar { PluginPickerView() - .environmentObject(navModel) } } } diff --git a/Ferrite/Views/RepresentableViews/ShareSheet.swift b/Ferrite/Views/RepresentableViews/ShareSheet.swift new file mode 100644 index 0000000..6748b8a --- /dev/null +++ b/Ferrite/Views/RepresentableViews/ShareSheet.swift @@ -0,0 +1,18 @@ +// +// ShareSheet.swift +// Ferrite +// +// Created by Brian Dashore on 3/28/23. +// + +import SwiftUI + +struct ShareSheet: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} diff --git a/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift b/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift deleted file mode 100644 index c643486..0000000 --- a/Ferrite/Views/RepresentableViews/ViewDidAppearHandler.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// ViewDidAppearHandler.swift -// Ferrite -// -// Created by Brian Dashore on 2/8/23. -// -// UIKit onAppear hook to fix onAppear behavior in iOS 14 -// - -import SwiftUI - -struct ViewDidAppearHandler: UIViewControllerRepresentable { - let callback: () -> Void - - class Coordinator: UIViewController { - let callback: () -> Void - - init(callback: @escaping () -> Void) { - self.callback = callback - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - callback() - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(callback: callback) - } - - func makeUIViewController(context: Context) -> UIViewController { - context.coordinator - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} -} diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index 00e1cd5..2d1ea5f 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -53,7 +53,7 @@ struct SettingsView: View { } Section(header: InlineHeader("Playback services")) { - NavigationLink(destination: SettingsKodiView(), label: { + NavigationLink(destination: SettingsKodiView(kodiServers: kodiServers), label: { HStack { Text("Kodi") Spacer() diff --git a/Ferrite/Views/SheetViews/ActionChoiceView.swift b/Ferrite/Views/SheetViews/ActionChoiceView.swift index d9b323d..73e5751 100644 --- a/Ferrite/Views/SheetViews/ActionChoiceView.swift +++ b/Ferrite/Views/SheetViews/ActionChoiceView.swift @@ -6,10 +6,9 @@ // import SwiftUI -import SwiftUIX struct ActionChoiceView: View { - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var debridManager: DebridManager @@ -32,7 +31,7 @@ struct ActionChoiceView: View { var body: some View { NavView { Form { - Section(header: "Now Playing") { + Section(header: InlineHeader("Now Playing")) { VStack(alignment: .leading, spacing: 5) { Text(navModel.selectedTitle) .font(.callout) @@ -47,7 +46,7 @@ struct ActionChoiceView: View { } if !debridManager.downloadUrl.isEmpty { - Section(header: "Debrid options") { + Section(header: InlineHeader("Debrid options")) { ForEach(actions, id: \.id) { action in if action.requires.contains(ActionRequirement.debrid.rawValue) { ListRowButtonView(action.name, systemImage: "arrow.up.forward.app.fill") { @@ -66,22 +65,21 @@ struct ActionChoiceView: View { } label: { KodiServerView(server: server) } - .backport.tint(.primary) + .tint(.primary) } } - .backport.tint(.secondary) + .tint(.secondary) } ListRowButtonView("Copy download URL", systemImage: "doc.on.doc.fill") { UIPasteboard.general.string = debridManager.downloadUrl showLinkCopyAlert.toggle() } - .backport.alert( - isPresented: $showLinkCopyAlert, - title: "Copied", - message: "Download link copied successfully", - buttons: [AlertButton("OK")] - ) + .alert("Copied", isPresented: $showLinkCopyAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Download link copied successfully") + } ListRowButtonView("Share download URL", systemImage: "square.and.arrow.up.fill") { if let url = URL(string: debridManager.downloadUrl) { @@ -93,7 +91,7 @@ struct ActionChoiceView: View { } if !navModel.resultFromCloud { - Section(header: "Magnet options") { + Section(header: InlineHeader("Magnet options")) { ForEach(actions, id: \.id) { action in if action.requires.contains(ActionRequirement.magnet.rawValue) { ListRowButtonView(action.name, systemImage: "arrow.up.forward.app.fill") { @@ -106,12 +104,11 @@ struct ActionChoiceView: View { UIPasteboard.general.string = navModel.selectedMagnet?.link showMagnetCopyAlert.toggle() } - .backport.alert( - isPresented: $showMagnetCopyAlert, - title: "Copied", - message: "Magnet link copied successfully", - buttons: [AlertButton("OK")] - ) + .alert("Copied", isPresented: $showMagnetCopyAlert) { + Button("OK", role: .cancel) {} + } message: { + Text("Magnet link copied successfully") + } ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") { if let magnetLink = navModel.selectedMagnet?.link, @@ -124,25 +121,26 @@ struct ActionChoiceView: View { } } } - .backport.tint(.primary) + .tint(.primary) .sheet(isPresented: $navModel.showLocalActivitySheet) { + // TODO: Fix share sheet if #available(iOS 16, *) { - AppActivityView(activityItems: navModel.activityItems) + ShareSheet(activityItems: navModel.activityItems) .presentationDetents([.medium, .large]) } else { - AppActivityView(activityItems: navModel.activityItems) + ShareSheet(activityItems: navModel.activityItems) } } - .backport.alert( - isPresented: $pluginManager.showActionSuccessAlert, - title: "Action successful", - message: pluginManager.actionSuccessAlertMessage - ) - .backport.alert( - isPresented: $pluginManager.showActionErrorAlert, - title: "Action error", - message: pluginManager.actionErrorAlertMessage - ) + .alert("Action successful", isPresented: $pluginManager.showActionSuccessAlert) { + Button("OK", role: .cancel) {} + } message: { + Text(pluginManager.actionSuccessAlertMessage) + } + .alert("Action error", isPresented: $pluginManager.showActionErrorAlert) { + Button("OK", role: .cancel) {} + } message: { + Text(pluginManager.actionErrorAlertMessage) + } .onDisappear { debridManager.downloadUrl = "" navModel.selectedTitle = "" @@ -158,7 +156,7 @@ struct ActionChoiceView: View { navModel.selectedTitle = "" navModel.selectedBatchTitle = "" - presentationMode.wrappedValue.dismiss() + dismiss() } } } diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index c22c359..0dfb6ee 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -48,7 +48,7 @@ struct BatchChoiceView: View { EmptyView() } } - .backport.tint(.primary) + .tint(.primary) .listStyle(.insetGrouped) .inlinedList(inset: -20) .navigationTitle("Select a file")