diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index aa8efc3..b405b4f 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */; }; 0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */; }; 0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */ = {isa = PBXBuildFile; productRef = 0C90E32B2888E5D000C0BC89 /* ActivityView */; }; + 0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */; }; + 0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */; }; 0CA05457288EE58200850554 /* SettingsSourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsSourceListView.swift */; }; 0CA05459288EE9E600850554 /* SourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05458288EE9E600850554 /* SourceManager.swift */; }; 0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA0545A288EEA4E00850554 /* SourceListEditorView.swift */; }; @@ -93,6 +95,8 @@ 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = ""; }; 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataClass.swift"; sourceTree = ""; }; 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataProperties.swift"; sourceTree = ""; }; + 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultActionsPickerViews.swift; sourceTree = ""; }; + 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = ""; }; 0CA05456288EE58200850554 /* SettingsSourceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSourceListView.swift; sourceTree = ""; }; 0CA05458288EE9E600850554 /* SourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceManager.swift; sourceTree = ""; }; 0CA0545A288EEA4E00850554 /* SourceListEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceListEditorView.swift; sourceTree = ""; }; @@ -167,6 +171,7 @@ children = ( 0CA148C4288903F000DE2211 /* RealDebridModels.swift */, 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */, + 0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */, ); path = Models; sourceTree = ""; @@ -187,6 +192,7 @@ children = ( 0CA05456288EE58200850554 /* SettingsSourceListView.swift */, 0CA0545A288EEA4E00850554 /* SourceListEditorView.swift */, + 0C95D8D728A55B03005E22B3 /* DefaultActionsPickerViews.swift */, ); path = SettingsViews; sourceTree = ""; @@ -415,6 +421,7 @@ 0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */, 0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */, 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, + 0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, 0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */, 0C794B69289DACC800DD1CC8 /* InstalledSourceView.swift in Sources */, @@ -422,6 +429,7 @@ 0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */, 0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */, 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */, + 0C95D8DA28A55BB6005E22B3 /* SettingsModels.swift in Sources */, 0CA148E3288903F000DE2211 /* Task.swift in Sources */, 0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */, 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */, diff --git a/Ferrite/FerriteApp.swift b/Ferrite/FerriteApp.swift index 56cf03f..4d486fd 100644 --- a/Ferrite/FerriteApp.swift +++ b/Ferrite/FerriteApp.swift @@ -14,7 +14,7 @@ struct FerriteApp: App { @StateObject var scrapingModel: ScrapingViewModel = .init() @StateObject var toastModel: ToastViewModel = .init() @StateObject var debridManager: DebridManager = .init() - @StateObject var navigationModel: NavigationViewModel = .init() + @StateObject var navModel: NavigationViewModel = .init() @StateObject var sourceManager: SourceManager = .init() var body: some Scene { @@ -24,11 +24,12 @@ struct FerriteApp: App { scrapingModel.toastModel = toastModel debridManager.toastModel = toastModel sourceManager.toastModel = toastModel + navModel.toastModel = toastModel } .environmentObject(debridManager) .environmentObject(scrapingModel) .environmentObject(toastModel) - .environmentObject(navigationModel) + .environmentObject(navModel) .environmentObject(sourceManager) .environment(\.managedObjectContext, persistenceController.container.viewContext) } diff --git a/Ferrite/Models/SettingsModels.swift b/Ferrite/Models/SettingsModels.swift new file mode 100644 index 0000000..b4dc8c9 --- /dev/null +++ b/Ferrite/Models/SettingsModels.swift @@ -0,0 +1,32 @@ +// +// SettingsModels.swift +// Ferrite +// +// Created by Brian Dashore on 8/11/22. +// + +import Foundation + +public enum DefaultMagnetActionType: Int { + // Let the user choose + case none = 0 + + // Open in actions come first + case webtor = 1 + + // Sharing actions come last + case shareMagnet = 2 +} + +public enum DefaultDebridActionType: Int { + // Let the user choose + case none = 0 + + // Open in actions come first + case outplayer = 1 + case vlc = 2 + case infuse = 3 + + // Sharing actions come last + case shareDownload = 4 +} diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index b2642b2..5130d5c 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -124,9 +124,12 @@ public class DebridManager: ObservableObject { let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId, selectedIndex: iaFile == nil ? 0 : iaFile?.batchFileIndex) let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink) - Task { @MainActor in + let downloadUrlTask = Task { @MainActor in realDebridDownloadUrl = downloadLink } + + // Prevent a race condition when setting the published variable + await downloadUrlTask.value } catch { Task { @MainActor in toastModel?.toastDescription = "RealDebrid download error: \(error)" diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index e7d8818..5cdb6d1 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -5,6 +5,7 @@ // Created by Brian Dashore on 7/24/22. // +import ActivityView import SwiftUI enum ViewTab { @@ -13,7 +14,10 @@ enum ViewTab { case settings } +@MainActor class NavigationViewModel: ObservableObject { + var toastModel: ToastViewModel? + // Used between SearchResultsView and MagnetChoiceView enum ChoiceSheetType: Identifiable { var id: Int { @@ -25,6 +29,7 @@ class NavigationViewModel: ObservableObject { } @Published var currentChoiceSheet: ChoiceSheetType? + @Published var currentActivityItem: ActivityItem? @Published var selectedTab: ViewTab = .search @Published var showSearchProgress: Bool = false @@ -35,4 +40,61 @@ class NavigationViewModel: ObservableObject { @Published var showSourceListEditor: Bool = false @Published var selectedSourceList: SourceList? + + @AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none + @AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none + + public func runDebridAction(action: DefaultDebridActionType?, urlString: String) { + let selectedAction = action ?? defaultDebridAction + + switch selectedAction { + case .none: + currentChoiceSheet = .magnet + case .outplayer: + if let downloadUrl = URL(string: "outplayer://\(urlString)") { + UIApplication.shared.open(downloadUrl) + } else { + toastModel?.toastDescription = "Could not create an Outplayer URL" + } + case .vlc: + if let downloadUrl = URL(string: "vlc://\(urlString)") { + UIApplication.shared.open(downloadUrl) + } else { + toastModel?.toastDescription = "Could not create a VLC URL" + } + case .infuse: + if let downloadUrl = URL(string: "infuse://x-callback-url/play?url=\(urlString)") { + UIApplication.shared.open(downloadUrl) + } else { + toastModel?.toastDescription = "Could not create a Infuse URL" + } + case .shareDownload: + if let downloadUrl = URL(string: urlString), currentChoiceSheet == nil { + currentActivityItem = ActivityItem(items: downloadUrl) + } else { + toastModel?.toastDescription = "Could not create object for sharing" + } + } + } + + public func runMagnetAction(action: DefaultMagnetActionType?, searchResult: SearchResult) { + let selectedAction = action ?? defaultMagnetAction + + switch selectedAction { + case .none: + currentChoiceSheet = .magnet + case .webtor: + if let url = URL(string: "https://webtor.io/#/show?magnet=\(searchResult.magnetLink)") { + UIApplication.shared.open(url) + } else { + toastModel?.toastDescription = "Could not create a WebTor URL" + } + case .shareMagnet: + if let magnetUrl = URL(string: searchResult.magnetLink), currentChoiceSheet == nil { + currentActivityItem = ActivityItem(items: magnetUrl) + } else { + toastModel?.toastDescription = "Could not create object for sharing" + } + } + } } diff --git a/Ferrite/Views/BatchChoiceView.swift b/Ferrite/Views/BatchChoiceView.swift index d31d124..8722689 100644 --- a/Ferrite/Views/BatchChoiceView.swift +++ b/Ferrite/Views/BatchChoiceView.swift @@ -12,7 +12,7 @@ struct BatchChoiceView: View { @EnvironmentObject var debridManager: DebridManager @EnvironmentObject var scrapingModel: ScrapingViewModel - @EnvironmentObject var navigationModel: NavigationViewModel + @EnvironmentObject var navModel: NavigationViewModel var body: some View { NavView { @@ -27,7 +27,7 @@ struct BatchChoiceView: View { // The download may complete before this sheet dismisses try? await Task.sleep(seconds: 1) - navigationModel.currentChoiceSheet = .magnet + navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl) debridManager.selectedRealDebridFile = nil debridManager.selectedRealDebridItem = nil diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index ad76303..fccc578 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -5,6 +5,7 @@ // Created by Brian Dashore on 7/1/22. // +import ActivityView import SwiftUI struct ContentView: View { @@ -96,6 +97,7 @@ struct ContentView: View { } .tint(.primary) } + .activitySheet($navModel.currentActivityItem) } } diff --git a/Ferrite/Views/MagnetChoiceView.swift b/Ferrite/Views/MagnetChoiceView.swift index a8edee3..b08f674 100644 --- a/Ferrite/Views/MagnetChoiceView.swift +++ b/Ferrite/Views/MagnetChoiceView.swift @@ -13,6 +13,7 @@ struct MagnetChoiceView: View { @EnvironmentObject var scrapingModel: ScrapingViewModel @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var navModel: NavigationViewModel @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false @@ -27,27 +28,15 @@ struct MagnetChoiceView: View { if realDebridEnabled, debridManager.matchSearchResult(result: scrapingModel.selectedSearchResult) != .none { Section("Real Debrid options") { ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") { - guard let downloadUrl = URL(string: "outplayer://\(debridManager.realDebridDownloadUrl)") else { - return - } - - UIApplication.shared.open(downloadUrl) + navModel.runDebridAction(action: .outplayer, urlString: debridManager.realDebridDownloadUrl) } ListRowButtonView("Play on VLC", systemImage: "arrow.up.forward.app.fill") { - guard let downloadUrl = URL(string: "vlc://\(debridManager.realDebridDownloadUrl)") else { - return - } - - UIApplication.shared.open(downloadUrl) + navModel.runDebridAction(action: .vlc, urlString: debridManager.realDebridDownloadUrl) } ListRowButtonView("Play on Infuse", systemImage: "arrow.up.forward.app.fill") { - guard let downloadUrl = URL(string: "infuse://x-callback-url/play?url=\(debridManager.realDebridDownloadUrl)") else { - return - } - - UIApplication.shared.open(downloadUrl) + navModel.runDebridAction(action: .infuse, urlString: debridManager.realDebridDownloadUrl) } ListRowButtonView("Copy download URL", systemImage: "doc.on.doc.fill") { @@ -68,7 +57,6 @@ struct MagnetChoiceView: View { } activityItem = ActivityItem(items: url) - showActivityView.toggle() } } } @@ -89,15 +77,12 @@ struct MagnetChoiceView: View { ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") { if let result = scrapingModel.selectedSearchResult, let url = URL(string: result.magnetLink) { activityItem = ActivityItem(items: url) - showActivityView.toggle() } } ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") { - if let result = scrapingModel.selectedSearchResult, - let url = URL(string: "https://webtor.io/#/show?magnet=\(result.magnetLink)") - { - UIApplication.shared.open(url) + if let result = scrapingModel.selectedSearchResult { + navModel.runMagnetAction(action: .webtor, searchResult: result) } } } diff --git a/Ferrite/Views/SearchResultsView.swift b/Ferrite/Views/SearchResultsView.swift index 626c268..7077f16 100644 --- a/Ferrite/Views/SearchResultsView.swift +++ b/Ferrite/Views/SearchResultsView.swift @@ -29,14 +29,14 @@ struct SearchResultsView: View { case .full: Task { await debridManager.fetchRdDownload(searchResult: result) - navModel.currentChoiceSheet = .magnet + navModel.runDebridAction(action: nil, urlString: debridManager.realDebridDownloadUrl) } case .partial: if debridManager.setSelectedRdResult(result: result) { navModel.currentChoiceSheet = .batch } case .none: - navModel.currentChoiceSheet = .magnet + navModel.runMagnetAction(action: nil, searchResult: result) } } label: { Text(result.title) diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index dae6a4c..bc49f05 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -14,6 +14,8 @@ struct SettingsView: View { let backgroundContext = PersistenceController.shared.backgroundContext @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false + @AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none + @AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none @State private var isProcessing = false @@ -44,6 +46,56 @@ struct SettingsView: View { NavigationLink("Source lists", destination: SettingsSourceListView()) } + Section("Default actions") { + if realDebridEnabled { + NavigationLink( + destination: DebridActionPickerView(), + label: { + HStack { + Text("Default debrid action") + Spacer() + Group { + switch defaultDebridAction { + case .none: + Text("User choice") + case .outplayer: + Text("Outplayer") + case .vlc: + Text("VLC") + case .infuse: + Text("Infuse") + case .shareDownload: + Text("Share") + } + } + .foregroundColor(.gray) + } + } + ) + } + + NavigationLink( + destination: MagnetActionPickerView(), + label: { + HStack { + Text("Default magnet action") + Spacer() + Group { + switch defaultMagnetAction { + case .none: + Text("User choice") + case .webtor: + Text("Webtor") + case .shareMagnet: + Text("Share") + } + } + .foregroundColor(.gray) + } + } + ) + } + Section { ListRowLinkView(text: "Report issues", link: "https://github.com/bdashore3/Ferrite/issues") diff --git a/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift b/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift new file mode 100644 index 0000000..cc13169 --- /dev/null +++ b/Ferrite/Views/SettingsViews/DefaultActionsPickerViews.swift @@ -0,0 +1,54 @@ +// +// DefaultActionsPickerViews.swift +// Ferrite +// +// Created by Brian Dashore on 8/11/22. +// + +import SwiftUI + +struct MagnetActionPickerView: View { + @AppStorage("Actions.DefaultMagnet") var defaultMagnetAction: DefaultMagnetActionType = .none + + var body: some View { + List { + Picker(selection: $defaultMagnetAction, label: EmptyView()) { + Text("Let me choose") + .tag(DefaultMagnetActionType.none) + Text("Open in Webtor") + .tag(DefaultMagnetActionType.webtor) + Text("Share magnet link") + .tag(DefaultMagnetActionType.shareMagnet) + } + } + .pickerStyle(.inline) + .listStyle(.insetGrouped) + .navigationTitle("Default magnet action") + .navigationBarTitleDisplayMode(.inline) + } +} + +struct DebridActionPickerView: View { + @AppStorage("Actions.DefaultDebrid") var defaultDebridAction: DefaultDebridActionType = .none + + var body: some View { + List { + Picker(selection: $defaultDebridAction, label: EmptyView()) { + Text("Let me choose") + .tag(DefaultDebridActionType.none) + Text("Open in Outplayer") + .tag(DefaultDebridActionType.outplayer) + Text("Open in VLC") + .tag(DefaultDebridActionType.vlc) + Text("Open in Infuse") + .tag(DefaultDebridActionType.infuse) + Text("Share download link") + .tag(DefaultDebridActionType.shareDownload) + } + } + .pickerStyle(.inline) + .listStyle(.insetGrouped) + .navigationTitle("Default debrid action") + .navigationBarTitleDisplayMode(.inline) + } +}