diff --git a/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserGrid.swift b/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserGrid.swift new file mode 100644 index 000000000..e75687706 --- /dev/null +++ b/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserGrid.swift @@ -0,0 +1,70 @@ +// +// 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 PublicUserGrid: 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 { + RelativeSystemImageView(systemName: "person.fill", ratio: 0.5) + .foregroundStyle(.secondary) + .clipShape(.circle) + .aspectRatio(1, contentMode: .fill) + .frame(width: 100, height: 100) + } + + var body: some View { + Button { + action() + } label: { + VStack { + ImageView(user.profileImageSource(client: client)) + .image { image in + image + .posterBorder(ratio: 0.5, of: \.width) + } + .placeholder { _ in + personView + } + .failure { + personView + } + .aspectRatio(1, contentMode: .fill) + .clipShape(.circle) + .frame(width: 100, height: 100) + + Text(user.name ?? .emptyDash) + .foregroundStyle(.primary) + .lineLimit(1) + } + } + // .buttonStyle(.card) + .foregroundStyle(.primary) + } + } +} diff --git a/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift b/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift deleted file mode 100644 index 7843b6219..000000000 --- a/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift +++ /dev/null @@ -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) - } - } -} diff --git a/Swiftfin tvOS/Views/UserSignInView/UserSignInView.swift b/Swiftfin tvOS/Views/UserSignInView/UserSignInView.swift index daf0098a3..58855aa9a 100644 --- a/Swiftfin tvOS/Views/UserSignInView/UserSignInView.swift +++ b/Swiftfin tvOS/Views/UserSignInView/UserSignInView.swift @@ -57,7 +57,7 @@ struct UserSignInView: View { focusedTextField = 1 } - TextField(L10n.password, text: $password) { + SecureField(L10n.password, text: $password) { focusedTextField = nil } .autocorrectionDisabled() @@ -65,24 +65,22 @@ struct UserSignInView: View { .focused($focusedTextField, equals: 1) } header: { Text(L10n.signInToServer(viewModel.server.name)) + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) } if case .signingIn = viewModel.state { - Button(L10n.cancel) { + ListRowButton(L10n.cancel) { viewModel.send(.cancel) } .foregroundStyle(.red, .red.opacity(0.2)) } else { - Button(L10n.signIn) { + ListRowButton(L10n.signIn) { focusedTextField = nil viewModel.send(.signIn(username: username, password: password, policy: .none)) } .disabled(username.isEmpty) - .foregroundStyle( - accentColor.overlayColor, - accentColor - ) .opacity(username.isEmpty ? 0.5 : 1) } @@ -109,104 +107,121 @@ struct UserSignInView: View { @ViewBuilder private var publisUsersSection: some View { - Section(L10n.publicUsers) { + Section { if viewModel.publicUsers.isEmpty { L10n.noPublicUsers.text .font(.callout) .foregroundColor(.secondary) .frame(maxWidth: .infinity) } else { - ForEach(viewModel.publicUsers, id: \.id) { user in - PublicUserRow( - user: user, - client: viewModel.server.client - ) { - username = user.name ?? "" - password = "" - focusedTextField = 1 + HStack { + Grid { + GridRow { + ForEach(viewModel.publicUsers, id: \.id) { user in + PublicUserGrid( + user: user, + client: viewModel.server.client + ) { + username = user.name ?? "" + password = "" + focusedTextField = 1 + } + } + } } } } + } header: { + Text(L10n.publicUsers) + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) } } var body: some View { - VStack { - HStack { - Spacer() - - if viewModel.state == .signingIn { - ProgressView() + ZStack(alignment: .center) { + VStack { + HStack { + Image(.jellyfinBlobBlue) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 100) + .edgePadding() } - } - .frame(height: 100) - .overlay { - Image(.jellyfinBlobBlue) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 100) - .edgePadding() - } - HStack(alignment: .top) { - VStack(alignment: .leading) { - signInSection + VStack { + Spacer() + + VStack(alignment: .center) { + publisUsersSection + } + + Spacer() + + VStack(alignment: .leading) { + signInSection + } + .frame(maxWidth: 800) + + Spacer() } - VStack(alignment: .leading) { - publisUsersSection + Spacer() + } + .onReceive(viewModel.events) { event in + switch event { + case let .duplicateUser(duplicateUser): + self.duplicateUser = duplicateUser + isPresentingDuplicateUser = true + case let .error(eventError): + error = eventError + isPresentingError = true + case let .signedIn(user): + router.dismissCoordinator() + + Defaults[.lastSignedInUserID] = user.id + UserSession.current.reset() + Notifications[.didSignIn].post() } } + .onFirstAppear { + focusedTextField = 0 + viewModel.send(.getPublicData) + } + .alert( + Text("Duplicate User"), + isPresented: $isPresentingDuplicateUser, + presenting: duplicateUser + ) { _ in + + // TODO: uncomment when duplicate user fixed + // Button(L10n.signIn) { + // signInUplicate(user: user, replace: false) + // } + + // Button("Replace") { + // signInUplicate(user: user, replace: true) + // } + + Button(L10n.dismiss, role: .cancel) + } message: { duplicateUser in + Text("\(duplicateUser.username) is already saved") + } + .alert( + L10n.error.text, + isPresented: $isPresentingError, + presenting: error + ) { _ in + Button(L10n.dismiss, role: .cancel) + } message: { error in + Text(error.localizedDescription) + } - Spacer() - } - .onReceive(viewModel.events) { event in - switch event { - case let .duplicateUser(duplicateUser): - self.duplicateUser = duplicateUser - isPresentingDuplicateUser = true - case let .error(eventError): - error = eventError - isPresentingError = true - case let .signedIn(user): - router.dismissCoordinator() - - Defaults[.lastSignedInUserID] = user.id - UserSession.current.reset() - Notifications[.didSignIn].post() + if viewModel.state == .signingIn { + Color.black.opacity(0.5) + .edgesIgnoringSafeArea(.all) + ProgressView() } } - .onFirstAppear { - focusedTextField = 0 - viewModel.send(.getPublicData) - } - .alert( - Text("Duplicate User"), - isPresented: $isPresentingDuplicateUser, - presenting: duplicateUser - ) { _ in - - // TODO: uncomment when duplicate user fixed -// Button(L10n.signIn) { -// signInUplicate(user: user, replace: false) -// } - -// Button("Replace") { -// signInUplicate(user: user, replace: true) -// } - - Button(L10n.dismiss, role: .cancel) - } message: { duplicateUser in - Text("\(duplicateUser.username) is already saved") - } - .alert( - L10n.error.text, - isPresented: $isPresentingError, - presenting: error - ) { _ in - Button(L10n.dismiss, role: .cancel) - } message: { error in - Text(error.localizedDescription) - } } }