diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 9635827..0bbbdfb 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */; }; 0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; }; 0C12D43D28CC332A000195BF /* HistoryButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C12D43C28CC332A000195BF /* HistoryButtonView.swift */; }; + 0C1A3E5229C8A7F500DA9730 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */; }; + 0C1A3E5629C9488C00DA9730 /* CodableWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */; }; 0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; }; 0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; }; 0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.swift */; }; @@ -153,6 +155,8 @@ 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginAggregateView.swift; sourceTree = ""; }; 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = ""; }; 0C12D43C28CC332A000195BF /* HistoryButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryButtonView.swift; sourceTree = ""; }; + 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = ""; }; + 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableWrapper.swift; sourceTree = ""; }; 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridCloudView.swift; sourceTree = ""; }; 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridCloudView.swift; sourceTree = ""; }; 0C2D9652299316CC00A504B6 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; @@ -369,6 +373,7 @@ 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */, 0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */, 0C6771F929B3D1AE005D38D2 /* KodiModels.swift */, + 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */, ); path = Models; sourceTree = ""; @@ -414,12 +419,13 @@ path = Plugin; sourceTree = ""; }; - 0C44E2A628D4DDC6007711AE /* Classes */ = { + 0C44E2A628D4DDC6007711AE /* Utils */ = { isa = PBXGroup; children = ( 0C44E2A728D4DDDC007711AE /* Application.swift */, + 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */, ); - path = Classes; + path = Utils; sourceTree = ""; }; 0C44E2A928D4DFC4007711AE /* Modifiers */ = { @@ -494,7 +500,7 @@ 0C0D50E3288DFE6E0035ECC8 /* Models */, 0CA148EF2889061600DE2211 /* ViewModels */, 0CA148EE2889061200DE2211 /* Views */, - 0C44E2A628D4DDC6007711AE /* Classes */, + 0C44E2A628D4DDC6007711AE /* Utils */, 0C5005552992B9C20064606A /* Protocols */, 0CA148C8288903F000DE2211 /* Extensions */, 0CA148C5288903F000DE2211 /* Preview Content */, @@ -775,6 +781,7 @@ 0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */, 0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */, 0C50055A2992BA6A0064606A /* PluginTag+CoreDataClass.swift in Sources */, + 0C1A3E5229C8A7F500DA9730 /* SettingsModels.swift in Sources */, 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */, 0C0755C8293425B500ECA142 /* DebridManagerModels.swift in Sources */, 0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */, @@ -828,6 +835,7 @@ 0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */, 0CA148E3288903F000DE2211 /* Task.swift in Sources */, 0CA148E7288903F000DE2211 /* LoggingManager.swift in Sources */, + 0C1A3E5629C9488C00DA9730 /* CodableWrapper.swift in Sources */, 0C6771FC29B3E0DB005D38D2 /* HybridSecureField.swift in Sources */, 0C6C7C9D29315292002DF910 /* AllDebridModels.swift in Sources */, 0C6C7C9B2931521B002DF910 /* AllDebridWrapper.swift in Sources */, diff --git a/Ferrite/API/KodiWrapper.swift b/Ferrite/API/KodiWrapper.swift index f0ade8e..95bc23f 100644 --- a/Ferrite/API/KodiWrapper.swift +++ b/Ferrite/API/KodiWrapper.swift @@ -94,27 +94,21 @@ public class Kodi { } } - public func sendVideoUrl(urlString: String) async throws { - guard let baseUrl = UserDefaults.standard.string(forKey: "ExternalServices.KodiUrl") else { - throw KodiError.InvalidBaseUrl - } - + public func sendVideoUrl(urlString: String, server: KodiServer) async throws { if URL(string: urlString) == nil { throw KodiError.InvalidPlaybackUrl } - let username = UserDefaults.standard.string(forKey: "ExternalServices.KodiUsername") - let password = UserDefaults.standard.string(forKey: "ExternalServices.KodiPassword") let requestBody = RPCPayload( method: "Player.Open", params: Params(item: Item(file: urlString)) ) - var request = URLRequest(url: URL(string: "\(baseUrl)/jsonrpc")!) + var request = URLRequest(url: URL(string: "\(server.urlString)/jsonrpc")!) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") - if let username, let password { + if let username = server.username, let password = server.password { request.setValue("Basic \(Data("\(username):\(password)".utf8).base64EncodedString())", forHTTPHeaderField: "Authorization") } diff --git a/Ferrite/Models/SettingsModels.swift b/Ferrite/Models/SettingsModels.swift new file mode 100644 index 0000000..96320fc --- /dev/null +++ b/Ferrite/Models/SettingsModels.swift @@ -0,0 +1,19 @@ +// +// SettingsModels.swift +// Ferrite +// +// Created by Brian Dashore on 3/20/23. +// + +import Foundation + +enum DefaultAction: Codable, CaseIterable, Hashable { + static var allCases: [DefaultAction] { + [.none, .share, .kodi, .custom(name: "", listId: "")] + } + + case none + case share + case kodi + case custom(name: String, listId: String) +} diff --git a/Ferrite/Classes/Application.swift b/Ferrite/Utils/Application.swift similarity index 100% rename from Ferrite/Classes/Application.swift rename to Ferrite/Utils/Application.swift diff --git a/Ferrite/Utils/CodableWrapper.swift b/Ferrite/Utils/CodableWrapper.swift new file mode 100644 index 0000000..1b45754 --- /dev/null +++ b/Ferrite/Utils/CodableWrapper.swift @@ -0,0 +1,36 @@ +// +// CodableWrapper.swift +// Ferrite +// +// Created by Brian Dashore on 3/20/23. +// +// From https://forums.swift.org/t/rawrepresentable-conformance-leads-to-crash/51912/4 +// Prevents recursion when using Codable with RawRepresentable without needing manual conformance + +import Foundation + +struct CodableWrapper { + var value: Value +} + +extension CodableWrapper: RawRepresentable { + var rawValue: String { + guard + let data = try? JSONEncoder().encode(value), + let string = String(data: data, encoding: .utf8) + else { + return "" + } + return string + } + + init?(rawValue: String) { + guard + let data = rawValue.data(using: .utf8), + let decoded = try? JSONDecoder().decode(Value.self, from: data) + else { + return nil + } + value = decoded + } +} diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index e710a47..749632f 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -48,7 +48,7 @@ public class NavigationViewModel: ObservableObject { @Published var selectedTitle: String = "" @Published var selectedBatchTitle: String = "" - @Published var hideNavigationBar = false + @Published var kodiExpanded: Bool = false @Published var currentChoiceSheet: ChoiceSheetType? var activityItems: [Any] = [] diff --git a/Ferrite/ViewModels/PluginManager.swift b/Ferrite/ViewModels/PluginManager.swift index 5dfc965..201703a 100644 --- a/Ferrite/ViewModels/PluginManager.swift +++ b/Ferrite/ViewModels/PluginManager.swift @@ -258,57 +258,51 @@ public class PluginManager: ObservableObject { } @MainActor - public func runDebridAction(urlString: String?, navModel: NavigationViewModel) { + public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) { let context = PersistenceController.shared.backgroundContext - if - let defaultDebridActionName = UserDefaults.standard.string(forKey: "Actions.DefaultDebridName"), - let defaultDebridActionList = UserDefaults.standard.string(forKey: "Actions.DefaultDebridList") - { - let actionFetchRequest = Action.fetchRequest() - actionFetchRequest.fetchLimit = 1 - actionFetchRequest.predicate = NSPredicate(format: "name == %@ AND listId == %@", defaultDebridActionName, defaultDebridActionList) - - if let fetchedAction = try? context.fetch(actionFetchRequest).first { - runDeeplinkAction(fetchedAction, urlString: urlString) - } else { - navModel.currentChoiceSheet = .action - UserDefaults.standard.set(nil, forKey: "Actions.DefaultDebridName") - UserDefaults.standard.set(nil, forKey: "Action.DefaultDebridList") - - actionErrorAlertMessage = - "The default action could not be run. The action choice sheet has been opened. \n\n" + - "Please check your default actions in Settings" - showActionErrorAlert.toggle() - } - } else { - navModel.currentChoiceSheet = .action + guard let urlString else { + logManager?.error("Default action: Could not run because the URL is invalid") + return } - } - @MainActor - public func runMagnetAction(urlString: String?, navModel: NavigationViewModel) { - let context = PersistenceController.shared.backgroundContext + let defaultsKey: String + // Assume this is a magnet link + if urlString.starts(with: "magnet") { + defaultsKey = "Actions.DefaultMagnet" + } else { + defaultsKey = "Actions.DefaultDebrid" + } if - let defaultMagnetActionName = UserDefaults.standard.string(forKey: "Actions.DefaultMagnetName"), - let defaultMagnetActionList = UserDefaults.standard.string(forKey: "Actions.DefaultMagnetList") + let rawValue = UserDefaults.standard.string(forKey: defaultsKey), + let defaultAction = CodableWrapper(rawValue: rawValue)?.value { - let actionFetchRequest = Action.fetchRequest() - actionFetchRequest.fetchLimit = 1 - actionFetchRequest.predicate = NSPredicate(format: "name == %@ AND listId == %@", defaultMagnetActionName, defaultMagnetActionList) - - if let fetchedAction = try? context.fetch(actionFetchRequest).first { - runDeeplinkAction(fetchedAction, urlString: urlString) - } else { + switch defaultAction { + case .none: navModel.currentChoiceSheet = .action - UserDefaults.standard.set(nil, forKey: "Actions.DefaultMagnetName") - UserDefaults.standard.set(nil, forKey: "Actions.DefaultMagnetList") + case .share: + navModel.activityItems = [urlString] + navModel.currentChoiceSheet = .activity + case .kodi: + navModel.kodiExpanded = true + navModel.currentChoiceSheet = .action + case .custom(let name, let listId): + let actionFetchRequest = Action.fetchRequest() + actionFetchRequest.fetchLimit = 1 + actionFetchRequest.predicate = NSPredicate(format: "name == %@ AND listId == %@", name, listId) - actionErrorAlertMessage = - "The default action could not be run. The action choice sheet has been opened. \n\n" + - "Please check your default actions in Settings" - showActionErrorAlert.toggle() + if let fetchedAction = try? context.fetch(actionFetchRequest).first { + runDeeplinkAction(fetchedAction, urlString: urlString) + } else { + navModel.currentChoiceSheet = .action + UserDefaults.standard.set(CodableWrapper(value: .none).rawValue, forKey: "Actions.DefaultDebrid") + + actionErrorAlertMessage = + "The default action could not be run. The action choice sheet has been opened. \n\n" + + "Please check your default actions in Settings" + showActionErrorAlert.toggle() + } } } else { navModel.currentChoiceSheet = .action @@ -340,7 +334,7 @@ public class PluginManager: ObservableObject { } @MainActor - public func sendToKodi(urlString: String?) async { + public func sendToKodi(urlString: String?, server: KodiServer) async { guard let urlString else { actionErrorAlertMessage = "Could not send URL to Kodi since there is no playback URL to send" showActionErrorAlert.toggle() @@ -351,7 +345,7 @@ public class PluginManager: ObservableObject { } do { - try await kodi.sendVideoUrl(urlString: urlString) + try await kodi.sendVideoUrl(urlString: urlString, server: server) actionSuccessAlertMessage = "Your URL should be playing on Kodi" showActionSuccessAlert.toggle() diff --git a/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift b/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift index dc2da73..65f14ef 100644 --- a/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift +++ b/Ferrite/Views/CommonViews/Modifiers/CustomScopeBar.swift @@ -12,18 +12,10 @@ struct CustomScopeBarModifier: ViewModifier { let hostingContent: V @State private var hostingController: UIHostingController? - // Don't use AppStorage since it causes a view update - var autocorrectSearch: Bool { - UserDefaults.standard.bool(forKey: "Behavior.AutocorrectSearch") - } - func body(content: Content) -> some View { if #available(iOS 15, *) { content .backport.introspectSearchController { searchController in - searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no - searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none - // MARK: One-time setup guard hostingController == nil else { return } @@ -64,10 +56,6 @@ struct CustomScopeBarModifier: ViewModifier { content Spacer() } - .backport.introspectSearchController { searchController in - searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no - searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none - } } } } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift index a1928aa..eec82e8 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift @@ -39,7 +39,7 @@ struct AllDebridCloudView: View { if !debridManager.downloadUrl.isEmpty { historyInfo.url = debridManager.downloadUrl PersistenceController.shared.createHistory(historyInfo, performSave: true) - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel ) diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift index c5d6b93..f49b128 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/PremiumizeCloudView.swift @@ -39,7 +39,7 @@ struct PremiumizeCloudView: View { performSave: true ) - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel ) diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift index 14bd2d9..e796da3 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift @@ -36,7 +36,7 @@ struct RealDebridCloudView: View { performSave: true ) - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel ) @@ -76,7 +76,7 @@ struct RealDebridCloudView: View { historyInfo.url = debridManager.downloadUrl PersistenceController.shared.createHistory(historyInfo, performSave: true) - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel ) diff --git a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift index 7cda667..7f9d10e 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift @@ -24,7 +24,7 @@ struct HistoryButtonView: View { if url.starts(with: "https://") { Task { debridManager.downloadUrl = url - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: url, navModel: navModel ) @@ -34,7 +34,7 @@ struct HistoryButtonView: View { } } } else { - pluginManager.runMagnetAction( + pluginManager.runDefaultAction( urlString: url, navModel: navModel ) diff --git a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift index 4b0a5bc..76293f7 100644 --- a/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift +++ b/Ferrite/Views/ComponentViews/Plugin/PluginAggregateView.swift @@ -80,6 +80,7 @@ struct PluginAggregateView: View { .onChange(of: installedPlugins.count) { newCount in pluginsEmpty = newCount == 0 } + .id(UUID()) } } } diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift index a825628..89361a4 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift @@ -43,7 +43,7 @@ struct SearchResultButtonView: View { performSave: true ) - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel ) @@ -72,7 +72,7 @@ struct SearchResultButtonView: View { performSave: true ) - pluginManager.runMagnetAction( + pluginManager.runDefaultAction( urlString: result.magnet.link, navModel: navModel ) diff --git a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift index c863724..e4a59ca 100644 --- a/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift +++ b/Ferrite/Views/ComponentViews/Settings/DefaultActionPickerView.swift @@ -11,8 +11,8 @@ struct DefaultActionPickerView: View { @EnvironmentObject var logManager: LoggingManager let actionRequirement: ActionRequirement - @Binding var defaultActionName: String? - @Binding var defaultActionList: String? + + @Binding var defaultAction: DefaultAction @FetchRequest( entity: Action.entity(), @@ -24,19 +24,25 @@ struct DefaultActionPickerView: View { sortDescriptors: [] ) var pluginLists: FetchedResults + @FetchRequest( + entity: KodiServer.entity(), + sortDescriptors: [] + ) var kodiServers: FetchedResults + var body: some View { List { - UserChoiceButton( - defaultActionName: $defaultActionName, - defaultActionList: $defaultActionList, - pluginLists: pluginLists - ) + DefaultChoiceButton(defaultAction: $defaultAction, selectedOption: .none) + DefaultChoiceButton(defaultAction: $defaultAction, selectedOption: .share) + if actionRequirement == .debrid && !kodiServers.isEmpty { + DefaultChoiceButton(defaultAction: $defaultAction, selectedOption: .kodi) + } + + // Handle custom here ForEach(actions.filter { $0.requires.contains(actionRequirement.rawValue) }, id: \.id) { action in Button { if let actionListId = action.listId?.uuidString { - defaultActionName = action.name - defaultActionList = actionListId + defaultAction = .custom(name: action.name, listId: actionListId) } else { logManager.error( "Default action: This action doesn't have a corresponding plugin list! Please uninstall the action" @@ -63,9 +69,9 @@ struct DefaultActionPickerView: View { Spacer() if - let defaultActionList, - action.listId?.uuidString == defaultActionList, - action.name == defaultActionName + case let .custom(name, listId) = defaultAction, + action.listId?.uuidString == listId, + action.name == name { Image(systemName: "checkmark") .foregroundColor(.blue) @@ -77,28 +83,24 @@ struct DefaultActionPickerView: View { } .listStyle(.insetGrouped) .inlinedList(inset: -20) - .navigationTitle("Default \(actionRequirement == .debrid ? "debrid" : "magnet") action") + .navigationTitle("Default \(actionRequirement == .debrid ? "Debrid" : "Magnet") Action") .navigationBarTitleDisplayMode(.inline) } } -private struct UserChoiceButton: View { - @Binding var defaultActionName: String? - @Binding var defaultActionList: String? - var pluginLists: FetchedResults +private struct DefaultChoiceButton: View { + @Binding var defaultAction: DefaultAction + let selectedOption: DefaultAction var body: some View { Button { - defaultActionName = nil - defaultActionList = nil + defaultAction = selectedOption } label: { HStack { - Text("Let me choose") + Text(fetchButtonName()) Spacer() - // Force "Let me choose" if the name OR list ID is nil - // Prevents any mismatches - if defaultActionName == nil || pluginLists.contains(where: { $0.id.uuidString == defaultActionList }) { + if defaultAction == selectedOption { Image(systemName: "checkmark") .foregroundColor(.blue) } @@ -106,4 +108,18 @@ private struct UserChoiceButton: View { } .backport.tint(.primary) } + + func fetchButtonName() -> String { + switch selectedOption { + case .none: + return "Let me choose" + case .share: + return "Share link" + case .kodi: + return "Open in Kodi" + case .custom(_, _): + // This should not be called + return "Custom button" + } + } } diff --git a/Ferrite/Views/ComponentViews/Settings/SettingsDebridInfoView.swift b/Ferrite/Views/ComponentViews/Settings/SettingsDebridInfoView.swift index b2fb787..0872a2a 100644 --- a/Ferrite/Views/ComponentViews/Settings/SettingsDebridInfoView.swift +++ b/Ferrite/Views/ComponentViews/Settings/SettingsDebridInfoView.swift @@ -44,6 +44,7 @@ struct SettingsDebridInfoView: View { } } } + .listStyle(.insetGrouped) .navigationTitle(debridType.toString()) .navigationBarTitleDisplayMode(.inline) } diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index e8d57a1..c56c508 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -15,6 +15,7 @@ struct ContentView: View { @EnvironmentObject var pluginManager: PluginManager @EnvironmentObject var logManager: LoggingManager + @AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch: Bool = false @AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false @State private var isEditingSearch = false @@ -110,6 +111,11 @@ struct ContentView: View { searchBarText = getSearchBarText() } } + .autocorrectionDisabled(!autocorrectSearch) + .backport.introspectSearchController { searchController in + // TODO: Replace with SwiftUI autocapitalization modifier + searchController.searchBar.autocapitalizationType = .none + } .navigationSearchBarHiddenWhenScrolling(false) .customScopeBar { SearchFilterHeaderView() diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index 302bff9..e0ab6a9 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -27,14 +27,12 @@ struct SettingsView: View { @AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true - @AppStorage("Actions.DefaultDebridName") var defaultDebridActionName: String? - @AppStorage("Actions.DefaultDebridList") var defaultDebridActionList: String? - - @AppStorage("Actions.DefaultMagnetName") var defaultMagnetActionName: String? - @AppStorage("Actions.DefaultMagnetList") var defaultMagnetActionList: String? + @AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: CodableWrapper = .init(value: .none) + @AppStorage("Actions.DefaultDebrid") var defaultDebridAction: CodableWrapper = .init(value: .none) @AppStorage("Debug.ShowErrorToasts") var showErrorToasts = true + var body: some View { NavView { Form { @@ -85,17 +83,26 @@ struct SettingsView: View { NavigationLink( destination: DefaultActionPickerView( actionRequirement: .debrid, - defaultActionName: $defaultDebridActionName, - defaultActionList: $defaultDebridActionList + defaultAction: $defaultDebridAction.value ), label: { HStack { Text("Debrid action") Spacer() - // TODO: Maybe make this check for nil list as well? - Text(defaultDebridActionName.map { $0 } ?? "User choice") - .foregroundColor(.secondary) + Group { + switch defaultDebridAction.value { + case .none: + Text("User choice") + case .share: + Text("Share") + case .kodi: + Text("Kodi") + case .custom(let name, _): + Text(name) + } + } + .foregroundColor(.secondary) } } ) @@ -104,17 +111,26 @@ struct SettingsView: View { NavigationLink( destination: DefaultActionPickerView( actionRequirement: .magnet, - defaultActionName: $defaultMagnetActionName, - defaultActionList: $defaultMagnetActionList + defaultAction: $defaultMagnetAction.value ), label: { HStack { Text("Magnet action") Spacer() - // TODO: Maybe make this check for nil list as well? - Text(defaultMagnetActionName.map { $0 } ?? "User choice") - .foregroundColor(.secondary) + Group { + switch defaultMagnetAction.value { + case .none: + Text("User choice") + case .share: + Text("Share") + case .kodi: + Text("Kodi") + case .custom(let name, _): + Text(name) + } + } + .foregroundColor(.secondary) } } ) diff --git a/Ferrite/Views/SheetViews/ActionChoiceView.swift b/Ferrite/Views/SheetViews/ActionChoiceView.swift index bb91655..d9b323d 100644 --- a/Ferrite/Views/SheetViews/ActionChoiceView.swift +++ b/Ferrite/Views/SheetViews/ActionChoiceView.swift @@ -57,11 +57,11 @@ struct ActionChoiceView: View { } if !kodiServers.isEmpty { - DisclosureGroup("Open in Kodi") { + DisclosureGroup("Open in Kodi", isExpanded: $navModel.kodiExpanded) { ForEach(kodiServers, id: \.self) { server in Button { Task { - await pluginManager.sendToKodi(urlString: debridManager.downloadUrl) + await pluginManager.sendToKodi(urlString: debridManager.downloadUrl, server: server) } } label: { KodiServerView(server: server) diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index fa4c36d..c22c359 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -84,7 +84,7 @@ struct BatchChoiceView: View { PersistenceController.shared.createHistory(selectedHistoryInfo, performSave: true) } - pluginManager.runDebridAction( + pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, navModel: navModel )