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

View File

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

View File

@ -21,22 +21,22 @@ final class UserListCoordinator: NavigationCoordinatable {
@Route(.push)
var serverDetail = makeServerDetail
let viewModel: UserListViewModel
let serverState: ServerState
init(viewModel: UserListViewModel) {
self.viewModel = viewModel
init(server: ServerState) {
self.serverState = server
}
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
UserSignInCoordinator(viewModel: .init(server: server))
}
func makeServerDetail(server: SwiftfinStore.State.Server) -> ServerDetailCoordinator {
ServerDetailCoordinator(viewModel: .init(server: server))
func makeServerDetail(server: SwiftfinStore.State.Server) -> some View {
ServerDetailView(server: server)
}
@ViewBuilder
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 processDeepLink = NotificationKey("processDeepLink")
static let didPurge = NotificationKey("didPurge")
static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI")
static let didChangeCurrentServerURL = NotificationKey("didChangeCurrentServerURL")
static let didSendStopReport = NotificationKey("didSendStopReport")
static let didRequestGlobalRefresh = NotificationKey("didRequestGlobalRefresh")

View File

@ -6,6 +6,7 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import CoreStore
import Foundation
import JellyfinAPI
@ -18,16 +19,36 @@ class ServerDetailViewModel: ViewModel {
self.server = server
}
func setServerCurrentURI(uri: String) {
func setCurrentServerURL(to url: URL) {
// SessionManager.main.setServerCurrentURI(server: server, uri: uri)
// .sink { c in
// print(c)
// } receiveValue: { newServerState in
// self.server = newServerState
//
// Notifications[.didChangeServerCurrentURI].post(object: newServerState)
// }
// .store(in: &cancellables)
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(
From<ServerModel>(),
[Where<ServerModel>("id == %@", server.id)]
) else {
logger.error("Unable to find server")
return
}
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
private(set) var users: [UserState] = []
@Published
private(set) var server: ServerState
let client: JellyfinClient
let server: ServerState
init(server: ServerState) {
self.client = JellyfinClient(
var client: JellyfinClient {
JellyfinClient(
configuration: .swiftfinConfiguration(url: server.currentURL),
sessionDelegate: URLSessionProxyDelegate()
)
}
init(server: ServerState) {
self.server = server
super.init()
// Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeCurrentLoginURI(_:)))
}
@objc
func didChangeCurrentLoginURI(_ notification: Notification) {
// guard let newServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new state server") }
// self.server = newServerState
Notifications[.didChangeCurrentServerURL]
.publisher
.sink { [weak self] notification in
guard let serverState = notification.object as? SwiftfinStore.State.Server else {
return
}
self?.server = serverState
}
.store(in: &cancellables)
}
func fetchUsers() {
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(
From<SwiftfinStore.Models.StoredServer>(),
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)
From<ServerModel>(),
Where<ServerModel>("id == %@", server.id)
)
else { fatalError("No stored server associated with given state server?") }
@ -58,7 +62,23 @@ class UserListViewModel: ViewModel {
Notifications[.didSignIn].post()
}
func remove(user: SwiftfinStore.State.User) {
fetchUsers()
func remove(user: UserState) {
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>()
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()
}
.padding(10)
}
.buttonStyle(.card)
}
}

View File

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

View File

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

View File

@ -6,7 +6,7 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import CollectionView
import CollectionVGrid
import Factory
import JellyfinAPI
import SwiftUI
@ -16,15 +16,27 @@ struct UserListView: View {
@EnvironmentObject
private var router: UserListCoordinator.Router
@ObservedObject
var viewModel: UserListViewModel
@State
private var longPressedUser: SwiftfinStore.State.User?
@StateObject
private var viewModel: UserListViewModel
init(server: ServerState) {
self._viewModel = StateObject(wrappedValue: UserListViewModel(server: server))
}
@ViewBuilder
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)
.onSelect {
viewModel.signIn(user: user)
@ -33,16 +45,6 @@ struct UserListView: View {
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

View File

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

View File

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

View File

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