Support DeepLink jellyfin://Users/{UserID}/Items/{ItemID}
This commit is contained in:
parent
b07e345efd
commit
b92d66e26e
|
@ -249,6 +249,7 @@
|
||||||
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
||||||
62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
||||||
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
||||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||||
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
||||||
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
|
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
|
||||||
|
@ -483,6 +484,7 @@
|
||||||
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
|
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
|
||||||
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
||||||
|
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
||||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||||
BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = "<group>"; };
|
BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = "<group>"; };
|
D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
@ -771,6 +773,7 @@
|
||||||
children = (
|
children = (
|
||||||
62C29E9D26D0FE5900C1D2E7 /* Coordinators */,
|
62C29E9D26D0FE5900C1D2E7 /* Coordinators */,
|
||||||
53F866422687A45400DCD1D7 /* Components */,
|
53F866422687A45400DCD1D7 /* Components */,
|
||||||
|
62ECA01926FA6D6900E8EBB7 /* Singleton */,
|
||||||
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
|
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
|
||||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
||||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||||
|
@ -796,6 +799,7 @@
|
||||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
||||||
625CB5672678B6FB00530A6E /* SplashView.swift */,
|
625CB5672678B6FB00530A6E /* SplashView.swift */,
|
||||||
625CB56E2678C23300530A6E /* HomeView.swift */,
|
625CB56E2678C23300530A6E /* HomeView.swift */,
|
||||||
|
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */,
|
||||||
);
|
);
|
||||||
path = JellyfinPlayer;
|
path = JellyfinPlayer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1019,6 +1023,13 @@
|
||||||
53649AB0269CFB1900A2D8B7 /* LogManager.swift */,
|
53649AB0269CFB1900A2D8B7 /* LogManager.swift */,
|
||||||
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
|
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
|
||||||
62EC352E267666A5000E9F2D /* SessionManager.swift */,
|
62EC352E267666A5000E9F2D /* SessionManager.swift */,
|
||||||
|
);
|
||||||
|
path = Singleton;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
62ECA01926FA6D6900E8EBB7 /* Singleton */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
6220D0CB26D640C400B8E046 /* AppURLHandler.swift */,
|
6220D0CB26D640C400B8E046 /* AppURLHandler.swift */,
|
||||||
);
|
);
|
||||||
path = Singleton;
|
path = Singleton;
|
||||||
|
@ -1587,6 +1598,7 @@
|
||||||
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
|
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
|
||||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||||
625CB5682678B6FB00530A6E /* SplashView.swift in Sources */,
|
625CB5682678B6FB00530A6E /* SplashView.swift in Sources */,
|
||||||
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */,
|
||||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
||||||
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */,
|
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import SwiftUI
|
||||||
let nc = NotificationCenter.default
|
let nc = NotificationCenter.default
|
||||||
nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil)
|
nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil)
|
||||||
nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil)
|
nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil)
|
||||||
|
nc.addObserver(self, selector: #selector(processDeepLink), name: Notification.Name("processDeepLink"), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didLogIn() {
|
@objc func didLogIn() {
|
||||||
|
@ -51,6 +52,19 @@ import SwiftUI
|
||||||
root(\.connectToServer)
|
root(\.connectToServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func processDeepLink(_ notification: Notification) {
|
||||||
|
guard let deepLink = notification.object as? DeepLink else { return }
|
||||||
|
if let coordinator = hasRoot(\.mainTab) {
|
||||||
|
switch deepLink {
|
||||||
|
case let .item(item):
|
||||||
|
coordinator.focusFirst(\.home)
|
||||||
|
.child
|
||||||
|
.popToRoot()
|
||||||
|
.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeMainTab() -> MainTabCoordinator {
|
func makeMainTab() -> MainTabCoordinator {
|
||||||
MainTabCoordinator()
|
MainTabCoordinator()
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,13 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
Image(systemName: "folder")
|
Image(systemName: "folder")
|
||||||
Text("All Media")
|
Text("All Media")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder func customize(_ view: AnyView) -> some View {
|
||||||
|
view.onAppear {
|
||||||
|
AppURLHandler.shared.appURLState = .allowed
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||||
|
AppURLHandler.shared.processLaunchedURLIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* SwiftFin is subject to the terms of the Mozilla Public
|
||||||
|
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
enum DeepLinkError: LocalizedError {
|
||||||
|
case general
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .general:
|
||||||
|
return "Couldn't create deep link"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DeepLink {
|
||||||
|
case item(BaseItemDto)
|
||||||
|
}
|
|
@ -7,15 +7,14 @@
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
|
||||||
final class AppURLHandler {
|
final class AppURLHandler {
|
||||||
static let deepLinkScheme = "jellyfin"
|
static let deepLinkScheme = "jellyfin"
|
||||||
|
|
||||||
@RouterObject
|
|
||||||
var router: HomeCoordinator.Router?
|
|
||||||
|
|
||||||
enum AppURLState {
|
enum AppURLState {
|
||||||
case launched
|
case launched
|
||||||
case allowedInLogin
|
case allowedInLogin
|
||||||
|
@ -35,6 +34,8 @@ final class AppURLHandler {
|
||||||
|
|
||||||
static let shared = AppURLHandler()
|
static let shared = AppURLHandler()
|
||||||
|
|
||||||
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var appURLState: AppURLState = .launched
|
var appURLState: AppURLState = .launched
|
||||||
var launchURL: URL?
|
var launchURL: URL?
|
||||||
}
|
}
|
||||||
|
@ -46,9 +47,7 @@ extension AppURLHandler {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if AppURLHandler.shared.appURLState.allowedScheme(with: url) {
|
if AppURLHandler.shared.appURLState.allowedScheme(with: url) {
|
||||||
if launchURL == nil {
|
return processURL(url)
|
||||||
return processURL(url)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
launchURL = url
|
launchURL = url
|
||||||
}
|
}
|
||||||
|
@ -56,7 +55,8 @@ extension AppURLHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLaunchedURLIfNeeded() {
|
func processLaunchedURLIfNeeded() {
|
||||||
guard let launchURL = launchURL else { return }
|
guard let launchURL = launchURL,
|
||||||
|
!launchURL.absoluteString.isEmpty else { return }
|
||||||
if processDeepLink(url: launchURL) {
|
if processDeepLink(url: launchURL) {
|
||||||
self.launchURL = nil
|
self.launchURL = nil
|
||||||
}
|
}
|
||||||
|
@ -76,12 +76,35 @@ extension AppURLHandler {
|
||||||
|
|
||||||
// /Users/{UserID}/Items/{ItemID}
|
// /Users/{UserID}/Items/{ItemID}
|
||||||
if url.pathComponents[safe: 2]?.lowercased() == "items",
|
if url.pathComponents[safe: 2]?.lowercased() == "items",
|
||||||
|
let userID = url.pathComponents[safe: 1],
|
||||||
let itemID = url.pathComponents[safe: 3]
|
let itemID = url.pathComponents[safe: 3]
|
||||||
{
|
{
|
||||||
// router?.route(to: \.item(item: item))
|
// It would be nice if the ItemViewModel could be initialized to id later.
|
||||||
|
getItem(userID: userID, itemID: itemID) { item in
|
||||||
|
guard let item = item else { return }
|
||||||
|
NotificationCenter.default.post(name: Notification.Name("processDeepLink"), object: DeepLink.item(item))
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppURLHandler {
|
||||||
|
func getItem(userID: String, itemID: String, completion: @escaping (BaseItemDto?) -> Void) {
|
||||||
|
UserLibraryAPI.getItem(userId: userID, itemId: itemID)
|
||||||
|
.sink(receiveCompletion: { innerCompletion in
|
||||||
|
switch innerCompletion {
|
||||||
|
case .failure:
|
||||||
|
completion(nil)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, receiveValue: { item in
|
||||||
|
completion(item)
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue