Fix Basic Multi Server URL (#1012)

This commit is contained in:
Ethan Pippin 2024-04-02 23:37:45 -06:00 committed by GitHub
parent 0e21fb0369
commit 8a0ef0e48f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 195 additions and 168 deletions

View File

@ -50,18 +50,18 @@ final class MainCoordinator: NavigationCoordinatable {
Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn))
Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut))
Notifications[.processDeepLink].subscribe(self, selector: #selector(processDeepLink(_:))) Notifications[.processDeepLink].subscribe(self, selector: #selector(processDeepLink(_:)))
Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeServerCurrentURI(_:))) Notifications[.didChangeCurrentServerURL].subscribe(self, selector: #selector(didChangeCurrentServerURL(_:)))
} }
@objc @objc
func didSignIn() { func didSignIn() {
logger.info("Received `didSignIn` from SwiftfinNotificationCenter.") logger.info("Signed in")
root(\.mainTab) root(\.mainTab)
} }
@objc @objc
func didSignOut() { func didSignOut() {
logger.info("Received `didSignOut` from SwiftfinNotificationCenter.") logger.info("Signed out")
root(\.serverList) root(\.serverList)
} }
@ -80,13 +80,12 @@ final class MainCoordinator: NavigationCoordinatable {
} }
@objc @objc
func didChangeServerCurrentURI(_ notification: Notification) { func didChangeCurrentServerURL(_ notification: Notification) {
// guard let newCurrentServerState = notification.object as? SwiftfinStore.State.Server
// else { fatalError("Need to have new current login state server") } guard Container.userSession().authenticated else { return }
// guard SessionManager.main.currentLogin != nil else { return }
// if newCurrentServerState.id == SessionManager.main.currentLogin.server.id { Container.userSession.reset()
// SessionManager.main.signInUser(server: newCurrentServerState, user: SessionManager.main.currentLogin.user) Notifications[.didSignIn].post()
// }
} }
func makeMainTab() -> MainTabCoordinator { func makeMainTab() -> MainTabCoordinator {

View File

@ -1,30 +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 (c) 2024 Jellyfin & Jellyfin Contributors
//
import Foundation
import Stinsen
import SwiftUI
final class ServerDetailCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \ServerDetailCoordinator.start)
@Root
var start = makeStart
let viewModel: ServerDetailViewModel
init(viewModel: ServerDetailViewModel) {
self.viewModel = viewModel
}
@ViewBuilder
func makeStart() -> some View {
ServerDetailView(viewModel: viewModel)
}
}

View File

@ -28,8 +28,8 @@ final class ServerListCoordinator: NavigationCoordinatable {
ConnectToServerCoodinator() ConnectToServerCoodinator()
} }
func makeUserList(server: SwiftfinStore.State.Server) -> UserListCoordinator { func makeUserList(server: ServerState) -> UserListCoordinator {
UserListCoordinator(viewModel: .init(server: server)) UserListCoordinator(server: server)
} }
func makeBasicAppSettings() -> NavigationViewCoordinator<BasicAppSettingsCoordinator> { func makeBasicAppSettings() -> NavigationViewCoordinator<BasicAppSettingsCoordinator> {

View File

@ -107,7 +107,7 @@ final class SettingsCoordinator: NavigationCoordinatable {
@ViewBuilder @ViewBuilder
func makeServerDetail(server: ServerState) -> some View { func makeServerDetail(server: ServerState) -> some View {
ServerDetailView(viewModel: .init(server: server)) ServerDetailView(server: server)
} }
#if DEBUG #if DEBUG
@ -155,7 +155,7 @@ final class SettingsCoordinator: NavigationCoordinatable {
func makeServerDetail(server: ServerState) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> { func makeServerDetail(server: ServerState) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator( NavigationViewCoordinator(
BasicNavigationViewCoordinator { BasicNavigationViewCoordinator {
ServerDetailView(viewModel: .init(server: server)) ServerDetailView(server: server)
} }
) )
} }

View File

@ -21,22 +21,22 @@ final class UserListCoordinator: NavigationCoordinatable {
@Route(.push) @Route(.push)
var serverDetail = makeServerDetail var serverDetail = makeServerDetail
let viewModel: UserListViewModel let serverState: ServerState
init(viewModel: UserListViewModel) { init(server: ServerState) {
self.viewModel = viewModel self.serverState = server
} }
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator { func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
UserSignInCoordinator(viewModel: .init(server: server)) UserSignInCoordinator(viewModel: .init(server: server))
} }
func makeServerDetail(server: SwiftfinStore.State.Server) -> ServerDetailCoordinator { func makeServerDetail(server: SwiftfinStore.State.Server) -> some View {
ServerDetailCoordinator(viewModel: .init(server: server)) ServerDetailView(server: server)
} }
@ViewBuilder @ViewBuilder
func makeStart() -> some View { func makeStart() -> some View {
UserListView(viewModel: viewModel) UserListView(server: serverState)
} }
} }

View File

@ -73,7 +73,7 @@ extension Notifications.Key {
static let didSignOut = NotificationKey("didSignOut") static let didSignOut = NotificationKey("didSignOut")
static let processDeepLink = NotificationKey("processDeepLink") static let processDeepLink = NotificationKey("processDeepLink")
static let didPurge = NotificationKey("didPurge") static let didPurge = NotificationKey("didPurge")
static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI") static let didChangeCurrentServerURL = NotificationKey("didChangeCurrentServerURL")
static let didSendStopReport = NotificationKey("didSendStopReport") static let didSendStopReport = NotificationKey("didSendStopReport")
static let didRequestGlobalRefresh = NotificationKey("didRequestGlobalRefresh") static let didRequestGlobalRefresh = NotificationKey("didRequestGlobalRefresh")

View File

@ -6,6 +6,7 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors // Copyright (c) 2024 Jellyfin & Jellyfin Contributors
// //
import CoreStore
import Foundation import Foundation
import JellyfinAPI import JellyfinAPI
@ -18,16 +19,36 @@ class ServerDetailViewModel: ViewModel {
self.server = server self.server = server
} }
func setServerCurrentURI(uri: String) { func setCurrentServerURL(to url: URL) {
// SessionManager.main.setServerCurrentURI(server: server, uri: uri) guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(
// .sink { c in From<ServerModel>(),
// print(c) [Where<ServerModel>("id == %@", server.id)]
// } receiveValue: { newServerState in ) else {
// self.server = newServerState logger.error("Unable to find server")
// return
// Notifications[.didChangeServerCurrentURI].post(object: newServerState) }
// }
// .store(in: &cancellables) guard storedServer.urls.contains(url) else {
logger.error("Server did not have matching URL")
return
}
let transaction = SwiftfinStore.dataStack.beginUnsafe()
guard let editServer = transaction.edit(storedServer) else {
logger.error("Unable to create edit server instance")
return
}
editServer.currentURL = url
do {
try transaction.commitAndWait()
Notifications[.didChangeCurrentServerURL].post(object: editServer.state)
} catch {
logger.error("Unable to edit server")
}
} }
} }

View File

@ -18,32 +18,36 @@ class UserListViewModel: ViewModel {
@Published @Published
private(set) var users: [UserState] = [] private(set) var users: [UserState] = []
@Published
private(set) var server: ServerState
let client: JellyfinClient var client: JellyfinClient {
let server: ServerState JellyfinClient(
init(server: ServerState) {
self.client = JellyfinClient(
configuration: .swiftfinConfiguration(url: server.currentURL), configuration: .swiftfinConfiguration(url: server.currentURL),
sessionDelegate: URLSessionProxyDelegate() sessionDelegate: URLSessionProxyDelegate()
) )
}
init(server: ServerState) {
self.server = server self.server = server
super.init() super.init()
// Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeCurrentLoginURI(_:))) Notifications[.didChangeCurrentServerURL]
} .publisher
.sink { [weak self] notification in
@objc guard let serverState = notification.object as? SwiftfinStore.State.Server else {
func didChangeCurrentLoginURI(_ notification: Notification) { return
// guard let newServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new state server") } }
// self.server = newServerState self?.server = serverState
}
.store(in: &cancellables)
} }
func fetchUsers() { func fetchUsers() {
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne( guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(
From<SwiftfinStore.Models.StoredServer>(), From<ServerModel>(),
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id) Where<ServerModel>("id == %@", server.id)
) )
else { fatalError("No stored server associated with given state server?") } else { fatalError("No stored server associated with given state server?") }
@ -58,7 +62,23 @@ class UserListViewModel: ViewModel {
Notifications[.didSignIn].post() Notifications[.didSignIn].post()
} }
func remove(user: SwiftfinStore.State.User) { func remove(user: UserState) {
fetchUsers() guard let storedUser = try? SwiftfinStore.dataStack.fetchOne(
From<SwiftfinStore.Models.StoredUser>(),
[Where<SwiftfinStore.Models.StoredUser>("id == %@", user.id)]
) else {
logger.error("Unable to find user to delete")
return
}
let transaction = SwiftfinStore.dataStack.beginUnsafe()
transaction.delete(storedUser)
do {
try transaction.commitAndWait()
fetchUsers()
} catch {
logger.error("Unable to delete user")
}
} }
} }

View File

@ -28,5 +28,13 @@ class ViewModel: ObservableObject {
var cancellables = Set<AnyCancellable>() var cancellables = Set<AnyCancellable>()
init() {} private var userSessionResolverCancellable: AnyCancellable?
init() {
userSessionResolverCancellable = Notifications[.didChangeCurrentServerURL]
.publisher
.sink { [weak self] _ in
self?.$userSession.resolve(reset: .scope)
}
}
} }

View File

@ -35,7 +35,9 @@ struct ServerButton: View {
Spacer() Spacer()
} }
.padding(10)
} }
.buttonStyle(.card)
} }
} }

View File

@ -10,8 +10,12 @@ import SwiftUI
struct ServerDetailView: View { struct ServerDetailView: View {
@ObservedObject @StateObject
var viewModel: ServerDetailViewModel private var viewModel: ServerDetailViewModel
init(server: ServerState) {
self._viewModel = StateObject(wrappedValue: ServerDetailViewModel(server: server))
}
var body: some View { var body: some View {
SplitFormWindowView() SplitFormWindowView()

View File

@ -6,7 +6,7 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors // Copyright (c) 2024 Jellyfin & Jellyfin Contributors
// //
import CollectionView import CollectionVGrid
import SwiftUI import SwiftUI
struct ServerListView: View { struct ServerListView: View {
@ -22,22 +22,23 @@ struct ServerListView: View {
@ViewBuilder @ViewBuilder
private var listView: some View { private var listView: some View {
ScrollView { CollectionVGrid(
LazyVStack { viewModel.servers,
ForEach(viewModel.servers, id: \.id) { server in layout: .columns(
ServerButton(server: server) 1,
.onSelect { insets: EdgeInsets.DefaultEdgeInsets,
router.route(to: \.userList, server) itemSpacing: EdgeInsets.defaultEdgePadding,
} lineSpacing: EdgeInsets.defaultEdgePadding
.onLongPressGesture { )
longPressedServer = server ) { server in
} ServerButton(server: server)
.padding(.horizontal, 100) .onSelect {
router.route(to: \.userList, server)
}
.onLongPressGesture {
longPressedServer = server
} }
}
.padding(.top, 50)
} }
.padding(.top, 50)
} }
@ViewBuilder @ViewBuilder
@ -72,27 +73,32 @@ struct ServerListView: View {
} }
var body: some View { var body: some View {
SplitFormWindowView() HStack {
.descriptionView { VStack {
VStack { Image(.jellyfinBlobBlue)
Image(.jellyfinBlobBlue) .resizable()
.resizable() .aspectRatio(contentMode: .fit)
.aspectRatio(contentMode: .fit) .frame(maxWidth: 400)
.frame(maxWidth: 400)
Button { Button {
router.route(to: \.connectToServer) router.route(to: \.connectToServer)
} label: { } label: {
L10n.connect.text L10n.connect.text
.bold() .bold()
.font(.callout) .font(.callout)
.frame(width: 400, height: 75) .frame(width: 400, height: 75)
.background(Color.jellyfinPurple) .background(Color.jellyfinPurple)
}
.buttonStyle(.card)
} }
.buttonStyle(.card)
} }
.contentView {} .frame(maxWidth: .infinity)
innerBody
.frame(maxWidth: .infinity)
}
.onAppear {
viewModel.fetchServers()
}
} }
// var body: some View { // var body: some View {

View File

@ -6,7 +6,7 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors // Copyright (c) 2024 Jellyfin & Jellyfin Contributors
// //
import CollectionView import CollectionVGrid
import Factory import Factory
import JellyfinAPI import JellyfinAPI
import SwiftUI import SwiftUI
@ -16,15 +16,27 @@ struct UserListView: View {
@EnvironmentObject @EnvironmentObject
private var router: UserListCoordinator.Router private var router: UserListCoordinator.Router
@ObservedObject
var viewModel: UserListViewModel
@State @State
private var longPressedUser: SwiftfinStore.State.User? private var longPressedUser: SwiftfinStore.State.User?
@StateObject
private var viewModel: UserListViewModel
init(server: ServerState) {
self._viewModel = StateObject(wrappedValue: UserListViewModel(server: server))
}
@ViewBuilder @ViewBuilder
private var listView: some View { private var listView: some View {
CollectionView(items: viewModel.users) { _, user, _ in CollectionVGrid(
viewModel.users,
layout: .minWidth(
250,
insets: EdgeInsets.DefaultEdgeInsets,
itemSpacing: EdgeInsets.defaultEdgePadding,
lineSpacing: EdgeInsets.defaultEdgePadding
)
) { user in
UserProfileButton(user: user) UserProfileButton(user: user)
.onSelect { .onSelect {
viewModel.signIn(user: user) viewModel.signIn(user: user)
@ -33,16 +45,6 @@ struct UserListView: View {
longPressedUser = user longPressedUser = user
} }
} }
.layout { _, layoutEnvironment in
.grid(
layoutEnvironment: layoutEnvironment,
layoutMode: .adaptive(withMinItemSize: 250),
itemSpacing: 20,
lineSpacing: 20,
sectionInsets: .init(top: 20, leading: 20, bottom: 20, trailing: 20)
)
}
.padding(50)
} }
@ViewBuilder @ViewBuilder

View File

@ -247,8 +247,6 @@
E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8C28999B4A003E74C7 /* Font.swift */; }; E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8C28999B4A003E74C7 /* Font.swift */; };
E11CEB9128999D84003E74C7 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */; }; E11CEB9128999D84003E74C7 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */; };
E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */; }; E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */; };
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
E11E374D293E7EC9009EF240 /* ItemFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D842902933F87500D1041A /* ItemFields.swift */; }; E11E374D293E7EC9009EF240 /* ItemFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D842902933F87500D1041A /* ItemFields.swift */; };
E11E374E293E7F08009EF240 /* MediaSourceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8428E2933F2D900D1041A /* MediaSourceInfo.swift */; }; E11E374E293E7F08009EF240 /* MediaSourceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8428E2933F2D900D1041A /* MediaSourceInfo.swift */; };
E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8428D60D0000333B9A /* VideoPlayerCoordinator.swift */; }; E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8428D60D0000333B9A /* VideoPlayerCoordinator.swift */; };
@ -993,7 +991,6 @@
E11CEB8C28999B4A003E74C7 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; }; E11CEB8C28999B4A003E74C7 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; }; E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; };
E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = "<group>"; }; E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = "<group>"; };
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; };
E122A9122788EAAD0060FA63 /* MediaStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStream.swift; sourceTree = "<group>"; }; E122A9122788EAAD0060FA63 /* MediaStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStream.swift; sourceTree = "<group>"; };
E12376AD2A33D680001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = "<group>"; }; E12376AD2A33D680001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = "<group>"; };
E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = "<group>"; }; E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = "<group>"; };
@ -1934,7 +1931,6 @@
E1A1528F28FD23D600600579 /* PlaybackSettingsCoordinator.swift */, E1A1528F28FD23D600600579 /* PlaybackSettingsCoordinator.swift */,
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */, E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */, 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */,
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */, E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */, 6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */,
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */, E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */,
@ -3302,7 +3298,6 @@
E1E1643E28BB074000323B0A /* SelectorView.swift in Sources */, E1E1643E28BB074000323B0A /* SelectorView.swift in Sources */,
E1A1529128FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */, E1A1529128FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */,
E187A60529AD2E25008387E6 /* StepperView.swift in Sources */, E187A60529AD2E25008387E6 /* StepperView.swift in Sources */,
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
E1575E71293E77B5001665B1 /* RepeatingTimer.swift in Sources */, E1575E71293E77B5001665B1 /* RepeatingTimer.swift in Sources */,
E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */, E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */, E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */,
@ -3702,7 +3697,6 @@
E1921B7428E61914003A5238 /* SpecialFeatureHStack.swift in Sources */, E1921B7428E61914003A5238 /* SpecialFeatureHStack.swift in Sources */,
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */, C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */,
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */, E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
6264E88C273850380081A12A /* Strings.swift in Sources */, 6264E88C273850380081A12A /* Strings.swift in Sources */,
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */, C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */,
E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */, E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */,
@ -4258,7 +4252,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 78; CURRENT_PROJECT_VERSION = 78;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
@ -4274,7 +4268,7 @@
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
OTHER_CFLAGS = ""; OTHER_CFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = org.jellyfin.swiftfin; PRODUCT_BUNDLE_IDENTIFIER = pip.jellyfin.swiftfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@ -4298,7 +4292,7 @@
CURRENT_PROJECT_VERSION = 78; CURRENT_PROJECT_VERSION = 78;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
@ -4314,7 +4308,7 @@
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
OTHER_CFLAGS = ""; OTHER_CFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = org.jellyfin.swiftfin; PRODUCT_BUNDLE_IDENTIFIER = pip.jellyfin.swiftfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;

View File

@ -6,58 +6,55 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors // Copyright (c) 2024 Jellyfin & Jellyfin Contributors
// //
import Factory
import SwiftUI import SwiftUI
struct ServerDetailView: View { struct ServerDetailView: View {
@ObservedObject
var viewModel: ServerDetailViewModel
@State @State
private var currentServerURI: String private var currentServerURL: URL
init(viewModel: ServerDetailViewModel) { @StateObject
self.viewModel = viewModel private var viewModel: ServerDetailViewModel
self._currentServerURI = State(initialValue: viewModel.server.currentURL.absoluteString)
init(server: ServerState) {
self._viewModel = StateObject(wrappedValue: ServerDetailViewModel(server: server))
self._currentServerURL = State(initialValue: server.currentURL)
} }
var body: some View { var body: some View {
Form { Form {
Section { Section {
HStack {
L10n.name.text
Spacer()
Text(viewModel.server.name)
.foregroundColor(.secondary)
}
Picker(L10n.url, selection: $currentServerURI) { TextPairView(
leading: L10n.name,
trailing: viewModel.server.name
)
Picker(L10n.url, selection: $currentServerURL) {
ForEach(viewModel.server.urls.sorted(using: \.absoluteString)) { url in ForEach(viewModel.server.urls.sorted(using: \.absoluteString)) { url in
Text(url.absoluteString) Text(url.absoluteString)
.tag(url) .tag(url)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.onChange(of: currentServerURI) { _ in .onChange(of: currentServerURL) { _ in
// TODO: change server url // TODO: change server url
viewModel.setCurrentServerURL(to: currentServerURL)
} }
} }
HStack { TextPairView(
L10n.version.text leading: L10n.version,
Spacer() trailing: viewModel.server.version
Text(viewModel.server.version) )
.foregroundColor(.secondary)
}
HStack { TextPairView(
L10n.operatingSystem.text leading: L10n.operatingSystem,
Spacer() trailing: viewModel.server.os
Text(viewModel.server.os) )
.foregroundColor(.secondary)
}
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationTitle(L10n.serverDetails.text) .navigationTitle(L10n.server)
} }
} }

View File

@ -14,8 +14,12 @@ struct UserListView: View {
@EnvironmentObject @EnvironmentObject
private var router: UserListCoordinator.Router private var router: UserListCoordinator.Router
@ObservedObject @StateObject
var viewModel: UserListViewModel private var viewModel: UserListViewModel
init(server: ServerState) {
self._viewModel = StateObject(wrappedValue: UserListViewModel(server: server))
}
private var noUserView: some View { private var noUserView: some View {
VStack { VStack {