changes
This commit is contained in:
parent
f1204238c0
commit
104bdaddb9
|
@ -0,0 +1,110 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* 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 SwiftUI
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
fileprivate struct CutOffShadow: Shape {
|
||||||
|
let radius = 6.0;
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
|
||||||
|
let tl = CGPoint(x: rect.minX, y: rect.minY)
|
||||||
|
let tr = CGPoint(x: rect.maxX, y: rect.minY)
|
||||||
|
let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius)
|
||||||
|
let brc = CGPoint(x: rect.maxX - radius, y: rect.maxY - radius)
|
||||||
|
let bls = CGPoint(x: rect.minX + radius, y: rect.maxY)
|
||||||
|
let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius)
|
||||||
|
|
||||||
|
path.move(to: tl)
|
||||||
|
path.addLine(to: tr)
|
||||||
|
path.addLine(to: brs)
|
||||||
|
path.addRelativeArc(center: brc, radius: radius,
|
||||||
|
startAngle: Angle.degrees(0), delta: Angle.degrees(90))
|
||||||
|
path.addLine(to: bls)
|
||||||
|
path.addRelativeArc(center: blc, radius: radius,
|
||||||
|
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LandscapeItemElement: View {
|
||||||
|
@Environment(\.isFocused) var envFocused: Bool
|
||||||
|
@State var focused: Bool = false;
|
||||||
|
@State var backgroundURL: URL?;
|
||||||
|
|
||||||
|
var item: BaseItemDto;
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack() {
|
||||||
|
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 375), bh: item.getBackdropImageBlurHash())
|
||||||
|
.frame(width: 375, height: 250)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
Group {
|
||||||
|
if(focused && item.userData?.playedPercentage != nil) {
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(LinearGradient(colors: [.black,.clear], startPoint: .bottom, endPoint: .top))
|
||||||
|
.frame(width: 375, height: 90)
|
||||||
|
.mask(CutOffShadow())
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("CONTINUE • \(item.getItemProgressString())")
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.offset(y: 5)
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.fill(Color.gray)
|
||||||
|
.opacity(0.4)
|
||||||
|
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12)
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
|
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0 * 3.59), height: 12)
|
||||||
|
}
|
||||||
|
}.padding(8)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}, alignment: .bottomLeading
|
||||||
|
)
|
||||||
|
.shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0)
|
||||||
|
.shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0)
|
||||||
|
if(focused) {
|
||||||
|
Text(item.seriesName ?? item.name ?? "")
|
||||||
|
.font(.callout)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(width: 375)
|
||||||
|
} else {
|
||||||
|
Spacer().frame(height: 25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: envFocused) { envFocus in
|
||||||
|
withAnimation(.linear(duration: 0.15)) {
|
||||||
|
self.focused = envFocus
|
||||||
|
}
|
||||||
|
|
||||||
|
if(envFocus == true) {
|
||||||
|
backgroundURL = item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: Int((UIScreen.main.currentMode?.size.width)!))
|
||||||
|
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||||
|
if(BackgroundManager.current.backgroundURL == backgroundURL) {
|
||||||
|
BackgroundManager.current.clearBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scaleEffect(focused ? 1.1 : 1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,6 @@ import SwiftUI
|
||||||
struct ConnectToServerView: View {
|
struct ConnectToServerView: View {
|
||||||
@StateObject var viewModel = ConnectToServerViewModel()
|
@StateObject var viewModel = ConnectToServerViewModel()
|
||||||
|
|
||||||
@Binding var isLoggedIn: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if viewModel.isConnectedServer {
|
if viewModel.isConnectedServer {
|
||||||
|
@ -31,6 +29,7 @@ struct ConnectToServerView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureField("Password (optional)", text: $viewModel.password)
|
SecureField("Password (optional)", text: $viewModel.password)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
|
@ -71,7 +70,10 @@ struct ConnectToServerView: View {
|
||||||
HStack() {
|
HStack() {
|
||||||
ForEach(viewModel.publicUsers, id: \.id) { publicUser in
|
ForEach(viewModel.publicUsers, id: \.id) { publicUser in
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if(!viewModel.userHasSavedCredentials(userID: publicUser.id!)) {
|
if(SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!)) {
|
||||||
|
let user = SessionManager.current.getSavedSession(userID: publicUser.id!)
|
||||||
|
SessionManager.current.loginWithSavedSession(user: user)
|
||||||
|
} else {
|
||||||
viewModel.username = publicUser.name ?? ""
|
viewModel.username = publicUser.name ?? ""
|
||||||
viewModel.selectedPublicUser = publicUser
|
viewModel.selectedPublicUser = publicUser
|
||||||
viewModel.hidePublicUsers()
|
viewModel.hidePublicUsers()
|
||||||
|
@ -79,8 +81,6 @@ struct ConnectToServerView: View {
|
||||||
viewModel.password = ""
|
viewModel.password = ""
|
||||||
viewModel.login()
|
viewModel.login()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
viewModel.loginWithSavedCredentials(user: publicUser)
|
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -141,9 +141,6 @@ struct ConnectToServerView: View {
|
||||||
.alert(item: $viewModel.errorMessage) { _ in
|
.alert(item: $viewModel.errorMessage) { _ in
|
||||||
Alert(title: Text("Error"), message: Text(viewModel.errorMessage ?? ""), dismissButton: .default(Text("Ok")))
|
Alert(title: Text("Error"), message: Text(viewModel.errorMessage ?? ""), dismissButton: .default(Text("Ok")))
|
||||||
}
|
}
|
||||||
.onReceive(viewModel.$isLoggedIn, perform: { flag in
|
|
||||||
isLoggedIn = flag
|
|
||||||
})
|
|
||||||
.navigationTitle(viewModel.isConnectedServer ? "Who's watching?" : "Connect to Jellyfin")
|
.navigationTitle(viewModel.isConnectedServer ? "Who's watching?" : "Connect to Jellyfin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
//
|
|
||||||
/*
|
|
||||||
* 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 SwiftUI
|
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
fileprivate struct ProgressBar: Shape {
|
|
||||||
func path(in rect: CGRect) -> Path {
|
|
||||||
var path = Path()
|
|
||||||
|
|
||||||
let tl = CGPoint(x: rect.minX, y: rect.minY)
|
|
||||||
let tr = CGPoint(x: rect.maxX, y: rect.minY)
|
|
||||||
let br = CGPoint(x: rect.maxX, y: rect.maxY)
|
|
||||||
let bls = CGPoint(x: rect.minX + 10, y: rect.maxY)
|
|
||||||
let blc = CGPoint(x: rect.minX + 10, y: rect.maxY - 10)
|
|
||||||
|
|
||||||
path.move(to: tl)
|
|
||||||
path.addLine(to: tr)
|
|
||||||
path.addLine(to: br)
|
|
||||||
path.addLine(to: bls)
|
|
||||||
path.addRelativeArc(center: blc, radius: 10,
|
|
||||||
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContinueWatchingItem: View {
|
|
||||||
@Environment(\.isFocused) var envFocused: Bool
|
|
||||||
@State var focused: Bool = false;
|
|
||||||
|
|
||||||
var item: BaseItemDto;
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack() {
|
|
||||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 375), bh: item.getBackdropImageBlurHash())
|
|
||||||
.frame(width: 375, height: 250)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.overlay(
|
|
||||||
Rectangle()
|
|
||||||
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
|
||||||
.mask(ProgressBar())
|
|
||||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0 * 3.75), height: 12)
|
|
||||||
.padding(6), alignment: .bottomLeading
|
|
||||||
)
|
|
||||||
if(focused) {
|
|
||||||
Text(item.seriesName ?? item.name ?? "")
|
|
||||||
.font(.callout)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.lineLimit(1)
|
|
||||||
.frame(width: 375)
|
|
||||||
} else {
|
|
||||||
Spacer().frame(height: 25)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: envFocused) { envFocus in
|
|
||||||
withAnimation(.linear(duration: 0.15)) {
|
|
||||||
self.focused = envFocus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scaleEffect(focused ? 1.1 : 1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,7 +25,7 @@ struct ContinueWatchingView: View {
|
||||||
Spacer().frame(width: 90)
|
Spacer().frame(width: 90)
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(items, id: \.id) { item in
|
||||||
NavigationLink(destination: Text("itemv")) {
|
NavigationLink(destination: Text("itemv")) {
|
||||||
ContinueWatchingItem(item: item)
|
LandscapeItemElement(item: item)
|
||||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
}
|
}
|
||||||
Spacer().frame(width: 90)
|
Spacer().frame(width: 90)
|
|
@ -24,10 +24,10 @@ struct HomeView: View {
|
||||||
if !viewModel.resumeItems.isEmpty {
|
if !viewModel.resumeItems.isEmpty {
|
||||||
ContinueWatchingView(items: viewModel.resumeItems)
|
ContinueWatchingView(items: viewModel.resumeItems)
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if !viewModel.nextUpItems.isEmpty {
|
||||||
NextUpView(items: viewModel.nextUpItems)
|
NextUpView(items: viewModel.nextUpItems)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if !viewModel.librariesShowRecentlyAddedIDs.isEmpty {
|
if !viewModel.librariesShowRecentlyAddedIDs.isEmpty {
|
||||||
ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in
|
ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
|
@ -12,8 +12,37 @@ import SwiftUI
|
||||||
|
|
||||||
struct MainTabView: View {
|
struct MainTabView: View {
|
||||||
@State private var tabSelection: Tab = .home
|
@State private var tabSelection: Tab = .home
|
||||||
|
@StateObject private var viewModel = MainTabViewModel()
|
||||||
|
@State private var backdropAnim: Bool = false
|
||||||
|
@State private var lastBackdropAnim: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
ZStack() {
|
||||||
|
//please do not touch my magical crossfading.
|
||||||
|
if(viewModel.backgroundURL != nil) {
|
||||||
|
if(viewModel.lastBackgroundURL != nil) {
|
||||||
|
ImageView(src: viewModel.lastBackgroundURL!, bh: viewModel.backgroundBlurHash)
|
||||||
|
.frame(width: UIScreen.main.currentMode?.size.width, height: UIScreen.main.currentMode?.size.height)
|
||||||
|
.blur(radius: 2)
|
||||||
|
.opacity(lastBackdropAnim ? 0.4 : 0)
|
||||||
|
.onChange(of: viewModel.backgroundURL) { _ in
|
||||||
|
withAnimation(.linear(duration: 0.15)) {
|
||||||
|
lastBackdropAnim = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView(src: viewModel.backgroundURL!, bh: viewModel.backgroundBlurHash)
|
||||||
|
.frame(width: UIScreen.main.currentMode?.size.width, height: UIScreen.main.currentMode?.size.height)
|
||||||
|
.blur(radius: 2)
|
||||||
|
.opacity(backdropAnim ? 0.4 : 0)
|
||||||
|
.onChange(of: viewModel.backgroundURL) { _ in
|
||||||
|
lastBackdropAnim = true
|
||||||
|
backdropAnim = false
|
||||||
|
withAnimation(.linear(duration: 0.15)) {
|
||||||
|
backdropAnim = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
TabView(selection: $tabSelection) {
|
TabView(selection: $tabSelection) {
|
||||||
HomeView()
|
HomeView()
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
@ -32,6 +61,7 @@ struct MainTabView: View {
|
||||||
.tag(Tab.allMedia)
|
.tag(Tab.allMedia)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainTabView {
|
extension MainTabView {
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/* JellyfinPlayer/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 SwiftUI
|
|
||||||
import Combine
|
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
struct NextUpView: View {
|
|
||||||
|
|
||||||
var items: [BaseItemDto]
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
if items.count != 0 {
|
|
||||||
Text("Next Up")
|
|
||||||
.font(.headline)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
LazyHStack {
|
|
||||||
Spacer().frame(width: 16)
|
|
||||||
ForEach(items, id: \.id) { item in
|
|
||||||
NavigationLink(destination: EmptyView()) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
ImageView(src: item.getSeriesPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
|
|
||||||
.frame(width: 100, height: 150)
|
|
||||||
.cornerRadius(10)
|
|
||||||
Spacer().frame(height: 5)
|
|
||||||
Text(item.seriesName!)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.lineLimit(1)
|
|
||||||
Text("S\(item.parentIndexNumber ?? 0):E\(item.indexNumber ?? 0)")
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.lineLimit(1)
|
|
||||||
}.frame(width: 100)
|
|
||||||
Spacer().frame(width: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* JellyfinPlayer/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 SwiftUI
|
||||||
|
import JellyfinAPI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct NextUpView: View {
|
||||||
|
var items: [BaseItemDto]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
if items.count > 0 {
|
||||||
|
Text("Next Up")
|
||||||
|
.font(.headline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.padding(.leading, 135)
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
LazyHStack {
|
||||||
|
Spacer().frame(width: 90)
|
||||||
|
ForEach(items, id: \.id) { item in
|
||||||
|
NavigationLink(destination: Text("itemv")) {
|
||||||
|
LandscapeItemElement(item: item)
|
||||||
|
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
|
}
|
||||||
|
Spacer().frame(width: 90)
|
||||||
|
}
|
||||||
|
}.frame(height: 330)
|
||||||
|
.offset(y: -10)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import SwiftUI
|
||||||
|
|
||||||
struct SplashView: View {
|
struct SplashView: View {
|
||||||
@StateObject var viewModel = SplashViewModel()
|
@StateObject var viewModel = SplashViewModel()
|
||||||
@State var showingAlert: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
@ -23,16 +22,10 @@ struct SplashView: View {
|
||||||
.padding(.trailing, -60)
|
.padding(.trailing, -60)
|
||||||
} else {
|
} else {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ConnectToServerView(isLoggedIn: $viewModel.isLoggedIn)
|
ConnectToServerView()
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showingAlert) {
|
|
||||||
Alert(title: Text("Important message"), message: Text("\(ServerEnvironment.current.errorMessage)"), dismissButton: .default(Text("Got it!")))
|
|
||||||
}
|
|
||||||
.onChange(of: ServerEnvironment.current.hasErrorMessage) { hEM in
|
|
||||||
self.showingAlert = hEM
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,9 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E4267ABD5C005D8AB9 /* MainTabView.swift */; };
|
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E4267ABD5C005D8AB9 /* MainTabView.swift */; };
|
||||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
|
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
|
||||||
531690EC267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */; };
|
|
||||||
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */; };
|
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */; };
|
||||||
531690EF267ABF72005D8AB9 /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EE267ABF72005D8AB9 /* NextUpView.swift */; };
|
|
||||||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EE267ABF72005D8AB9 /* NextUpView.swift */; };
|
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EE267ABF72005D8AB9 /* NextUpView.swift */; };
|
||||||
531690F7267ACC00005D8AB9 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F6267ACC00005D8AB9 /* ContinueWatchingItem.swift */; };
|
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */; };
|
||||||
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; };
|
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; };
|
||||||
531690FD267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
531690FD267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
||||||
531690FE267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
531690FE267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
||||||
|
@ -47,6 +45,11 @@
|
||||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; };
|
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; };
|
||||||
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
|
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; };
|
||||||
|
536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D75267BA9BB0004248C /* MainTabViewModel.swift */; };
|
||||||
|
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
||||||
|
536D3D79267BD5D00004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
||||||
|
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 536D3D7C267BD5F90004248C /* ActivityIndicator */; };
|
||||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; };
|
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; };
|
||||||
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
||||||
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
||||||
|
@ -94,7 +97,6 @@
|
||||||
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
|
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
|
||||||
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
|
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
|
||||||
625CB57C2678CE1000530A6E /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
|
||||||
625CB57E2678E81E00530A6E /* TVVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; };
|
625CB57E2678E81E00530A6E /* TVVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; };
|
||||||
625CB57F2678E81E00530A6E /* TVVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
625CB57F2678E81E00530A6E /* TVVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
|
@ -186,7 +188,7 @@
|
||||||
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||||
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
||||||
531690EE267ABF72005D8AB9 /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
531690EE267ABF72005D8AB9 /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
||||||
531690F6267ACC00005D8AB9 /* ContinueWatchingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingItem.swift; sourceTree = "<group>"; };
|
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapeItemElement.swift; sourceTree = "<group>"; };
|
||||||
531690F8267AD135005D8AB9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
531690F8267AD135005D8AB9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainNavigationLinkButton.swift; sourceTree = "<group>"; };
|
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainNavigationLinkButton.swift; sourceTree = "<group>"; };
|
||||||
531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = "<group>"; };
|
531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = "<group>"; };
|
||||||
|
@ -205,6 +207,8 @@
|
||||||
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
||||||
535BAEA4264A151C005FA86D /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
|
535BAEA4264A151C005FA86D /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
|
||||||
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
|
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
|
||||||
|
536D3D73267BA8170004248C /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
||||||
|
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
|
||||||
5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
|
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
|
||||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
@ -299,6 +303,7 @@
|
||||||
628B95332670CAEA0091AF3B /* NukeUI in Frameworks */,
|
628B95332670CAEA0091AF3B /* NukeUI in Frameworks */,
|
||||||
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */,
|
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */,
|
||||||
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */,
|
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */,
|
||||||
|
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */,
|
||||||
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */,
|
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */,
|
||||||
628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */,
|
628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */,
|
||||||
);
|
);
|
||||||
|
@ -307,23 +312,6 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
531690F5267ACBF2005D8AB9 /* ContinueWatching */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */,
|
|
||||||
531690F6267ACC00005D8AB9 /* ContinueWatchingItem.swift */,
|
|
||||||
);
|
|
||||||
path = ContinueWatching;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
531690FB267AD7FA005D8AB9 /* NextUp */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
531690EE267ABF72005D8AB9 /* NextUpView.swift */,
|
|
||||||
);
|
|
||||||
path = NextUp;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
532175392671BCED005491E6 /* ViewModels */ = {
|
532175392671BCED005491E6 /* ViewModels */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -333,6 +321,7 @@
|
||||||
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
|
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
|
||||||
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
||||||
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
||||||
|
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = ViewModels;
|
path = ViewModels;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -340,8 +329,7 @@
|
||||||
535870612669D21600D05A09 /* JellyfinPlayer tvOS */ = {
|
535870612669D21600D05A09 /* JellyfinPlayer tvOS */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
531690FB267AD7FA005D8AB9 /* NextUp */,
|
536D3D77267BB9650004248C /* Components */,
|
||||||
531690F5267ACBF2005D8AB9 /* ContinueWatching */,
|
|
||||||
53ABFDDA267972BF00886593 /* JellyfinPlayer tvOS.entitlements */,
|
53ABFDDA267972BF00886593 /* JellyfinPlayer tvOS.entitlements */,
|
||||||
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */,
|
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */,
|
||||||
535870622669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift */,
|
535870622669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift */,
|
||||||
|
@ -350,6 +338,8 @@
|
||||||
535870702669D21700D05A09 /* Info.plist */,
|
535870702669D21700D05A09 /* Info.plist */,
|
||||||
535870682669D21700D05A09 /* Preview Content */,
|
535870682669D21700D05A09 /* Preview Content */,
|
||||||
53ABFDDD267974E300886593 /* SplashView.swift */,
|
53ABFDDD267974E300886593 /* SplashView.swift */,
|
||||||
|
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */,
|
||||||
|
531690EE267ABF72005D8AB9 /* NextUpView.swift */,
|
||||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
||||||
531690E4267ABD5C005D8AB9 /* MainTabView.swift */,
|
531690E4267ABD5C005D8AB9 /* MainTabView.swift */,
|
||||||
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
||||||
|
@ -386,6 +376,14 @@
|
||||||
path = Typings;
|
path = Typings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
536D3D77267BB9650004248C /* Components */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */,
|
||||||
|
);
|
||||||
|
path = Components;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5377CBE8263B596A003A4E83 = {
|
5377CBE8263B596A003A4E83 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -500,6 +498,7 @@
|
||||||
children = (
|
children = (
|
||||||
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
|
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
|
||||||
62EC352E267666A5000E9F2D /* SessionManager.swift */,
|
62EC352E267666A5000E9F2D /* SessionManager.swift */,
|
||||||
|
536D3D73267BA8170004248C /* BackgroundManager.swift */,
|
||||||
);
|
);
|
||||||
path = Singleton;
|
path = Singleton;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -586,6 +585,7 @@
|
||||||
628B95322670CAEA0091AF3B /* NukeUI */,
|
628B95322670CAEA0091AF3B /* NukeUI */,
|
||||||
628B95342670CAEA0091AF3B /* JellyfinAPI */,
|
628B95342670CAEA0091AF3B /* JellyfinAPI */,
|
||||||
628B95392670CE250091AF3B /* KeychainSwift */,
|
628B95392670CE250091AF3B /* KeychainSwift */,
|
||||||
|
536D3D7C267BD5F90004248C /* ActivityIndicator */,
|
||||||
);
|
);
|
||||||
productName = WidgetExtensionExtension;
|
productName = WidgetExtensionExtension;
|
||||||
productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */;
|
productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */;
|
||||||
|
@ -686,7 +686,7 @@
|
||||||
53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */,
|
53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */,
|
||||||
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
||||||
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
531690F7267ACC00005D8AB9 /* ContinueWatchingItem.swift in Sources */,
|
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
|
||||||
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
||||||
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
||||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
|
@ -703,6 +703,8 @@
|
||||||
535870A62669D8AE00D05A09 /* LazyView.swift in Sources */,
|
535870A62669D8AE00D05A09 /* LazyView.swift in Sources */,
|
||||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */,
|
5321753E2671DE9C005491E6 /* Typings.swift in Sources */,
|
||||||
53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */,
|
53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */,
|
||||||
|
536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */,
|
||||||
|
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */,
|
||||||
535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */,
|
535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */,
|
||||||
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */,
|
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */,
|
||||||
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
||||||
|
@ -725,6 +727,7 @@
|
||||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
||||||
|
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
|
||||||
53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */,
|
53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */,
|
||||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||||
|
@ -743,19 +746,16 @@
|
||||||
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
||||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||||
625CB57C2678CE1000530A6E /* ViewModel.swift in Sources */,
|
|
||||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
||||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
||||||
531690EF267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
|
||||||
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
||||||
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
||||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||||
625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */,
|
625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */,
|
||||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||||
531690EC267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
|
||||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||||
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
||||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||||
|
@ -779,6 +779,7 @@
|
||||||
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
|
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
|
||||||
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */,
|
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */,
|
||||||
62EC353226766849000E9F2D /* SessionManager.swift in Sources */,
|
62EC353226766849000E9F2D /* SessionManager.swift in Sources */,
|
||||||
|
536D3D79267BD5D00004248C /* ViewModel.swift in Sources */,
|
||||||
531690FF267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */,
|
531690FF267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -1206,6 +1207,11 @@
|
||||||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||||
productName = NukeUI;
|
productName = NukeUI;
|
||||||
};
|
};
|
||||||
|
536D3D7C267BD5F90004248C /* ActivityIndicator */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
||||||
|
productName = ActivityIndicator;
|
||||||
|
};
|
||||||
53A431BC266B0FF20016769F /* JellyfinAPI */ = {
|
53A431BC266B0FF20016769F /* JellyfinAPI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
package = 53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||||
|
|
|
@ -11,11 +11,7 @@ import KeychainSwift
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ConnectToServerView: View {
|
struct ConnectToServerView: View {
|
||||||
@StateObject
|
@StateObject var viewModel = ConnectToServerViewModel()
|
||||||
var viewModel = ConnectToServerViewModel()
|
|
||||||
|
|
||||||
@Binding
|
|
||||||
var isLoggedIn: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -132,9 +128,6 @@ struct ConnectToServerView: View {
|
||||||
.alert(item: $viewModel.errorMessage) { _ in
|
.alert(item: $viewModel.errorMessage) { _ in
|
||||||
Alert(title: Text("Error"), message: Text("message"), dismissButton: .default(Text("Try again")))
|
Alert(title: Text("Error"), message: Text("message"), dismissButton: .default(Text("Try again")))
|
||||||
}
|
}
|
||||||
.onReceive(viewModel.$isLoggedIn, perform: { flag in
|
|
||||||
isLoggedIn = flag
|
|
||||||
})
|
|
||||||
.navigationTitle("Connect to Server")
|
.navigationTitle("Connect to Server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ struct EpisodeItemView: View {
|
||||||
didSet {
|
didSet {
|
||||||
if !settingState {
|
if !settingState {
|
||||||
if watched == true {
|
if watched == true {
|
||||||
PlaystateAPI.markPlayedItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
})
|
})
|
||||||
.store(in: &tempViewModel.cancellables)
|
.store(in: &tempViewModel.cancellables)
|
||||||
} else {
|
} else {
|
||||||
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
|
@ -47,14 +47,14 @@ struct EpisodeItemView: View {
|
||||||
didSet {
|
didSet {
|
||||||
if !settingState {
|
if !settingState {
|
||||||
if favorite == true {
|
if favorite == true {
|
||||||
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
})
|
})
|
||||||
.store(in: &tempViewModel.cancellables)
|
.store(in: &tempViewModel.cancellables)
|
||||||
} else {
|
} else {
|
||||||
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct LatestMediaView: View {
|
||||||
viewDidLoad = true
|
viewDidLoad = true
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.userID!, parentId: library_id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true, limit: 12)
|
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true, limit: 12)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct LibrarySearchView: View {
|
||||||
func requestSearch(query: String) {
|
func requestSearch(query: String) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.userID!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
|
|
|
@ -70,7 +70,7 @@ struct LibraryView: View {
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.userID!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true)
|
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
|
|
@ -30,14 +30,14 @@ struct MovieItemView: View {
|
||||||
didSet {
|
didSet {
|
||||||
if !settingState {
|
if !settingState {
|
||||||
if watched == true {
|
if watched == true {
|
||||||
PlaystateAPI.markPlayedItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
})
|
})
|
||||||
.store(in: &tempViewModel.cancellables)
|
.store(in: &tempViewModel.cancellables)
|
||||||
} else {
|
} else {
|
||||||
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
|
@ -53,14 +53,14 @@ struct MovieItemView: View {
|
||||||
didSet {
|
didSet {
|
||||||
if !settingState {
|
if !settingState {
|
||||||
if favorite == true {
|
if favorite == true {
|
||||||
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
})
|
})
|
||||||
.store(in: &tempViewModel.cancellables)
|
.store(in: &tempViewModel.cancellables)
|
||||||
} else {
|
} else {
|
||||||
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.userID!, itemId: item.id!)
|
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
}, receiveValue: { _ in
|
}, receiveValue: { _ in
|
||||||
|
|
|
@ -33,7 +33,7 @@ struct SeasonItemView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.current.userID!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seasonId: item.id ?? "")
|
TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seasonId: item.id ?? "")
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
|
|
@ -83,14 +83,8 @@ struct SettingsView: View {
|
||||||
// TODO: handle the error
|
// TODO: handle the error
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
SessionManager.current.logout()
|
||||||
try SessionManager.current.logout()
|
ServerEnvironment.current.reset()
|
||||||
try ServerEnvironment.current.reset()
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
// TODO: This should redirect to the server selection screen
|
|
||||||
exit(-1)
|
|
||||||
} label: {
|
} label: {
|
||||||
Text("Log out").font(.callout)
|
Text("Log out").font(.callout)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct SplashView: View {
|
||||||
MainTabView()
|
MainTabView()
|
||||||
} else {
|
} else {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ConnectToServerView(isLoggedIn: $viewModel.isLoggedIn)
|
ConnectToServerView()
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,11 +290,11 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
builder.setMaxBitrate(bitrate: maxBitrate)
|
builder.setMaxBitrate(bitrate: maxBitrate)
|
||||||
let profile = builder.buildProfile()
|
let profile = builder.buildProfile()
|
||||||
|
|
||||||
let playbackInfo = PlaybackInfoDto(userId: SessionManager.current.userID!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true)
|
let playbackInfo = PlaybackInfoDto(userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true)
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
||||||
delegate?.showLoadingView(self)
|
delegate?.showLoadingView(self)
|
||||||
MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.userID!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo)
|
MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { result in
|
||||||
print(result)
|
print(result)
|
||||||
}, receiveValue: { [self] response in
|
}, receiveValue: { [self] response in
|
||||||
|
@ -348,7 +348,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
playbackItem = item
|
playbackItem = item
|
||||||
} else {
|
} else {
|
||||||
// Item will be directly played by the client.
|
// Item will be directly played by the client.
|
||||||
let streamURL: URL = URL(string: "\(ServerEnvironment.current.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.authToken)&Tag=\(mediaSource.eTag!)")!
|
let streamURL: URL = URL(string: "\(ServerEnvironment.current.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.accessToken)&Tag=\(mediaSource.eTag!)")!
|
||||||
|
|
||||||
let item = PlaybackItem()
|
let item = PlaybackItem()
|
||||||
item.videoUrl = streamURL
|
item.videoUrl = streamURL
|
||||||
|
|
|
@ -126,7 +126,7 @@ extension BaseItemDto {
|
||||||
let proghours = Int(remainingSecs / 3600)
|
let proghours = Int(remainingSecs / 3600)
|
||||||
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
|
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
|
||||||
if proghours != 0 {
|
if proghours != 0 {
|
||||||
return "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
return "\(proghours)h \(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
} else {
|
} else {
|
||||||
return "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
return "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
|
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>, vm: ViewModel) {
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished:
|
case .finished:
|
||||||
break
|
break
|
||||||
|
@ -17,12 +17,10 @@ func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
|
||||||
if let err = error as? ErrorResponse {
|
if let err = error as? ErrorResponse {
|
||||||
switch err {
|
switch err {
|
||||||
case .error(401, _, _, _):
|
case .error(401, _, _, _):
|
||||||
ServerEnvironment.current.errorMessage = "User unauthorized."
|
vm.errorMessage = err.localizedDescription
|
||||||
ServerEnvironment.current.hasErrorMessage = true
|
|
||||||
SessionManager.current.logout()
|
SessionManager.current.logout()
|
||||||
case .error:
|
case .error:
|
||||||
ServerEnvironment.current.errorMessage = err.localizedDescription
|
vm.errorMessage = err.localizedDescription
|
||||||
ServerEnvironment.current.hasErrorMessage = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="SignedInUser" representedClassName="SignedInUser" syncable="YES" codeGenerationType="class">
|
<entity name="SignedInUser" representedClassName="SignedInUser" syncable="YES" codeGenerationType="class">
|
||||||
<attribute name="appletv_id" optional="YES" attributeType="String"/>
|
<attribute name="appletv_id" optional="YES" attributeType="String"/>
|
||||||
<attribute name="device_uuid" attributeType="String" defaultValueString=""/>
|
|
||||||
<attribute name="user_id" attributeType="String" defaultValueString=""/>
|
<attribute name="user_id" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="username" attributeType="String" defaultValueString=""/>
|
<attribute name="username" attributeType="String" defaultValueString=""/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Server" positionX="-63" positionY="-9" width="128" height="74"/>
|
<element name="Server" positionX="-63" positionY="-9" width="128" height="74"/>
|
||||||
<element name="SignedInUser" positionX="-63" positionY="9" width="128" height="89"/>
|
<element name="SignedInUser" positionX="-63" positionY="9" width="128" height="74"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
final class BackgroundManager {
|
||||||
|
static let current = BackgroundManager()
|
||||||
|
fileprivate(set) var backgroundURL: URL?
|
||||||
|
fileprivate(set) var blurhash: String = "001fC^"
|
||||||
|
|
||||||
|
init() {
|
||||||
|
backgroundURL = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBackground(to: URL, hash: String) {
|
||||||
|
self.backgroundURL = to
|
||||||
|
self.blurhash = hash
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.post(name: Notification.Name("backgroundDidChange"), object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearBackground() {
|
||||||
|
self.backgroundURL = nil
|
||||||
|
self.blurhash = "001fC^"
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.post(name: Notification.Name("backgroundDidChange"), object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,14 +17,16 @@ final class ServerEnvironment {
|
||||||
fileprivate(set) var server: Server!
|
fileprivate(set) var server: Server!
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let serverRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Server")
|
let serverRequest = Server.fetchRequest()
|
||||||
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest) as? [Server]
|
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest)
|
||||||
|
|
||||||
|
if(servers?.count != 0) {
|
||||||
server = servers?.first
|
server = servers?.first
|
||||||
guard let baseURI = server?.baseURI else { return }
|
JellyfinAPI.basePath = server.baseURI!
|
||||||
JellyfinAPI.basePath = baseURI
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUp(with uri: String) -> AnyPublisher<Server, Error> {
|
func create(with uri: String) -> AnyPublisher<Server, Error> {
|
||||||
var uri = uri
|
var uri = uri
|
||||||
if !uri.contains("http") {
|
if !uri.contains("http") {
|
||||||
uri = "https://" + uri
|
uri = "https://" + uri
|
||||||
|
@ -32,6 +34,7 @@ final class ServerEnvironment {
|
||||||
if uri.last == "/" {
|
if uri.last == "/" {
|
||||||
uri = String(uri.dropLast())
|
uri = String(uri.dropLast())
|
||||||
}
|
}
|
||||||
|
|
||||||
JellyfinAPI.basePath = uri
|
JellyfinAPI.basePath = uri
|
||||||
return SystemAPI.getPublicSystemInfo()
|
return SystemAPI.getPublicSystemInfo()
|
||||||
.map { response in
|
.map { response in
|
||||||
|
@ -47,13 +50,14 @@ final class ServerEnvironment {
|
||||||
}).eraseToAnyPublisher()
|
}).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset() throws {
|
func reset() {
|
||||||
JellyfinAPI.basePath = ""
|
JellyfinAPI.basePath = ""
|
||||||
server = nil
|
server = nil
|
||||||
|
|
||||||
let serverRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
|
let serverRequest: NSFetchRequest<NSFetchRequestResult> = Server.fetchRequest()
|
||||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest)
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest)
|
||||||
|
|
||||||
try PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
//coredata will theoretically never throw
|
||||||
|
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,95 +14,147 @@ import JellyfinAPI
|
||||||
import KeychainSwift
|
import KeychainSwift
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
import TVServices
|
||||||
|
#endif
|
||||||
|
|
||||||
final class SessionManager {
|
final class SessionManager {
|
||||||
static let current = SessionManager()
|
static let current = SessionManager()
|
||||||
fileprivate(set) var user: SignedInUser!
|
fileprivate(set) var user: SignedInUser!
|
||||||
fileprivate(set) var authHeader: String!
|
fileprivate(set) var deviceID: String = ""
|
||||||
fileprivate(set) var authToken: String!
|
fileprivate(set) var accessToken: String = ""
|
||||||
fileprivate(set) var deviceID: String
|
|
||||||
var userID: String? {
|
#if os(tvOS)
|
||||||
user?.user_id
|
let tvUserManager = TVUserManager()
|
||||||
}
|
#endif
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let savedUserRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SignedInUser")
|
let savedUserRequest = SignedInUser.fetchRequest()
|
||||||
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) as? [SignedInUser]
|
|
||||||
|
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
savedUsers?.forEach() { savedUser in
|
||||||
|
if(savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "") {
|
||||||
|
self.user = savedUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
user = savedUsers?.first
|
user = savedUsers?.first
|
||||||
|
#endif
|
||||||
|
|
||||||
let keychain = KeychainSwift()
|
if(user != nil) {
|
||||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
let authToken = getAuthToken(userID: user.user_id!)
|
||||||
if let deviceID = keychain.get("DeviceID") {
|
generateAuthHeader(with: authToken)
|
||||||
self.deviceID = deviceID
|
}
|
||||||
} else {
|
|
||||||
self.deviceID = UUID().uuidString
|
|
||||||
keychain.set(deviceID, forKey: "DeviceID")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let authToken = keychain.get("AccessToken_\(user?.user_id ?? "")") else {
|
fileprivate func generateAuthHeader(with authToken: String?) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHeader(with: authToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func updateHeader(with authToken: String?) {
|
|
||||||
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||||
var deviceName = UIDevice.current.name
|
var deviceName = UIDevice.current.name
|
||||||
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
|
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
|
||||||
deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]")
|
deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]")
|
||||||
|
|
||||||
var header = "MediaBrowser "
|
var header = "MediaBrowser "
|
||||||
header.append("Client=\"SwiftFin\", ")
|
#if os(tvOS)
|
||||||
|
header.append("Client=\"SwiftFin tvOS\", ")
|
||||||
|
#else
|
||||||
|
header.append("Client=\"SwiftFin iOS\", ")
|
||||||
|
#endif
|
||||||
header.append("Device=\"\(deviceName)\", ")
|
header.append("Device=\"\(deviceName)\", ")
|
||||||
header.append("DeviceId=\"\(deviceID)\", ")
|
#if os(tvOS)
|
||||||
|
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")\", ")
|
||||||
|
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")"
|
||||||
|
#else
|
||||||
|
header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")\", ")
|
||||||
|
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")"
|
||||||
|
#endif
|
||||||
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
|
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
|
||||||
if let token = authToken {
|
|
||||||
self.authToken = token
|
if(authToken != nil) {
|
||||||
header.append("Token=\"\(token)\"")
|
header.append("Token=\"\(authToken!)\"")
|
||||||
|
accessToken = authToken!
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeader = header
|
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
|
||||||
JellyfinAPI.customHeaders["X-Emby-Authorization"] = authHeader
|
}
|
||||||
|
|
||||||
|
fileprivate func getAuthToken(userID: String) -> String? {
|
||||||
|
let keychain = KeychainSwift()
|
||||||
|
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||||
|
return keychain.get("AccessToken_\(userID)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesUserHaveSavedSession(userID: String) -> Bool {
|
||||||
|
let savedUserRequest = SignedInUser.fetchRequest()
|
||||||
|
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
|
||||||
|
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
|
||||||
|
|
||||||
|
if(savedUsers!.isEmpty) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSavedSession(userID: String) -> SignedInUser {
|
||||||
|
let savedUserRequest = SignedInUser.fetchRequest()
|
||||||
|
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
|
||||||
|
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
|
||||||
|
return savedUsers!.first!
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginWithSavedSession(user: SignedInUser) {
|
||||||
|
let accessToken = getAuthToken(userID: user.user_id!)
|
||||||
|
|
||||||
|
self.user = user
|
||||||
|
generateAuthHeader(with: accessToken)
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.post(name: Notification.Name("didSignIn"), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
|
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
|
||||||
updateHeader(with: nil)
|
|
||||||
|
|
||||||
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
||||||
.map { [unowned self] response -> (SignedInUser, String?) in
|
.map { response -> (SignedInUser, String?) in
|
||||||
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
|
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
|
||||||
user.device_uuid = deviceID
|
|
||||||
user.username = response.user?.name
|
user.username = response.user?.name
|
||||||
user.user_id = response.user?.id
|
user.user_id = response.user?.id
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
//user.appletv_id = tvUserManager.currentUserIdentifier ?? ""
|
||||||
|
#endif
|
||||||
|
|
||||||
return (user, response.accessToken)
|
return (user, response.accessToken)
|
||||||
}
|
}
|
||||||
.handleEvents(receiveOutput: { [unowned self] response, accessToken in
|
.handleEvents(receiveOutput: { [unowned self] response, accessToken in
|
||||||
user = response
|
user = response
|
||||||
_ = try? PersistenceController.shared.container.viewContext.save()
|
_ = try? PersistenceController.shared.container.viewContext.save()
|
||||||
if let userID = user.user_id,
|
|
||||||
let token = accessToken
|
|
||||||
{
|
|
||||||
let keychain = KeychainSwift()
|
let keychain = KeychainSwift()
|
||||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||||
keychain.set(token, forKey: "AccessToken_\(userID)")
|
keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
|
||||||
}
|
|
||||||
updateHeader(with: accessToken)
|
generateAuthHeader(with: accessToken)
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.post(name: Notification.Name("didSignIn"), object: nil)
|
||||||
})
|
})
|
||||||
.map(\.0)
|
.map(\.0)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func logout() throws {
|
func logout() {
|
||||||
let keychain = KeychainSwift()
|
let keychain = KeychainSwift()
|
||||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||||
keychain.delete("AccessToken_\(user.user_id ?? "")")
|
keychain.delete("AccessToken_\(user.user_id ?? "")")
|
||||||
JellyfinAPI.customHeaders["X-Emby-Authorization"] = nil
|
generateAuthHeader(with: nil)
|
||||||
|
|
||||||
|
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
|
||||||
user = nil
|
user = nil
|
||||||
authHeader = nil
|
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
||||||
|
|
||||||
let userRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "SignedInUser")
|
let nc = NotificationCenter.default
|
||||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: userRequest)
|
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||||
|
|
||||||
try PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,38 +12,32 @@ import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
final class ConnectToServerViewModel: ViewModel {
|
final class ConnectToServerViewModel: ViewModel {
|
||||||
@Published
|
|
||||||
var publicUsers = [UserDto]()
|
|
||||||
@Published
|
@Published
|
||||||
var isConnectedServer = false
|
var isConnectedServer = false
|
||||||
@Published
|
@Published
|
||||||
var isLoggedIn = false
|
|
||||||
@Published
|
|
||||||
var uri = ""
|
var uri = ""
|
||||||
@Published
|
@Published
|
||||||
var username = ""
|
var username = ""
|
||||||
@Published
|
@Published
|
||||||
var password = ""
|
var password = ""
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var lastPublicUsers = [UserDto]()
|
var lastPublicUsers = [UserDto]()
|
||||||
|
@Published
|
||||||
|
var publicUsers = [UserDto]()
|
||||||
|
@Published
|
||||||
|
var selectedPublicUser = UserDto()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
getPublicUsers()
|
||||||
refresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh() {
|
func getPublicUsers() {
|
||||||
if ServerEnvironment.current.server != nil {
|
if ServerEnvironment.current.server != nil {
|
||||||
UserAPI.getPublicUsers()
|
UserAPI.getPublicUsers()
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
switch completion {
|
HandleAPIRequestCompletion(completion: completion, vm: self)
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
case .failure:
|
|
||||||
self.isConnectedServer = false
|
|
||||||
}
|
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
self.publicUsers = response
|
self.publicUsers = response
|
||||||
self.isConnectedServer = true
|
self.isConnectedServer = true
|
||||||
|
@ -63,7 +57,7 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectToServer() {
|
func connectToServer() {
|
||||||
ServerEnvironment.current.setUp(with: uri)
|
ServerEnvironment.current.create(with: uri)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
|
@ -72,28 +66,17 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
guard response.server_id != nil else {
|
self.getPublicUsers()
|
||||||
return
|
|
||||||
}
|
|
||||||
self.refresh()
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func login() {
|
func login() {
|
||||||
SessionManager.current.login(username: username, password: password)
|
SessionManager.current.login(username: username, password: password)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { completion in
|
||||||
switch result {
|
HandleAPIRequestCompletion(completion: completion, vm: self)
|
||||||
case let .failure(error):
|
}, receiveValue: { _ in
|
||||||
self.errorMessage = error.localizedDescription
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}, receiveValue: { response in
|
|
||||||
guard response.user_id != nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.isLoggedIn = true
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ final class HomeViewModel: ViewModel {
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.current.userID ?? "")
|
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
|
@ -54,7 +54,7 @@ final class HomeViewModel: ViewModel {
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
ItemsAPI.getResumeItems(userId: SessionManager.current.userID!, limit: 12,
|
ItemsAPI.getResumeItems(userId: SessionManager.current.user.user_id!, limit: 12,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -65,7 +65,7 @@ final class HomeViewModel: ViewModel {
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
TvShowsAPI.getNextUp(userId: SessionManager.current.userID!, limit: 12,
|
TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, limit: 12,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { result in
|
.sink(receiveCompletion: { result in
|
||||||
|
|
|
@ -26,7 +26,7 @@ final class LibraryListViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh() {
|
func refresh() {
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.current.userID ?? "")
|
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
print(completion)
|
print(completion)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
final class MainTabViewModel: ViewModel {
|
||||||
|
@Published var backgroundURL: URL?
|
||||||
|
@Published var lastBackgroundURL: URL?
|
||||||
|
@Published var backgroundBlurHash: String = "001fC^"
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func backgroundDidChange() {
|
||||||
|
self.lastBackgroundURL = self.backgroundURL
|
||||||
|
self.backgroundURL = BackgroundManager.current.backgroundURL
|
||||||
|
self.backgroundBlurHash = BackgroundManager.current.blurhash
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,8 +17,7 @@ import WidgetKit
|
||||||
|
|
||||||
final class SplashViewModel: ViewModel {
|
final class SplashViewModel: ViewModel {
|
||||||
|
|
||||||
@Published
|
@Published var isLoggedIn: Bool = false
|
||||||
var isLoggedIn: Bool
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil
|
isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil
|
||||||
|
@ -38,5 +37,19 @@ final class SplashViewModel: ViewModel {
|
||||||
if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 {
|
if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 {
|
||||||
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
|
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil)
|
||||||
|
nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didLogIn() {
|
||||||
|
print("didLogIn")
|
||||||
|
isLoggedIn = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didLogOut() {
|
||||||
|
print("didLogOut")
|
||||||
|
isLoggedIn = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,24 +25,11 @@ struct NextUpWidgetProvider: TimelineProvider {
|
||||||
|
|
||||||
func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) {
|
func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) {
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
guard let server = ServerEnvironment.current.server else { return
|
let server = ServerEnvironment.current.server!
|
||||||
DispatchQueue.main.async {
|
let savedUser = SessionManager.current.user!
|
||||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyServer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let savedUser = SessionManager.current.user else { return
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyUser))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let header = SessionManager.current.authHeader else { return
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyHeader))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tempCancellables = Set<AnyCancellable>()
|
var tempCancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
JellyfinAPI.basePath = server.baseURI ?? ""
|
JellyfinAPI.basePath = server.baseURI ?? ""
|
||||||
JellyfinAPI.customHeaders = ["X-Emby-Authorization": header]
|
|
||||||
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||||
|
@ -80,27 +67,11 @@ struct NextUpWidgetProvider: TimelineProvider {
|
||||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
|
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
|
let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
|
||||||
guard let server = ServerEnvironment.current.server else { return
|
let server = ServerEnvironment.current.server!
|
||||||
DispatchQueue.main.async {
|
let savedUser = SessionManager.current.user!
|
||||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyServer)],
|
|
||||||
policy: .after(entryDate)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let savedUser = SessionManager.current.user else { return
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyUser)],
|
|
||||||
policy: .after(entryDate)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let header = SessionManager.current.authHeader else { return
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyHeader)],
|
|
||||||
policy: .after(entryDate)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tempCancellables = Set<AnyCancellable>()
|
var tempCancellables = Set<AnyCancellable>()
|
||||||
JellyfinAPI.basePath = server.baseURI ?? ""
|
JellyfinAPI.basePath = server.baseURI ?? ""
|
||||||
JellyfinAPI.customHeaders = ["X-Emby-Authorization": header]
|
|
||||||
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||||
|
|
Loading…
Reference in New Issue