Create new SessionManager and begin new connect flow
This commit is contained in:
parent
7dd253c530
commit
1576d9d6b7
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ?? "")
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
import Stinsen
|
||||
|
||||
final class MainTabCoordinator: TabCoordinatable {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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!,
|
||||
|
|
|
@ -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
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)!
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue