Create new SessionManager and begin new connect flow

This commit is contained in:
Ethan Pippin 2021-10-13 15:58:45 -06:00
parent 7dd253c530
commit 1576d9d6b7
40 changed files with 915 additions and 693 deletions

View File

@ -19,7 +19,7 @@ struct PublicUserButton: View {
var body: some View { var body: some View {
VStack { VStack {
if publicUser.primaryImageTag != nil { if publicUser.primaryImageTag != nil {
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!) ImageView(src: URL(string: "\(SessionManager.main.currentLogin.server.uri)/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!)
.frame(width: 250, height: 250) .frame(width: 250, height: 250)
.cornerRadius(125.0) .cornerRadius(125.0)
} else { } else {

View File

@ -26,7 +26,7 @@ struct ConnectToServerView: View {
} else { } else {
HStack { HStack {
Spacer() Spacer()
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(viewModel.selectedPublicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(viewModel.selectedPublicUser.primaryImageTag ?? "")")!) ImageView(src: URL(string: "\(SessionManager.main.currentLogin.server.uri)/Users/\(viewModel.selectedPublicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(viewModel.selectedPublicUser.primaryImageTag ?? "")")!)
.frame(width: 250, height: 250) .frame(width: 250, height: 250)
.cornerRadius(125.0) .cornerRadius(125.0)
Spacer() Spacer()

View File

@ -27,7 +27,7 @@ struct LatestMediaView: View {
viewDidLoad = true viewDidLoad = true
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true, limit: 12) UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.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

View File

@ -166,12 +166,12 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
// Item is being transcoded by request of server // Item is being transcoded by request of server
if let transcodiungUrl = mediaSource.transcodingUrl { if let transcodiungUrl = mediaSource.transcodingUrl {
item.videoType = .transcode item.videoType = .transcode
streamURL = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(transcodiungUrl)")! streamURL = URL(string: "\(SessionManager.main.currentLogin.server.uri)\(transcodiungUrl)")!
} }
// Item will be directly played by the client // Item will be directly played by the client
else { else {
item.videoType = .directPlay item.videoType = .directPlay
streamURL = 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!)")! streamURL = URL(string: "\(SessionManager.main.currentLogin.server.uri)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.accessToken)&Tag=\(mediaSource.eTag!)")!
} }
item.videoUrl = streamURL item.videoUrl = streamURL
@ -186,7 +186,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
var deliveryUrl: URL? var deliveryUrl: URL?
if stream.deliveryMethod == .external { if stream.deliveryMethod == .external {
deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl!)")! deliveryUrl = URL(string: "\(SessionManager.main.currentLogin.server.uri)\(stream.deliveryUrl!)")!
} }
let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt", languageCode: stream.language ?? "") let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt", languageCode: stream.language ?? "")

View File

@ -71,7 +71,6 @@
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; }; 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; }; 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; };
53628C6D26B5AA0D008A64A0 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 53628C6C26B5AA0D008A64A0 /* Defaults */; };
53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAC269CFAEA00A2D8B7 /* Puppy */; }; 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAC269CFAEA00A2D8B7 /* Puppy */; };
53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAE269CFAF600A2D8B7 /* Puppy */; }; 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAE269CFAF600A2D8B7 /* Puppy */; };
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; }; 53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
@ -177,8 +176,6 @@
6220D0B426D5ED8000B8E046 /* LibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */; }; 6220D0B426D5ED8000B8E046 /* LibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */; };
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */; }; 6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */; };
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */; }; 6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */; };
6220D0BD26D60D6600B8E046 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BC26D60D6600B8E046 /* ItemViewModel.swift */; };
6220D0BE26D60D6600B8E046 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BC26D60D6600B8E046 /* ItemViewModel.swift */; };
6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */; }; 6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */; };
6220D0C626D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */; }; 6220D0C626D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */; };
6220D0C726D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */; }; 6220D0C726D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */; };
@ -217,8 +214,6 @@
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; }; 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; };
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; }; 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; };
62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; }; 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; };
62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 62CB3F452685BAF7003D0A6F /* Defaults */; };
62CB3F482685BB3B003D0A6F /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 62CB3F472685BB3B003D0A6F /* Defaults */; };
62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; }; 62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; };
62CB3F4C2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; }; 62CB3F4C2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; };
62D8535B26FC631300FDFC59 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */; }; 62D8535B26FC631300FDFC59 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */; };
@ -240,11 +235,8 @@
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; }; 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; }; 62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; }; 62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
62EC352D26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; }; 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; }; 62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; }; 62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; }; 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; }; 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
@ -267,6 +259,23 @@
E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3C727164B1E009D4DAF /* UIDeviceExtensions.swift */; }; E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3C727164B1E009D4DAF /* UIDeviceExtensions.swift */; };
E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3CC27164CA7009D4DAF /* CoreStore */; }; E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3CC27164CA7009D4DAF /* CoreStore */; };
E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3CE27164E1F009D4DAF /* CoreStore */; }; E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3CE27164E1F009D4DAF /* CoreStore */; };
E13DD3D327168E65009D4DAF /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3D227168E65009D4DAF /* Defaults */; };
E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D4271693CD009D4DAF /* SwiftfinStoreDefaults.swift */; };
E13DD3D6271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D4271693CD009D4DAF /* SwiftfinStoreDefaults.swift */; };
E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D4271693CD009D4DAF /* SwiftfinStoreDefaults.swift */; };
E13DD3D927169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D827169406009D4DAF /* SwiftfinStoreKeychain.swift */; };
E13DD3DA27169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D827169406009D4DAF /* SwiftfinStoreKeychain.swift */; };
E13DD3DB27169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3D827169406009D4DAF /* SwiftfinStoreKeychain.swift */; };
E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3DC27175CE3009D4DAF /* Defaults */; };
E13DD3DF27175CEA009D4DAF /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E13DD3DE27175CEA009D4DAF /* Defaults */; };
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */; };
E13DD3E227176BD3009D4DAF /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */; };
E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E427177D15009D4DAF /* ServerListView.swift */; };
E13DD3E627177D15009D4DAF /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E427177D15009D4DAF /* ServerListView.swift */; };
E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */; };
E13DD3EA27177ED6009D4DAF /* ServerListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */; };
E13DD3EC27178A54009D4DAF /* UserLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3EB27178A54009D4DAF /* UserLoginViewModel.swift */; };
E13DD3ED27178A54009D4DAF /* UserLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3EB27178A54009D4DAF /* UserLoginViewModel.swift */; };
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */; }; E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */; };
E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */; }; E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */; };
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; }; E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; };
@ -449,7 +458,6 @@
6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryCoordinator.swift; sourceTree = "<group>"; }; 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryCoordinator.swift; sourceTree = "<group>"; };
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCoordinator.swift; sourceTree = "<group>"; }; 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCoordinator.swift; sourceTree = "<group>"; };
6220D0B926D6092100B8E046 /* FilterCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterCoordinator.swift; sourceTree = "<group>"; }; 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterCoordinator.swift; sourceTree = "<group>"; };
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>"; }; 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>"; }; 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>"; }; 6220D0CB26D640C400B8E046 /* AppURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppURLHandler.swift; sourceTree = "<group>"; };
@ -488,7 +496,6 @@
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = "<group>"; }; 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = "<group>"; };
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterViewModel.swift; sourceTree = "<group>"; }; 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterViewModel.swift; sourceTree = "<group>"; };
62E632F2267D54030063E547 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = "<group>"; }; 62E632F2267D54030063E547 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = "<group>"; };
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; }; 62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; }; 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; }; 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
@ -504,6 +511,12 @@
E13DD3BE27163DD7009D4DAF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; E13DD3BE27163DD7009D4DAF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
E13DD3C127164941009D4DAF /* SwiftfinStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftfinStore.swift; sourceTree = "<group>"; }; E13DD3C127164941009D4DAF /* SwiftfinStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftfinStore.swift; sourceTree = "<group>"; };
E13DD3C727164B1E009D4DAF /* UIDeviceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDeviceExtensions.swift; sourceTree = "<group>"; }; E13DD3C727164B1E009D4DAF /* UIDeviceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDeviceExtensions.swift; sourceTree = "<group>"; };
E13DD3D4271693CD009D4DAF /* SwiftfinStoreDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftfinStoreDefaults.swift; sourceTree = "<group>"; };
E13DD3D827169406009D4DAF /* SwiftfinStoreKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftfinStoreKeychain.swift; sourceTree = "<group>"; };
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = "<group>"; };
E13DD3E427177D15009D4DAF /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = "<group>"; };
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListCoordinator.swift; sourceTree = "<group>"; };
E13DD3EB27178A54009D4DAF /* UserLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLoginViewModel.swift; sourceTree = "<group>"; };
E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPortraitMainView.swift; sourceTree = "<group>"; }; E14F7D0626DB36EF007C3AE6 /* ItemPortraitMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemPortraitMainView.swift; sourceTree = "<group>"; };
E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLandscapeMainView.swift; sourceTree = "<group>"; }; E14F7D0826DB36F7007C3AE6 /* ItemLandscapeMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLandscapeMainView.swift; sourceTree = "<group>"; };
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; }; E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
@ -536,7 +549,7 @@
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */, 53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */,
535870912669D7A800D05A09 /* Introspect in Frameworks */, 535870912669D7A800D05A09 /* Introspect in Frameworks */,
6261A0E026A0AB710072EF1C /* CombineExt in Frameworks */, 6261A0E026A0AB710072EF1C /* CombineExt in Frameworks */,
62CB3F482685BB3B003D0A6F /* Defaults in Frameworks */, E13DD3DF27175CEA009D4DAF /* Defaults in Frameworks */,
53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */, 53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */,
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */, 5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
536D3D84267BEA550004248C /* ParallaxView in Frameworks */, 536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
@ -551,9 +564,9 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E13DD3D327168E65009D4DAF /* Defaults in Frameworks */,
53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */, 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */,
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */,
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */, 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */, 53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */,
53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */, 53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */,
@ -570,10 +583,10 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
53628C6D26B5AA0D008A64A0 /* Defaults in Frameworks */,
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 */,
E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */,
53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */, 53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */,
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */, 536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */,
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */, 628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */,
@ -585,13 +598,13 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
091B5A852683142E00D78B61 /* ServerLocator */ = { 091B5A852683142E00D78B61 /* ServerDiscovery */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
091B5A872683142E00D78B61 /* ServerDiscovery.swift */, 091B5A872683142E00D78B61 /* ServerDiscovery.swift */,
091B5A882683142E00D78B61 /* UDPBroadCastConnection.swift */, 091B5A882683142E00D78B61 /* UDPBroadCastConnection.swift */,
); );
path = ServerLocator; path = ServerDiscovery;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = { 5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
@ -612,9 +625,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */, 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
62E632F2267D54030063E547 /* ItemViewModel.swift */,
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */, 62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
625CB5722678C32A00530A6E /* HomeViewModel.swift */, 625CB5722678C32A00530A6E /* HomeViewModel.swift */,
62E632F2267D54030063E547 /* ItemViewModel.swift */,
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */, 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */, 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
625CB5742678C33500530A6E /* LibraryListViewModel.swift */, 625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
@ -625,11 +638,12 @@
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */, 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */,
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */, 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */,
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */, E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */,
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */, 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
625CB5692678B71200530A6E /* SplashViewModel.swift */, 625CB5692678B71200530A6E /* SplashViewModel.swift */,
E13DD3EB27178A54009D4DAF /* UserLoginViewModel.swift */,
09389CC626819B4500AE350E /* VideoPlayerModel.swift */, 09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
625CB57B2678CE1000530A6E /* ViewModel.swift */, 625CB57B2678CE1000530A6E /* ViewModel.swift */,
6220D0BC26D60D6600B8E046 /* ItemViewModel.swift */,
); );
path = ViewModels; path = ViewModels;
sourceTree = "<group>"; sourceTree = "<group>";
@ -730,7 +744,7 @@
621338912660106C00A81A2A /* Extensions */, 621338912660106C00A81A2A /* Extensions */,
535870AB2669D8D300D05A09 /* Objects */, 535870AB2669D8D300D05A09 /* Objects */,
AE8C3157265D6F5E008AA076 /* Resources */, AE8C3157265D6F5E008AA076 /* Resources */,
091B5A852683142E00D78B61 /* ServerLocator */, 091B5A852683142E00D78B61 /* ServerDiscovery */,
62EC352A26766657000E9F2D /* Singleton */, 62EC352A26766657000E9F2D /* Singleton */,
532175392671BCED005491E6 /* ViewModels */, 532175392671BCED005491E6 /* ViewModels */,
E1AD105326D96F5A003E4A08 /* Views */, E1AD105326D96F5A003E4A08 /* Views */,
@ -1014,6 +1028,7 @@
62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */, 62C29E9E26D1016600C1D2E7 /* MainCoordinator.swift */,
62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */, 62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */, 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */, 6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */,
6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */, 6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */,
); );
@ -1025,7 +1040,6 @@
children = ( children = (
536D3D73267BA8170004248C /* BackgroundManager.swift */, 536D3D73267BA8170004248C /* BackgroundManager.swift */,
53649AB0269CFB1900A2D8B7 /* LogManager.swift */, 53649AB0269CFB1900A2D8B7 /* LogManager.swift */,
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
62EC352E267666A5000E9F2D /* SessionManager.swift */, 62EC352E267666A5000E9F2D /* SessionManager.swift */,
); );
path = Singleton; path = Singleton;
@ -1074,6 +1088,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E13DD3C127164941009D4DAF /* SwiftfinStore.swift */, E13DD3C127164941009D4DAF /* SwiftfinStore.swift */,
E13DD3D4271693CD009D4DAF /* SwiftfinStoreDefaults.swift */,
E13DD3D827169406009D4DAF /* SwiftfinStoreKeychain.swift */,
); );
path = SwiftfinStore; path = SwiftfinStore;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1093,6 +1109,7 @@
53892771263C8C6F0035E14B /* LoadingView.swift */, 53892771263C8C6F0035E14B /* LoadingView.swift */,
5389276F263C25230035E14B /* NextUpView.swift */, 5389276F263C25230035E14B /* NextUpView.swift */,
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */, E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */, 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
625CB5672678B6FB00530A6E /* SplashView.swift */, 625CB5672678B6FB00530A6E /* SplashView.swift */,
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */, 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
@ -1188,12 +1205,12 @@
53A431BE266B0FFE0016769F /* JellyfinAPI */, 53A431BE266B0FFE0016769F /* JellyfinAPI */,
53ABFDEC26799D7700886593 /* ActivityIndicator */, 53ABFDEC26799D7700886593 /* ActivityIndicator */,
536D3D83267BEA550004248C /* ParallaxView */, 536D3D83267BEA550004248C /* ParallaxView */,
62CB3F472685BB3B003D0A6F /* Defaults */,
53272534268BF9710035FBF1 /* SwiftUIFocusGuide */, 53272534268BF9710035FBF1 /* SwiftUIFocusGuide */,
53649AAE269CFAF600A2D8B7 /* Puppy */, 53649AAE269CFAF600A2D8B7 /* Puppy */,
6261A0DF26A0AB710072EF1C /* CombineExt */, 6261A0DF26A0AB710072EF1C /* CombineExt */,
6220D0C826D63F3700B8E046 /* Stinsen */, 6220D0C826D63F3700B8E046 /* Stinsen */,
E13DD3CC27164CA7009D4DAF /* CoreStore */, E13DD3CC27164CA7009D4DAF /* CoreStore */,
E13DD3DE27175CEA009D4DAF /* Defaults */,
); );
productName = "JellyfinPlayer tvOS"; productName = "JellyfinPlayer tvOS";
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */; productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
@ -1225,11 +1242,11 @@
53A431BC266B0FF20016769F /* JellyfinAPI */, 53A431BC266B0FF20016769F /* JellyfinAPI */,
625CB5792678C4A400530A6E /* ActivityIndicator */, 625CB5792678C4A400530A6E /* ActivityIndicator */,
53EC6E24267EB10F006DD26A /* SwiftyJSON */, 53EC6E24267EB10F006DD26A /* SwiftyJSON */,
62CB3F452685BAF7003D0A6F /* Defaults */,
53649AAC269CFAEA00A2D8B7 /* Puppy */, 53649AAC269CFAEA00A2D8B7 /* Puppy */,
6260FFF826A09754003FA968 /* CombineExt */, 6260FFF826A09754003FA968 /* CombineExt */,
62C29E9B26D0FE4200C1D2E7 /* Stinsen */, 62C29E9B26D0FE4200C1D2E7 /* Stinsen */,
E13DD3C52716499E009D4DAF /* CoreStore */, E13DD3C52716499E009D4DAF /* CoreStore */,
E13DD3D227168E65009D4DAF /* Defaults */,
); );
productName = JellyfinPlayer; productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */; productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
@ -1254,8 +1271,8 @@
628B95392670CE250091AF3B /* KeychainSwift */, 628B95392670CE250091AF3B /* KeychainSwift */,
536D3D7C267BD5F90004248C /* ActivityIndicator */, 536D3D7C267BD5F90004248C /* ActivityIndicator */,
53649AB4269D423A00A2D8B7 /* Puppy */, 53649AB4269D423A00A2D8B7 /* Puppy */,
53628C6C26B5AA0D008A64A0 /* Defaults */,
E13DD3CE27164E1F009D4DAF /* CoreStore */, E13DD3CE27164E1F009D4DAF /* CoreStore */,
E13DD3DC27175CE3009D4DAF /* Defaults */,
); );
productName = WidgetExtensionExtension; productName = WidgetExtensionExtension;
productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */; productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */;
@ -1315,12 +1332,12 @@
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */, 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */,
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */, 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */,
53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */, 53272533268BF9710035FBF1 /* XCRemoteSwiftPackageReference "SwiftUIFocusGuide" */,
53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */, 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */,
6260FFF726A09754003FA968 /* XCRemoteSwiftPackageReference "CombineExt" */, 6260FFF726A09754003FA968 /* XCRemoteSwiftPackageReference "CombineExt" */,
62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */, 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */,
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */, E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */,
E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */,
); );
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -1524,12 +1541,13 @@
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */, C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */,
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */, 53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
53116A17268B919A003024C9 /* SeriesItemView.swift in Sources */, 53116A17268B919A003024C9 /* SeriesItemView.swift in Sources */,
62EC352D26766675000E9F2D /* ServerEnvironment.swift in Sources */, E13DD3DA27169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */,
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */, 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
53ABFDDE267974E300886593 /* SplashView.swift in Sources */, 53ABFDDE267974E300886593 /* SplashView.swift in Sources */,
53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */, 53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */,
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */, 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */,
E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */, E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */,
E13DD3EA27177ED6009D4DAF /* ServerListCoordinator.swift in Sources */,
53116A19268B947A003024C9 /* PlainLinkButton.swift in Sources */, 53116A19268B947A003024C9 /* PlainLinkButton.swift in Sources */,
536D3D88267C17350004248C /* PublicUserButton.swift in Sources */, 536D3D88267C17350004248C /* PublicUserButton.swift in Sources */,
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */, 62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
@ -1539,8 +1557,10 @@
091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */, 091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */,
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */, E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */,
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
E13DD3ED27178A54009D4DAF /* UserLoginViewModel.swift in Sources */,
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */, 62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
E1AD104B26D94822003E4A08 /* DetailItem.swift in Sources */, E1AD104B26D94822003E4A08 /* DetailItem.swift in Sources */,
E13DD3E227176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
53272539268C20100035FBF1 /* EpisodeItemView.swift in Sources */, 53272539268C20100035FBF1 /* EpisodeItemView.swift in Sources */,
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */, 531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
@ -1574,6 +1594,7 @@
62E632E4267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, 62E632E4267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
5358706C2669D21700D05A09 /* PersistenceController.swift in Sources */, 5358706C2669D21700D05A09 /* PersistenceController.swift in Sources */,
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */, 53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */,
E13DD3D6271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */, 535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */,
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */, 53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */, C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
@ -1592,10 +1613,10 @@
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */, C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */,
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */, 536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */,
535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */, 535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */,
E13DD3E627177D15009D4DAF /* ServerListView.swift in Sources */,
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */, 53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */,
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
6220D0BE26D60D6600B8E046 /* ItemViewModel.swift in Sources */,
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */, 531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */,
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */, E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */,
E1AD105726D981CE003E4A08 /* PortraitHStackView.swift in Sources */, E1AD105726D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
@ -1620,7 +1641,6 @@
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */, 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */,
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
6220D0BD26D60D6600B8E046 /* ItemViewModel.swift in Sources */,
62C29E9F26D1016600C1D2E7 /* MainCoordinator.swift in Sources */, 62C29E9F26D1016600C1D2E7 /* MainCoordinator.swift in Sources */,
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */, 53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */,
@ -1629,10 +1649,12 @@
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */, 6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
E13DD3EC27178A54009D4DAF /* UserLoginViewModel.swift in Sources */,
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */, 625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */, 536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */, 62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */,
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */, E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */,
E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */,
E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */, E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */,
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */, E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */,
E188460426DEF04800B0C5B7 /* EpisodeCardVStackView.swift in Sources */, E188460426DEF04800B0C5B7 /* EpisodeCardVStackView.swift in Sources */,
@ -1662,6 +1684,7 @@
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */, 6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */, E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */, 53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */, 62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */, 625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */, 62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
@ -1676,6 +1699,7 @@
6220D0B126D5EC9900B8E046 /* SettingsCoordinator.swift in Sources */, 6220D0B126D5EC9900B8E046 /* SettingsCoordinator.swift in Sources */,
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */, 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */,
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */, 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */,
E13DD3BD27163C63009D4DAF /* EmailHelper.swift in Sources */, E13DD3BD27163C63009D4DAF /* EmailHelper.swift in Sources */,
E13DD3C227164941009D4DAF /* SwiftfinStore.swift in Sources */, E13DD3C227164941009D4DAF /* SwiftfinStore.swift in Sources */,
E1AD104A26D94822003E4A08 /* DetailItem.swift in Sources */, E1AD104A26D94822003E4A08 /* DetailItem.swift in Sources */,
@ -1686,13 +1710,14 @@
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
E13DD3D927169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */,
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */, E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */, E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */, E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */,
E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */, 6220D0BA26D6092100B8E046 /* FilterCoordinator.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 */,
@ -1714,7 +1739,6 @@
files = ( files = (
53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */, 53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */,
E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */, E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */,
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */,
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */, 6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */,
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */, 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */,
6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */, 6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */,
@ -1725,9 +1749,11 @@
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */, 628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */, 6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */,
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */, 628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */,
E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */, E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */,
E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */, E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */,
E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */, E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */,
E13DD3DB27169406009D4DAF /* SwiftfinStoreKeychain.swift in Sources */,
62EC353226766849000E9F2D /* SessionManager.swift in Sources */, 62EC353226766849000E9F2D /* SessionManager.swift in Sources */,
536D3D79267BD5D00004248C /* ViewModel.swift in Sources */, 536D3D79267BD5D00004248C /* ViewModel.swift in Sources */,
); );
@ -2312,14 +2338,6 @@
minimumVersion = 2.0.2; minimumVersion = 2.0.2;
}; };
}; };
62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/acvigue/Defaults";
requirement = {
branch = main;
kind = branch;
};
};
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = { E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
@ -2328,6 +2346,14 @@
version = 8.1.0; version = 8.1.0;
}; };
}; };
E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sindresorhus/Defaults";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 6.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -2361,11 +2387,6 @@
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */; package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
productName = NukeUI; productName = NukeUI;
}; };
53628C6C26B5AA0D008A64A0 /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
53649AAC269CFAEA00A2D8B7 /* Puppy */ = { 53649AAC269CFAEA00A2D8B7 /* Puppy */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */; package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */;
@ -2456,16 +2477,6 @@
package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */; package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */;
productName = Stinsen; productName = Stinsen;
}; };
62CB3F452685BAF7003D0A6F /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
62CB3F472685BB3B003D0A6F /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
E13DD3C52716499E009D4DAF /* CoreStore */ = { E13DD3C52716499E009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
@ -2481,6 +2492,21 @@
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore; productName = CoreStore;
}; };
E13DD3D227168E65009D4DAF /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
E13DD3DC27175CE3009D4DAF /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
E13DD3DE27175CEA009D4DAF /* Defaults */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */

View File

@ -48,11 +48,11 @@
}, },
{ {
"package": "Defaults", "package": "Defaults",
"repositoryURL": "https://github.com/acvigue/Defaults", "repositoryURL": "https://github.com/sindresorhus/Defaults",
"state": { "state": {
"branch": "main", "branch": null,
"revision": "a4153b523ab3df9f5e3f70e9cfe9c54bed98c7e3", "revision": "8a6e4a96fd38504a05903d136c85634b65fd7c4d",
"version": null "version": "6.0.0"
} }
}, },
{ {

View File

@ -11,6 +11,14 @@ import UIKit
class AppDelegate: NSObject, UIApplicationDelegate { class AppDelegate: NSObject, UIApplicationDelegate {
static var orientationLock = UIInterfaceOrientationMask.all static var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Lazily initialize datastack
let _ = SwiftfinStore.dataStack
return true
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
AppDelegate.orientationLock AppDelegate.orientationLock

View File

@ -146,8 +146,6 @@ struct JellyfinPlayerApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Default(.appAppearance) var appAppearance @Default(.appAppearance) var appAppearance
let persistenceController = PersistenceController.shared
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
MainCoordinator().view() MainCoordinator().view()

View File

@ -20,13 +20,13 @@ import SwiftUI
var stack: NavigationStack<MainCoordinator> var stack: NavigationStack<MainCoordinator>
@Root var mainTab = makeMainTab @Root var mainTab = makeMainTab
@Root var connectToServer = makeConnectToServer @Root var serverList = makeServerList
init() { init() {
if ServerEnvironment.current.server != nil, SessionManager.current.user != nil { if SessionManager.main.currentLogin != nil {
self.stack = NavigationStack(initial: \MainCoordinator.mainTab) self.stack = NavigationStack(initial: \MainCoordinator.mainTab)
} else { } else {
self.stack = NavigationStack(initial: \MainCoordinator.connectToServer) self.stack = NavigationStack(initial: \MainCoordinator.serverList)
} }
ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory
@ -50,7 +50,7 @@ import SwiftUI
@objc func didLogOut() { @objc func didLogOut() {
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.") LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
root(\.connectToServer) root(\.serverList)
} }
@objc func processDeepLink(_ notification: Notification) { @objc func processDeepLink(_ notification: Notification) {
@ -70,8 +70,8 @@ import SwiftUI
MainTabCoordinator() MainTabCoordinator()
} }
func makeConnectToServer() -> NavigationViewCoordinator<ConnectToServerCoodinator> { func makeServerList() -> NavigationViewCoordinator<ServerListCoordinator> {
NavigationViewCoordinator(ConnectToServerCoodinator()) NavigationViewCoordinator(ServerListCoordinator())
} }
} }

View File

@ -9,7 +9,6 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import Stinsen import Stinsen
final class MainTabCoordinator: TabCoordinatable { final class MainTabCoordinator: TabCoordinatable {

View File

@ -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 Stinsen
import SwiftUI
final class ServerListCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \ServerListCoordinator.start)
@Root var start = makeStart
@Route(.push) var connectToServer = makeConnectToServer
// @Route(.push) var loginUser = makeLoginuser
func makeConnectToServer() -> ConnectToServerCoodinator {
ConnectToServerCoodinator()
}
// func makeLoginUser ->
@ViewBuilder func makeStart() -> some View {
ServerListView(viewModel: ServerListViewModel())
}
}

View File

@ -518,13 +518,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
let builder = DeviceProfileBuilder() let builder = DeviceProfileBuilder()
builder.setMaxBitrate(bitrate: maxBitrate) builder.setMaxBitrate(bitrate: maxBitrate)
let profile = builder.buildProfile() let profile = builder.buildProfile()
let playbackInfo = PlaybackInfoDto(userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id, maxStreamingBitrate: Int(maxBitrate),
startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile,
autoOpenLiveStream: true) 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.user.user_id!, MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.main.currentLogin.user.id,
maxStreamingBitrate: Int(maxBitrate), maxStreamingBitrate: Int(maxBitrate),
startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true,
playbackInfoDto: playbackInfo) playbackInfoDto: playbackInfo)
@ -537,7 +537,8 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
switch err { switch err {
case .error(401, _, _, _): case .error(401, _, _, _):
self.delegate?.exitPlayer(self) self.delegate?.exitPlayer(self)
SessionManager.current.logout() // TODO: todo
// SessionManager.current.logout()
main?.root(\.connectToServer) main?.root(\.connectToServer)
case .error: case .error:
self.delegate?.exitPlayer(self) self.delegate?.exitPlayer(self)
@ -550,7 +551,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
let mediaSource = response.mediaSources!.first.self! let mediaSource = response.mediaSources!.first.self!
if mediaSource.transcodingUrl != nil { if mediaSource.transcodingUrl != nil {
// Item is being transcoded by request of server // Item is being transcoded by request of server
let streamURL = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(mediaSource.transcodingUrl!)") let streamURL = URL(string: "\(SessionManager.main.currentLogin.server.uri)\(mediaSource.transcodingUrl!)")
let item = PlaybackItem() let item = PlaybackItem()
item.videoType = .transcode item.videoType = .transcode
item.videoUrl = streamURL! item.videoUrl = streamURL!
@ -564,7 +565,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
if stream.type == .subtitle { if stream.type == .subtitle {
var deliveryUrl: URL? var deliveryUrl: URL?
if stream.deliveryMethod == .external { if stream.deliveryMethod == .external {
deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl ?? "")")! deliveryUrl = URL(string: "\(SessionManager.main.currentLogin.server.uri)\(stream.deliveryUrl ?? "")")!
} else { } else {
deliveryUrl = nil deliveryUrl = nil
} }
@ -596,9 +597,10 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
self.sendPlayReport() self.sendPlayReport()
playbackItem = item playbackItem = item
} else { } else {
// TODO: todo
// Item will be directly played by the client. // Item will be directly played by the client.
let streamURL = let streamURL = URL(string: "\(SessionManager.main.currentLogin.server.uri)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&Tag=\(mediaSource.eTag ?? "")")!
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 ?? "")")! // URL(string: "\(SessionManager.main.currentLogin.server.uri)/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
@ -613,7 +615,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
if stream.type == .subtitle { if stream.type == .subtitle {
var deliveryUrl: URL? var deliveryUrl: URL?
if stream.deliveryMethod == .external { if stream.deliveryMethod == .external {
deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl!)")! deliveryUrl = URL(string: "\(SessionManager.main.currentLogin.server.uri)\(stream.deliveryUrl!)")!
} else { } else {
deliveryUrl = nil deliveryUrl = nil
} }
@ -771,7 +773,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
} }
func getNextEpisode() { func getNextEpisode() {
TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, startItemId: manifest.id, TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.main.currentLogin.user.id, startItemId: manifest.id,
limit: 2) limit: 2)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
print(completion) print(completion)
@ -873,11 +875,11 @@ extension PlayerViewController: GCKGenericChannelDelegate {
let payload: [String: Any] = [ let payload: [String: Any] = [
"options": options, "options": options,
"command": command, "command": command,
"userId": SessionManager.current.user.user_id!, "userId": SessionManager.main.currentLogin.user.id,
"deviceId": SessionManager.current.deviceID, // "deviceId": SessionManager.main.currentLogin.de.deviceID,
"accessToken": SessionManager.current.accessToken, "accessToken": SessionManager.main.currentLogin.user.accessToken?.value ?? "",
"serverAddress": ServerEnvironment.current.server.baseURI!, "serverAddress": SessionManager.main.currentLogin.server.uri,
"serverId": ServerEnvironment.current.server.server_id!, "serverId": SessionManager.main.currentLogin.server.id,
"serverVersion": "10.8.0", "serverVersion": "10.8.0",
"receiverName": castSessionManager.currentCastSession!.device.friendlyName!, "receiverName": castSessionManager.currentCastSession!.device.friendlyName!,
"subtitleBurnIn": false, "subtitleBurnIn": false,
@ -931,7 +933,7 @@ extension PlayerViewController: GCKSessionManagerListener {
let playNowOptions: [String: Any] = [ let playNowOptions: [String: Any] = [
"items": [[ "items": [[
"Id": manifest.id!, "Id": manifest.id!,
"ServerId": ServerEnvironment.current.server.server_id!, "ServerId": SessionManager.main.currentLogin.server.id,
"Name": manifest.name!, "Name": manifest.name!,
"Type": manifest.type!, "Type": manifest.type!,
"MediaType": manifest.mediaType!, "MediaType": manifest.mediaType!,

View File

@ -9,176 +9,243 @@ import SwiftUI
import Stinsen import Stinsen
struct ConnectToServerView: View { struct ConnectToServerView: View {
@EnvironmentObject var mainRouter: MainCoordinator.Router @EnvironmentObject var mainRouter: MainCoordinator.Router
@StateObject var viewModel = ConnectToServerViewModel() @StateObject var viewModel = ConnectToServerViewModel()
@State var username = ""
@State var password = ""
@State var uri = "" @State var uri = ""
var body: some View { var body: some View {
ZStack { List {
Form { Section {
if viewModel.isConnectedServer { TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
if viewModel.publicUsers.isEmpty { .disableAutocorrection(true)
Section(header: Text("Login to \(ServerEnvironment.current.server.name ?? "")")) { .autocapitalization(.none)
TextField(NSLocalizedString("Username", comment: ""), text: $username) .keyboardType(.URL)
.disableAutocorrection(true) Button {
.autocapitalization(.none) viewModel.connectToServer(uri: uri)
SecureField(NSLocalizedString("Password", comment: ""), text: $password) } label: {
.disableAutocorrection(true) HStack {
.autocapitalization(.none) Text("Connect")
Button { Spacer()
viewModel.login() if viewModel.isLoading {
} label: {
HStack {
Text("Login")
Spacer()
if viewModel.isLoading {
ProgressView()
}
}
}.disabled(viewModel.isLoading || username.isEmpty)
}
Section {
Button {
viewModel.isConnectedServer = false
} label: {
HStack {
HStack {
Image(systemName: "chevron.left")
Text("Change Server")
}
Spacer()
}
}
}
} else {
Section(header: Text("Login to \(ServerEnvironment.current.server.name ?? "")")) {
ForEach(viewModel.publicUsers, id: \.id) { publicUser in
HStack {
Button(action: {
if SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!) {
let user = SessionManager.current.getSavedSession(userID: publicUser.id!)
SessionManager.current.loginWithSavedSession(user: user)
mainRouter.root(\.mainTab)
} else {
username = publicUser.name ?? ""
viewModel.selectedPublicUser = publicUser
viewModel.hidePublicUsers()
if !(publicUser.hasPassword ?? true) {
password = ""
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
viewModel.login()
}
}
}
}) {
HStack {
Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold)
Spacer()
if publicUser.primaryImageTag != nil {
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=60&quality=80&tag=\(publicUser.primaryImageTag!)")!)
.frame(width: 60, height: 60)
.cornerRadius(30.0)
} else {
Image(systemName: "person.fill")
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
.font(.system(size: 35))
.frame(width: 60, height: 60)
.background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
.cornerRadius(30.0)
.shadow(radius: 6)
}
}
}
}
}
}
Section {
Button {
viewModel.publicUsers.removeAll()
username = ""
} label: {
HStack {
Text("Other User").font(.subheadline).fontWeight(.semibold)
Spacer()
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
.font(.system(size: 35))
.frame(width: 60, height: 60)
.background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
.cornerRadius(30.0)
.shadow(radius: 6)
}
}
}
}
} else {
Section(header: Text("Connect Manually")) {
TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
.disableAutocorrection(true)
.autocapitalization(.none)
.keyboardType(.URL)
Button {
viewModel.connectToServer()
} label: {
HStack {
Text("Connect")
Spacer()
if viewModel.isLoading {
ProgressView()
}
}
}
.disabled(viewModel.isLoading || uri.isEmpty)
}
Section(header: Text("Discovered Servers")) {
if self.viewModel.searching {
ProgressView() ProgressView()
} }
ForEach(self.viewModel.servers, id: \.id) { server in
Button(action: {
viewModel.connectToServer(at: server.url)
}, label: {
HStack {
Text(server.name)
.font(.headline)
Text("\(server.host)")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
if viewModel.isLoading {
ProgressView()
}
}
})
}
} }
.onAppear(perform: self.viewModel.discoverServers) }
.disabled(viewModel.isLoading || uri.isEmpty)
}
Section(header: Text("Discovered Servers")) {
if viewModel.searching {
ProgressView()
}
ForEach(viewModel.discoveredServers.sorted(by: { $0.name < $1.name }), id: \.id) { discoveredServer in
Button(action: {
viewModel.connectToServer(uri: discoveredServer.url.absoluteString)
}, label: {
HStack {
Text(discoveredServer.name)
.font(.headline)
Text("\(discoveredServer.host)")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
if viewModel.isLoading {
ProgressView()
}
}
})
} }
} }
} .onAppear(perform: self.viewModel.discoverServers)
.onChange(of: uri) { uri in .headerProminence(.increased)
viewModel.uriSubject.send(uri)
}
.onChange(of: username) { username in
viewModel.usernameSubject.send(username)
}
.onChange(of: password) { password in
viewModel.passwordSubject.send(password)
} }
.alert(item: $viewModel.errorMessage) { _ in .alert(item: $viewModel.errorMessage) { _ in
Alert(title: Text("\(viewModel.errorMessage?.code ?? -1)\n\(viewModel.errorMessage?.title ?? "Error")"), Alert(title: Text("\(viewModel.errorMessage?.code ?? -1)\n\(viewModel.errorMessage?.title ?? "Error")"),
message: Text(viewModel.errorMessage?.displayMessage ?? "Error"), message: Text(viewModel.errorMessage?.displayMessage ?? "Error"),
dismissButton: .cancel()) dismissButton: .cancel())
} }
.navigationTitle(NSLocalizedString("Connect to Server", comment: "")) .navigationTitle("Connect")
.onAppear { .onAppear {
AppURLHandler.shared.appURLState = .allowedInLogin AppURLHandler.shared.appURLState = .allowedInLogin
} }
} }
} }
//struct ConnectToServerView: View {
// @EnvironmentObject var mainRouter: MainCoordinator.Router
// @StateObject var viewModel = ConnectToServerViewModel()
// @State var username = ""
// @State var password = ""
// @State var uri = ""
//
// var body: some View {
// ZStack {
// Form {
// if viewModel.isConnectedServer {
// if viewModel.publicUsers.isEmpty {
// Section(header: Text("Login to \(SessionManager.main.currentLogin.server.name)")) {
// TextField(NSLocalizedString("Username", comment: ""), text: $username)
// .disableAutocorrection(true)
// .autocapitalization(.none)
// SecureField(NSLocalizedString("Password", comment: ""), text: $password)
// .disableAutocorrection(true)
// .autocapitalization(.none)
// Button {
// viewModel.login()
// } label: {
// HStack {
// Text("Login")
// Spacer()
// if viewModel.isLoading {
// ProgressView()
// }
// }
// }.disabled(viewModel.isLoading || username.isEmpty)
// }
//
// Section {
// Button {
// viewModel.isConnectedServer = false
// } label: {
// HStack {
// HStack {
// Image(systemName: "chevron.left")
// Text("Change Server")
// }
// Spacer()
// }
// }
// }
// } else {
// Section(header: Text("Login to \(SessionManager.main.currentLogin.server.name)")) {
// ForEach(viewModel.publicUsers, id: \.id) { publicUser in
// HStack {
// Button(action: {
// // TODO: todo
// print("TODO")
//// if SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!) {
//// let user = SessionManager.current.getSavedSession(userID: publicUser.id!)
//// SessionManager.current.loginWithSavedSession(user: user)
//// mainRouter.root(\.mainTab)
//// } else {
//// username = publicUser.name ?? ""
//// viewModel.selectedPublicUser = publicUser
//// viewModel.hidePublicUsers()
//// if !(publicUser.hasPassword ?? true) {
//// password = ""
//// DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
//// viewModel.login()
//// }
//// }
//// }
// }) {
// HStack {
// Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold)
// Spacer()
// if publicUser.primaryImageTag != nil {
// ImageView(src: URL(string: "\(SessionManager.main.currentLogin.server.uri)/Users/\(publicUser.id ?? "")/Images/Primary?width=60&quality=80&tag=\(publicUser.primaryImageTag!)")!)
// .frame(width: 60, height: 60)
// .cornerRadius(30.0)
// } else {
// Image(systemName: "person.fill")
// .foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
// .font(.system(size: 35))
// .frame(width: 60, height: 60)
// .background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
// .cornerRadius(30.0)
// .shadow(radius: 6)
// }
// }
// }
// }
// }
// }
//
// Section {
// Button {
// viewModel.publicUsers.removeAll()
// username = ""
// } label: {
// HStack {
// Text("Other User").font(.subheadline).fontWeight(.semibold)
// Spacer()
// Image(systemName: "person.fill.questionmark")
// .foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
// .font(.system(size: 35))
// .frame(width: 60, height: 60)
// .background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
// .cornerRadius(30.0)
// .shadow(radius: 6)
// }
// }
// }
// }
// } else {
// Section(header: Text("Connect Manually")) {
// TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
// .disableAutocorrection(true)
// .autocapitalization(.none)
// .keyboardType(.URL)
// Button {
// viewModel.connectToServer()
// } label: {
// HStack {
// Text("Connect")
// Spacer()
// if viewModel.isLoading {
// ProgressView()
// }
// }
// }
// .disabled(viewModel.isLoading || uri.isEmpty)
// }
//
// Section(header: Text("Discovered Servers")) {
// if self.viewModel.searching {
// ProgressView()
// }
// ForEach(self.viewModel.servers, id: \.id) { server in
// Button(action: {
// viewModel.connectToServer(at: server.url)
// }, label: {
// HStack {
// Text(server.name)
// .font(.headline)
// Text(" \(server.host)")
// .font(.subheadline)
// .foregroundColor(.secondary)
// Spacer()
// if viewModel.isLoading {
// ProgressView()
// }
// }
//
// })
// }
// }
// .onAppear(perform: self.viewModel.discoverServers)
// }
// }
// }
// .onChange(of: uri) { uri in
// viewModel.uriSubject.send(uri)
// }
// .onChange(of: username) { username in
// viewModel.usernameSubject.send(username)
// }
// .onChange(of: password) { password in
// viewModel.passwordSubject.send(password)
// }
// .alert(item: $viewModel.errorMessage) { _ in
// Alert(title: Text("\(viewModel.errorMessage?.code ?? -1)\n\(viewModel.errorMessage?.title ?? "Error")"),
// message: Text(viewModel.errorMessage?.displayMessage ?? "Error"),
// dismissButton: .cancel())
// }
// .navigationTitle(NSLocalizedString("Connect to Server", comment: ""))
// .onAppear {
// AppURLHandler.shared.appURLState = .allowedInLogin
// }
// }
//}

View File

@ -19,28 +19,28 @@ struct ServerDetailView: View {
HStack { HStack {
Text("Name") Text("Name")
Spacer() Spacer()
Text(ServerEnvironment.current.server.name ?? "") Text(SessionManager.main.currentLogin.server.name)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
HStack { HStack {
Text("URI") Text("URI")
Spacer() Spacer()
Text(ServerEnvironment.current.server.baseURI ?? "") Text(SessionManager.main.currentLogin.server.uri)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
HStack { HStack {
Text("Version") Text("Version")
Spacer() Spacer()
Text(ServerEnvironment.current.server.version ?? "") Text(SessionManager.main.currentLogin.server.version)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
HStack { HStack {
Text("Operating System") Text("Operating System")
Spacer() Spacer()
Text(ServerEnvironment.current.server.os ?? "") Text(SessionManager.main.currentLogin.server.os)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
} }

View File

@ -0,0 +1,34 @@
//
/*
* 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
struct ServerListView: View {
@EnvironmentObject var serverListRouter: ServerListCoordinator.Router
@ObservedObject var viewModel: ServerListViewModel
var body: some View {
List {
ForEach(viewModel.servers, id: \.id) { server in
Text(server.name)
}
}
.navigationTitle("Servers")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
serverListRouter.route(to: \.connectToServer)
} label: {
Text("Connect")
}
}
}
}
}

View File

@ -31,7 +31,7 @@ struct SettingsView: View {
HStack { HStack {
Text("User") Text("User")
Spacer() Spacer()
Text(SessionManager.current.user?.username ?? "") Text(SessionManager.main.currentLogin.user.username)
.foregroundColor(.jellyfinPurple) .foregroundColor(.jellyfinPurple)
} }
@ -41,7 +41,7 @@ struct SettingsView: View {
HStack { HStack {
Text("Server") Text("Server")
Spacer() Spacer()
Text(ServerEnvironment.current.server?.name ?? "") Text(SessionManager.main.currentLogin.server.name)
.foregroundColor(.jellyfinPurple) .foregroundColor(.jellyfinPurple)
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
@ -51,7 +51,8 @@ struct SettingsView: View {
Button { Button {
settingsRouter.dismissCoordinator() settingsRouter.dismissCoordinator()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
SessionManager.current.logout() // TODO: todo
// SessionManager.current.logout()
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignOut"), object: nil) nc.post(name: Notification.Name("didSignOut"), object: nil)
} }

View File

@ -74,7 +74,7 @@ public extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = let urlString =
"\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" "\(SessionManager.main.currentLogin.server.uri)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -91,7 +91,7 @@ public extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = let urlString =
"\(ServerEnvironment.current.server.baseURI!)/Items/\(parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" "\(SessionManager.main.currentLogin.server.uri)/Items/\(parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -100,7 +100,7 @@ public extension BaseItemDto {
let imageTag = seriesPrimaryImageTag ?? "" let imageTag = seriesPrimaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = let urlString =
"\(ServerEnvironment.current.server.baseURI!)/Items/\(seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" "\(SessionManager.main.currentLogin.server.uri)/Items/\(seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -117,7 +117,7 @@ public extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = let urlString =
"\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" "\(SessionManager.main.currentLogin.server.uri)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
// print(urlString) // print(urlString)
return URL(string: urlString)! return URL(string: urlString)!
} }

View File

@ -57,7 +57,7 @@ extension BaseItemPerson {
// MARK: PortraitImageStackable // MARK: PortraitImageStackable
extension BaseItemPerson: PortraitImageStackable { extension BaseItemPerson: PortraitImageStackable {
public func imageURLContsructor(maxWidth: Int) -> URL { public func imageURLContsructor(maxWidth: Int) -> URL {
return self.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: maxWidth) return self.getImage(baseURL: SessionManager.main.currentLogin.server.uri, maxWidth: maxWidth)
} }
public var title: String { public var title: String {

View File

@ -11,22 +11,6 @@
import Foundation import Foundation
public class ServerDiscovery { public class ServerDiscovery {
public struct ServerCredential: Codable {
public let host: String
public let port: Int
public let username: String
public let password: String
public let deviceId: String
public init(_ host: String, _ port: Int, _ username: String, _ password: String, _ deviceId: String = UUID().uuidString) {
self.host = host
self.port = port
self.username = username
self.password = password
self.deviceId = deviceId
}
}
public struct ServerLookupResponse: Codable, Hashable, Identifiable { public struct ServerLookupResponse: Codable, Hashable, Identifiable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
@ -62,6 +46,7 @@ public class ServerDiscovery {
case name = "Name" case name = "Name"
} }
} }
private let broadcastConn: UDPBroadcastConnection private let broadcastConn: UDPBroadcastConnection
public init() { public init() {

View File

@ -1,67 +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 Combine
import CoreData
import Foundation
import JellyfinAPI
final class ServerEnvironment {
static let current = ServerEnvironment()
fileprivate(set) var server: Server!
init() {
let serverRequest: NSFetchRequest<Server> = Server.fetchRequest()
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest)
if servers?.count != 0 {
server = servers?.first
JellyfinAPI.basePath = server.baseURI!
}
}
func create(with uri: String) -> AnyPublisher<Server, Error> {
LogManager.shared.log.debug("Initializing new Server object with raw URI: \"\(uri)\"")
var uri = uri
if !uri.contains("http") {
uri = "https://" + uri
}
if uri.last == "/" {
uri = String(uri.dropLast())
}
LogManager.shared.log.debug("Normalized URI: \"\(uri)\", attempting to getPublicSystemInfo()")
JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo()
.map { response in
let server = Server(context: PersistenceController.shared.container.viewContext)
server.baseURI = uri
server.name = response.serverName
server.server_id = response.id
server.version = response.version
server.os = response.operatingSystem
return server
}
.handleEvents(receiveOutput: { [unowned self] response in
server = response
_ = try? PersistenceController.shared.container.viewContext.save()
}).eraseToAnyPublisher()
}
func reset() {
JellyfinAPI.basePath = ""
server = nil
let serverRequest: NSFetchRequest<NSFetchRequestResult> = Server.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest)
// coredata will theoretically never throw
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
}
}

View File

@ -21,8 +21,8 @@ import TVServices
typealias CurrentLogin = (server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User) typealias CurrentLogin = (server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User)
// MARK: New SessionManager // MARK: NewSessionManager
final class NewSessionManager { final class SessionManager {
// MARK: currentLogin // MARK: currentLogin
private(set) var currentLogin: CurrentLogin! private(set) var currentLogin: CurrentLogin!
@ -32,13 +32,30 @@ final class NewSessionManager {
private let JellyfinDefaults = UserDefaults(suiteName: "jellyfin-defaults")! private let JellyfinDefaults = UserDefaults(suiteName: "jellyfin-defaults")!
private init() { } private init() {
if let lastServerUserID = SwiftfinStore.Defaults.suite[.lastServerUserID],
let userID = lastServerUserID.split(separator: "-")[safe: 1],
let user = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.User>(),
[Where<SwiftfinStore.Models.User>("id == %@", userID)]) {
// Strongly assuming that we didn't delete the server associate with the user
guard let server = user.server, let accessToken = user.accessToken else { return }
setAuthHeader(with: accessToken.value)
currentLogin = (server: server, user: user)
}
}
func generateServerUserID(server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User) -> String { private func generateServerUserID(server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User) -> String {
return "\(server.id)-\(user.id)" return "\(server.id)-\(user.id)"
} }
func connectToServer(with uri: String) -> AnyPublisher<PublicSystemInfo, Error> { func fetchServers() -> [SwiftfinStore.Models.Server] {
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.Server>())
return servers
}
// Connects to a server at the given uri, storing if successful
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.Models.Server, Error> {
var uri = uri var uri = uri
if !uri.contains("http") { if !uri.contains("http") {
uri = "https://" + uri uri = "https://" + uri
@ -50,23 +67,58 @@ final class NewSessionManager {
JellyfinAPI.basePath = uri JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo() return SystemAPI.getPublicSystemInfo()
.handleEvents(receiveOutput: { response in .map({ response -> (SwiftfinStore.Models.Server, UnsafeDataTransaction) in
print(response) let transaction = SwiftfinStore.dataStack.beginUnsafe()
}).eraseToAnyPublisher() let newServer = transaction.create(Into<SwiftfinStore.Models.Server>())
newServer.uri = response.localAddress ?? "SfUri"
newServer.name = response.serverName ?? "SfServerName"
newServer.id = response.id ?? ""
newServer.os = response.operatingSystem ?? "SfOS"
newServer.version = response.version ?? "SfVersion"
newServer.users = []
return (newServer, transaction)
})
.handleEvents(receiveOutput: { (_, transaction) in
try? transaction.commitAndWait()
})
.map({ (server, _) in
return server
})
.eraseToAnyPublisher()
} }
func fetchServers() -> [SwiftfinStore.Models.Server] { // Logs in a user with an associated server, storing if successful
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.Server>()) func loginUser(server: SwiftfinStore.Models.Server, username: String, password: String) -> AnyPublisher<SwiftfinStore.Models.User, Error> {
return servers
}
func loginUser(server: SwiftfinStore.Models.Server, username: String, password: String) -> AnyPublisher<AuthenticationResult, Error> {
setAuthHeader(with: "") setAuthHeader(with: "")
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password)) return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.handleEvents(receiveOutput: { [unowned self] response in .map({ response -> (SwiftfinStore.Models.User, UnsafeDataTransaction) in
guard let accessToken = response.accessToken else { fatalError() }
setAuthHeader(with: accessToken) guard let accessToken = response.accessToken else { fatalError("Received successful user with no access token") }
let transaction = SwiftfinStore.dataStack.beginUnsafe()
let newUser = transaction.create(Into<SwiftfinStore.Models.User>())
newUser.username = response.user?.name ?? "SfUsername"
newUser.id = response.user?.id ?? "SfID"
newUser.appleTVID = ""
let newAccessToken = transaction.create(Into<SwiftfinStore.Models.AccessToken>())
newAccessToken.value = accessToken
newUser.accessToken = newAccessToken
let userServer = transaction.edit(server)
userServer?.users.insert(newUser)
return (newUser, transaction)
})
.handleEvents(receiveOutput: { [unowned self] (user, transaction) in
setAuthHeader(with: user.accessToken?.value ?? "")
try? transaction.commitAndWait()
currentLogin = (server: server, user: user)
})
.map({ (user, _) in
return user
}) })
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -76,8 +128,6 @@ final class NewSessionManager {
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 = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) }) deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
var header = "MediaBrowser "
let platform: String let platform: String
#if os(tvOS) #if os(tvOS)
@ -86,6 +136,7 @@ final class NewSessionManager {
platform = "iOS" platform = "iOS"
#endif #endif
var header = "MediaBrowser "
header.append("Client=\"Jellyfin \(platform)\", ") header.append("Client=\"Jellyfin \(platform)\", ")
header.append("Device=\"\(deviceName)\", ") header.append("Device=\"\(deviceName)\", ")
header.append("DeviceId=\"\(platform)_\(UIDevice.vendorUUIDString)_\(String(Date().timeIntervalSince1970))\", ") header.append("DeviceId=\"\(platform)_\(UIDevice.vendorUUIDString)_\(String(Date().timeIntervalSince1970))\", ")
@ -96,170 +147,170 @@ final class NewSessionManager {
} }
} }
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 deviceID: String = "" // fileprivate(set) var deviceID: String = ""
fileprivate(set) var accessToken: String = "" // fileprivate(set) var accessToken: String = ""
//
#if os(tvOS) // #if os(tvOS)
let tvUserManager = TVUserManager() // let tvUserManager = TVUserManager()
#endif // #endif
let userDefaults = UserDefaults() // let userDefaults = UserDefaults()
//
init() { // init() {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest() // let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID") // let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID")
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) // let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
#if os(tvOS) // #if os(tvOS)
savedUsers?.forEach { savedUser in // savedUsers?.forEach { savedUser in
if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" { // if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
self.user = savedUser // self.user = savedUser
} // }
} // }
#else // #else
if lastUsedUserID != nil { // if lastUsedUserID != nil {
savedUsers?.forEach { savedUser in // savedUsers?.forEach { savedUser in
if savedUser.user_id ?? "" == lastUsedUserID! { // if savedUser.user_id ?? "" == lastUsedUserID! {
user = savedUser // user = savedUser
} // }
} // }
} else { // } else {
user = savedUsers?.first // user = savedUsers?.first
} // }
#endif // #endif
//
if user != nil { // if user != nil {
let authToken = getAuthToken(userID: user.user_id!) // let authToken = getAuthToken(userID: user.user_id!)
generateAuthHeader(with: authToken, deviceID: user.device_uuid) // generateAuthHeader(with: authToken, deviceID: user.device_uuid)
} // }
} // }
//
fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) { // fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: 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 = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) }) // deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
//
var header = "MediaBrowser " // var header = "MediaBrowser "
#if os(tvOS) // #if os(tvOS)
header.append("Client=\"Jellyfin tvOS\", ") // header.append("Client=\"Jellyfin tvOS\", ")
#else // #else
header.append("Client=\"SwiftFin iOS\", ") // header.append("Client=\"SwiftFin iOS\", ")
#endif // #endif
//
header.append("Device=\"\(deviceName)\", ") // header.append("Device=\"\(deviceName)\", ")
//
if devID == nil { // if devID == nil {
LogManager.shared.log.info("Generating device ID...") // LogManager.shared.log.info("Generating device ID...")
#if os(tvOS) // #if os(tvOS)
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ") // header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))" // deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
#else // #else
header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ") // header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))" // deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
#endif // #endif
} else { // } else {
LogManager.shared.log.info("Using stored device ID...") // LogManager.shared.log.info("Using stored device ID...")
header.append("DeviceId=\"\(devID!)\", ") // header.append("DeviceId=\"\(devID!)\", ")
deviceID = devID! // deviceID = devID!
} // }
//
header.append("Version=\"\(appVersion ?? "0.0.1")\", ") // header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
//
if authToken != nil { // if authToken != nil {
header.append("Token=\"\(authToken!)\"") // header.append("Token=\"\(authToken!)\"")
accessToken = authToken! // accessToken = authToken!
} // }
//
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header // JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
} // }
//
fileprivate func getAuthToken(userID: String) -> String? { // fileprivate func getAuthToken(userID: String) -> String? {
let keychain = KeychainSwift() // let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain" // keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
return keychain.get("AccessToken_\(userID)") // return keychain.get("AccessToken_\(userID)")
} // }
//
func doesUserHaveSavedSession(userID: String) -> Bool { // func doesUserHaveSavedSession(userID: String) -> Bool {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest() // let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID) // savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) // let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
if savedUsers!.isEmpty { // if savedUsers!.isEmpty {
return false // return false
} // }
//
return true // return true
} // }
//
func getSavedSession(userID: String) -> SignedInUser { // func getSavedSession(userID: String) -> SignedInUser {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest() // let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID) // savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) // let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
return savedUsers!.first! // return savedUsers!.first!
} // }
//
func loginWithSavedSession(user: SignedInUser) { // func loginWithSavedSession(user: SignedInUser) {
let accessToken = getAuthToken(userID: user.user_id!) // let accessToken = getAuthToken(userID: user.user_id!)
userDefaults.set(user.user_id!, forKey: "lastUsedUserID") // userDefaults.set(user.user_id!, forKey: "lastUsedUserID")
self.user = user // self.user = user
generateAuthHeader(with: accessToken, deviceID: user.device_uuid) // generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
print(JellyfinAPI.customHeaders) // print(JellyfinAPI.customHeaders)
let nc = NotificationCenter.default // let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignIn"), object: nil) // 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> {
generateAuthHeader(with: nil, deviceID: nil) // generateAuthHeader(with: nil, deviceID: nil)
//
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password)) // return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.map { 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.username = response.user?.name // user.username = response.user?.name
user.user_id = response.user?.id // user.user_id = response.user?.id
user.device_uuid = self.deviceID // user.device_uuid = self.deviceID
//
#if os(tvOS) // #if os(tvOS)
let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!) // let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!)
self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in // self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in
if should { // if should {
user.appletv_id = self.tvUserManager.currentUserIdentifier ?? "" // user.appletv_id = self.tvUserManager.currentUserIdentifier ?? ""
} // }
} // }
#endif // #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()
//
let keychain = KeychainSwift() // let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain" // keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)") // keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
//
generateAuthHeader(with: accessToken, deviceID: user.device_uuid) // generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
//
let nc = NotificationCenter.default // let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignIn"), object: nil) // nc.post(name: Notification.Name("didSignIn"), object: nil)
}) // })
.map(\.0) // .map(\.0)
.eraseToAnyPublisher() // .eraseToAnyPublisher()
} // }
//
func logout() { // func logout() {
let nc = NotificationCenter.default // let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignOut"), object: nil) // nc.post(name: Notification.Name("didSignOut"), object: nil)
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 ?? "")")
generateAuthHeader(with: nil, deviceID: nil) // generateAuthHeader(with: nil, deviceID: nil)
if user != nil { // if user != nil {
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID]) // let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
user = nil // user = nil
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest) // _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
} // }
} // }
} //}

View File

@ -9,6 +9,7 @@
import Foundation import Foundation
import CoreStore import CoreStore
import Defaults
enum SwiftfinStore { enum SwiftfinStore {
@ -17,7 +18,7 @@ enum SwiftfinStore {
final class Server: CoreStoreObject { final class Server: CoreStoreObject {
@Field.Stored("uri") @Field.Stored("uri")
var url: String = "" var uri: String = ""
@Field.Stored("name") @Field.Stored("name")
var name: String = "" var name: String = ""
@ -38,7 +39,7 @@ enum SwiftfinStore {
final class User: CoreStoreObject { final class User: CoreStoreObject {
@Field.Stored("username") @Field.Stored("username")
var name: String = "" var username: String = ""
@Field.Stored("id") @Field.Stored("id")
var id: String = "" var id: String = ""
@ -48,6 +49,18 @@ enum SwiftfinStore {
@Field.Relationship("server") @Field.Relationship("server")
var server: Server? var server: Server?
@Field.Relationship("accessToken", inverse: \AccessToken.$user)
var accessToken: AccessToken?
}
final class AccessToken: CoreStoreObject {
@Field.Stored("value")
var value: String = ""
@Field.Relationship("user")
var user: User?
} }
} }
@ -56,8 +69,9 @@ enum SwiftfinStore {
entities: [ entities: [
Entity<SwiftfinStore.Models.Server>("Server"), Entity<SwiftfinStore.Models.Server>("Server"),
Entity<SwiftfinStore.Models.User>("User"), Entity<SwiftfinStore.Models.User>("User"),
Entity<SwiftfinStore.Models.AccessToken>("AccessToken")
], ],
versionLock: nil) versionLock: nil) // TODO: todo
let _dataStack = DataStack(schema) let _dataStack = DataStack(schema)
try! _dataStack.addStorageAndWait( try! _dataStack.addStorageAndWait(

View File

@ -0,0 +1,29 @@
//
/*
* 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 Defaults
import Foundation
extension SwiftfinStore {
enum Defaults {
static let suite: UserDefaults = {
return UserDefaults(suiteName: "swiftfinstore-defaults")!
}()
// enum Keys {
// static let lastUserID = Defaults.Key<String?>("lastUserID", suite: SwiftfinStore.Defaults.suite)
// }
}
}
extension Defaults.Keys {
static let lastServerUserID = Defaults.Key<String?>("lastServerUserID", suite: SwiftfinStore.Defaults.suite)
}

View File

@ -0,0 +1,33 @@
//
/*
* 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 KeychainSwift
extension SwiftfinStore {
enum Keychain {
private static let keychainAccessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
private static let keychain: KeychainSwift = {
let keychain = KeychainSwift()
keychain.accessGroup = keychainAccessGroup
return keychain
}()
static func getAuthToken(serverUserID: String) -> String? {
return keychain.get("AccessToken_\(serverUserID)")
}
static func delete(serverUserID: String) {
// TODO: todo
}
}
}

View File

@ -13,77 +13,32 @@ import JellyfinAPI
import Stinsen import Stinsen
final class ConnectToServerViewModel: ViewModel { final class ConnectToServerViewModel: ViewModel {
@RouterObject
var main: MainCoordinator.Router? @RouterObject var main: MainCoordinator.Router?
@Published var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
@Published var isConnectedServer = false
var uriSubject = CurrentValueSubject<String, Never>("")
var usernameSubject = CurrentValueSubject<String, Never>("")
var passwordSubject = CurrentValueSubject<String, Never>("")
@Published var lastPublicUsers = [UserDto]()
@Published var publicUsers = [UserDto]()
@Published var selectedPublicUser = UserDto()
private let discovery = ServerDiscovery()
@Published var servers: [ServerDiscovery.ServerLookupResponse] = []
@Published var searching = false @Published var searching = false
private let discovery = ServerDiscovery()
func getPublicUsers() { func connectToServer(uri: String) {
if ServerEnvironment.current.server != nil {
LogManager.shared.log.debug("Attempting to read public users from \(ServerEnvironment.current.server.baseURI!)",
tag: "getPublicUsers")
UserAPI.getPublicUsers()
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
}, receiveValue: { response in
self.publicUsers = response
LogManager.shared.log.debug("Received \(String(response.count)) public users.", tag: "getPublicUsers")
self.isConnectedServer = true
})
.store(in: &cancellables)
} else {
LogManager.shared.log.debug("Not getting users - server is nil", tag: "getPublicUsers")
}
}
func hidePublicUsers() {
lastPublicUsers = publicUsers
publicUsers = []
}
func showPublicUsers() {
publicUsers = lastPublicUsers
lastPublicUsers = []
}
func connectToServer() {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
if uriSubject.value == "localhost" { var uri = uri
uriSubject.value = "http://localhost:8096" if uri == "localhost" {
} uri = "http://localhost:8096"
}
#endif #endif
LogManager.shared.log.debug("Attempting to connect to server at \"\(uriSubject.value)\"", tag: "connectToServer") LogManager.shared.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
ServerEnvironment.current.create(with: uriSubject.value) SessionManager.main.connectToServer(with: uri)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer", self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer",
completion: completion) completion: completion)
}, receiveValue: { _ in }, receiveValue: { _ in
LogManager.shared.log.debug("Connected to server at \"\(self.uriSubject.value)\"", tag: "connectToServer") LogManager.shared.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
self.getPublicUsers()
}) })
.store(in: &cancellables) .store(in: &cancellables)
} }
func connectToServer(at url: URL) {
uriSubject.send(url.absoluteString)
connectToServer()
}
func discoverServers() { func discoverServers() {
searching = true searching = true
@ -93,26 +48,10 @@ final class ConnectToServerViewModel: ViewModel {
} }
discovery.locateServer { [self] server in discovery.locateServer { [self] server in
if let server = server, !servers.contains(server) { if let server = server {
servers.append(server) discoveredServers.insert(server)
} }
searching = false searching = false
} }
} }
func login() {
LogManager.shared.log.debug("Attempting to login to server at \"\(uriSubject.value)\"", tag: "login")
LogManager.shared.log
.debug("username == \"\": \(usernameSubject.value.isEmpty), password == \"\": \(passwordSubject.value.isEmpty)",
tag: "login")
SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
completion: completion)
}, receiveValue: { [weak self] _ in
self?.main?.root(\.mainTab)
})
.store(in: &cancellables)
}
} }

View File

@ -26,7 +26,7 @@ final class EpisodeItemViewModel: ItemViewModel {
func routeToSeasonItem() { func routeToSeasonItem() {
guard let id = item.seasonId else { return } guard let id = item.seasonId else { return }
UserLibraryAPI.getItem(userId: SessionManager.current.user.user_id!, itemId: id) UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -38,7 +38,7 @@ final class EpisodeItemViewModel: ItemViewModel {
func routeToSeriesItem() { func routeToSeriesItem() {
guard let id = item.seriesId else { return } guard let id = item.seriesId else { return }
UserLibraryAPI.getItem(userId: SessionManager.current.user.user_id!, itemId: id) UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)

View File

@ -29,7 +29,7 @@ final class HomeViewModel: ViewModel {
func refresh() { func refresh() {
LogManager.shared.log.debug("Refresh called.") LogManager.shared.log.debug("Refresh called.")
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!) UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion) self.handleAPIRequestError(completion: completion)
@ -57,7 +57,7 @@ final class HomeViewModel: ViewModel {
}) })
.store(in: &cancellables) .store(in: &cancellables)
ItemsAPI.getResumeItems(userId: SessionManager.current.user.user_id!, limit: 12, ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.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)
@ -69,7 +69,7 @@ final class HomeViewModel: ViewModel {
}) })
.store(in: &cancellables) .store(in: &cancellables)
TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, limit: 12, TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, limit: 12,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in

View File

@ -47,7 +47,7 @@ class ItemViewModel: ViewModel {
} }
func getSimilarItems() { func getSimilarItems() {
LibraryAPI.getSimilarItems(itemId: item.id!, userId: SessionManager.current.user.user_id!, limit: 20, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) LibraryAPI.getSimilarItems(itemId: item.id!, userId: SessionManager.main.currentLogin.user.id, limit: 20, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -59,7 +59,7 @@ class ItemViewModel: ViewModel {
func updateWatchState() { func updateWatchState() {
if isWatched { if isWatched {
PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!) PlaystateAPI.markUnplayedItem(userId: SessionManager.main.currentLogin.user.id, itemId: item.id!)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -68,7 +68,7 @@ class ItemViewModel: ViewModel {
}) })
.store(in: &cancellables) .store(in: &cancellables)
} else { } else {
PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: item.id!) PlaystateAPI.markPlayedItem(userId: SessionManager.main.currentLogin.user.id, itemId: item.id!)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -81,7 +81,7 @@ class ItemViewModel: ViewModel {
func updateFavoriteState() { func updateFavoriteState() {
if isFavorited { if isFavorited {
UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!) UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.main.currentLogin.user.id, itemId: item.id!)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -90,7 +90,7 @@ class ItemViewModel: ViewModel {
}) })
.store(in: &cancellables) .store(in: &cancellables)
} else { } else {
UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: item.id!) UserLibraryAPI.markFavoriteItem(userId: SessionManager.main.currentLogin.user.id, itemId: item.id!)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)

View File

@ -25,8 +25,8 @@ final class LatestMediaViewModel: ViewModel {
} }
func requestLatestMedia() { func requestLatestMedia() {
LogManager.shared.log.debug("Requesting latest media for user id \(SessionManager.current.user.user_id ?? "NIL")") LogManager.shared.log.debug("Requesting latest media for user id \(SessionManager.main.currentLogin.user.id ?? "NIL")")
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.user.user_id!, UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.user.id,
parentId: libraryID, parentId: libraryID,
fields: [ fields: [
.primaryImageAspectRatio, .primaryImageAspectRatio,

View File

@ -58,7 +58,7 @@ final class LibraryFilterViewModel: ViewModel {
} }
func requestQueryFilters() { func requestQueryFilters() {
FilterAPI.getQueryFilters(userId: SessionManager.current.user.user_id!, parentId: self.parentId) FilterAPI.getQueryFilters(userId: SessionManager.main.currentLogin.user.id, parentId: self.parentId)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)

View File

@ -24,7 +24,7 @@ final class LibraryListViewModel: ViewModel {
} }
func requestLibraries() { func requestLibraries() {
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id ?? "val was nil") UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id ?? "val was nil")
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion) self.handleAPIRequestError(completion: completion)

View File

@ -77,7 +77,7 @@ final class LibrarySearchViewModel: ViewModel {
} }
func requestSuggestions() { func requestSuggestions() {
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id,
limit: 20, limit: 20,
recursive: true, recursive: true,
parentId: parentID, parentId: parentID,
@ -96,7 +96,7 @@ final class LibrarySearchViewModel: ViewModel {
} }
func search(with query: String) { func search(with query: String) {
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, limit: 50, recursive: true, searchTerm: query,
sortOrder: [.ascending], parentId: parentID, sortOrder: [.ascending], parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: [ItemType.movie.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true) includeItemTypes: [ItemType.movie.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
@ -107,7 +107,7 @@ final class LibrarySearchViewModel: ViewModel {
self?.movieItems = response.items ?? [] self?.movieItems = response.items ?? []
}) })
.store(in: &cancellables) .store(in: &cancellables)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, limit: 50, recursive: true, searchTerm: query,
sortOrder: [.ascending], parentId: parentID, sortOrder: [.ascending], parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: [ItemType.series.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true) includeItemTypes: [ItemType.series.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
@ -118,7 +118,7 @@ final class LibrarySearchViewModel: ViewModel {
self?.showItems = response.items ?? [] self?.showItems = response.items ?? []
}) })
.store(in: &cancellables) .store(in: &cancellables)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, limit: 50, recursive: true, searchTerm: query,
sortOrder: [.ascending], parentId: parentID, sortOrder: [.ascending], parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: [ItemType.episode.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true) includeItemTypes: [ItemType.episode.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)

View File

@ -63,7 +63,7 @@ final class LibraryViewModel: ViewModel {
} }
let sortBy = filters.sortBy.map(\.rawValue) let sortBy = filters.sortBy.map(\.rawValue)
let shouldBeRecursive: Bool = filters.filters.contains(.isFavorite) || personIDs != [] || studioIDs != [] || genreIDs != [] let shouldBeRecursive: Bool = filters.filters.contains(.isFavorite) || personIDs != [] || studioIDs != [] || genreIDs != []
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: shouldBeRecursive, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100, limit: 100, recursive: shouldBeRecursive,
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID, searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
filters: filters.filters, sortBy: sortBy, tags: filters.tags, filters: filters.filters, sortBy: sortBy, tags: filters.tags,
@ -94,7 +94,7 @@ final class LibraryViewModel: ViewModel {
} }
let sortBy = filters.sortBy.map(\.rawValue) let sortBy = filters.sortBy.map(\.rawValue)
let shouldBeRecursive: Bool = filters.filters.contains(.isFavorite) || personIDs != [] || studioIDs != [] || genreIDs != [] let shouldBeRecursive: Bool = filters.filters.contains(.isFavorite) || personIDs != [] || studioIDs != [] || genreIDs != []
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: shouldBeRecursive, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100, limit: 100, recursive: shouldBeRecursive,
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID, searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
filters: filters.filters, sortBy: sortBy, tags: filters.tags, filters: filters.filters, sortBy: sortBy, tags: filters.tags,

View File

@ -32,7 +32,7 @@ final class SeasonItemViewModel: ItemViewModel {
private func requestEpisodes() { private func requestEpisodes() {
LogManager.shared.log LogManager.shared.log
.debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))") .debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))")
TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.current.user.user_id!, TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.main.currentLogin.user.id,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
seasonId: item.id ?? "") seasonId: item.id ?? "")
.trackActivity(loading) .trackActivity(loading)
@ -73,7 +73,7 @@ final class SeasonItemViewModel: ItemViewModel {
func routeToSeriesItem() { func routeToSeriesItem() {
guard let id = item.seriesId else { return } guard let id = item.seriesId else { return }
UserLibraryAPI.getItem(userId: SessionManager.current.user.user_id!, itemId: id) UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)

View File

@ -35,7 +35,7 @@ final class SeriesItemViewModel: ItemViewModel {
private func getNextUp() { private func getNextUp() {
LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, enableUserData: true) TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, enableUserData: true)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)
@ -67,7 +67,7 @@ final class SeriesItemViewModel: ItemViewModel {
private func requestSeasons() { private func requestSeasons() {
LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))") LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))")
TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true) TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true)
.trackActivity(loading) .trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in .sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion) self?.handleAPIRequestError(completion: completion)

View File

@ -0,0 +1,20 @@
//
/*
* 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 SwiftUI
class ServerListViewModel: ObservableObject {
@Published var servers: [SwiftfinStore.Models.Server] = []
init() {
self.servers = SessionManager.main.fetchServers()
}
}

View File

@ -21,7 +21,8 @@ final class SplashViewModel: ViewModel {
@Published var isLoggedIn: Bool = false @Published var isLoggedIn: Bool = false
override init() { override init() {
isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil // TODO: Remove SplashViewModel
isLoggedIn = SessionManager.main.currentLogin != nil
super.init() super.init()
ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory

View File

@ -0,0 +1,47 @@
//
/*
* 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
import Stinsen
final class UserLoginViewModel: ViewModel {
let server: SwiftfinStore.Models.Server
init(server: SwiftfinStore.Models.Server) {
self.server = server
}
func login(username: String, password: String) {
LogManager.shared.log.debug("Attempting to login to server at \"\(server.uri)\"", tag: "login")
LogManager.shared.log.debug("username == \"\": \(username), password == \"\": \(password)", tag: "login")
SessionManager.main.loginUser(server: server, username: username, password: password)
.trackActivity(loading)
.sink { completion in
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
completion: completion)
} receiveValue: { user in
print(user)
}
.store(in: &cancellables)
//
//
// SessionManager.current.login(username: username, password: password)
// .trackActivity(loading)
// .sink(receiveCompletion: { completion in
// self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
// completion: completion)
// }, receiveValue: { [weak self] _ in
// self?.main?.root(\.mainTab)
// })
// .store(in: &cancellables)
}
}

View File

@ -24,92 +24,94 @@ struct NextUpWidgetProvider: TimelineProvider {
} }
func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) { func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) {
guard let currentLogin = SessionManager.main.currentLogin else { return }
let currentDate = Date() let currentDate = Date()
let server = ServerEnvironment.current.server let server = currentLogin.server
let savedUser = SessionManager.current.user let savedUser = currentLogin.user
var tempCancellables = Set<AnyCancellable>() var tempCancellables = Set<AnyCancellable>()
if server != nil && savedUser != nil { JellyfinAPI.basePath = server.uri
JellyfinAPI.basePath = server!.baseURI ?? "" TvShowsAPI.getNextUp(userId: savedUser.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]) .subscribe(on: DispatchQueue.global(qos: .background))
.subscribe(on: DispatchQueue.global(qos: .background)) .sink(receiveCompletion: { result in
.sink(receiveCompletion: { result in switch result {
switch result { case .finished:
case .finished: break
break case let .failure(error):
case let .failure(error): completion(NextUpEntry(date: currentDate, items: [], error: error))
completion(NextUpEntry(date: currentDate, items: [], error: error)) }
} }, receiveValue: { response in
}, receiveValue: { response in let dispatchGroup = DispatchGroup()
let dispatchGroup = DispatchGroup() let items = response.items ?? []
let items = response.items ?? [] var downloadedItems = [(BaseItemDto, UIImage?)]()
var downloadedItems = [(BaseItemDto, UIImage?)]() items.enumerated().forEach { _, item in
items.enumerated().forEach { _, item in dispatchGroup.enter()
dispatchGroup.enter() ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in guard case let .success(image) = result else {
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
dispatchGroup.leave() dispatchGroup.leave()
return
} }
downloadedItems.append((item, image.image))
dispatchGroup.leave()
} }
}
dispatchGroup.notify(queue: .main) { dispatchGroup.notify(queue: .main) {
completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil)) completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil))
} }
}) })
.store(in: &tempCancellables) .store(in: &tempCancellables)
}
} }
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) { func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
guard let currentLogin = SessionManager.main.currentLogin else { return }
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)!
let server = ServerEnvironment.current.server let server = currentLogin.server
let savedUser = SessionManager.current.user let savedUser = currentLogin.user
var tempCancellables = Set<AnyCancellable>() var tempCancellables = Set<AnyCancellable>()
if server != nil && savedUser != nil { JellyfinAPI.basePath = server.uri
JellyfinAPI.basePath = server!.baseURI ?? "" TvShowsAPI.getNextUp(userId: savedUser.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]) .subscribe(on: DispatchQueue.global(qos: .background))
.subscribe(on: DispatchQueue.global(qos: .background)) .sink(receiveCompletion: { result in
.sink(receiveCompletion: { result in switch result {
switch result { case .finished:
case .finished: break
break case let .failure(error):
case let .failure(error): completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate)))
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate))) }
} }, receiveValue: { response in
}, receiveValue: { response in let dispatchGroup = DispatchGroup()
let dispatchGroup = DispatchGroup() let items = response.items ?? []
let items = response.items ?? [] var downloadedItems = [(BaseItemDto, UIImage?)]()
var downloadedItems = [(BaseItemDto, UIImage?)]() items.enumerated().forEach { _, item in
items.enumerated().forEach { _, item in dispatchGroup.enter()
dispatchGroup.enter() ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in guard case let .success(image) = result else {
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
dispatchGroup.leave() dispatchGroup.leave()
return
} }
downloadedItems.append((item, image.image))
dispatchGroup.leave()
} }
}
dispatchGroup.notify(queue: .main) { dispatchGroup.notify(queue: .main) {
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)], completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)],
policy: .after(entryDate))) policy: .after(entryDate)))
} }
}) })
.store(in: &tempCancellables) .store(in: &tempCancellables)
}
} }
} }
@ -198,7 +200,8 @@ extension NextUpEntryView {
} }
func smallVideoView(item: (BaseItemDto, UIImage?)) -> some View { func smallVideoView(item: (BaseItemDto, UIImage?)) -> some View {
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(item.0.id!)")!, label: { let url = URL(string: "widget-extension://Users/\(SessionManager.main.currentLogin.user.id)/Items/\(item.0.id!)")!
return Link(destination: url, label: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let image = item.1 { if let image = item.1 {
Image(uiImage: image) Image(uiImage: image)
@ -223,7 +226,8 @@ extension NextUpEntryView {
} }
func largeVideoView(item: (BaseItemDto, UIImage?)) -> some View { func largeVideoView(item: (BaseItemDto, UIImage?)) -> some View {
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(item.0.id!)")!, label: { let url = URL(string: "widget-extension://Users/\(SessionManager.main.currentLogin.user.id)/Items/\(item.0.id!)")!
return Link(destination: url, label: {
HStack(spacing: 20) { HStack(spacing: 20) {
if let image = item.1 { if let image = item.1 {
Image(uiImage: image) Image(uiImage: image)
@ -285,7 +289,8 @@ extension NextUpEntryView {
func large(items: [(BaseItemDto, UIImage?)]) -> some View { func large(items: [(BaseItemDto, UIImage?)]) -> some View {
VStack(spacing: 0) { VStack(spacing: 0) {
if let firstItem = items[safe: 0] { if let firstItem = items[safe: 0] {
Link(destination: URL(string: "widget-extension://Users/\(SessionManager.current.user.user_id!)/Items/\(firstItem.0.id!)")!, let url = URL(string: "widget-extension://Users/\(SessionManager.main.currentLogin.user.id)/Items/\(firstItem.0.id!)")!
Link(destination: url,
label: { label: {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
ZStack(alignment: .bottomLeading) { ZStack(alignment: .bottomLeading) {