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

View file

@ -11,6 +11,13 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let id = "AllDebrid" let id = "AllDebrid"
let abbreviation = "AD" let abbreviation = "AD"
let website = "https://alldebrid.com" 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"] let cachedStatus: [String] = ["Ready"]
var authTask: Task<Void, Error>? var authTask: Task<Void, Error>?
@ -81,7 +88,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
URLQueryItem(name: "pin", value: pin) 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 // Timer to poll AD API for key
authTask = Task { authTask = Task {
@ -192,31 +199,22 @@ class AllDebrid: PollingDebridSource, ObservableObject {
} }
} }
if sendMagnets.isEmpty { // Fetch the user magnets to the latest version
return try await getUserMagnets()
}
let queryItems = sendMagnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) } for cloudMagnet in cloudMagnets {
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/instant", queryItems: queryItems)) if cachedStatus.contains(cloudMagnet.status),
sendMagnets.contains(where: { $0.hash == cloudMagnet.hash })
let data = try await performRequest(request: &request, requestName: #function) {
let rawResponse = try jsonDecoder.decode(ADResponse<InstantAvailabilityResponse>.self, from: data).data IAValues.append(
DebridIA(
let filteredMagnets = rawResponse.magnets.filter { $0.instant == true && $0.files != nil } magnet: Magnet(hash: cloudMagnet.hash, link: nil),
let availableHashes = filteredMagnets.map { magnetResp in expiryTimeStamp: Date().timeIntervalSince1970 + 300,
// Force unwrap is OK here since the filter caught any nil values files: []
let files = magnetResp.files!.enumerated().map { index, magnetFile in )
DebridIAFile(id: index, name: magnetFile.name) )
} }
return DebridIA(
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files
)
} }
IAValues += availableHashes
} }
// MARK: - Downloading // MARK: - Downloading
@ -225,19 +223,49 @@ class AllDebrid: PollingDebridSource, ObservableObject {
func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) { func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) {
let selectedMagnetId: String 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 selectedMagnetId = existingMagnet.id
} else { } else {
let magnetId = try await addMagnet(magnet: magnet) let magnetId = try await addMagnet(magnet: magnet)
selectedMagnetId = String(magnetId) selectedMagnetId = String(magnetId)
} }
let lockedLink = try await fetchMagnetStatus( let rawResponse = try await fetchMagnetStatus(
magnetId: selectedMagnetId, magnetId: selectedMagnetId,
selectedIndex: iaFile?.id ?? 0 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 // 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") 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.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") 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 let rawResponse = try jsonDecoder.decode(ADResponse<AddMagnetResponse>.self, from: data).data
if let magnet = rawResponse.magnets[safe: 0] { if let magnet = rawResponse.magnets[safe: 0] {
if !magnet.ready {
throw DebridError.IsCaching
}
return magnet.id return magnet.id
} else { } else {
throw DebridError.InvalidResponse throw DebridError.InvalidResponse
} }
} }
func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> DebridIAFile { func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> MagnetStatusResponse {
let queryItems = [ let queryItems = [
URLQueryItem(name: "id", value: magnetId) 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 data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data
// Better to fetch no link at all than the wrong link return rawResponse
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
}
} }
// Known as unlockLink in AD's API // Known as unlockLink in AD's API
@ -289,7 +316,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [ let queryItems = [
URLQueryItem(name: "link", value: restrictedFile.streamUrlString) 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 data = try await performRequest(request: &request, requestName: "unlockLink")
let rawResponse = try jsonDecoder.decode(ADResponse<UnlockLinkResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<UnlockLinkResponse>.self, from: data).data
@ -301,7 +328,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [ let queryItems = [
URLQueryItem(name: "links[]", value: link) 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) try await performRequest(request: &request, requestName: #function)
} }
@ -309,7 +336,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
// MARK: - Cloud methods // MARK: - Cloud methods
func getUserMagnets() async throws { 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 data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data
@ -333,13 +360,13 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [ let queryItems = [
URLQueryItem(name: "id", value: cloudMagnetId) 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) try await performRequest(request: &request, requestName: #function)
} }
func getUserDownloads() async throws { 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 data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(ADResponse<SavedLinksResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<SavedLinksResponse>.self, from: data).data
@ -362,7 +389,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let queryItems = [ let queryItems = [
URLQueryItem(name: "link", value: downloadId) 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) try await performRequest(request: &request, requestName: #function)
} }

View file

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

View file

@ -13,7 +13,11 @@ class RealDebrid: PollingDebridSource, ObservableObject {
let website = "https://real-debrid.com" let website = "https://real-debrid.com"
let description: String? = "RealDebrid is a debrid service that is used for downloads and media playback. " + 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" + "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"] let cachedStatus: [String] = ["downloaded"]
var authTask: Task<Void, Error>? var authTask: Task<Void, Error>?
@ -248,7 +252,8 @@ class RealDebrid: PollingDebridSource, ObservableObject {
// MARK: - Instant availability // 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 { func instantAvailability(magnets: [Magnet]) async throws {
let now = Date().timeIntervalSince1970 let now = Date().timeIntervalSince1970
@ -265,61 +270,21 @@ class RealDebrid: PollingDebridSource, ObservableObject {
} }
} }
if sendMagnets.isEmpty { // Fetch the user magnets to the latest version
return try await getUserMagnets()
}
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(sendMagnets.compactMap(\.hash).joined(separator: "/"))")!) for cloudMagnet in cloudMagnets {
if cachedStatus.contains(cloudMagnet.status),
let data = try await performRequest(request: &request, requestName: #function) sendMagnets.contains(where: { $0.hash == cloudMagnet.hash })
{
let rawResponseDict = try jsonDecoder.decode([String: InstantAvailabilityResponse].self, from: data) IAValues.append(
DebridIA(
for (hash, response) in rawResponseDict { magnet: Magnet(hash: cloudMagnet.hash, link: nil),
guard let data = response.data else { expiryTimeStamp: Date().timeIntervalSince1970 + 300,
continue files: []
} )
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
) )
) }
} }
} }
@ -331,7 +296,9 @@ class RealDebrid: PollingDebridSource, ObservableObject {
do { do {
// Don't queue a new job if the magnet already exists in the user's library // 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 selectedMagnetId = existingCloudMagnet.id
} else { } else {
selectedMagnetId = try await addMagnet(magnet: magnet) selectedMagnetId = try await addMagnet(magnet: magnet)
@ -342,9 +309,8 @@ class RealDebrid: PollingDebridSource, ObservableObject {
let response = try await torrentInfo(debridID: selectedMagnetId) let response = try await torrentInfo(debridID: selectedMagnetId)
let filteredFiles = response.files.filter { $0.selected == 1 } let filteredFiles = response.files.filter { $0.selected == 1 }
// Need to return this to the user
if filteredFiles.count > 1, iaFile == nil { if filteredFiles.count > 1, iaFile == nil {
// Need to return this to the user
var copiedIA = ia var copiedIA = ia
copiedIA?.files = response.files.enumerated().compactMap { index, file in copiedIA?.files = response.files.enumerated().compactMap { index, file in
@ -435,22 +401,6 @@ class RealDebrid: PollingDebridSource, ObservableObject {
default: default:
throw DebridError.EmptyUserMagnets 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 // Downloads link from selectFiles for playback

View file

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

View file

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

View file

@ -5,7 +5,6 @@
// Created by Brian Dashore on 8/15/22. // Created by Brian Dashore on 8/15/22.
// //
import Introspect
import SwiftUI import SwiftUI
extension View { extension View {
@ -20,16 +19,6 @@ extension View {
// MARK: Modifiers // 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 { func disabledAppearance(_ disabled: Bool, dimmedOpacity: Double? = nil, animation: Animation? = nil) -> some View {
modifier(DisabledAppearanceModifier(disabled: disabled, dimmedOpacity: dimmedOpacity, animation: animation)) modifier(DisabledAppearanceModifier(disabled: disabled, dimmedOpacity: dimmedOpacity, animation: animation))
} }

View file

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

View file

@ -174,7 +174,7 @@ class DebridManager: ObservableObject {
return true return true
} else { } 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 return false
} }
} }

View file

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

View file

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

View file

@ -21,13 +21,12 @@ struct IndeterminateProgressView: View {
.foregroundColor(Color.accentColor) .foregroundColor(Color.accentColor)
.frame(width: reader.size.width * 0.26, height: 6) .frame(width: reader.size.width * 0.26, height: 6)
.clipShape(Capsule()) .clipShape(Capsule())
.offset(x: -reader.size.width * 0.6, y: 0) .offset(x: -reader.size.width * 0.6, y: 0)
.offset(x: reader.size.width * 1.2 * self.offset, y: 0) .offset(x: reader.size.width * 1.2 * offset, y: 0)
.animation(.default.repeatForever().speed(0.5), value: self.offset) .animation(.default.repeatForever().speed(0.5), value: offset)
.onAppear { .onAppear {
withAnimation { 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. // Created by Brian Dashore on 9/4/22.
// //
// Removes the top padding on unsectioned lists // Removes the top padding on unsectioned lists
// If a list is sectioned, see InlineHeader
// //
import Introspect
import SwiftUI import SwiftUI
import SwiftUIIntrospect
struct InlinedListModifier: ViewModifier { struct InlinedListModifier: ViewModifier {
let inset: CGFloat let inset: CGFloat
func body(content: Content) -> some View { func body(content: Content) -> some View {
if #available(iOS 16, *) { content
content .introspect(.list, on: .iOS(.v16, .v17, .v18)) { collectionView in
.introspectCollectionView { collectionView in collectionView.contentInset.top = inset
collectionView.contentInset.top = inset }
}
} else {
content
.introspectTableView { tableView in
tableView.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) .frame(height: 15)
} }
.task { .task {
if !debridManager.enabledDebrids.isEmpty { await matchAgainstIA()
let magnets = bookmarks.compactMap { }
if let magnetHash = $0.magnetHash { .refreshable {
return Magnet(hash: magnetHash, link: $0.magnetLink) await matchAgainstIA()
} else {
return nil
}
}
await debridManager.populateDebridIA(magnets)
}
} }
} }
func fetchPredicate() { func fetchPredicate() {
bookmarks.nsPredicate = searchText.isEmpty ? nil : NSPredicate(format: "title CONTAINS[cd] %@", searchText) 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 { var body: some View {
if compareGroup(historyGroup) > 0 { 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(historyGroup, id: \.self) { history in
ForEach(history.entryArray.filter { allEntries.contains($0) }, id: \.self) { entry in ForEach(history.entryArray.filter { allEntries.contains($0) }, id: \.self) { entry in
HistoryButtonView(entry: entry) HistoryButtonView(entry: entry)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ struct SourceSettingsApiView: View {
var body: some View { var body: some View {
Section( 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.") 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 { if let clientId = selectedSourceApi.clientId, clientId.dynamic {

View file

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

View file

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

View file

@ -37,29 +37,7 @@ struct SearchResultButtonView: View {
case .full: case .full:
if debridManager.selectDebridResult(magnet: result.magnet) { if debridManager.selectDebridResult(magnet: result.magnet) {
debridManager.currentDebridTask = Task { debridManager.currentDebridTask = Task {
await debridManager.fetchDebridDownload(magnet: result.magnet) await downloadToDebrid()
// 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 = ""
}
}
} }
} }
case .partial: case .partial:
@ -90,7 +68,7 @@ struct SearchResultButtonView: View {
} }
.disableInteraction(navModel.currentChoiceSheet != nil) .disableInteraction(navModel.currentChoiceSheet != nil)
.tint(.primary) .tint(.primary)
.conditionalContextMenu(id: existingBookmark) { .contextMenu {
ZStack { ZStack {
if let bookmark = existingBookmark { if let bookmark = existingBookmark {
Button { 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) { .alert("Caching file", isPresented: $debridManager.showDeleteAlert) {
Button("Yes", role: .destructive) { 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 // Ferrite
// //
// Created by Brian Dashore on 7/26/22. // Created by Brian Dashore on 7/26/22.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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