Support DeepLink jellyfin://Users/{UserID}/Items/{ItemID}

This commit is contained in:
PangMo5 2021-09-22 05:28:14 +09:00
parent b07e345efd
commit b92d66e26e
5 changed files with 92 additions and 8 deletions

View File

@ -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 */,

View File

@ -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()
} }

View File

@ -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()
}
}
}
} }

View File

@ -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)
}

View File

@ -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)
}
}