mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2025-03-02 03:56:00 +00:00
Temporarily process deep link from HomeView
This commit is contained in:
parent
b544bd66cc
commit
5d96de329f
@ -189,6 +189,7 @@
|
||||
6220D0C726D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */; };
|
||||
6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */ = {isa = PBXBuildFile; productRef = 6220D0C826D63F3700B8E046 /* Stinsen */; };
|
||||
6220D0CA26D63F4D00B8E046 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */; };
|
||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0CB26D640C400B8E046 /* AppURLHandler.swift */; };
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */; };
|
||||
6228B1C22670EB010067FD35 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBFD263B596B003A4E83 /* PersistenceController.swift */; };
|
||||
624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624C21742685CF60007F1390 /* SearchablePickerView.swift */; };
|
||||
@ -430,6 +431,7 @@
|
||||
6220D0BC26D60D6600B8E046 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = "<group>"; };
|
||||
6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCoordinator.swift; sourceTree = "<group>"; };
|
||||
6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerCoordinator.swift; sourceTree = "<group>"; };
|
||||
6220D0CB26D640C400B8E046 /* AppURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppURLHandler.swift; sourceTree = "<group>"; };
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeader.swift; sourceTree = "<group>"; };
|
||||
624C21742685CF60007F1390 /* SearchablePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePickerView.swift; sourceTree = "<group>"; };
|
||||
625CB5672678B6FB00530A6E /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
||||
@ -997,6 +999,7 @@
|
||||
62EC352E267666A5000E9F2D /* SessionManager.swift */,
|
||||
536D3D73267BA8170004248C /* BackgroundManager.swift */,
|
||||
53649AB0269CFB1900A2D8B7 /* LogManager.swift */,
|
||||
6220D0CB26D640C400B8E046 /* AppURLHandler.swift */,
|
||||
);
|
||||
path = Singleton;
|
||||
sourceTree = "<group>";
|
||||
@ -1513,6 +1516,7 @@
|
||||
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
|
||||
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
||||
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */,
|
||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||
|
@ -177,5 +177,8 @@ struct ConnectToServerView: View {
|
||||
dismissButton: .cancel())
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Connect to Server", comment: ""))
|
||||
.onAppear {
|
||||
AppURLHandler.shared.appURLState = .allowedInLogin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
struct HomeView: View {
|
||||
@EnvironmentObject var homeRouter: NavigationRouter<HomeCoordinator.Route>
|
||||
@ -37,7 +37,9 @@ struct HomeView: View {
|
||||
.fontWeight(.bold)
|
||||
Spacer()
|
||||
Button {
|
||||
homeRouter.route(to: .library(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? ""))
|
||||
homeRouter
|
||||
.route(to: .library(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet),
|
||||
title: library?.name ?? ""))
|
||||
} label: {
|
||||
HStack {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
@ -45,7 +47,7 @@ struct HomeView: View {
|
||||
}
|
||||
}
|
||||
}.padding(.leading, 16)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.trailing, 16)
|
||||
LatestMediaView(viewModel: .init(libraryID: libraryID))
|
||||
}
|
||||
}
|
||||
@ -67,5 +69,11 @@ struct HomeView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
AppURLHandler.shared.appURLState = .allowed
|
||||
AppURLHandler.shared.processLaunchedURLIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,17 @@
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>jellyfin</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
@ -234,6 +234,9 @@ struct JellyfinPlayerApp: App {
|
||||
.onShake {
|
||||
EmailHelper.shared.sendLogs(logURL: LogManager.shared.logFileURL())
|
||||
}
|
||||
.onOpenURL { url in
|
||||
AppURLHandler.shared.processDeepLink(url: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
102
Shared/Singleton/AppURLHandler.swift
Normal file
102
Shared/Singleton/AppURLHandler.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
/*
|
||||
* 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 Stinsen
|
||||
import URLNavigator
|
||||
|
||||
final class AppURLHandler {
|
||||
static let deepLinkScheme = "jellyfin"
|
||||
|
||||
@RouterObject
|
||||
var router: NavigationRouter<HomeCoordinator.Route>?
|
||||
|
||||
enum AppURLState {
|
||||
case launched
|
||||
case allowedInLogin
|
||||
case allowed
|
||||
|
||||
func allowedScheme(with url: URL) -> Bool {
|
||||
switch self {
|
||||
case .launched:
|
||||
return false
|
||||
case .allowed:
|
||||
return true
|
||||
case .allowedInLogin:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let shared = AppURLHandler()
|
||||
|
||||
var appURLState: AppURLState = .launched
|
||||
var launchURL: URL?
|
||||
}
|
||||
|
||||
extension AppURLHandler {
|
||||
@discardableResult
|
||||
func processDeepLink(url: URL) -> Bool {
|
||||
guard url.scheme == Self.deepLinkScheme || url.scheme == "widget-extension" else {
|
||||
return false
|
||||
}
|
||||
print(AppURLHandler.shared.appURLState.allowedScheme(with: url))
|
||||
if AppURLHandler.shared.appURLState.allowedScheme(with: url) {
|
||||
if launchURL == nil {
|
||||
return processURL(url)
|
||||
}
|
||||
} else {
|
||||
launchURL = url
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func processLaunchedURLIfNeeded() {
|
||||
print("!@#!@#!@#!@#!@#!@")
|
||||
print(launchURL)
|
||||
guard let launchURL = launchURL else { return }
|
||||
if processDeepLink(url: launchURL) {
|
||||
self.launchURL = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func processURL(_ url: URL) -> Bool {
|
||||
print("processURL(_ url: URL) -> Bool")
|
||||
if processURLForUser(url: url) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func processURLForUser(url: URL) -> Bool {
|
||||
print("processURLForUser(_ url: URL) -> Bool")
|
||||
print(url)
|
||||
print(url.host)
|
||||
print(url.path)
|
||||
print(url.pathComponents)
|
||||
print(url.pathComponents[safe: 0])
|
||||
print(url.pathComponents[safe: 1])
|
||||
print(url.pathComponents[safe: 2])
|
||||
print(url.pathComponents[safe: 3])
|
||||
guard url.host?.lowercased() == "users",
|
||||
url.pathComponents[safe: 1]?.isEmpty == false else { return false }
|
||||
|
||||
// /Users/{UserID}/Items/{ItemID}
|
||||
if url.pathComponents[safe: 2]?.lowercased() == "items",
|
||||
let itemID = url.pathComponents[safe: 3]
|
||||
{
|
||||
print("Passed!@#")
|
||||
router?.route(to: .item(viewModel: .init(id: itemID)))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@ -29,6 +29,10 @@ final class ConnectToServerViewModel: ViewModel {
|
||||
private let discovery = ServerDiscovery()
|
||||
@Published var servers: [ServerDiscovery.ServerLookupResponse] = []
|
||||
@Published var searching = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
func getPublicUsers() {
|
||||
if ServerEnvironment.current.server != nil {
|
||||
|
@ -24,7 +24,6 @@ final class HomeViewModel: ViewModel {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
@ -142,14 +142,14 @@ struct NextUpEntryView: View {
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
} else {
|
||||
switch family {
|
||||
case .systemSmall:
|
||||
small(item: entry.items.first)
|
||||
case .systemMedium:
|
||||
medium(items: entry.items)
|
||||
case .systemLarge:
|
||||
large(items: entry.items)
|
||||
default:
|
||||
EmptyView()
|
||||
case .systemSmall:
|
||||
small(item: entry.items.first)
|
||||
case .systemMedium:
|
||||
medium(items: entry.items)
|
||||
case .systemLarge:
|
||||
large(items: entry.items)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -198,51 +198,55 @@ extension NextUpEntryView {
|
||||
}
|
||||
|
||||
func smallVideoView(item: (BaseItemDto, UIImage?)) -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
Text(item.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func largeVideoView(item: (BaseItemDto, UIImage?)) -> some View {
|
||||
HStack(spacing: 20) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(item.0.id!)")!, label: {
|
||||
VStack(alignment: .leading) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
Text(item.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func largeVideoView(item: (BaseItemDto, UIImage?)) -> some View {
|
||||
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(item.0.id!)")!, label: {
|
||||
HStack(spacing: 20) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(item.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,33 +285,36 @@ extension NextUpEntryView {
|
||||
func large(items: [(BaseItemDto, UIImage?)]) -> some View {
|
||||
VStack(spacing: 0) {
|
||||
if let firstItem = items[safe: 0] {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
if let image = firstItem.1 {
|
||||
Image(uiImage: image)
|
||||
.centerCropped()
|
||||
.innerShadow(color: Color.black.opacity(0.5), radius: 0.5)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(firstItem.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.white)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
Text("\(firstItem.0.name ?? "") · S\(firstItem.0.parentIndexNumber ?? 0):E\(firstItem.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.shadow(radius: 8)
|
||||
.padding(12)
|
||||
}
|
||||
headerSymbol
|
||||
.padding(12)
|
||||
}
|
||||
.clipped()
|
||||
.shadow(radius: 8)
|
||||
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(firstItem.0.id!)")!,
|
||||
label: {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
if let image = firstItem.1 {
|
||||
Image(uiImage: image)
|
||||
.centerCropped()
|
||||
.innerShadow(color: Color.black.opacity(0.5), radius: 0.5)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(firstItem.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.white)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
Text("\(firstItem.0.name ?? "") · S\(firstItem.0.parentIndexNumber ?? 0):E\(firstItem.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.shadow(radius: 8)
|
||||
.padding(12)
|
||||
}
|
||||
headerSymbol
|
||||
.padding(12)
|
||||
}
|
||||
.clipped()
|
||||
.shadow(radius: 8)
|
||||
})
|
||||
}
|
||||
VStack(spacing: 8) {
|
||||
if let secondItem = items[safe: 1] {
|
||||
@ -354,7 +361,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
@ -365,7 +372,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
@ -380,7 +387,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
@ -392,7 +399,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
@ -405,7 +412,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
@ -415,7 +422,7 @@ struct NextUpWidget_Previews: PreviewProvider {
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
|
Loading…
x
Reference in New Issue
Block a user