Settings: Fix Default actions and Kodi
Fix how default actions are picked and add in default app actions as options for both debrid and magnet defaults. Kodi shows the action choice sheet with the DisclosureGroup dropped down. The new Kodi server framework also wasn't implemented in the Kodi wrapper. Fix that. Finally, add some iOS 14 fixes and repair the autocorrect search setting to actually work. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
edfba1c62e
commit
6e95c6072c
20 changed files with 197 additions and 118 deletions
|
|
@ -16,6 +16,8 @@
|
||||||
0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */; };
|
0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */; };
|
||||||
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
|
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
|
||||||
0C12D43D28CC332A000195BF /* HistoryButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C12D43C28CC332A000195BF /* HistoryButtonView.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 */; };
|
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
||||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
||||||
0C2D9653299316CC00A504B6 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2D9652299316CC00A504B6 /* Tag.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 = "<group>"; };
|
0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginAggregateView.swift; sourceTree = "<group>"; };
|
||||||
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
|
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
|
||||||
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryButtonView.swift; sourceTree = "<group>"; };
|
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryButtonView.swift; sourceTree = "<group>"; };
|
||||||
|
0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
|
||||||
|
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableWrapper.swift; sourceTree = "<group>"; };
|
||||||
0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridCloudView.swift; sourceTree = "<group>"; };
|
0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridCloudView.swift; sourceTree = "<group>"; };
|
||||||
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridCloudView.swift; sourceTree = "<group>"; };
|
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridCloudView.swift; sourceTree = "<group>"; };
|
||||||
0C2D9652299316CC00A504B6 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
0C2D9652299316CC00A504B6 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -369,6 +373,7 @@
|
||||||
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */,
|
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */,
|
||||||
0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */,
|
0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */,
|
||||||
0C6771F929B3D1AE005D38D2 /* KodiModels.swift */,
|
0C6771F929B3D1AE005D38D2 /* KodiModels.swift */,
|
||||||
|
0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -414,12 +419,13 @@
|
||||||
path = Plugin;
|
path = Plugin;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0C44E2A628D4DDC6007711AE /* Classes */ = {
|
0C44E2A628D4DDC6007711AE /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0C44E2A728D4DDDC007711AE /* Application.swift */,
|
0C44E2A728D4DDDC007711AE /* Application.swift */,
|
||||||
|
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */,
|
||||||
);
|
);
|
||||||
path = Classes;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0C44E2A928D4DFC4007711AE /* Modifiers */ = {
|
0C44E2A928D4DFC4007711AE /* Modifiers */ = {
|
||||||
|
|
@ -494,7 +500,7 @@
|
||||||
0C0D50E3288DFE6E0035ECC8 /* Models */,
|
0C0D50E3288DFE6E0035ECC8 /* Models */,
|
||||||
0CA148EF2889061600DE2211 /* ViewModels */,
|
0CA148EF2889061600DE2211 /* ViewModels */,
|
||||||
0CA148EE2889061200DE2211 /* Views */,
|
0CA148EE2889061200DE2211 /* Views */,
|
||||||
0C44E2A628D4DDC6007711AE /* Classes */,
|
0C44E2A628D4DDC6007711AE /* Utils */,
|
||||||
0C5005552992B9C20064606A /* Protocols */,
|
0C5005552992B9C20064606A /* Protocols */,
|
||||||
0CA148C8288903F000DE2211 /* Extensions */,
|
0CA148C8288903F000DE2211 /* Extensions */,
|
||||||
0CA148C5288903F000DE2211 /* Preview Content */,
|
0CA148C5288903F000DE2211 /* Preview Content */,
|
||||||
|
|
@ -775,6 +781,7 @@
|
||||||
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */,
|
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */,
|
||||||
0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */,
|
0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */,
|
||||||
0C50055A2992BA6A0064606A /* PluginTag+CoreDataClass.swift in Sources */,
|
0C50055A2992BA6A0064606A /* PluginTag+CoreDataClass.swift in Sources */,
|
||||||
|
0C1A3E5229C8A7F500DA9730 /* SettingsModels.swift in Sources */,
|
||||||
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
|
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
|
||||||
0C0755C8293425B500ECA142 /* DebridManagerModels.swift in Sources */,
|
0C0755C8293425B500ECA142 /* DebridManagerModels.swift in Sources */,
|
||||||
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */,
|
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */,
|
||||||
|
|
@ -828,6 +835,7 @@
|
||||||
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */,
|
0C68135028BC1A2D00FAD890 /* GithubWrapper.swift in Sources */,
|
||||||
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
||||||
0CA148E7288903F000DE2211 /* LoggingManager.swift in Sources */,
|
0CA148E7288903F000DE2211 /* LoggingManager.swift in Sources */,
|
||||||
|
0C1A3E5629C9488C00DA9730 /* CodableWrapper.swift in Sources */,
|
||||||
0C6771FC29B3E0DB005D38D2 /* HybridSecureField.swift in Sources */,
|
0C6771FC29B3E0DB005D38D2 /* HybridSecureField.swift in Sources */,
|
||||||
0C6C7C9D29315292002DF910 /* AllDebridModels.swift in Sources */,
|
0C6C7C9D29315292002DF910 /* AllDebridModels.swift in Sources */,
|
||||||
0C6C7C9B2931521B002DF910 /* AllDebridWrapper.swift in Sources */,
|
0C6C7C9B2931521B002DF910 /* AllDebridWrapper.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -94,27 +94,21 @@ public class Kodi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendVideoUrl(urlString: String) async throws {
|
public func sendVideoUrl(urlString: String, server: KodiServer) async throws {
|
||||||
guard let baseUrl = UserDefaults.standard.string(forKey: "ExternalServices.KodiUrl") else {
|
|
||||||
throw KodiError.InvalidBaseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if URL(string: urlString) == nil {
|
if URL(string: urlString) == nil {
|
||||||
throw KodiError.InvalidPlaybackUrl
|
throw KodiError.InvalidPlaybackUrl
|
||||||
}
|
}
|
||||||
let username = UserDefaults.standard.string(forKey: "ExternalServices.KodiUsername")
|
|
||||||
let password = UserDefaults.standard.string(forKey: "ExternalServices.KodiPassword")
|
|
||||||
|
|
||||||
let requestBody = RPCPayload(
|
let requestBody = RPCPayload(
|
||||||
method: "Player.Open",
|
method: "Player.Open",
|
||||||
params: Params(item: Item(file: urlString))
|
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.httpMethod = "POST"
|
||||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
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")
|
request.setValue("Basic \(Data("\(username):\(password)".utf8).base64EncodedString())", forHTTPHeaderField: "Authorization")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
19
Ferrite/Models/SettingsModels.swift
Normal file
19
Ferrite/Models/SettingsModels.swift
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
36
Ferrite/Utils/CodableWrapper.swift
Normal file
36
Ferrite/Utils/CodableWrapper.swift
Normal file
|
|
@ -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<Value: Codable> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ public class NavigationViewModel: ObservableObject {
|
||||||
@Published var selectedTitle: String = ""
|
@Published var selectedTitle: String = ""
|
||||||
@Published var selectedBatchTitle: String = ""
|
@Published var selectedBatchTitle: String = ""
|
||||||
|
|
||||||
@Published var hideNavigationBar = false
|
@Published var kodiExpanded: Bool = false
|
||||||
|
|
||||||
@Published var currentChoiceSheet: ChoiceSheetType?
|
@Published var currentChoiceSheet: ChoiceSheetType?
|
||||||
var activityItems: [Any] = []
|
var activityItems: [Any] = []
|
||||||
|
|
|
||||||
|
|
@ -258,57 +258,51 @@ public class PluginManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func runDebridAction(urlString: String?, navModel: NavigationViewModel) {
|
public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) {
|
||||||
let context = PersistenceController.shared.backgroundContext
|
let context = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
if
|
guard let urlString else {
|
||||||
let defaultDebridActionName = UserDefaults.standard.string(forKey: "Actions.DefaultDebridName"),
|
logManager?.error("Default action: Could not run because the URL is invalid")
|
||||||
let defaultDebridActionList = UserDefaults.standard.string(forKey: "Actions.DefaultDebridList")
|
return
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
let defaultsKey: String
|
||||||
public func runMagnetAction(urlString: String?, navModel: NavigationViewModel) {
|
// Assume this is a magnet link
|
||||||
let context = PersistenceController.shared.backgroundContext
|
if urlString.starts(with: "magnet") {
|
||||||
|
defaultsKey = "Actions.DefaultMagnet"
|
||||||
|
} else {
|
||||||
|
defaultsKey = "Actions.DefaultDebrid"
|
||||||
|
}
|
||||||
|
|
||||||
if
|
if
|
||||||
let defaultMagnetActionName = UserDefaults.standard.string(forKey: "Actions.DefaultMagnetName"),
|
let rawValue = UserDefaults.standard.string(forKey: defaultsKey),
|
||||||
let defaultMagnetActionList = UserDefaults.standard.string(forKey: "Actions.DefaultMagnetList")
|
let defaultAction = CodableWrapper<DefaultAction>(rawValue: rawValue)?.value
|
||||||
{
|
{
|
||||||
let actionFetchRequest = Action.fetchRequest()
|
switch defaultAction {
|
||||||
actionFetchRequest.fetchLimit = 1
|
case .none:
|
||||||
actionFetchRequest.predicate = NSPredicate(format: "name == %@ AND listId == %@", defaultMagnetActionName, defaultMagnetActionList)
|
|
||||||
|
|
||||||
if let fetchedAction = try? context.fetch(actionFetchRequest).first {
|
|
||||||
runDeeplinkAction(fetchedAction, urlString: urlString)
|
|
||||||
} else {
|
|
||||||
navModel.currentChoiceSheet = .action
|
navModel.currentChoiceSheet = .action
|
||||||
UserDefaults.standard.set(nil, forKey: "Actions.DefaultMagnetName")
|
case .share:
|
||||||
UserDefaults.standard.set(nil, forKey: "Actions.DefaultMagnetList")
|
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 =
|
if let fetchedAction = try? context.fetch(actionFetchRequest).first {
|
||||||
"The default action could not be run. The action choice sheet has been opened. \n\n" +
|
runDeeplinkAction(fetchedAction, urlString: urlString)
|
||||||
"Please check your default actions in Settings"
|
} else {
|
||||||
showActionErrorAlert.toggle()
|
navModel.currentChoiceSheet = .action
|
||||||
|
UserDefaults.standard.set(CodableWrapper<DefaultAction>(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 {
|
} else {
|
||||||
navModel.currentChoiceSheet = .action
|
navModel.currentChoiceSheet = .action
|
||||||
|
|
@ -340,7 +334,7 @@ public class PluginManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func sendToKodi(urlString: String?) async {
|
public func sendToKodi(urlString: String?, server: KodiServer) async {
|
||||||
guard let urlString else {
|
guard let urlString else {
|
||||||
actionErrorAlertMessage = "Could not send URL to Kodi since there is no playback URL to send"
|
actionErrorAlertMessage = "Could not send URL to Kodi since there is no playback URL to send"
|
||||||
showActionErrorAlert.toggle()
|
showActionErrorAlert.toggle()
|
||||||
|
|
@ -351,7 +345,7 @@ public class PluginManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await kodi.sendVideoUrl(urlString: urlString)
|
try await kodi.sendVideoUrl(urlString: urlString, server: server)
|
||||||
|
|
||||||
actionSuccessAlertMessage = "Your URL should be playing on Kodi"
|
actionSuccessAlertMessage = "Your URL should be playing on Kodi"
|
||||||
showActionSuccessAlert.toggle()
|
showActionSuccessAlert.toggle()
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,10 @@ struct CustomScopeBarModifier<V: View>: ViewModifier {
|
||||||
let hostingContent: V
|
let hostingContent: V
|
||||||
@State private var hostingController: UIHostingController<V>?
|
@State private var hostingController: UIHostingController<V>?
|
||||||
|
|
||||||
// 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 {
|
func body(content: Content) -> some View {
|
||||||
if #available(iOS 15, *) {
|
if #available(iOS 15, *) {
|
||||||
content
|
content
|
||||||
.backport.introspectSearchController { searchController in
|
.backport.introspectSearchController { searchController in
|
||||||
searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no
|
|
||||||
searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none
|
|
||||||
|
|
||||||
// MARK: One-time setup
|
// MARK: One-time setup
|
||||||
|
|
||||||
guard hostingController == nil else { return }
|
guard hostingController == nil else { return }
|
||||||
|
|
@ -64,10 +56,6 @@ struct CustomScopeBarModifier<V: View>: ViewModifier {
|
||||||
content
|
content
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.backport.introspectSearchController { searchController in
|
|
||||||
searchController.searchBar.autocorrectionType = autocorrectSearch ? .default : .no
|
|
||||||
searchController.searchBar.autocapitalizationType = autocorrectSearch ? .sentences : .none
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ struct AllDebridCloudView: View {
|
||||||
if !debridManager.downloadUrl.isEmpty {
|
if !debridManager.downloadUrl.isEmpty {
|
||||||
historyInfo.url = debridManager.downloadUrl
|
historyInfo.url = debridManager.downloadUrl
|
||||||
PersistenceController.shared.createHistory(historyInfo, performSave: true)
|
PersistenceController.shared.createHistory(historyInfo, performSave: true)
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ struct PremiumizeCloudView: View {
|
||||||
performSave: true
|
performSave: true
|
||||||
)
|
)
|
||||||
|
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ struct RealDebridCloudView: View {
|
||||||
performSave: true
|
performSave: true
|
||||||
)
|
)
|
||||||
|
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
@ -76,7 +76,7 @@ struct RealDebridCloudView: View {
|
||||||
historyInfo.url = debridManager.downloadUrl
|
historyInfo.url = debridManager.downloadUrl
|
||||||
PersistenceController.shared.createHistory(historyInfo, performSave: true)
|
PersistenceController.shared.createHistory(historyInfo, performSave: true)
|
||||||
|
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ struct HistoryButtonView: View {
|
||||||
if url.starts(with: "https://") {
|
if url.starts(with: "https://") {
|
||||||
Task {
|
Task {
|
||||||
debridManager.downloadUrl = url
|
debridManager.downloadUrl = url
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: url,
|
urlString: url,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
@ -34,7 +34,7 @@ struct HistoryButtonView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pluginManager.runMagnetAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: url,
|
urlString: url,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ struct PluginAggregateView<P: Plugin, PJ: PluginJson>: View {
|
||||||
.onChange(of: installedPlugins.count) { newCount in
|
.onChange(of: installedPlugins.count) { newCount in
|
||||||
pluginsEmpty = newCount == 0
|
pluginsEmpty = newCount == 0
|
||||||
}
|
}
|
||||||
|
.id(UUID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ struct SearchResultButtonView: View {
|
||||||
performSave: true
|
performSave: true
|
||||||
)
|
)
|
||||||
|
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
@ -72,7 +72,7 @@ struct SearchResultButtonView: View {
|
||||||
performSave: true
|
performSave: true
|
||||||
)
|
)
|
||||||
|
|
||||||
pluginManager.runMagnetAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: result.magnet.link,
|
urlString: result.magnet.link,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ struct DefaultActionPickerView: View {
|
||||||
@EnvironmentObject var logManager: LoggingManager
|
@EnvironmentObject var logManager: LoggingManager
|
||||||
|
|
||||||
let actionRequirement: ActionRequirement
|
let actionRequirement: ActionRequirement
|
||||||
@Binding var defaultActionName: String?
|
|
||||||
@Binding var defaultActionList: String?
|
@Binding var defaultAction: DefaultAction
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
entity: Action.entity(),
|
entity: Action.entity(),
|
||||||
|
|
@ -24,19 +24,25 @@ struct DefaultActionPickerView: View {
|
||||||
sortDescriptors: []
|
sortDescriptors: []
|
||||||
) var pluginLists: FetchedResults<PluginList>
|
) var pluginLists: FetchedResults<PluginList>
|
||||||
|
|
||||||
|
@FetchRequest(
|
||||||
|
entity: KodiServer.entity(),
|
||||||
|
sortDescriptors: []
|
||||||
|
) var kodiServers: FetchedResults<KodiServer>
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
UserChoiceButton(
|
DefaultChoiceButton(defaultAction: $defaultAction, selectedOption: .none)
|
||||||
defaultActionName: $defaultActionName,
|
DefaultChoiceButton(defaultAction: $defaultAction, selectedOption: .share)
|
||||||
defaultActionList: $defaultActionList,
|
|
||||||
pluginLists: pluginLists
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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
|
ForEach(actions.filter { $0.requires.contains(actionRequirement.rawValue) }, id: \.id) { action in
|
||||||
Button {
|
Button {
|
||||||
if let actionListId = action.listId?.uuidString {
|
if let actionListId = action.listId?.uuidString {
|
||||||
defaultActionName = action.name
|
defaultAction = .custom(name: action.name, listId: actionListId)
|
||||||
defaultActionList = actionListId
|
|
||||||
} else {
|
} else {
|
||||||
logManager.error(
|
logManager.error(
|
||||||
"Default action: This action doesn't have a corresponding plugin list! Please uninstall the action"
|
"Default action: This action doesn't have a corresponding plugin list! Please uninstall the action"
|
||||||
|
|
@ -63,9 +69,9 @@ struct DefaultActionPickerView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if
|
if
|
||||||
let defaultActionList,
|
case let .custom(name, listId) = defaultAction,
|
||||||
action.listId?.uuidString == defaultActionList,
|
action.listId?.uuidString == listId,
|
||||||
action.name == defaultActionName
|
action.name == name
|
||||||
{
|
{
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
|
|
@ -77,28 +83,24 @@ struct DefaultActionPickerView: View {
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped)
|
.listStyle(.insetGrouped)
|
||||||
.inlinedList(inset: -20)
|
.inlinedList(inset: -20)
|
||||||
.navigationTitle("Default \(actionRequirement == .debrid ? "debrid" : "magnet") action")
|
.navigationTitle("Default \(actionRequirement == .debrid ? "Debrid" : "Magnet") Action")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct UserChoiceButton: View {
|
private struct DefaultChoiceButton: View {
|
||||||
@Binding var defaultActionName: String?
|
@Binding var defaultAction: DefaultAction
|
||||||
@Binding var defaultActionList: String?
|
let selectedOption: DefaultAction
|
||||||
var pluginLists: FetchedResults<PluginList>
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button {
|
Button {
|
||||||
defaultActionName = nil
|
defaultAction = selectedOption
|
||||||
defaultActionList = nil
|
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Let me choose")
|
Text(fetchButtonName())
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Force "Let me choose" if the name OR list ID is nil
|
if defaultAction == selectedOption {
|
||||||
// Prevents any mismatches
|
|
||||||
if defaultActionName == nil || pluginLists.contains(where: { $0.id.uuidString == defaultActionList }) {
|
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
|
|
@ -106,4 +108,18 @@ private struct UserChoiceButton: View {
|
||||||
}
|
}
|
||||||
.backport.tint(.primary)
|
.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ struct SettingsDebridInfoView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
.navigationTitle(debridType.toString())
|
.navigationTitle(debridType.toString())
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ struct ContentView: View {
|
||||||
@EnvironmentObject var pluginManager: PluginManager
|
@EnvironmentObject var pluginManager: PluginManager
|
||||||
@EnvironmentObject var logManager: LoggingManager
|
@EnvironmentObject var logManager: LoggingManager
|
||||||
|
|
||||||
|
@AppStorage("Behavior.AutocorrectSearch") var autocorrectSearch: Bool = false
|
||||||
@AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false
|
@AppStorage("Behavior.UsesRandomSearchText") var usesRandomSearchText: Bool = false
|
||||||
|
|
||||||
@State private var isEditingSearch = false
|
@State private var isEditingSearch = false
|
||||||
|
|
@ -110,6 +111,11 @@ struct ContentView: View {
|
||||||
searchBarText = getSearchBarText()
|
searchBarText = getSearchBarText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.autocorrectionDisabled(!autocorrectSearch)
|
||||||
|
.backport.introspectSearchController { searchController in
|
||||||
|
// TODO: Replace with SwiftUI autocapitalization modifier
|
||||||
|
searchController.searchBar.autocapitalizationType = .none
|
||||||
|
}
|
||||||
.navigationSearchBarHiddenWhenScrolling(false)
|
.navigationSearchBarHiddenWhenScrolling(false)
|
||||||
.customScopeBar {
|
.customScopeBar {
|
||||||
SearchFilterHeaderView()
|
SearchFilterHeaderView()
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,12 @@ struct SettingsView: View {
|
||||||
|
|
||||||
@AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true
|
@AppStorage("Updates.AutomaticNotifs") var autoUpdateNotifs = true
|
||||||
|
|
||||||
@AppStorage("Actions.DefaultDebridName") var defaultDebridActionName: String?
|
@AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: CodableWrapper<DefaultAction> = .init(value: .none)
|
||||||
@AppStorage("Actions.DefaultDebridList") var defaultDebridActionList: String?
|
@AppStorage("Actions.DefaultDebrid") var defaultDebridAction: CodableWrapper<DefaultAction> = .init(value: .none)
|
||||||
|
|
||||||
@AppStorage("Actions.DefaultMagnetName") var defaultMagnetActionName: String?
|
|
||||||
@AppStorage("Actions.DefaultMagnetList") var defaultMagnetActionList: String?
|
|
||||||
|
|
||||||
@AppStorage("Debug.ShowErrorToasts") var showErrorToasts = true
|
@AppStorage("Debug.ShowErrorToasts") var showErrorToasts = true
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavView {
|
NavView {
|
||||||
Form {
|
Form {
|
||||||
|
|
@ -85,17 +83,26 @@ struct SettingsView: View {
|
||||||
NavigationLink(
|
NavigationLink(
|
||||||
destination: DefaultActionPickerView(
|
destination: DefaultActionPickerView(
|
||||||
actionRequirement: .debrid,
|
actionRequirement: .debrid,
|
||||||
defaultActionName: $defaultDebridActionName,
|
defaultAction: $defaultDebridAction.value
|
||||||
defaultActionList: $defaultDebridActionList
|
|
||||||
),
|
),
|
||||||
label: {
|
label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Debrid action")
|
Text("Debrid action")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// TODO: Maybe make this check for nil list as well?
|
Group {
|
||||||
Text(defaultDebridActionName.map { $0 } ?? "User choice")
|
switch defaultDebridAction.value {
|
||||||
.foregroundColor(.secondary)
|
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(
|
NavigationLink(
|
||||||
destination: DefaultActionPickerView(
|
destination: DefaultActionPickerView(
|
||||||
actionRequirement: .magnet,
|
actionRequirement: .magnet,
|
||||||
defaultActionName: $defaultMagnetActionName,
|
defaultAction: $defaultMagnetAction.value
|
||||||
defaultActionList: $defaultMagnetActionList
|
|
||||||
),
|
),
|
||||||
label: {
|
label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Magnet action")
|
Text("Magnet action")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// TODO: Maybe make this check for nil list as well?
|
Group {
|
||||||
Text(defaultMagnetActionName.map { $0 } ?? "User choice")
|
switch defaultMagnetAction.value {
|
||||||
.foregroundColor(.secondary)
|
case .none:
|
||||||
|
Text("User choice")
|
||||||
|
case .share:
|
||||||
|
Text("Share")
|
||||||
|
case .kodi:
|
||||||
|
Text("Kodi")
|
||||||
|
case .custom(let name, _):
|
||||||
|
Text(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,11 @@ struct ActionChoiceView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !kodiServers.isEmpty {
|
if !kodiServers.isEmpty {
|
||||||
DisclosureGroup("Open in Kodi") {
|
DisclosureGroup("Open in Kodi", isExpanded: $navModel.kodiExpanded) {
|
||||||
ForEach(kodiServers, id: \.self) { server in
|
ForEach(kodiServers, id: \.self) { server in
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
await pluginManager.sendToKodi(urlString: debridManager.downloadUrl)
|
await pluginManager.sendToKodi(urlString: debridManager.downloadUrl, server: server)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
KodiServerView(server: server)
|
KodiServerView(server: server)
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ struct BatchChoiceView: View {
|
||||||
PersistenceController.shared.createHistory(selectedHistoryInfo, performSave: true)
|
PersistenceController.shared.createHistory(selectedHistoryInfo, performSave: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginManager.runDebridAction(
|
pluginManager.runDefaultAction(
|
||||||
urlString: debridManager.downloadUrl,
|
urlString: debridManager.downloadUrl,
|
||||||
navModel: navModel
|
navModel: navModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue