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 {
VStack {
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)
.cornerRadius(125.0)
} else {

View File

@ -26,7 +26,7 @@ struct ConnectToServerView: View {
} else {
HStack {
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)
.cornerRadius(125.0)
Spacer()

View File

@ -27,7 +27,7 @@ struct LatestMediaView: View {
viewDidLoad = true
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
print(completion)
}, receiveValue: { response in

View File

@ -166,12 +166,12 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
// Item is being transcoded by request of server
if let transcodiungUrl = mediaSource.transcodingUrl {
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
else {
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
@ -186,7 +186,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
var deliveryUrl: URL?
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 ?? "")

View File

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

View File

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

View File

@ -11,6 +11,14 @@ import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
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 {
AppDelegate.orientationLock

View File

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

View File

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

View File

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

View File

@ -9,176 +9,243 @@ import SwiftUI
import Stinsen
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 \(ServerEnvironment.current.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 \(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 {
List {
Section {
TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
.disableAutocorrection(true)
.autocapitalization(.none)
.keyboardType(.URL)
Button {
viewModel.connectToServer(uri: uri)
} label: {
HStack {
Text("Connect")
Spacer()
if viewModel.isLoading {
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()
}
}
})
}
}
}
.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)
.onAppear(perform: self.viewModel.discoverServers)
.headerProminence(.increased)
}
.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 {
.navigationTitle("Connect")
.onAppear {
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 {
Text("Name")
Spacer()
Text(ServerEnvironment.current.server.name ?? "")
Text(SessionManager.main.currentLogin.server.name)
.foregroundColor(.secondary)
}
HStack {
Text("URI")
Spacer()
Text(ServerEnvironment.current.server.baseURI ?? "")
Text(SessionManager.main.currentLogin.server.uri)
.foregroundColor(.secondary)
}
HStack {
Text("Version")
Spacer()
Text(ServerEnvironment.current.server.version ?? "")
Text(SessionManager.main.currentLogin.server.version)
.foregroundColor(.secondary)
}
HStack {
Text("Operating System")
Spacer()
Text(ServerEnvironment.current.server.os ?? "")
Text(SessionManager.main.currentLogin.server.os)
.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 {
Text("User")
Spacer()
Text(SessionManager.current.user?.username ?? "")
Text(SessionManager.main.currentLogin.user.username)
.foregroundColor(.jellyfinPurple)
}
@ -41,7 +41,7 @@ struct SettingsView: View {
HStack {
Text("Server")
Spacer()
Text(ServerEnvironment.current.server?.name ?? "")
Text(SessionManager.main.currentLogin.server.name)
.foregroundColor(.jellyfinPurple)
Image(systemName: "chevron.right")
@ -51,7 +51,8 @@ struct SettingsView: View {
Button {
settingsRouter.dismissCoordinator()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
SessionManager.current.logout()
// TODO: todo
// SessionManager.current.logout()
let nc = NotificationCenter.default
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 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)!
}
@ -91,7 +91,7 @@ public extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
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)!
}
@ -100,7 +100,7 @@ public extension BaseItemDto {
let imageTag = seriesPrimaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
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)!
}
@ -117,7 +117,7 @@ public extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
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)
return URL(string: urlString)!
}

View File

@ -57,7 +57,7 @@ extension BaseItemPerson {
// MARK: PortraitImageStackable
extension BaseItemPerson: PortraitImageStackable {
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 {

View File

@ -11,22 +11,6 @@
import Foundation
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 func hash(into hasher: inout Hasher) {
@ -62,6 +46,7 @@ public class ServerDiscovery {
case name = "Name"
}
}
private let broadcastConn: UDPBroadcastConnection
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)
// MARK: New SessionManager
final class NewSessionManager {
// MARK: NewSessionManager
final class SessionManager {
// MARK: currentLogin
private(set) var currentLogin: CurrentLogin!
@ -32,13 +32,30 @@ final class NewSessionManager {
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)"
}
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
if !uri.contains("http") {
uri = "https://" + uri
@ -50,23 +67,58 @@ final class NewSessionManager {
JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo()
.handleEvents(receiveOutput: { response in
print(response)
}).eraseToAnyPublisher()
.map({ response -> (SwiftfinStore.Models.Server, UnsafeDataTransaction) in
let transaction = SwiftfinStore.dataStack.beginUnsafe()
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] {
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.Server>())
return servers
}
func loginUser(server: SwiftfinStore.Models.Server, username: String, password: String) -> AnyPublisher<AuthenticationResult, Error> {
// Logs in a user with an associated server, storing if successful
func loginUser(server: SwiftfinStore.Models.Server, username: String, password: String) -> AnyPublisher<SwiftfinStore.Models.User, Error> {
setAuthHeader(with: "")
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.handleEvents(receiveOutput: { [unowned self] response in
guard let accessToken = response.accessToken else { fatalError() }
setAuthHeader(with: accessToken)
.map({ response -> (SwiftfinStore.Models.User, UnsafeDataTransaction) in
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()
}
@ -76,8 +128,6 @@ final class NewSessionManager {
var deviceName = UIDevice.current.name
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
var header = "MediaBrowser "
let platform: String
#if os(tvOS)
@ -86,6 +136,7 @@ final class NewSessionManager {
platform = "iOS"
#endif
var header = "MediaBrowser "
header.append("Client=\"Jellyfin \(platform)\", ")
header.append("Device=\"\(deviceName)\", ")
header.append("DeviceId=\"\(platform)_\(UIDevice.vendorUUIDString)_\(String(Date().timeIntervalSince1970))\", ")
@ -96,170 +147,170 @@ final class NewSessionManager {
}
}
final class SessionManager {
static let current = SessionManager()
fileprivate(set) var user: SignedInUser!
fileprivate(set) var deviceID: String = ""
fileprivate(set) var accessToken: String = ""
#if os(tvOS)
let tvUserManager = TVUserManager()
#endif
let userDefaults = UserDefaults()
init() {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID")
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
#if os(tvOS)
savedUsers?.forEach { savedUser in
if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
self.user = savedUser
}
}
#else
if lastUsedUserID != nil {
savedUsers?.forEach { savedUser in
if savedUser.user_id ?? "" == lastUsedUserID! {
user = savedUser
}
}
} else {
user = savedUsers?.first
}
#endif
if user != nil {
let authToken = getAuthToken(userID: user.user_id!)
generateAuthHeader(with: authToken, deviceID: user.device_uuid)
}
}
fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var deviceName = UIDevice.current.name
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
var header = "MediaBrowser "
#if os(tvOS)
header.append("Client=\"Jellyfin tvOS\", ")
#else
header.append("Client=\"SwiftFin iOS\", ")
#endif
header.append("Device=\"\(deviceName)\", ")
if devID == nil {
LogManager.shared.log.info("Generating device ID...")
#if os(tvOS)
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
#else
header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
#endif
} else {
LogManager.shared.log.info("Using stored device ID...")
header.append("DeviceId=\"\(devID!)\", ")
deviceID = devID!
}
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
if authToken != nil {
header.append("Token=\"\(authToken!)\"")
accessToken = authToken!
}
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
}
fileprivate func getAuthToken(userID: String) -> String? {
let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
return keychain.get("AccessToken_\(userID)")
}
func doesUserHaveSavedSession(userID: String) -> Bool {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
if savedUsers!.isEmpty {
return false
}
return true
}
func getSavedSession(userID: String) -> SignedInUser {
let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
return savedUsers!.first!
}
func loginWithSavedSession(user: SignedInUser) {
let accessToken = getAuthToken(userID: user.user_id!)
userDefaults.set(user.user_id!, forKey: "lastUsedUserID")
self.user = user
generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
print(JellyfinAPI.customHeaders)
let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignIn"), object: nil)
}
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
generateAuthHeader(with: nil, deviceID: nil)
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.map { response -> (SignedInUser, String?) in
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
user.username = response.user?.name
user.user_id = response.user?.id
user.device_uuid = self.deviceID
#if os(tvOS)
let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!)
self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in
if should {
user.appletv_id = self.tvUserManager.currentUserIdentifier ?? ""
}
}
#endif
return (user, response.accessToken)
}
.handleEvents(receiveOutput: { [unowned self] response, accessToken in
user = response
_ = try? PersistenceController.shared.container.viewContext.save()
let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignIn"), object: nil)
})
.map(\.0)
.eraseToAnyPublisher()
}
func logout() {
let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignOut"), object: nil)
let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
keychain.delete("AccessToken_\(user?.user_id ?? "")")
generateAuthHeader(with: nil, deviceID: nil)
if user != nil {
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
user = nil
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
}
}
}
//final class SessionManager {
// static let current = SessionManager()
// fileprivate(set) var user: SignedInUser!
// fileprivate(set) var deviceID: String = ""
// fileprivate(set) var accessToken: String = ""
//
// #if os(tvOS)
// let tvUserManager = TVUserManager()
// #endif
// let userDefaults = UserDefaults()
//
// init() {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID")
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// #if os(tvOS)
// savedUsers?.forEach { savedUser in
// if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
// self.user = savedUser
// }
// }
// #else
// if lastUsedUserID != nil {
// savedUsers?.forEach { savedUser in
// if savedUser.user_id ?? "" == lastUsedUserID! {
// user = savedUser
// }
// }
// } else {
// user = savedUsers?.first
// }
// #endif
//
// if user != nil {
// let authToken = getAuthToken(userID: user.user_id!)
// generateAuthHeader(with: authToken, deviceID: user.device_uuid)
// }
// }
//
// fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) {
// let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
// var deviceName = UIDevice.current.name
// deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
// deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
//
// var header = "MediaBrowser "
// #if os(tvOS)
// header.append("Client=\"Jellyfin tvOS\", ")
// #else
// header.append("Client=\"SwiftFin iOS\", ")
// #endif
//
// header.append("Device=\"\(deviceName)\", ")
//
// if devID == nil {
// LogManager.shared.log.info("Generating device ID...")
// #if os(tvOS)
// header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #else
// header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #endif
// } else {
// LogManager.shared.log.info("Using stored device ID...")
// header.append("DeviceId=\"\(devID!)\", ")
// deviceID = devID!
// }
//
// header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
//
// if authToken != nil {
// header.append("Token=\"\(authToken!)\"")
// accessToken = authToken!
// }
//
// JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
// }
//
// fileprivate func getAuthToken(userID: String) -> String? {
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// return keychain.get("AccessToken_\(userID)")
// }
//
// func doesUserHaveSavedSession(userID: String) -> Bool {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// if savedUsers!.isEmpty {
// return false
// }
//
// return true
// }
//
// func getSavedSession(userID: String) -> SignedInUser {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
// return savedUsers!.first!
// }
//
// func loginWithSavedSession(user: SignedInUser) {
// let accessToken = getAuthToken(userID: user.user_id!)
// userDefaults.set(user.user_id!, forKey: "lastUsedUserID")
// self.user = user
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
// print(JellyfinAPI.customHeaders)
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// }
//
// func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
// generateAuthHeader(with: nil, deviceID: nil)
//
// return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
// .map { response -> (SignedInUser, String?) in
// let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
// user.username = response.user?.name
// user.user_id = response.user?.id
// user.device_uuid = self.deviceID
//
// #if os(tvOS)
// let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!)
// self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in
// if should {
// user.appletv_id = self.tvUserManager.currentUserIdentifier ?? ""
// }
// }
// #endif
//
// return (user, response.accessToken)
// }
// .handleEvents(receiveOutput: { [unowned self] response, accessToken in
// user = response
// _ = try? PersistenceController.shared.container.viewContext.save()
//
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
//
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
//
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// })
// .map(\.0)
// .eraseToAnyPublisher()
// }
//
// func logout() {
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignOut"), object: nil)
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.delete("AccessToken_\(user?.user_id ?? "")")
// generateAuthHeader(with: nil, deviceID: nil)
// if user != nil {
// let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
// user = nil
// _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
// }
// }
//}

View File

@ -9,6 +9,7 @@
import Foundation
import CoreStore
import Defaults
enum SwiftfinStore {
@ -17,7 +18,7 @@ enum SwiftfinStore {
final class Server: CoreStoreObject {
@Field.Stored("uri")
var url: String = ""
var uri: String = ""
@Field.Stored("name")
var name: String = ""
@ -38,7 +39,7 @@ enum SwiftfinStore {
final class User: CoreStoreObject {
@Field.Stored("username")
var name: String = ""
var username: String = ""
@Field.Stored("id")
var id: String = ""
@ -48,6 +49,18 @@ enum SwiftfinStore {
@Field.Relationship("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: [
Entity<SwiftfinStore.Models.Server>("Server"),
Entity<SwiftfinStore.Models.User>("User"),
Entity<SwiftfinStore.Models.AccessToken>("AccessToken")
],
versionLock: nil)
versionLock: nil) // TODO: todo
let _dataStack = DataStack(schema)
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
final class ConnectToServerViewModel: ViewModel {
@RouterObject
var main: MainCoordinator.Router?
@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] = []
@RouterObject var main: MainCoordinator.Router?
@Published var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
@Published var searching = false
private let discovery = ServerDiscovery()
func getPublicUsers() {
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() {
func connectToServer(uri: String) {
#if targetEnvironment(simulator)
if uriSubject.value == "localhost" {
uriSubject.value = "http://localhost:8096"
}
var uri = uri
if uri == "localhost" {
uri = "http://localhost:8096"
}
#endif
LogManager.shared.log.debug("Attempting to connect to server at \"\(uriSubject.value)\"", tag: "connectToServer")
ServerEnvironment.current.create(with: uriSubject.value)
LogManager.shared.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
SessionManager.main.connectToServer(with: uri)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer",
completion: completion)
}, receiveValue: { _ in
LogManager.shared.log.debug("Connected to server at \"\(self.uriSubject.value)\"", tag: "connectToServer")
self.getPublicUsers()
LogManager.shared.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
})
.store(in: &cancellables)
}
func connectToServer(at url: URL) {
uriSubject.send(url.absoluteString)
connectToServer()
}
func discoverServers() {
searching = true
@ -93,26 +48,10 @@ final class ConnectToServerViewModel: ViewModel {
}
discovery.locateServer { [self] server in
if let server = server, !servers.contains(server) {
servers.append(server)
if let server = server {
discoveredServers.insert(server)
}
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() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -38,7 +38,7 @@ final class EpisodeItemViewModel: ItemViewModel {
func routeToSeriesItem() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)

View File

@ -29,7 +29,7 @@ final class HomeViewModel: ViewModel {
func refresh() {
LogManager.shared.log.debug("Refresh called.")
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
@ -57,7 +57,7 @@ final class HomeViewModel: ViewModel {
})
.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],
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.trackActivity(loading)
@ -69,7 +69,7 @@ final class HomeViewModel: ViewModel {
})
.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])
.trackActivity(loading)
.sink(receiveCompletion: { completion in

View File

@ -47,7 +47,7 @@ class ItemViewModel: ViewModel {
}
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -59,7 +59,7 @@ class ItemViewModel: ViewModel {
func updateWatchState() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -68,7 +68,7 @@ class ItemViewModel: ViewModel {
})
.store(in: &cancellables)
} 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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -81,7 +81,7 @@ class ItemViewModel: ViewModel {
func updateFavoriteState() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -90,7 +90,7 @@ class ItemViewModel: ViewModel {
})
.store(in: &cancellables)
} 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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)

View File

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

View File

@ -58,7 +58,7 @@ final class LibraryFilterViewModel: ViewModel {
}
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)

View File

@ -24,7 +24,7 @@ final class LibraryListViewModel: ViewModel {
}
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)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)

View File

@ -77,7 +77,7 @@ final class LibrarySearchViewModel: ViewModel {
}
func requestSuggestions() {
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!,
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id,
limit: 20,
recursive: true,
parentId: parentID,
@ -96,7 +96,7 @@ final class LibrarySearchViewModel: ViewModel {
}
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,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: [ItemType.movie.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
@ -107,7 +107,7 @@ final class LibrarySearchViewModel: ViewModel {
self?.movieItems = response.items ?? []
})
.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,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
includeItemTypes: [ItemType.series.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
@ -118,7 +118,7 @@ final class LibrarySearchViewModel: ViewModel {
self?.showItems = response.items ?? []
})
.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,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
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 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,
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,
@ -94,7 +94,7 @@ final class LibraryViewModel: ViewModel {
}
let sortBy = filters.sortBy.map(\.rawValue)
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,
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,

View File

@ -32,7 +32,7 @@ final class SeasonItemViewModel: ItemViewModel {
private func requestEpisodes() {
LogManager.shared.log
.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],
seasonId: item.id ?? "")
.trackActivity(loading)
@ -73,7 +73,7 @@ final class SeasonItemViewModel: ItemViewModel {
func routeToSeriesItem() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)

View File

@ -35,7 +35,7 @@ final class SeriesItemViewModel: ItemViewModel {
private func getNextUp() {
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)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
@ -67,7 +67,7 @@ final class SeriesItemViewModel: ItemViewModel {
private func requestSeasons() {
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)
.sink(receiveCompletion: { [weak self] completion in
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
override init() {
isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil
// TODO: Remove SplashViewModel
isLoggedIn = SessionManager.main.currentLogin != nil
super.init()
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) {
guard let currentLogin = SessionManager.main.currentLogin else { return }
let currentDate = Date()
let server = ServerEnvironment.current.server
let savedUser = SessionManager.current.user
let server = currentLogin.server
let savedUser = currentLogin.user
var tempCancellables = Set<AnyCancellable>()
if server != nil && savedUser != nil {
JellyfinAPI.basePath = server!.baseURI ?? ""
TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.subscribe(on: DispatchQueue.global(qos: .background))
.sink(receiveCompletion: { result in
switch result {
case .finished:
break
case let .failure(error):
completion(NextUpEntry(date: currentDate, items: [], error: error))
}
}, receiveValue: { response in
let dispatchGroup = DispatchGroup()
let items = response.items ?? []
var downloadedItems = [(BaseItemDto, UIImage?)]()
items.enumerated().forEach { _, item in
dispatchGroup.enter()
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
JellyfinAPI.basePath = server.uri
TvShowsAPI.getNextUp(userId: savedUser.id, limit: 3,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.subscribe(on: DispatchQueue.global(qos: .background))
.sink(receiveCompletion: { result in
switch result {
case .finished:
break
case let .failure(error):
completion(NextUpEntry(date: currentDate, items: [], error: error))
}
}, receiveValue: { response in
let dispatchGroup = DispatchGroup()
let items = response.items ?? []
var downloadedItems = [(BaseItemDto, UIImage?)]()
items.enumerated().forEach { _, item in
dispatchGroup.enter()
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil))
}
})
.store(in: &tempCancellables)
}
dispatchGroup.notify(queue: .main) {
completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil))
}
})
.store(in: &tempCancellables)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
guard let currentLogin = SessionManager.main.currentLogin else { return }
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
let server = ServerEnvironment.current.server
let savedUser = SessionManager.current.user
let server = currentLogin.server
let savedUser = currentLogin.user
var tempCancellables = Set<AnyCancellable>()
if server != nil && savedUser != nil {
JellyfinAPI.basePath = server!.baseURI ?? ""
TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.subscribe(on: DispatchQueue.global(qos: .background))
.sink(receiveCompletion: { result in
switch result {
case .finished:
break
case let .failure(error):
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate)))
}
}, receiveValue: { response in
let dispatchGroup = DispatchGroup()
let items = response.items ?? []
var downloadedItems = [(BaseItemDto, UIImage?)]()
items.enumerated().forEach { _, item in
dispatchGroup.enter()
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
JellyfinAPI.basePath = server.uri
TvShowsAPI.getNextUp(userId: savedUser.id, limit: 3,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
.subscribe(on: DispatchQueue.global(qos: .background))
.sink(receiveCompletion: { result in
switch result {
case .finished:
break
case let .failure(error):
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate)))
}
}, receiveValue: { response in
let dispatchGroup = DispatchGroup()
let items = response.items ?? []
var downloadedItems = [(BaseItemDto, UIImage?)]()
items.enumerated().forEach { _, item in
dispatchGroup.enter()
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
guard case let .success(image) = result else {
dispatchGroup.leave()
return
}
downloadedItems.append((item, image.image))
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)],
policy: .after(entryDate)))
}
})
.store(in: &tempCancellables)
}
dispatchGroup.notify(queue: .main) {
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)],
policy: .after(entryDate)))
}
})
.store(in: &tempCancellables)
}
}
@ -198,7 +200,8 @@ extension NextUpEntryView {
}
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) {
if let image = item.1 {
Image(uiImage: image)
@ -223,7 +226,8 @@ extension NextUpEntryView {
}
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) {
if let image = item.1 {
Image(uiImage: image)
@ -285,7 +289,8 @@ extension NextUpEntryView {
func large(items: [(BaseItemDto, UIImage?)]) -> some View {
VStack(spacing: 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: {
ZStack(alignment: .topTrailing) {
ZStack(alignment: .bottomLeading) {