Compare commits

..

17 commits
v0.7.2 ... next

Author SHA1 Message Date
kingbri
f4184cf1b9 Search: Remove ConditionalContextMenu
Was required for iOS 15 to properly update its state. This is no longer
a requirement.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
cfc4a74afe Tree: Remove InlineHeader
Was a workaround for iOS 15. No longer required.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
7bb4ed5f7c Tree: Switch to NavigationStack
Since minVersion is iOS 16, remove the compatability view.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
f40f71bca3 Tree: Remove iOS 16 conditionals
iOS 16 is now the minimum version for the project.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
68a7c60c2d Dependencies: Update SwiftUI-Introspect
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
8b00d11e44 Ferrite: Minimum iOS 16
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:26 -05:00
kingbri
9d7bc9b314 Debrid: Update AllDebrid description
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:34:12 -05:00
kingbri
25bff02875 Tree: Format
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:08:02 -05:00
kingbri
20dd00fa85 Debrid: Fix fetching for AllDebrid
User now has to manually unrestrict batches similar to how RealDebrid
and OffCloud work.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 23:07:24 -05:00
kingbri
f9d2f38329 Ferrite: Bump version
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 22:11:03 -05:00
kingbri
a7e20f30e6 Settings: Properly report login status of Debrid services
Since protocols can't be observed in SwiftUI, use a roundabout way
to check if a user is logged in. Maybe this should be changed in the future,
but it works for now.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 21:33:27 -05:00
kingbri
ecf92239d2 Debrid: Add new IA method for AllDebrid and fix cache fetch
The new AllDebrid IA method follows the same behavior as RealDebrid.

Only add user magnets into the IA if they're actually cached and
not caching into the service.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-27 18:20:04 -05:00
kingbri
dd54ec027b Tree: Format
Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-26 23:43:06 -05:00
kingbri
84357ea2c5 Debrid: Update IA fetching for RealDebrid
To avoid inflating the IA value cache, restore the TTL logic and only
append IAs that are part of the sent magnets.

In addition, if an IA result isn't found for a model download, re-fetch
the IA cache.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-26 23:33:50 -05:00
kingbri
4fb5f77718 Debrid: Add context menu option to download to debrid
This is a manual button so users can download an item to their preferred
debrid service. If the item is cached in the debrid, it will download instantly
and display the result to the user. Otherwise, the caching alert is shown.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-26 22:26:53 -05:00
kingbri
e5a872e09f Debrid: Update for RealDebrid's API changes
The instantAvailability endpoint is now removed, so make IA return
a user's magnets instead with reliance on manual download as the
second solution.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-11-26 17:51:52 -05:00
kingbri
1d6ac13e84 Ferrite: Bump build number
Signed-off-by: kingbri <bdashore3@proton.me>
2024-10-04 12:33:47 -04:00
48 changed files with 304 additions and 436 deletions

View file

@ -41,7 +41,6 @@
0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C422E7F293542F300486D65 /* PremiumizeModels.swift */; };
0C42B5982932F6DD008057A0 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C42B5972932F6DD008057A0 /* Set.swift */; };
0C445C62293F9A0B0060744D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C445C61293F9A0B0060744D /* Bundle.swift */; };
0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = 0C448BE829A135F100F4E266 /* Introspect-Static */; };
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 */; };
@ -69,7 +68,6 @@
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 */; };
0C748EDA29D9256D0049B8BE /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 0C748ED929D9256D0049B8BE /* Yams */; };
@ -82,6 +80,7 @@
0C794B6D289EFA2E00DD1CC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C794B6C289EFA2E00DD1CC8 /* LaunchScreen.storyboard */; };
0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */; };
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */; };
0C7B4A002CB051550048FA28 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7B49FF2CB051550048FA28 /* SwiftUIIntrospect */; };
0C7C128628DAA3CD00381CD1 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C128528DAA3CD00381CD1 /* URL.swift */; };
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7D11FD28AA03FE00ED92DB /* View.swift */; };
0C7ED14128D61BBA009E29AD /* BackupModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7ED14028D61BBA009E29AD /* BackupModels.swift */; };
@ -104,6 +103,7 @@
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */; };
0C8DC35629CE2ABF008A83AD /* SourceSettingsApiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */; };
0C8DC35829CE2ACA008A83AD /* SourceSettingsMethodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35729CE2ACA008A83AD /* SourceSettingsMethodView.swift */; };
0C93DED82CF80101009EA8D2 /* SettingsDebridLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C93DED72CF80101009EA8D2 /* SettingsDebridLinkView.swift */; };
0C95D8D828A55B03005E22B3 /* DefaultActionPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D728A55B03005E22B3 /* DefaultActionPickerView.swift */; };
0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsPluginListView.swift */; };
0CA05459288EE9E600850554 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05458288EE9E600850554 /* PluginManager.swift */; };
@ -111,7 +111,6 @@
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148BB288903F000DE2211 /* SettingsView.swift */; };
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148BC288903F000DE2211 /* LoginWebView.swift */; };
0CA148D8288903F000DE2211 /* ActionChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148BD288903F000DE2211 /* ActionChoiceView.swift */; };
0CA148DB288903F000DE2211 /* NavView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C1288903F000DE2211 /* NavView.swift */; };
0CA148DC288903F000DE2211 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CA148C2288903F000DE2211 /* Assets.xcassets */; };
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */; };
0CA148DF288903F000DE2211 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CA148C6288903F000DE2211 /* Preview Assets.xcassets */; };
@ -135,9 +134,7 @@
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; };
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 */; };
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516928C5B4A600DCA721 /* InlineHeader.swift */; };
0CB725322C123E6F0047FC0B /* CloudDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB725312C123E6F0047FC0B /* CloudDownloadView.swift */; };
0CB725342C123E760047FC0B /* CloudMagnetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB725332C123E760047FC0B /* CloudMagnetView.swift */; };
0CBAB83628D12ED500AC903E /* DisableInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBAB83528D12ED500AC903E /* DisableInteraction.swift */; };
@ -226,7 +223,6 @@
0C6C7C9C29315292002DF910 /* AllDebridModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDebridModels.swift; sourceTree = "<group>"; };
0C7075E329D374C50093DB2D /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
0C7075E529D3845D0093DB2D /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalContextMenu.swift; sourceTree = "<group>"; };
0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = "<group>"; };
0C733286289C4C820058D1FE /* SourceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsView.swift; sourceTree = "<group>"; };
0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceRssParser+CoreDataClass.swift"; sourceTree = "<group>"; };
@ -259,6 +255,7 @@
0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = "<group>"; };
0C8DC35529CE2ABF008A83AD /* SourceSettingsApiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsApiView.swift; sourceTree = "<group>"; };
0C8DC35729CE2ACA008A83AD /* SourceSettingsMethodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsMethodView.swift; sourceTree = "<group>"; };
0C93DED72CF80101009EA8D2 /* SettingsDebridLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDebridLinkView.swift; sourceTree = "<group>"; };
0C95D8D728A55B03005E22B3 /* DefaultActionPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultActionPickerView.swift; sourceTree = "<group>"; };
0CA05456288EE58200850554 /* SettingsPluginListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPluginListView.swift; sourceTree = "<group>"; };
0CA05458288EE9E600850554 /* PluginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginManager.swift; sourceTree = "<group>"; };
@ -266,7 +263,6 @@
0CA148BB288903F000DE2211 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
0CA148BC288903F000DE2211 /* LoginWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginWebView.swift; sourceTree = "<group>"; };
0CA148BD288903F000DE2211 /* ActionChoiceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionChoiceView.swift; sourceTree = "<group>"; };
0CA148C1288903F000DE2211 /* NavView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavView.swift; sourceTree = "<group>"; };
0CA148C2288903F000DE2211 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrapingViewModel.swift; sourceTree = "<group>"; };
0CA148C6288903F000DE2211 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
@ -290,9 +286,7 @@
0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
0CB0115A29D36D9E009AFEDE /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
0CB0AB5E29BD2A200015422C /* KodiServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KodiServerView.swift; sourceTree = "<group>"; };
0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = "<group>"; };
0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = "<group>"; };
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = "<group>"; };
0CB725312C123E6F0047FC0B /* CloudDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudDownloadView.swift; sourceTree = "<group>"; };
0CB725332C123E760047FC0B /* CloudMagnetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudMagnetView.swift; sourceTree = "<group>"; };
0CBAB83528D12ED500AC903E /* DisableInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableInteraction.swift; sourceTree = "<group>"; };
@ -327,13 +321,13 @@
buildActionMask = 2147483647;
files = (
0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */,
0C448BE929A135F100F4E266 /* Introspect-Static in Frameworks */,
0C64A4B4288903680079976D /* Base32 in Frameworks */,
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
0C748EDA29D9256D0049B8BE /* Yams in Frameworks */,
0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */,
0C7B4A002CB051550048FA28 /* SwiftUIIntrospect in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -483,8 +477,6 @@
0C44E2A928D4DFC4007711AE /* Modifiers */ = {
isa = PBXGroup;
children = (
0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */,
0CB6516228C5A57300DCA721 /* ConditionalId.swift */,
0CD5E78828CD932B001BF684 /* DisabledAppearance.swift */,
0CBAB83528D12ED500AC903E /* DisableInteraction.swift */,
0CB6516428C5A5D700DCA721 /* InlinedList.swift */,
@ -555,6 +547,7 @@
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */,
0C6771FD29B521F1005D38D2 /* SettingsDebridInfoView.swift */,
0C5708EA29B8F89300BE07F9 /* SettingsLogView.swift */,
0C93DED72CF80101009EA8D2 /* SettingsDebridLinkView.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -584,9 +577,7 @@
children = (
0C44E2A928D4DFC4007711AE /* Modifiers */,
0CDCB91728C662640098B513 /* EmptyInstructionView.swift */,
0CA148C1288903F000DE2211 /* NavView.swift */,
0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */,
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */,
0C32FB562890D1F2002BD219 /* ListRowViews.swift */,
0C2D9652299316CC00A504B6 /* Tag.swift */,
0C6771FB29B3E0DB005D38D2 /* HybridSecureField.swift */,
@ -753,8 +744,8 @@
0C4CFC452897030D00AD9FAD /* Regex */,
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */,
0CDDDE042935235E006810B1 /* BetterSafariView */,
0C448BE829A135F100F4E266 /* Introspect-Static */,
0C748ED929D9256D0049B8BE /* Yams */,
0C7B49FF2CB051550048FA28 /* SwiftUIIntrospect */,
);
productName = Torrenter;
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
@ -791,8 +782,8 @@
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */,
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */,
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
0C748ED829D9256D0049B8BE /* XCRemoteSwiftPackageReference "Yams" */,
0C7B49FE2CB051550048FA28 /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
);
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
projectDirPath = "";
@ -852,13 +843,11 @@
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */,
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */,
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */,
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */,
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
0C2D9653299316CC00A504B6 /* Tag.swift in Sources */,
0C84FCE129E4B41D00B0DFE4 /* SourceFilterView.swift in Sources */,
0CD5E78928CD932B001BF684 /* DisabledAppearance.swift in Sources */,
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
0CA3B23C28C2AA5600616D3A /* Bookmark+CoreDataClass.swift in Sources */,
0CD4030A29DA01B6008D9F03 /* PluginInfoMetaView.swift in Sources */,
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */,
@ -885,7 +874,6 @@
0C7D11FE28AA03FE00ED92DB /* View.swift in Sources */,
0CD0265729FEFBF900A83D25 /* FerriteKeychain.swift in Sources */,
0CA3B23728C2660700616D3A /* HistoryView.swift in Sources */,
0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */,
0C50B7D0299DF63C00A9FA3C /* UIDevice.swift in Sources */,
0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */,
0CA3B23428C2658700616D3A /* LibraryView.swift in Sources */,
@ -912,7 +900,6 @@
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
0C445C62293F9A0B0060744D /* Bundle.swift in Sources */,
0C0755C6293424A200ECA142 /* DebridLabelView.swift in Sources */,
0CB6516A28C5B4A600DCA721 /* InlineHeader.swift in Sources */,
0CA148D8288903F000DE2211 /* ActionChoiceView.swift in Sources */,
0C41BC6528C2AEB900B47DD6 /* SearchModels.swift in Sources */,
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */,
@ -981,6 +968,7 @@
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */,
0CEC8AAE299B31B6007BFE8F /* SearchFilterHeaderView.swift in Sources */,
0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */,
0C93DED82CF80101009EA8D2 /* SettingsDebridLinkView.swift in Sources */,
0C3DD43F29B6968D006429DB /* KodiEditorView.swift in Sources */,
0CB725322C123E6F0047FC0B /* CloudDownloadView.swift in Sources */,
0C3DD44329B6ACD9006429DB /* KodiServer+CoreDataProperties.swift in Sources */,
@ -1122,7 +1110,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_ASSET_PATHS = "\"Ferrite/Preview Content\"";
DEVELOPMENT_TEAM = 8A74DBQ6S3;
ENABLE_PREVIEWS = YES;
@ -1137,12 +1125,12 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.7.2;
MARKETING_VERSION = 0.7.3;
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -1158,7 +1146,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_ASSET_PATHS = "\"Ferrite/Preview Content\"";
DEVELOPMENT_TEAM = 8A74DBQ6S3;
ENABLE_PREVIEWS = YES;
@ -1173,12 +1161,12 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.7.2;
MARKETING_VERSION = 0.7.3;
PRODUCT_BUNDLE_IDENTIFIER = me.kingbri.Ferrite;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -1212,14 +1200,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect/";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.2.3;
};
};
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sindresorhus/Regex";
@ -1260,6 +1240,14 @@
kind = branch;
};
};
0C7B49FE2CB051550048FA28 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/siteline/swiftui-introspect";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
};
};
0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
@ -1279,11 +1267,6 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
0C448BE829A135F100F4E266 /* Introspect-Static */ = {
isa = XCSwiftPackageProductDependency;
package = 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = "Introspect-Static";
};
0C4CFC452897030D00AD9FAD /* Regex */ = {
isa = XCSwiftPackageProductDependency;
package = 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */;
@ -1309,6 +1292,11 @@
package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
productName = SwiftyJSON;
};
0C7B49FF2CB051550048FA28 /* SwiftUIIntrospect */ = {
isa = XCSwiftPackageProductDependency;
package = 0C7B49FE2CB051550048FA28 /* XCRemoteSwiftPackageReference "swiftui-introspect" */;
productName = SwiftUIIntrospect;
};
0CAF1C7A286F5C8600296F86 /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = 0CAF1C79286F5C8600296F86 /* XCRemoteSwiftPackageReference "SwiftSoup" */;

View file

@ -11,6 +11,13 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let id = "AllDebrid"
let abbreviation = "AD"
let website = "https://alldebrid.com"
let description: String? = "AllDebrid is a debrid service that is used for downloads and media playback. " +
"You must pay to access this service. \n\n" +
"It is not recommended to use this service since media cache checks are not possible via the API. " +
"Ferrite's instant availability solely looks at a user's magnet library. \n\n" +
"If you must use this service, it is recommended to download search results manually using the context menu. \n\n" +
"This service does not inform if a magnet link is a batch before downloading."
let cachedStatus: [String] = ["Ready"]
var authTask: Task<Void, Error>?
@ -81,7 +88,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
URLQueryItem(name: "pin", value: pin)
]
let request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/pin/check", queryItems: queryItems))
let request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/pin/check", queryItems: queryItems))
// Timer to poll AD API for key
authTask = Task {
@ -192,31 +199,22 @@ class AllDebrid: PollingDebridSource, ObservableObject {
}
}
if sendMagnets.isEmpty {
return
}
// Fetch the user magnets to the latest version
try await getUserMagnets()
let queryItems = sendMagnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) }
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/instant", queryItems: queryItems))
let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<InstantAvailabilityResponse>.self, from: data).data
let filteredMagnets = rawResponse.magnets.filter { $0.instant == true && $0.files != nil }
let availableHashes = filteredMagnets.map { magnetResp in
// Force unwrap is OK here since the filter caught any nil values
let files = magnetResp.files!.enumerated().map { index, magnetFile in
DebridIAFile(id: index, name: magnetFile.name)
for cloudMagnet in cloudMagnets {
if cachedStatus.contains(cloudMagnet.status),
sendMagnets.contains(where: { $0.hash == cloudMagnet.hash })
{
IAValues.append(
DebridIA(
magnet: Magnet(hash: cloudMagnet.hash, link: nil),
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: []
)
)
}
return DebridIA(
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files
)
}
IAValues += availableHashes
}
// MARK: - Downloading
@ -225,19 +223,49 @@ class AllDebrid: PollingDebridSource, ObservableObject {
func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) {
let selectedMagnetId: String
if let existingMagnet = cloudMagnets.first(where: { $0.hash == magnet.hash && cachedStatus.contains($0.status) }) {
if let existingMagnet = cloudMagnets.first(where: {
$0.hash == magnet.hash && cachedStatus.contains($0.status)
}) {
selectedMagnetId = existingMagnet.id
} else {
let magnetId = try await addMagnet(magnet: magnet)
selectedMagnetId = String(magnetId)
}
let lockedLink = try await fetchMagnetStatus(
let rawResponse = try await fetchMagnetStatus(
magnetId: selectedMagnetId,
selectedIndex: iaFile?.id ?? 0
)
guard let magnets = rawResponse.magnets[safe: 0] else {
throw DebridError.EmptyUserMagnets
}
return (lockedLink, nil)
// Batches require an unrestrict from the user
if magnets.links.count > 1, iaFile == nil {
var copiedIA = ia
copiedIA?.files = magnets.links.enumerated().compactMap { index, file in
DebridIAFile(
id: index,
name: file.filename,
streamUrlString: file.link
)
}
return (nil, copiedIA)
}
if let cloudMagnetFile = magnets.links[safe: iaFile?.id ?? 0] {
let restrictedFile = DebridIAFile(
id: 0,
name: cloudMagnetFile.filename,
streamUrlString: cloudMagnetFile.link
)
return (restrictedFile, nil)
} else {
throw DebridError.EmptyUserMagnets
}
}
// Adds a magnet link to the user's AD account
@ -246,7 +274,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
throw DebridError.FailedRequest(description: "The magnet link is invalid")
}
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/upload"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/magnet/upload"))
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
@ -261,27 +289,26 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let rawResponse = try jsonDecoder.decode(ADResponse<AddMagnetResponse>.self, from: data).data
if let magnet = rawResponse.magnets[safe: 0] {
if !magnet.ready {
throw DebridError.IsCaching
}
return magnet.id
} else {
throw DebridError.InvalidResponse
}
}
func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> DebridIAFile {
func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> MagnetStatusResponse {
let queryItems = [
URLQueryItem(name: "id", value: magnetId)
]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status", queryItems: queryItems))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/magnet/status", queryItems: queryItems))
let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data
// Better to fetch no link at all than the wrong link
if let cloudMagnetFile = rawResponse.magnets[safe: 0]?.links[safe: selectedIndex ?? -1] {
return DebridIAFile(id: 0, name: cloudMagnetFile.filename, streamUrlString: cloudMagnetFile.link)
} else {
throw DebridError.EmptyUserMagnets
}
return rawResponse
}
// Known as unlockLink in AD's API
@ -289,7 +316,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [
URLQueryItem(name: "link", value: restrictedFile.streamUrlString)
]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/link/unlock", queryItems: queryItems))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/link/unlock", queryItems: queryItems))
let data = try await performRequest(request: &request, requestName: "unlockLink")
let rawResponse = try jsonDecoder.decode(ADResponse<UnlockLinkResponse>.self, from: data).data
@ -301,7 +328,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [
URLQueryItem(name: "links[]", value: link)
]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/user/links/save", queryItems: queryItems))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/user/links/save", queryItems: queryItems))
try await performRequest(request: &request, requestName: #function)
}
@ -309,7 +336,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
// MARK: - Cloud methods
func getUserMagnets() async throws {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/magnet/status"))
let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data
@ -333,13 +360,13 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [
URLQueryItem(name: "id", value: cloudMagnetId)
]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/delete", queryItems: queryItems))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/magnet/delete", queryItems: queryItems))
try await performRequest(request: &request, requestName: #function)
}
func getUserDownloads() async throws {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/user/links"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/user/links"))
let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<SavedLinksResponse>.self, from: data).data
@ -362,7 +389,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [
URLQueryItem(name: "link", value: downloadId)
]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/user/links/delete", queryItems: queryItems))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/user/links/delete", queryItems: queryItems))
try await performRequest(request: &request, requestName: #function)
}

View file

@ -118,7 +118,7 @@ class OffCloud: DebridSource, ObservableObject {
return
}
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/cache"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/cache"))
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
@ -202,7 +202,7 @@ class OffCloud: DebridSource, ObservableObject {
// Called as "cloud" in offcloud's API
private func offcloudDownload(magnet: Magnet) async throws -> CloudDownloadResponse {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/cloud"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/cloud"))
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
@ -220,7 +220,7 @@ class OffCloud: DebridSource, ObservableObject {
}
private func cloudExplore(requestId: String) async throws -> CloudExploreResponse {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/cloud/explore/\(requestId)"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/cloud/explore/\(requestId)"))
let data = try await performRequest(request: &request, requestName: "cloudExplore")
let rawResponse = try jsonDecoder.decode(CloudExploreResponse.self, from: data)
@ -245,7 +245,7 @@ class OffCloud: DebridSource, ObservableObject {
func deleteUserDownload(downloadId: String) {}
func getUserMagnets() async throws {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/cloud/history"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(baseApiUrl)/cloud/history"))
let data = try await performRequest(request: &request, requestName: "cloudHistory")
let rawResponse = try jsonDecoder.decode([CloudHistoryResponse].self, from: data)
@ -271,7 +271,7 @@ class OffCloud: DebridSource, ObservableObject {
throw DebridError.InvalidPostBody
}
var request = URLRequest(url: try buildRequestURL(urlString: "\(website)/cloud/remove/\(cloudMagnetId)"))
var request = try URLRequest(url: buildRequestURL(urlString: "\(website)/cloud/remove/\(cloudMagnetId)"))
try await performRequest(request: &request, requestName: "cloudRemove")
}
}

View file

@ -13,7 +13,11 @@ class RealDebrid: PollingDebridSource, ObservableObject {
let website = "https://real-debrid.com"
let description: String? = "RealDebrid is a debrid service that is used for downloads and media playback. " +
"You must pay to access this service. \n\n" +
"This service does not inform if a magnet link in a user's cloud library is a batch before downloading."
"It is not recommended to use this service since media cache checks are not possible via the API. " +
"Ferrite's instant availability solely looks at a user's magnet library. \n\n" +
"If you must use this service, it is recommended to download search results manually using the context menu. \n\n" +
"This service does not inform if a magnet link is a batch before downloading."
let cachedStatus: [String] = ["downloaded"]
var authTask: Task<Void, Error>?
@ -248,7 +252,8 @@ class RealDebrid: PollingDebridSource, ObservableObject {
// MARK: - Instant availability
// Checks if the magnet is streamable on RD
// Post-API changes
// Use user magnets to check for IA instead
func instantAvailability(magnets: [Magnet]) async throws {
let now = Date().timeIntervalSince1970
@ -265,61 +270,21 @@ class RealDebrid: PollingDebridSource, ObservableObject {
}
}
if sendMagnets.isEmpty {
return
}
// Fetch the user magnets to the latest version
try await getUserMagnets()
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(sendMagnets.compactMap(\.hash).joined(separator: "/"))")!)
let data = try await performRequest(request: &request, requestName: #function)
let rawResponseDict = try jsonDecoder.decode([String: InstantAvailabilityResponse].self, from: data)
for (hash, response) in rawResponseDict {
guard let data = response.data else {
continue
}
if data.rd.isEmpty {
continue
}
// Handle files array
let batches = data.rd.map { fileDict in
let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in
// Force unwrapped ID. Is safe because ID is guaranteed on a successful response
RealDebrid.IABatchFile(id: Int(key)!, fileName: value.filename)
}.sorted(by: { $0.id < $1.id })
return RealDebrid.IABatch(files: batchFiles)
}
var files: [DebridIAFile] = []
for batch in batches {
let batchFileIds = batch.files.map(\.id)
for batchFile in batch.files {
if !files.contains(where: { $0.id == batchFile.id }) {
files.append(
DebridIAFile(
id: batchFile.id,
name: batchFile.fileName,
batchIds: batchFileIds
)
)
}
}
}
// TTL: 5 minutes
IAValues.append(
DebridIA(
magnet: Magnet(hash: hash, link: nil),
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files
for cloudMagnet in cloudMagnets {
if cachedStatus.contains(cloudMagnet.status),
sendMagnets.contains(where: { $0.hash == cloudMagnet.hash })
{
IAValues.append(
DebridIA(
magnet: Magnet(hash: cloudMagnet.hash, link: nil),
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: []
)
)
)
}
}
}
@ -331,7 +296,9 @@ class RealDebrid: PollingDebridSource, ObservableObject {
do {
// Don't queue a new job if the magnet already exists in the user's library
if let existingCloudMagnet = cloudMagnets.first(where: { $0.hash == magnet.hash && $0.status == "downloaded" }) {
if let existingCloudMagnet = cloudMagnets.first(where: {
$0.hash == magnet.hash && cachedStatus.contains($0.status)
}) {
selectedMagnetId = existingCloudMagnet.id
} else {
selectedMagnetId = try await addMagnet(magnet: magnet)
@ -342,9 +309,8 @@ class RealDebrid: PollingDebridSource, ObservableObject {
let response = try await torrentInfo(debridID: selectedMagnetId)
let filteredFiles = response.files.filter { $0.selected == 1 }
// Need to return this to the user
if filteredFiles.count > 1, iaFile == nil {
// Need to return this to the user
var copiedIA = ia
copiedIA?.files = response.files.enumerated().compactMap { index, file in
@ -435,22 +401,6 @@ class RealDebrid: PollingDebridSource, ObservableObject {
default:
throw DebridError.EmptyUserMagnets
}
/*
if rawResponse.status == "downloaded" {
//let cloudMagnetLink = rawResponse.links[safe: linkIndex ?? -1],
/*
return DebridIAFile(
id: 0,
name: rawResponse.filename,
streamUrlString: cloudMagnetLink
)
*/
} else if rawResponse.status == "downloading" || rawResponse.status == "queued" {
throw DebridError.IsCaching
} else {
throw DebridError.EmptyUserMagnets
}
*/
}
// Downloads link from selectFiles for playback

View file

@ -1,5 +1,5 @@
//
// Array.swift
// Set.swift
// Ferrite
//
// Created by Brian Dashore on 11/26/22.

View file

@ -9,10 +9,6 @@ import UIKit
extension UIDevice {
var hasNotch: Bool {
if #available(iOS 11.0, *) {
return UIApplication.shared.currentUIWindow?.safeAreaInsets.bottom ?? 0 > 0
} else {
return false
}
UIApplication.shared.currentUIWindow?.safeAreaInsets.bottom ?? 0 > 0
}
}

View file

@ -5,7 +5,6 @@
// Created by Brian Dashore on 8/15/22.
//
import Introspect
import SwiftUI
extension View {
@ -20,16 +19,6 @@ extension View {
// MARK: Modifiers
func conditionalContextMenu(id: some Hashable,
@ViewBuilder _ internalContent: @escaping () -> some View) -> some View
{
modifier(ConditionalContextMenuModifier(internalContent, id: id))
}
func conditionalId(_ id: some Hashable) -> some View {
modifier(ConditionalIdModifier(id: id))
}
func disabledAppearance(_ disabled: Bool, dimmedOpacity: Double? = nil, animation: Animation? = nil) -> some View {
modifier(DisabledAppearanceModifier(disabled: disabled, dimmedOpacity: dimmedOpacity, animation: animation))
}

View file

@ -1,5 +1,5 @@
//
// MultipartFormDataRequest.swift
// FormDataBody.swift
// Ferrite
//
// Created by Brian Dashore on 6/12/24.

View file

@ -174,7 +174,7 @@ class DebridManager: ObservableObject {
return true
} else {
logManager?.error("DebridManager: Could not find the associated \(selectedSource.id) entry for magnet hash \(magnetHash)")
logManager?.warn("DebridManager: Could not find the associated \(selectedSource.id) entry for magnet hash \(magnetHash)")
return false
}
}

View file

@ -1,5 +1,5 @@
//
// ToastViewModel.swift
// LoggingManager.swift
// Ferrite
//
// Created by Brian Dashore on 7/19/22.

View file

@ -1,5 +1,5 @@
//
// SourceManager.swift
// PluginManager.swift
// Ferrite
//
// Created by Brian Dashore on 7/25/22.

View file

@ -21,13 +21,12 @@ struct IndeterminateProgressView: View {
.foregroundColor(Color.accentColor)
.frame(width: reader.size.width * 0.26, height: 6)
.clipShape(Capsule())
.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)
.offset(x: reader.size.width * 1.2 * offset, y: 0)
.animation(.default.repeatForever().speed(0.5), value: offset)
.onAppear {
withAnimation {
self.offset = 1
offset = 1
}
}
)

View file

@ -1,27 +0,0 @@
//
// InlineHeader.swift
// Ferrite
//
// Created by Brian Dashore on 9/5/22.
//
// For iOS 15's weird defaults regarding sectioned list padding
//
import SwiftUI
struct InlineHeader: View {
let title: String
init(_ title: String) {
self.title = title
}
var body: some View {
if #available(iOS 16, *) {
Text(title)
} else {
Text(title)
.listRowInsets(EdgeInsets(top: 10, leading: 15, bottom: 0, trailing: 0))
}
}
}

View file

@ -1,39 +0,0 @@
//
// ConditionalContextMenu.swift
// Ferrite
//
// Created by Brian Dashore on 9/3/22.
//
// Used as a workaround for iOS 15 not updating context views with conditional variables
// A stateful ID is required for the contextMenu to update itself.
//
import SwiftUI
struct ConditionalContextMenuModifier<InternalContent: View, ID: Hashable>: ViewModifier {
let internalContent: () -> InternalContent
let id: ID
init(@ViewBuilder _ internalContent: @escaping () -> InternalContent, id: ID) {
self.internalContent = internalContent
self.id = id
}
func body(content: Content) -> some View {
if #available(iOS 16, *) {
content
.contextMenu {
internalContent()
}
} else {
content
.background {
Color.clear
.contextMenu {
internalContent()
}
.id(id)
}
}
}
}

View file

@ -1,24 +0,0 @@
//
// ConditionalId.swift
// Ferrite
//
// Created by Brian Dashore on 9/4/22.
//
// Applies an ID below iOS 16
// This is due to ID workarounds making iOS 16 apps crash
//
import SwiftUI
struct ConditionalIdModifier<ID: Hashable>: ViewModifier {
let id: ID
func body(content: Content) -> some View {
if #available(iOS 16, *) {
content
} else {
content
.id(id)
}
}
}

View file

@ -5,26 +5,18 @@
// Created by Brian Dashore on 9/4/22.
//
// Removes the top padding on unsectioned lists
// If a list is sectioned, see InlineHeader
//
import Introspect
import SwiftUI
import SwiftUIIntrospect
struct InlinedListModifier: ViewModifier {
let inset: CGFloat
func body(content: Content) -> some View {
if #available(iOS 16, *) {
content
.introspectCollectionView { collectionView in
collectionView.contentInset.top = inset
}
} else {
content
.introspectTableView { tableView in
tableView.contentInset.top = inset
}
}
content
.introspect(.list, on: .iOS(.v16, .v17, .v18)) { collectionView in
collectionView.contentInset.top = inset
}
}
}

View file

@ -1,29 +0,0 @@
//
// NavView.swift
// Ferrite
//
// Created by Brian Dashore on 7/4/22.
// Contributed by Mantton
//
// A wrapper that switches between NavigationStack and the legacy NavigationView
//
import SwiftUI
struct NavView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
// NavigationStack issues are fixed on iOS 17
if #available(iOS 17, *) {
NavigationStack {
content
}
} else {
NavigationView {
content
}
.navigationViewStyle(.stack)
}
}
}

View file

@ -56,20 +56,27 @@ struct BookmarksView: View {
.frame(height: 15)
}
.task {
if !debridManager.enabledDebrids.isEmpty {
let magnets = bookmarks.compactMap {
if let magnetHash = $0.magnetHash {
return Magnet(hash: magnetHash, link: $0.magnetLink)
} else {
return nil
}
}
await debridManager.populateDebridIA(magnets)
}
await matchAgainstIA()
}
.refreshable {
await matchAgainstIA()
}
}
func fetchPredicate() {
bookmarks.nsPredicate = searchText.isEmpty ? nil : NSPredicate(format: "title CONTAINS[cd] %@", searchText)
}
func matchAgainstIA() async {
if !debridManager.enabledDebrids.isEmpty {
let magnets = bookmarks.compactMap {
if let magnetHash = $0.magnetHash {
return Magnet(hash: magnetHash, link: $0.magnetLink)
} else {
return nil
}
}
await debridManager.populateDebridIA(magnets)
}
}
}

View file

@ -76,7 +76,7 @@ struct HistorySectionView: View {
var body: some View {
if compareGroup(historyGroup) > 0 {
Section(header: InlineHeader(formatter.string(from: historyGroup[0].date ?? Date()))) {
Section(formatter.string(from: historyGroup[0].date ?? Date())) {
ForEach(historyGroup, id: \.self) { history in
ForEach(history.entryArray.filter { allEntries.contains($0) }, id: \.self) { entry in
HistoryButtonView(entry: entry)

View file

@ -1,5 +1,5 @@
//
// InstalledSourceButtonView.swift
// InstalledPluginButtonView.swift
// Ferrite
//
// Created by Brian Dashore on 8/5/22.

View file

@ -1,5 +1,5 @@
//
// SourceCatalogButtonView.swift
// PluginCatalogButtonView.swift
// Ferrite
//
// Created by Brian Dashore on 8/5/22.

View file

@ -11,7 +11,7 @@ struct PluginInfoAboutView<P: Plugin>: View {
@ObservedObject var selectedPlugin: P
var body: some View {
Section(header: InlineHeader("Description")) {
Section("Description") {
VStack(alignment: .leading, spacing: 10) {
if let pluginAbout = selectedPlugin.about {
if pluginAbout.last == "\n" {

View file

@ -16,7 +16,7 @@ struct PluginInfoMetaView<P: Plugin>: View {
) var pluginLists: FetchedResults<PluginList>
var body: some View {
Section(header: InlineHeader("Metadata")) {
Section("Metadata") {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 5) {
HStack(spacing: 5) {
@ -32,8 +32,7 @@ struct PluginInfoMetaView<P: Plugin>: View {
Group {
Text("ID: \(selectedPlugin.id)")
if let pluginList = pluginLists.first(where: { $0.id == selectedPlugin.listId })
{
if let pluginList = pluginLists.first(where: { $0.id == selectedPlugin.listId }) {
Text("List: \(pluginList.name)")
Text("List ID: \(pluginList.id.uuidString)")
} else {

View file

@ -39,7 +39,7 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
searchText: searchText
)
if !filteredUpdatedPlugins.isEmpty {
Section(header: InlineHeader("Updates")) {
Section("Updates") {
ForEach(filteredUpdatedPlugins, id: \.self) { (updatedPlugin: PJ) in
PluginCatalogButtonView(availablePlugin: updatedPlugin, needsUpdate: true)
}
@ -47,7 +47,7 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
}
if !installedPlugins.isEmpty {
Section(header: InlineHeader("Installed")) {
Section("Installed") {
ForEach(installedPlugins, id: \.self) { installedPlugin in
InstalledPluginButtonView(
installedPlugin: installedPlugin,
@ -64,7 +64,7 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
searchText: searchText
)
if !filteredAvailablePlugins.isEmpty {
Section(header: InlineHeader("Catalog")) {
Section("Catalog") {
ForEach(filteredAvailablePlugins, id: \.self) { availablePlugin in
PluginCatalogButtonView(availablePlugin: availablePlugin, needsUpdate: false)
}

View file

@ -13,7 +13,7 @@ struct PluginInfoView<P: Plugin>: View {
@Binding var selectedPlugin: P?
var body: some View {
NavView {
NavigationStack {
List {
if let selectedPlugin {
PluginInfoMetaView(selectedPlugin: selectedPlugin)

View file

@ -1,5 +1,5 @@
//
// PluginTagView.swift
// PluginTagsView.swift
// Ferrite
//
// Created by Brian Dashore on 2/7/23.

View file

@ -19,7 +19,7 @@ struct SourceSettingsApiView: View {
var body: some View {
Section(
header: InlineHeader("API credentials"),
header: Text("API credentials"),
footer: Text("Grab the required API credentials from the website. A client secret can be an API token.")
) {
if let clientId = selectedSourceApi.clientId, clientId.dynamic {

View file

@ -13,7 +13,7 @@ struct SourceSettingsBaseUrlView: View {
@State private var tempSite: String = ""
var body: some View {
Section(
header: InlineHeader("Base URL"),
header: Text("Base URL"),
footer: Text("Enter the base URL of your server.")
) {
TextField("https://...", text: $tempSite, onEditingChanged: { isFocused in

View file

@ -11,7 +11,7 @@ struct SourceSettingsMethodView: View {
@ObservedObject var selectedSource: Source
var body: some View {
Section(header: InlineHeader("Fetch method")) {
Section("Fetch method") {
Picker("", selection: $selectedSource.preferredParser) {
if selectedSource.jsonParser != nil {
Text("Website API").tag(SourcePreferredParser.siteApi.rawValue)

View file

@ -37,29 +37,7 @@ struct SearchResultButtonView: View {
case .full:
if debridManager.selectDebridResult(magnet: result.magnet) {
debridManager.currentDebridTask = Task {
await debridManager.fetchDebridDownload(magnet: result.magnet)
// Bump to batch
if debridManager.requiresUnrestrict {
navModel.selectedHistoryInfo = historyEntry
navModel.currentChoiceSheet = .batch
return
}
if !debridManager.downloadUrl.isEmpty {
historyEntry.url = debridManager.downloadUrl
PersistenceController.shared.createHistory(historyEntry, performSave: true)
pluginManager.runDefaultAction(
urlString: debridManager.downloadUrl,
navModel: navModel
)
if navModel.currentChoiceSheet != .action {
debridManager.downloadUrl = ""
}
}
await downloadToDebrid()
}
}
case .partial:
@ -90,7 +68,7 @@ struct SearchResultButtonView: View {
}
.disableInteraction(navModel.currentChoiceSheet != nil)
.tint(.primary)
.conditionalContextMenu(id: existingBookmark) {
.contextMenu {
ZStack {
if let bookmark = existingBookmark {
Button {
@ -121,6 +99,33 @@ struct SearchResultButtonView: View {
}
}
}
Button {
if debridManager.currentDebridTask == nil {
let foundIAResult = debridManager.selectDebridResult(magnet: result.magnet)
// Add a fake IA because we don't know if the magnet is cached at this point
if !foundIAResult {
debridManager.selectedDebridItem = DebridIA(
magnet: result.magnet,
expiryTimeStamp: Date().timeIntervalSince1970,
files: []
)
}
debridManager.currentDebridTask = Task {
await downloadToDebrid()
// Re-populate the IA cache if a result wasn't initially found
if !foundIAResult {
await debridManager.populateDebridIA([result.magnet])
}
}
}
} label: {
Text("Download to Debrid")
Image(systemName: "arrow.down.circle")
}
}
.alert("Caching file", isPresented: $debridManager.showDeleteAlert) {
Button("Yes", role: .destructive) {
@ -166,4 +171,35 @@ struct SearchResultButtonView: View {
}
}
}
// Common function to download
func downloadToDebrid() async {
var historyEntry = HistoryEntryJson(
name: result.title,
source: result.source
)
await debridManager.fetchDebridDownload(magnet: result.magnet)
navModel.selectedTitle = result.title ?? ""
if debridManager.requiresUnrestrict {
navModel.currentChoiceSheet = .batch
return
}
if !debridManager.downloadUrl.isEmpty {
historyEntry.url = debridManager.downloadUrl
PersistenceController.shared.createHistory(historyEntry, performSave: true)
pluginManager.runDefaultAction(
urlString: debridManager.downloadUrl,
navModel: navModel
)
if navModel.currentChoiceSheet != .action {
debridManager.downloadUrl = ""
}
}
}
}

View file

@ -1,5 +1,5 @@
//
// SearchResultRDView.swift
// SearchResultInfoView.swift
// Ferrite
//
// Created by Brian Dashore on 7/26/22.

View file

@ -1,5 +1,5 @@
//
// DefaultActionsPickerViews.swift
// DefaultActionPickerView.swift
// Ferrite
//
// Created by Brian Dashore on 8/11/22.

View file

@ -25,11 +25,11 @@ struct KodiEditorView: View {
@State private var errorAlertText: String = ""
var body: some View {
NavView {
NavigationStack {
Form {
Group {
Section(
header: InlineHeader("URL"),
header: Text("URL"),
footer: Text("Must follow the format http(s)://<ip>:<port>")
) {
TextField("Enter URL", text: $serverUrl)
@ -37,14 +37,14 @@ struct KodiEditorView: View {
}
Section(
header: InlineHeader("Friendly name"),
header: Text("Friendly name"),
footer: Text("Defaults to the URL if not provided")
) {
TextField("Friendly name", text: $friendlyName)
}
Section(
header: InlineHeader("Credentials"),
header: Text("Credentials"),
footer: Text("Only use for clients with authentication")
) {
TextField("Username", text: $username)

View file

@ -18,7 +18,7 @@ struct SettingsKodiView: View {
var body: some View {
List {
Section(header: InlineHeader("Description")) {
Section("Description") {
VStack(alignment: .leading, spacing: 10) {
Text("Kodi is an external application that is used to manage a local media library and playback.")
@ -27,7 +27,7 @@ struct SettingsKodiView: View {
}
Section(
header: InlineHeader("Servers"),
header: Text("Servers"),
footer: Text("Edit a server by holding it and accessing the context menu")
) {
if kodiServers.isEmpty {

View file

@ -1,5 +1,5 @@
//
// SourceListEditorView.swift
// PluginListEditorView.swift
// Ferrite
//
// Created by Brian Dashore on 7/25/22.
@ -25,7 +25,7 @@ struct PluginListEditorView: View {
@State private var loadedSelectedList = false
var body: some View {
NavView {
NavigationStack {
Form {
TextField("Enter URL", text: $pluginListUrl)
.disableAutocorrection(true)

View file

@ -1,5 +1,5 @@
//
// SettingsSourceListView.swift
// SettingsPluginListView.swift
// Ferrite
//
// Created by Brian Dashore on 7/25/22.
@ -69,12 +69,8 @@ struct SettingsPluginListView: View {
}
}
.sheet(isPresented: $presentEditSheet) {
if #available(iOS 16, *) {
PluginListEditorView()
.presentationDetents([.medium])
} else {
PluginListEditorView()
}
PluginListEditorView()
.presentationDetents([.medium])
}
.navigationTitle("Plugin Lists")
.navigationBarTitleDisplayMode(.inline)

View file

@ -20,7 +20,7 @@ struct SettingsAppVersionView: View {
ProgressView()
} else if !releases.isEmpty {
List {
Section(header: InlineHeader("GitHub links")) {
Section("GitHub links") {
ForEach(releases, id: \.self) { release in
ListRowLinkView(text: release.tagName, link: release.htmlUrl)
}

View file

@ -1,5 +1,5 @@
//
// DebridInfoView.swift
// SettingsDebridInfoView.swift
// Ferrite
//
// Created by Brian Dashore on 3/5/23.
@ -16,7 +16,7 @@ struct SettingsDebridInfoView: View {
var body: some View {
List {
Section(header: InlineHeader("Description")) {
Section("Description") {
VStack(alignment: .leading, spacing: 10) {
Text(debridSource.description ??
"\(debridSource.id) is a debrid service that is used for downloads and media playback. You must pay to access the service."
@ -27,7 +27,7 @@ struct SettingsDebridInfoView: View {
}
Section(
header: InlineHeader("Login status"),
header: Text("Login status"),
footer: Text("A WebView will show up to prompt you for credentials")
) {
Button {
@ -58,7 +58,7 @@ struct SettingsDebridInfoView: View {
}
Section(
header: InlineHeader("API key"),
header: Text("API key"),
footer: Text("Add a permanent API key here. Only use this if web authentication does not work!")
) {
HybridSecureField(

View file

@ -0,0 +1,31 @@
//
// SettingsDebridLinkView.swift
// Ferrite
//
// Created by Brian Dashore on 11/27/24.
//
import SwiftUI
struct SettingsDebridLinkView: View {
var debridSource: DebridSource
// TODO: Use a roundabout state for now
@State private var isLoggedIn = false
var body: some View {
NavigationLink {
SettingsDebridInfoView(debridSource: debridSource)
} label: {
HStack {
Text(debridSource.id)
Spacer()
Text(isLoggedIn ? "Enabled" : "Disabled")
.foregroundColor(.secondary)
}
}
.onAppear {
isLoggedIn = debridSource.isLoggedIn
}
}
}

View file

@ -27,7 +27,7 @@ struct ContentView: View {
@State private var dismissAction: () -> Void = {}
var body: some View {
NavView {
NavigationStack {
List {
SearchResultsView(searchText: $searchText)
}

View file

@ -30,7 +30,7 @@ struct LibraryView: View {
@State private var searchText: String = ""
var body: some View {
NavView {
NavigationStack {
ZStack {
switch navModel.libraryPickerSelection {
case .bookmarks:

View file

@ -12,7 +12,7 @@ struct LoginWebView: View {
var url: URL
var body: some View {
NavView {
NavigationStack {
WebView(url: url)
.navigationTitle("Sign in")
.navigationBarTitleDisplayMode(.inline)

View file

@ -54,12 +54,8 @@ struct MainView: View {
case .batch:
BatchChoiceView()
case .activity:
if #available(iOS 16, *) {
ShareSheet(activityItems: navModel.activityItems)
.presentationDetents([.medium, .large])
} else {
ShareSheet(activityItems: navModel.activityItems)
}
ShareSheet(activityItems: navModel.activityItems)
.presentationDetents([.medium, .large])
}
}
.onAppear {

View file

@ -30,7 +30,7 @@ struct PluginsView: View {
@State private var searchText: String = ""
var body: some View {
NavView {
NavigationStack {
ZStack {
if checkedForPlugins {
switch navModel.pluginPickerSelection {

View file

@ -212,10 +212,7 @@ struct SearchBar<ScopeContent: View>: UIViewControllerRepresentable {
private func setup() {
parent?.navigationItem.searchController = searchController
parent?.navigationItem.hidesSearchBarWhenScrolling = false
if #available(iOS 16, *) {
parent?.navigationItem.preferredSearchBarPlacement = .stacked
}
parent?.navigationItem.preferredSearchBarPlacement = .stacked
// Makes search bar appear when application starts
parent?.navigationController?.navigationBar.sizeToFit()

View file

@ -6,7 +6,6 @@
//
import BetterSafariView
import Introspect
import SwiftUI
import WebKit
@ -43,24 +42,15 @@ struct SettingsView: View {
@FocusState private var focusedField: Field?
var body: some View {
NavView {
NavigationStack {
Form {
Section(header: InlineHeader("Debrid services")) {
Section("Debrid services") {
ForEach(debridManager.debridSources, id: \.id) { (debridSource: DebridSource) in
NavigationLink {
SettingsDebridInfoView(debridSource: debridSource)
} label: {
HStack {
Text(debridSource.id)
Spacer()
Text(debridSource.isLoggedIn ? "Enabled" : "Disabled")
.foregroundColor(.secondary)
}
}
SettingsDebridLinkView(debridSource: debridSource)
}
}
Section(header: InlineHeader("Playback services")) {
Section("Playback services") {
NavigationLink {
SettingsKodiView(kodiServers: kodiServers)
} label: {
@ -74,7 +64,7 @@ struct SettingsView: View {
}
Section(
header: InlineHeader("Behavior"),
header: Text("Behavior"),
footer: VStack(alignment: .leading, spacing: 8) {
Text("Temporarily disable ephemeral auth if you cannot log into a service")
Text("Only disable search timeout if results are slow to fetch")
@ -121,13 +111,13 @@ struct SettingsView: View {
}
}
Section(header: InlineHeader("Plugin management")) {
Section("Plugin management") {
NavigationLink("Plugin lists") {
SettingsPluginListView()
}
}
Section(header: InlineHeader("Default actions")) {
Section("Default actions") {
if !debridManager.enabledDebrids.isEmpty {
NavigationLink {
DefaultActionPickerView(
@ -185,13 +175,13 @@ struct SettingsView: View {
}
}
Section(header: InlineHeader("Backups")) {
Section("Backups") {
NavigationLink("Backups") {
BackupsView()
}
}
Section(header: InlineHeader("Updates")) {
Section("Updates") {
Toggle(isOn: $autoUpdateNotifs) {
Text("Show update alerts")
}
@ -201,7 +191,7 @@ struct SettingsView: View {
}
}
Section(header: InlineHeader("Information")) {
Section("Information") {
ListRowLinkView(text: "Donate", link: "https://ko-fi.com/kingbri")
ListRowLinkView(text: "Report issues", link: "https://github.com/bdashore3/Ferrite/issues")
@ -210,7 +200,7 @@ struct SettingsView: View {
}
}
Section(header: InlineHeader("Debug")) {
Section("Debug") {
NavigationLink("Logs") {
SettingsLogView()
}

View file

@ -1,5 +1,5 @@
//
// MagnetChoiceView.swift
// ActionChoiceView.swift
// Ferrite
//
// Created by Brian Dashore on 7/20/22.
@ -29,9 +29,9 @@ struct ActionChoiceView: View {
@State private var showMagnetCopyAlert = false
var body: some View {
NavView {
NavigationStack {
Form {
Section(header: InlineHeader("Now Playing")) {
Section("Now Playing") {
VStack(alignment: .leading, spacing: 5) {
Text(navModel.selectedTitle)
.font(.callout)
@ -46,7 +46,7 @@ struct ActionChoiceView: View {
}
if !debridManager.downloadUrl.isEmpty {
Section(header: InlineHeader("Debrid options")) {
Section("Debrid options") {
ForEach(actions, id: \.id) { action in
if action.requires.contains(ActionRequirement.debrid.rawValue) {
ListRowButtonView(action.name, systemImage: "arrow.up.forward.app.fill") {
@ -91,7 +91,7 @@ struct ActionChoiceView: View {
}
if !navModel.resultFromCloud {
Section(header: InlineHeader("Magnet options")) {
Section("Magnet options") {
ForEach(actions, id: \.id) { action in
if action.requires.contains(ActionRequirement.magnet.rawValue) {
ListRowButtonView(action.name, systemImage: "arrow.up.forward.app.fill") {
@ -123,13 +123,8 @@ struct ActionChoiceView: View {
}
.tint(.primary)
.sheet(isPresented: $navModel.showLocalActivitySheet) {
// TODO: Fix share sheet
if #available(iOS 16, *) {
ShareSheet(activityItems: navModel.activityItems)
.presentationDetents([.medium, .large])
} else {
ShareSheet(activityItems: navModel.activityItems)
}
ShareSheet(activityItems: navModel.activityItems)
.presentationDetents([.medium, .large])
}
.alert("Action successful", isPresented: $pluginManager.showActionSuccessAlert) {
Button("OK", role: .cancel) {}

View file

@ -19,9 +19,8 @@ struct BatchChoiceView: View {
@State private var searchText: String = ""
// TODO: Make this generic for an IA protocol
var body: some View {
NavView {
NavigationStack {
List {
ForEach(debridManager.selectedDebridItem?.files ?? [], id: \.self) { file in
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {