Add UserList for server

This commit is contained in:
Ethan Pippin 2021-10-13 23:09:41 -06:00
parent c69df2c7c4
commit 1d6047840f
10 changed files with 161 additions and 175 deletions

View File

@ -277,6 +277,12 @@
E13DD3F5271793BB009D4DAF /* UserLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F4271793BB009D4DAF /* UserLoginView.swift */; };
E13DD3F6271793BB009D4DAF /* UserLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F4271793BB009D4DAF /* UserLoginView.swift */; };
E13DD3F72717E87D009D4DAF /* SwiftfinNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3EE27178F87009D4DAF /* SwiftfinNotificationCenter.swift */; };
E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F82717E961009D4DAF /* UserListViewModel.swift */; };
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F82717E961009D4DAF /* UserListViewModel.swift */; };
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3FB2717EAE8009D4DAF /* UserListView.swift */; };
E13DD3FD2717EAE8009D4DAF /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3FB2717EAE8009D4DAF /* UserListView.swift */; };
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */; };
E13DD4032717EE79009D4DAF /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD4012717EE79009D4DAF /* UserListCoordinator.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 */; };
@ -519,6 +525,9 @@
E13DD3EE27178F87009D4DAF /* SwiftfinNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftfinNotificationCenter.swift; sourceTree = "<group>"; };
E13DD3F127179378009D4DAF /* UserLoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLoginCoordinator.swift; sourceTree = "<group>"; };
E13DD3F4271793BB009D4DAF /* UserLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLoginView.swift; sourceTree = "<group>"; };
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListViewModel.swift; sourceTree = "<group>"; };
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.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>"; };
@ -643,6 +652,7 @@
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
625CB5692678B71200530A6E /* SplashViewModel.swift */,
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */,
E13DD3EB27178A54009D4DAF /* UserLoginViewModel.swift */,
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
625CB57B2678CE1000530A6E /* ViewModel.swift */,
@ -1031,8 +1041,9 @@
62C29EA026D102A500C1D2E7 /* MainTabCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
E13DD3F127179378009D4DAF /* UserLoginCoordinator.swift */,
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */,
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */,
E13DD3F127179378009D4DAF /* UserLoginCoordinator.swift */,
6220D0C526D62D8700B8E046 /* VideoPlayerCoordinator.swift */,
);
path = Coordinators;
@ -1114,6 +1125,7 @@
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
625CB5672678B6FB00530A6E /* SplashView.swift */,
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */,
E13DD3F4271793BB009D4DAF /* UserLoginView.swift */,
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
);
@ -1546,6 +1558,7 @@
53116A17268B919A003024C9 /* SeriesItemView.swift in Sources */,
E13DD3F027178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */,
53ABFDDE267974E300886593 /* SplashView.swift in Sources */,
53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */,
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */,
@ -1584,6 +1597,7 @@
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
E13DD4032717EE79009D4DAF /* UserListCoordinator.swift in Sources */,
62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */,
@ -1599,6 +1613,7 @@
5358706C2669D21700D05A09 /* PersistenceController.swift in Sources */,
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */,
E13DD3D6271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
E13DD3FD2717EAE8009D4DAF /* UserListView.swift in Sources */,
535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */,
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
@ -1693,6 +1708,7 @@
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */,
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
@ -1725,12 +1741,14 @@
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */,
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */,
E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */,
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,

View File

@ -16,11 +16,16 @@ final class ServerListCoordinator: NavigationCoordinatable {
@Root var start = makeStart
@Route(.push) var connectToServer = makeConnectToServer
@Route(.push) var userList = makeUserList
func makeConnectToServer() -> ConnectToServerCoodinator {
ConnectToServerCoodinator()
}
func makeUserList(server: SwiftfinStore.State.Server) -> UserListCoordinator {
UserListCoordinator(viewModel: .init(server: server))
}
@ViewBuilder func makeStart() -> some View {
ServerListView(viewModel: ServerListViewModel())
}

View File

@ -0,0 +1,33 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import Stinsen
import SwiftUI
final class UserListCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \UserListCoordinator.start)
@Root var start = makeStart
@Route(.push) var userLogin = makeUserLogin
let viewModel: UserListViewModel
init(viewModel: UserListViewModel) {
self.viewModel = viewModel
}
func makeUserLogin(server: SwiftfinStore.State.Server) -> UserLoginCoordinator {
return UserLoginCoordinator(viewModel: .init(server: server))
}
@ViewBuilder func makeStart() -> some View {
UserListView(viewModel: viewModel)
}
}

View File

@ -10,7 +10,6 @@ import Stinsen
struct ConnectToServerView: View {
@EnvironmentObject var mainRouter: MainCoordinator.Router
@StateObject var viewModel = ConnectToServerViewModel()
@State var uri = ""

View File

@ -18,12 +18,15 @@ struct ServerListView: View {
var body: some View {
List {
ForEach(viewModel.servers, id: \.id) { server in
Text(server.name)
Button {
serverListRouter.route(to: \.userList, server)
} label: {
Text(server.name)
}
}
}
.navigationTitle("Servers")
.toolbar {
ToolbarItemGroup(placement: .navigation) {
HStack {
Button {

View File

@ -0,0 +1,49 @@
//
/*
* 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 UserListView: View {
@EnvironmentObject var userListRouter: UserListCoordinator.Router
@ObservedObject var viewModel: UserListViewModel
var body: some View {
List {
ForEach(viewModel.users, id: \.id) { user in
Button {
viewModel.login(user: user)
} label: {
HStack {
Text(user.username)
Spacer()
if viewModel.isLoading {
ProgressView()
}
}
}
}
}
.navigationTitle("Users")
.toolbar {
ToolbarItem(placement: .navigation) {
HStack {
Button {
userListRouter.route(to: \.userLogin, viewModel.server)
} label: {
Text("Connect")
}
}
}
}
.onAppear {
viewModel.fetchUsers()
}
}
}

View File

@ -54,6 +54,13 @@ final class SessionManager {
return servers.map({ $0.state })
}
func fetchUsers(for server: SwiftfinStore.State.Server) -> [SwiftfinStore.State.User] {
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id))
else { fatalError("No stored server associated with given state server?") }
return storedServer.users.map({ $0.state }).sorted(by: { $0.username < $1.username })
}
// Connects to a server at the given uri, storing if successful
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
var uri = uri
@ -108,7 +115,8 @@ final class SessionManager {
newUser.accessToken = newAccessToken
guard let userServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)]) else { fatalError("No stored server associated with given state server?")}
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)])
else { fatalError("No stored server associated with given state server?") }
guard let editUserServer = transaction.edit(userServer) else { fatalError("Can't get proxy for existing object?") }
editUserServer.users.insert(newUser)
@ -133,7 +141,15 @@ final class SessionManager {
.eraseToAnyPublisher()
}
func loginUser(server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User) {
JellyfinAPI.basePath = server.uri
setAuthHeader(with: user.accessToken)
currentLogin = (server: server, user: user)
}
func logout() {
JellyfinAPI.basePath = ""
setAuthHeader(with: "")
SwiftfinStore.Defaults.suite[.lastServerUserID] = nil
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
}
@ -161,171 +177,3 @@ final class SessionManager {
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
}
}
//final class SessionManager {
// static let current = SessionManager()
// fileprivate(set) var user: SignedInUser!
// fileprivate(set) var deviceID: String = ""
// fileprivate(set) var accessToken: String = ""
//
// #if os(tvOS)
// let tvUserManager = TVUserManager()
// #endif
// let userDefaults = UserDefaults()
//
// init() {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID")
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// #if os(tvOS)
// savedUsers?.forEach { savedUser in
// if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
// self.user = savedUser
// }
// }
// #else
// if lastUsedUserID != nil {
// savedUsers?.forEach { savedUser in
// if savedUser.user_id ?? "" == lastUsedUserID! {
// user = savedUser
// }
// }
// } else {
// user = savedUsers?.first
// }
// #endif
//
// if user != nil {
// let authToken = getAuthToken(userID: user.user_id!)
// generateAuthHeader(with: authToken, deviceID: user.device_uuid)
// }
// }
//
// fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) {
// let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
// var deviceName = UIDevice.current.name
// deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
// deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
//
// var header = "MediaBrowser "
// #if os(tvOS)
// header.append("Client=\"Jellyfin tvOS\", ")
// #else
// header.append("Client=\"SwiftFin iOS\", ")
// #endif
//
// header.append("Device=\"\(deviceName)\", ")
//
// if devID == nil {
// LogManager.shared.log.info("Generating device ID...")
// #if os(tvOS)
// header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #else
// header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #endif
// } else {
// LogManager.shared.log.info("Using stored device ID...")
// header.append("DeviceId=\"\(devID!)\", ")
// deviceID = devID!
// }
//
// header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
//
// if authToken != nil {
// header.append("Token=\"\(authToken!)\"")
// accessToken = authToken!
// }
//
// JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
// }
//
// fileprivate func getAuthToken(userID: String) -> String? {
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// return keychain.get("AccessToken_\(userID)")
// }
//
// func doesUserHaveSavedSession(userID: String) -> Bool {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// if savedUsers!.isEmpty {
// return false
// }
//
// return true
// }
//
// func getSavedSession(userID: String) -> SignedInUser {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
// return savedUsers!.first!
// }
//
// func loginWithSavedSession(user: SignedInUser) {
// let accessToken = getAuthToken(userID: user.user_id!)
// userDefaults.set(user.user_id!, forKey: "lastUsedUserID")
// self.user = user
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
// print(JellyfinAPI.customHeaders)
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// }
//
// func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
// generateAuthHeader(with: nil, deviceID: nil)
//
// return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
// .map { response -> (SignedInUser, String?) in
// let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
// user.username = response.user?.name
// user.user_id = response.user?.id
// user.device_uuid = self.deviceID
//
// #if os(tvOS)
// let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!)
// self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in
// if should {
// user.appletv_id = self.tvUserManager.currentUserIdentifier ?? ""
// }
// }
// #endif
//
// return (user, response.accessToken)
// }
// .handleEvents(receiveOutput: { [unowned self] response, accessToken in
// user = response
// _ = try? PersistenceController.shared.container.viewContext.save()
//
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
//
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
//
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// })
// .map(\.0)
// .eraseToAnyPublisher()
// }
//
// func logout() {
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignOut"), object: nil)
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.delete("AccessToken_\(user?.user_id ?? "")")
// generateAuthHeader(with: nil, deviceID: nil)
// if user != nil {
// let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
// user = nil
// _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
// }
// }
//}

View File

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

View File

@ -0,0 +1,32 @@
//
/*
* 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 UserListViewModel: ViewModel {
@Published var users: [SwiftfinStore.State.User] = []
let server: SwiftfinStore.State.Server
init(server: SwiftfinStore.State.Server) {
self.server = server
}
func fetchUsers() {
self.users = SessionManager.main.fetchUsers(for: server)
}
func login(user: SwiftfinStore.State.User) {
self.isLoading = true
SessionManager.main.loginUser(server: server, user: user)
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
}
}

View File

@ -9,7 +9,6 @@
import CoreStore
import Foundation
import JellyfinAPI
import Stinsen
final class UserLoginViewModel: ViewModel {