[tvOS] Update ConnectToServerView & UserSignInView (#1365)
* UserSignInView and ConnectToServerView Cleanup * Public User icon changes, move the Jellyfin 'NavigationBar' to a `View Modifier` for easier re-use. * A better solution * isLoading == isLoading NOT isLoading == true * clean up --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
7685048258
commit
97affd198e
|
@ -46,8 +46,8 @@ internal enum L10n {
|
|||
internal static let additionalSecurityAccessDescription = L10n.tr("Localizable", "additionalSecurityAccessDescription", fallback: "Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.")
|
||||
/// Add Server
|
||||
internal static let addServer = L10n.tr("Localizable", "addServer", fallback: "Add Server")
|
||||
/// Add Trigger
|
||||
internal static let addTrigger = L10n.tr("Localizable", "addTrigger", fallback: "Add Trigger")
|
||||
/// Add trigger
|
||||
internal static let addTrigger = L10n.tr("Localizable", "addTrigger", fallback: "Add trigger")
|
||||
/// Add URL
|
||||
internal static let addURL = L10n.tr("Localizable", "addURL", fallback: "Add URL")
|
||||
/// Add User
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// 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 SwiftUI
|
||||
|
||||
struct SplitLoginWindowView<Leading: View, Trailing: View>: View {
|
||||
|
||||
// MARK: - Loading State
|
||||
|
||||
private let isLoading: Bool
|
||||
|
||||
// MARK: - Content Variables
|
||||
|
||||
private let leadingTitle: String
|
||||
private let leadingContentView: () -> Leading
|
||||
private let trailingTitle: String
|
||||
private let trailingContentView: () -> Trailing
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .leading) {
|
||||
Section(leadingTitle) {
|
||||
VStack(alignment: .leading) {
|
||||
leadingContentView()
|
||||
.eraseToAnyView()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 100)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Section(trailingTitle) {
|
||||
VStack(alignment: .leading) {
|
||||
trailingContentView()
|
||||
.eraseToAnyView()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarBranding(isLoading: isLoading)
|
||||
}
|
||||
}
|
||||
|
||||
extension SplitLoginWindowView {
|
||||
|
||||
init(
|
||||
isLoading: Bool = false,
|
||||
leadingTitle: String,
|
||||
trailingTitle: String,
|
||||
@ViewBuilder leadingContentView: @escaping () -> Leading,
|
||||
@ViewBuilder trailingContentView: @escaping () -> Trailing
|
||||
) {
|
||||
self.isLoading = isLoading
|
||||
self.leadingTitle = leadingTitle
|
||||
self.trailingTitle = trailingTitle
|
||||
self.leadingContentView = leadingContentView
|
||||
self.trailingContentView = trailingContentView
|
||||
}
|
||||
}
|
|
@ -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 (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct NavigationBarBrandingModifier: ViewModifier {
|
||||
|
||||
let isLoading: Bool
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
Image(.jellyfinBlobBlue)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 100)
|
||||
.padding(.bottom, 25)
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// 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 Defaults
|
||||
import SwiftUI
|
||||
import SwiftUIIntrospect
|
||||
|
||||
extension View {
|
||||
|
||||
@ViewBuilder
|
||||
func navigationBarBranding(
|
||||
isLoading: Bool = false
|
||||
) -> some View {
|
||||
modifier(
|
||||
NavigationBarBrandingModifier(
|
||||
isLoading: isLoading
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// 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 Combine
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
extension ConnectToServerView {
|
||||
|
||||
struct LocalServerButton: View {
|
||||
|
||||
// MARK: - Environment Variables
|
||||
|
||||
@Environment(\.isEnabled)
|
||||
private var isEnabled: Bool
|
||||
|
||||
// MARK: - Local Server Variables
|
||||
|
||||
private let server: ServerState
|
||||
private let action: () -> Void
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init(server: ServerState, action: @escaping () -> Void) {
|
||||
self.server = server
|
||||
self.action = action
|
||||
}
|
||||
|
||||
// MARK: - Local Server Button
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(server.name)
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Text(server.currentURL.absoluteString)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.body.weight(.regular))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.disabled(!isEnabled)
|
||||
.buttonStyle(.card)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,82 +55,56 @@ struct ConnectToServerView: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var connectSection: some View {
|
||||
Section(L10n.connectToServer) {
|
||||
TextField(L10n.serverURL, text: $url)
|
||||
.disableAutocorrection(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.URL)
|
||||
.focused($isURLFocused)
|
||||
}
|
||||
TextField(L10n.serverURL, text: $url)
|
||||
.disableAutocorrection(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.URL)
|
||||
.focused($isURLFocused)
|
||||
|
||||
if viewModel.state == .connecting {
|
||||
// ListRowButton(L10n.cancel) {
|
||||
// viewModel.send(.cancel)
|
||||
// }
|
||||
Button(L10n.cancel) {
|
||||
ListRowButton(L10n.cancel) {
|
||||
viewModel.send(.cancel)
|
||||
}
|
||||
.foregroundStyle(.red, .red.opacity(0.2))
|
||||
.foregroundStyle(.red, accentColor)
|
||||
.padding(.vertical)
|
||||
} else {
|
||||
// ListRowButton(L10n.connect) {
|
||||
// isURLFocused = false
|
||||
// viewModel.send(.connect(url))
|
||||
// }
|
||||
Button(L10n.connect) {
|
||||
ListRowButton(L10n.connect) {
|
||||
isURLFocused = false
|
||||
viewModel.send(.connect(url))
|
||||
}
|
||||
.disabled(url.isEmpty)
|
||||
.foregroundStyle(
|
||||
accentColor.overlayColor,
|
||||
accentColor
|
||||
url.isEmpty ? Color.white.opacity(0.5) : accentColor
|
||||
)
|
||||
.opacity(url.isEmpty ? 0.5 : 1)
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Local Server Button
|
||||
|
||||
private func localServerButton(for server: ServerState) -> some View {
|
||||
Button {
|
||||
url = server.currentURL.absoluteString
|
||||
viewModel.send(.connect(server.currentURL.absoluteString))
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(server.name)
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Text(server.currentURL.absoluteString)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.body.weight(.regular))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.disabled(viewModel.state == .connecting)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
// MARK: - Local Servers Section
|
||||
|
||||
@ViewBuilder
|
||||
private var localServersSection: some View {
|
||||
Section(L10n.localServers) {
|
||||
if viewModel.localServers.isEmpty {
|
||||
L10n.noLocalServersFound.text
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
ForEach(viewModel.localServers) { server in
|
||||
localServerButton(for: server)
|
||||
if viewModel.localServers.isEmpty {
|
||||
L10n.noLocalServersFound.text
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
LazyVGrid(
|
||||
columns: Array(repeating: GridItem(.flexible()), count: 1),
|
||||
spacing: 30
|
||||
) {
|
||||
ForEach(viewModel.localServers, id: \.id) { server in
|
||||
LocalServerButton(server: server) {
|
||||
url = server.currentURL.absoluteString
|
||||
viewModel.send(.connect(server.currentURL.absoluteString))
|
||||
}
|
||||
.environment(
|
||||
\.isEnabled,
|
||||
viewModel.state != .connecting && server.currentURL.absoluteString != url
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,34 +113,14 @@ struct ConnectToServerView: View {
|
|||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
if viewModel.state == .connecting {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.frame(height: 100)
|
||||
.overlay {
|
||||
Image(.jellyfinBlobBlue)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 100)
|
||||
.edgePadding()
|
||||
}
|
||||
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .leading) {
|
||||
connectSection
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
localServersSection
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
SplitLoginWindowView(
|
||||
isLoading: viewModel.state == .connecting,
|
||||
leadingTitle: L10n.connectToServer,
|
||||
trailingTitle: L10n.localServers
|
||||
) {
|
||||
connectSection
|
||||
} trailingContentView: {
|
||||
localServersSection
|
||||
}
|
||||
.onFirstAppear {
|
||||
isURLFocused = true
|
|
@ -289,6 +289,7 @@ struct SelectUserView: View {
|
|||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.navigationBarBranding()
|
||||
.onAppear {
|
||||
viewModel.send(.getServers)
|
||||
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// 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 JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
extension UserSignInView {
|
||||
|
||||
struct PublicUserButton: View {
|
||||
|
||||
// MARK: - Environment Variables
|
||||
|
||||
@Environment(\.isEnabled)
|
||||
private var isEnabled: Bool
|
||||
|
||||
// MARK: - Public User Variables
|
||||
|
||||
private let user: UserDto
|
||||
private let client: JellyfinClient
|
||||
private let action: () -> Void
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init(
|
||||
user: UserDto,
|
||||
client: JellyfinClient,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.user = user
|
||||
self.client = client
|
||||
self.action = action
|
||||
}
|
||||
|
||||
// MARK: - Fallback Person View
|
||||
|
||||
@ViewBuilder
|
||||
private var fallbackPersonView: some View {
|
||||
ZStack {
|
||||
Color.secondarySystemFill
|
||||
|
||||
RelativeSystemImageView(systemName: "person.fill", ratio: 0.5)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.clipShape(.circle)
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
}
|
||||
|
||||
// MARK: - Person View
|
||||
|
||||
@ViewBuilder
|
||||
private var personView: some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
|
||||
ImageView(user.profileImageSource(client: client, maxWidth: 120))
|
||||
.image { image in
|
||||
image
|
||||
.posterBorder(ratio: 0.5, of: \.width)
|
||||
}
|
||||
.placeholder { _ in
|
||||
fallbackPersonView
|
||||
}
|
||||
.failure {
|
||||
fallbackPersonView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
personView
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.posterShadow()
|
||||
.clipShape(.circle)
|
||||
.frame(width: 150, height: 150)
|
||||
.hoverEffect(.highlight)
|
||||
|
||||
Text(user.name ?? .emptyDash)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.buttonBorderShape(.circle)
|
||||
.buttonStyle(.borderless)
|
||||
.disabled(!isEnabled)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +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 JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
// TODO: change from list to grid button
|
||||
|
||||
extension UserSignInView {
|
||||
|
||||
struct PublicUserRow: View {
|
||||
|
||||
private let user: UserDto
|
||||
private let client: JellyfinClient
|
||||
private let action: () -> Void
|
||||
|
||||
init(
|
||||
user: UserDto,
|
||||
client: JellyfinClient,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.user = user
|
||||
self.client = client
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var personView: some View {
|
||||
ZStack {
|
||||
Color.secondarySystemFill
|
||||
|
||||
RelativeSystemImageView(systemName: "person.fill", ratio: 0.5)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.clipShape(.circle)
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
action()
|
||||
} label: {
|
||||
HStack {
|
||||
ZStack {
|
||||
Color.clear
|
||||
|
||||
ImageView(user.profileImageSource(client: client, maxWidth: 120))
|
||||
.image { image in
|
||||
image
|
||||
.posterBorder(ratio: 0.5, of: \.width)
|
||||
}
|
||||
.placeholder { _ in
|
||||
personView
|
||||
}
|
||||
.failure {
|
||||
personView
|
||||
}
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.posterShadow()
|
||||
.clipShape(.circle)
|
||||
.frame(width: 50, height: 50)
|
||||
|
||||
Text(user.name ?? .emptyDash)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.card)
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,6 @@ import JellyfinAPI
|
|||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
// TODO: change public users from list to grid
|
||||
|
||||
struct UserSignInView: View {
|
||||
|
||||
// MARK: - Defaults
|
||||
|
@ -30,13 +28,16 @@ struct UserSignInView: View {
|
|||
}
|
||||
|
||||
@FocusState
|
||||
private var focusedTextField: FocusField?
|
||||
private var focusedField: FocusField?
|
||||
|
||||
// MARK: - State & Environment Objects
|
||||
|
||||
@EnvironmentObject
|
||||
private var router: UserSignInCoordinator.Router
|
||||
|
||||
@StateObject
|
||||
private var focusGuide: FocusGuide = .init()
|
||||
|
||||
@StateObject
|
||||
private var viewModel: UserSignInViewModel
|
||||
|
||||
|
@ -69,39 +70,37 @@ struct UserSignInView: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var signInSection: some View {
|
||||
Section {
|
||||
TextField(L10n.username, text: $username)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.focused($focusedTextField, equals: .username)
|
||||
TextField(L10n.username, text: $username)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.focused($focusedField, equals: .username)
|
||||
|
||||
SecureField(L10n.password, text: $password)
|
||||
.focused($focusedTextField, equals: .password)
|
||||
.onSubmit {
|
||||
guard username.isNotEmpty else {
|
||||
return
|
||||
}
|
||||
viewModel.send(.signIn(username: username, password: password, policy: .none))
|
||||
SecureField(L10n.password, text: $password)
|
||||
.focused($focusedField, equals: .password)
|
||||
.onSubmit {
|
||||
guard username.isNotEmpty else {
|
||||
return
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.signInToServer(viewModel.server.name))
|
||||
}
|
||||
viewModel.send(.signIn(username: username, password: password, policy: .none))
|
||||
}
|
||||
|
||||
if case .signingIn = viewModel.state {
|
||||
Button(L10n.cancel) {
|
||||
ListRowButton(L10n.cancel) {
|
||||
viewModel.send(.cancel)
|
||||
}
|
||||
.foregroundStyle(.red, .red.opacity(0.2))
|
||||
.foregroundStyle(.red, accentColor)
|
||||
.padding(.vertical)
|
||||
} else {
|
||||
Button(L10n.signIn) {
|
||||
ListRowButton(L10n.signIn) {
|
||||
viewModel.send(.signIn(username: username, password: password, policy: .none))
|
||||
}
|
||||
.disabled(username.isEmpty)
|
||||
.foregroundStyle(
|
||||
accentColor.overlayColor,
|
||||
accentColor
|
||||
username.isEmpty ? Color.white.opacity(0.5) : accentColor
|
||||
)
|
||||
.opacity(username.isEmpty ? 0.5 : 1)
|
||||
.padding(.vertical)
|
||||
}
|
||||
|
||||
if viewModel.isQuickConnectEnabled {
|
||||
|
@ -114,14 +113,17 @@ struct UserSignInView: View {
|
|||
accentColor.overlayColor,
|
||||
accentColor
|
||||
)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
if let disclaimer = viewModel.serverDisclaimer {
|
||||
Section(L10n.disclaimer) {
|
||||
Text(disclaimer)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.callout)
|
||||
}
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,22 +131,30 @@ struct UserSignInView: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var publicUsersSection: some View {
|
||||
Section(L10n.publicUsers) {
|
||||
if viewModel.publicUsers.isEmpty {
|
||||
L10n.noPublicUsers.text
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
if viewModel.publicUsers.isEmpty {
|
||||
L10n.noPublicUsers.text
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.frame(maxHeight: .infinity, alignment: .center)
|
||||
} else {
|
||||
LazyVGrid(
|
||||
columns: Array(repeating: GridItem(.flexible()), count: 4),
|
||||
spacing: 30
|
||||
) {
|
||||
ForEach(viewModel.publicUsers, id: \.id) { user in
|
||||
PublicUserRow(
|
||||
PublicUserButton(
|
||||
user: user,
|
||||
client: viewModel.server.client
|
||||
) {
|
||||
username = user.name ?? ""
|
||||
password = ""
|
||||
focusedTextField = .password
|
||||
focusedField = .password
|
||||
}
|
||||
.environment(
|
||||
\.isEnabled,
|
||||
viewModel.state != .signingIn
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,34 +163,14 @@ struct UserSignInView: View {
|
|||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
if viewModel.state == .signingIn {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.frame(height: 100)
|
||||
.overlay {
|
||||
Image(.jellyfinBlobBlue)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 100)
|
||||
.edgePadding()
|
||||
}
|
||||
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .leading) {
|
||||
signInSection
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
publicUsersSection
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
SplitLoginWindowView(
|
||||
isLoading: viewModel.state == .signingIn,
|
||||
leadingTitle: L10n.signInToServer(viewModel.server.name),
|
||||
trailingTitle: L10n.publicUsers
|
||||
) {
|
||||
signInSection
|
||||
} trailingContentView: {
|
||||
publicUsersSection
|
||||
}
|
||||
.onReceive(viewModel.events) { event in
|
||||
switch event {
|
||||
|
@ -198,7 +188,7 @@ struct UserSignInView: View {
|
|||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
focusedTextField = .username
|
||||
focusedField = .username
|
||||
viewModel.send(.getPublicData)
|
||||
}
|
||||
.alert(
|
||||
|
@ -209,11 +199,11 @@ struct UserSignInView: View {
|
|||
|
||||
// TODO: uncomment when duplicate user fixed
|
||||
// Button(L10n.signIn) {
|
||||
// signInUplicate(user: user, replace: false)
|
||||
// signInDuplicate(user: user, replace: false)
|
||||
// }
|
||||
|
||||
// Button("Replace") {
|
||||
// signInUplicate(user: user, replace: true)
|
||||
// signInDuplicate(user: user, replace: true)
|
||||
// }
|
||||
|
||||
Button(L10n.dismiss, role: .cancel)
|
||||
|
|
|
@ -84,6 +84,8 @@
|
|||
4E49DEE42CE55FB900352DCD /* SyncPlayUserAccessType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */; };
|
||||
4E49DEE62CE5616800352DCD /* UserProfileImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */; };
|
||||
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
|
||||
4E4DAC372D11EE5E00E13FF9 /* SplitLoginWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */; };
|
||||
4E4DAC3D2D11F94400E13FF9 /* LocalServerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */; };
|
||||
4E4E9C672CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; };
|
||||
4E4E9C682CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; };
|
||||
4E4E9C6A2CFEDCA400A6946F /* PeopleEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */; };
|
||||
|
@ -158,6 +160,8 @@
|
|||
4E90F76A2CC72B1F00417C31 /* DetailsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F7592CC72B1F00417C31 /* DetailsSection.swift */; };
|
||||
4E97D1832D064748004B89AD /* ItemSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1822D064748004B89AD /* ItemSection.swift */; };
|
||||
4E97D1852D064B43004B89AD /* RefreshMetadataButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */; };
|
||||
4E98F7D22D123AD4001E7518 /* NavigationBarMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */; };
|
||||
4E98F7D32D123AD4001E7518 /* View-tvOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98F7C92D123AD4001E7518 /* View-tvOS.swift */; };
|
||||
4E9A24E62C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */; };
|
||||
4E9A24E82C82B6190023DA83 /* CustomProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */; };
|
||||
4E9A24E92C82B79D0023DA83 /* EditCustomDeviceProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8572C80332500E2879E /* EditCustomDeviceProfileCoordinator.swift */; };
|
||||
|
@ -759,7 +763,7 @@
|
|||
E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */; };
|
||||
E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */; };
|
||||
E1763A662BF3CA83004DF6AB /* FullScreenMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */; };
|
||||
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A692BF3D177004DF6AB /* PublicUserRow.swift */; };
|
||||
E1763A6A2BF3D177004DF6AB /* PublicUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A692BF3D177004DF6AB /* PublicUserButton.swift */; };
|
||||
E1763A712BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
|
||||
E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
|
||||
E1763A742BF3FA4C004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */; };
|
||||
|
@ -1227,6 +1231,8 @@
|
|||
4E49DEDD2CE55F7F00352DCD /* SquareImageCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquareImageCropView.swift; sourceTree = "<group>"; };
|
||||
4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPlayUserAccessType.swift; sourceTree = "<group>"; };
|
||||
4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileImagePicker.swift; sourceTree = "<group>"; };
|
||||
4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitLoginWindowView.swift; sourceTree = "<group>"; };
|
||||
4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalServerButton.swift; sourceTree = "<group>"; };
|
||||
4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudioEditorViewModel.swift; sourceTree = "<group>"; };
|
||||
4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleEditorViewModel.swift; sourceTree = "<group>"; };
|
||||
4E5071D62CFCEB6F003FA2AD /* TagEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEditorViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -1283,6 +1289,8 @@
|
|||
4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditServerTaskView.swift; sourceTree = "<group>"; };
|
||||
4E97D1822D064748004B89AD /* ItemSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSection.swift; sourceTree = "<group>"; };
|
||||
4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshMetadataButton.swift; sourceTree = "<group>"; };
|
||||
4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarMenuButton.swift; sourceTree = "<group>"; };
|
||||
4E98F7C92D123AD4001E7518 /* View-tvOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View-tvOS.swift"; sourceTree = "<group>"; };
|
||||
4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileSettingsView.swift; sourceTree = "<group>"; };
|
||||
4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProfileButton.swift; sourceTree = "<group>"; };
|
||||
4E9A24EA2C82B9ED0023DA83 /* CustomDeviceProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileCoordinator.swift; sourceTree = "<group>"; };
|
||||
|
@ -1689,7 +1697,7 @@
|
|||
E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGridButton.swift; sourceTree = "<group>"; };
|
||||
E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowButton.swift; sourceTree = "<group>"; };
|
||||
E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMenu.swift; sourceTree = "<group>"; };
|
||||
E1763A692BF3D177004DF6AB /* PublicUserRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserRow.swift; sourceTree = "<group>"; };
|
||||
E1763A692BF3D177004DF6AB /* PublicUserButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserButton.swift; sourceTree = "<group>"; };
|
||||
E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftfinStore+Mappings.swift"; sourceTree = "<group>"; };
|
||||
E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; };
|
||||
E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; };
|
||||
|
@ -2260,6 +2268,23 @@
|
|||
path = UserProfileImagePicker;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E4DAC3B2D11F69000E13FF9 /* Components */,
|
||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
||||
);
|
||||
path = ConnectToServerView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E4DAC3B2D11F69000E13FF9 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E5071D52CFCEB03003FA2AD /* ItemEditorViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2499,6 +2524,31 @@
|
|||
path = EditServerTaskView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E98F7C82D123AD4001E7518 /* Modifiers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */,
|
||||
);
|
||||
path = Modifiers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E98F7CA2D123AD4001E7518 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E98F7C82D123AD4001E7518 /* Modifiers */,
|
||||
4E98F7C92D123AD4001E7518 /* View-tvOS.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E98F7CB2D123AD4001E7518 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E98F7CA2D123AD4001E7518 /* View */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E9A24E32C82B4700023DA83 /* CustomDeviceProfileSettingsView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2839,6 +2889,7 @@
|
|||
children = (
|
||||
E12186DF2718F2030010884C /* App */,
|
||||
536D3D77267BB9650004248C /* Components */,
|
||||
4E98F7CB2D123AD4001E7518 /* Extensions */,
|
||||
E185920B28CEF23F00326F80 /* Objects */,
|
||||
E1DABAD62A26E28E008AC34A /* Resources */,
|
||||
E12186E02718F23B0010884C /* Views */,
|
||||
|
@ -2941,6 +2992,7 @@
|
|||
E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */,
|
||||
E17885A3278105170094FBCF /* SFSymbolButton.swift */,
|
||||
E12E30F0296383810022FAC9 /* SplitFormWindowView.swift */,
|
||||
4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */,
|
||||
E187A60429AD2E25008387E6 /* StepperView.swift */,
|
||||
);
|
||||
path = Components;
|
||||
|
@ -3673,7 +3725,7 @@
|
|||
E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */,
|
||||
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */,
|
||||
E10231522BCF8AF8009D71FC /* ChannelLibraryView */,
|
||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
||||
4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */,
|
||||
E154967B296CBB1A00C4EF88 /* FontPickerView.swift */,
|
||||
E1A42E4D28CBD3B200A14DCB /* HomeView */,
|
||||
E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */,
|
||||
|
@ -4001,7 +4053,7 @@
|
|||
E1763A682BF3D16E004DF6AB /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1763A692BF3D177004DF6AB /* PublicUserRow.swift */,
|
||||
E1763A692BF3D177004DF6AB /* PublicUserButton.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5043,6 +5095,8 @@
|
|||
E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */,
|
||||
E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */,
|
||||
E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */,
|
||||
4E98F7D22D123AD4001E7518 /* NavigationBarMenuButton.swift in Sources */,
|
||||
4E98F7D32D123AD4001E7518 /* View-tvOS.swift in Sources */,
|
||||
C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */,
|
||||
C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */,
|
||||
E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */,
|
||||
|
@ -5056,6 +5110,7 @@
|
|||
4EF18B2A2CB993BD00343666 /* ListRow.swift in Sources */,
|
||||
4EF659E32CDD270D00E0BE5D /* ActionMenu.swift in Sources */,
|
||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
||||
4E4DAC372D11EE5E00E13FF9 /* SplitLoginWindowView.swift in Sources */,
|
||||
4E97D1832D064748004B89AD /* ItemSection.swift in Sources */,
|
||||
E145EB232BDCCA43003BF6F3 /* BulletedList.swift in Sources */,
|
||||
E104DC972B9E7E29008F506D /* AssertionFailureView.swift in Sources */,
|
||||
|
@ -5263,6 +5318,7 @@
|
|||
E1B4E4372CA7795200DC49DE /* OrderedDictionary.swift in Sources */,
|
||||
E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
|
||||
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
||||
4E4DAC3D2D11F94400E13FF9 /* LocalServerButton.swift in Sources */,
|
||||
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */,
|
||||
BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */,
|
||||
E1575EA2293E7B1E001665B1 /* Color.swift in Sources */,
|
||||
|
@ -5313,7 +5369,7 @@
|
|||
E1575E7D293E77B5001665B1 /* PosterDisplayType.swift in Sources */,
|
||||
E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */,
|
||||
E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */,
|
||||
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */,
|
||||
E1763A6A2BF3D177004DF6AB /* PublicUserButton.swift in Sources */,
|
||||
E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */,
|
||||
4E35CE672CBED8B600DBD886 /* ServerTicks.swift in Sources */,
|
||||
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue