diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index a9fe5576..62f1ced4 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -138,6 +138,8 @@ internal enum L10n { internal static func jumpLengthSeconds(_ p1: UnsafePointer) -> String { return L10n.tr("Localizable", "jumpLengthSeconds", p1) } + /// Known users + internal static var knownUsers: String { return L10n.tr("Localizable", "knownUsers") } /// Larger internal static var larger: String { return L10n.tr("Localizable", "larger") } /// Largest diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index a9b154dc..c6e4ea7d 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -9,12 +9,16 @@ import CoreStore import Foundation import Stinsen +import JellyfinAPI final class UserSignInViewModel: ViewModel { @RouterObject var router: UserSignInCoordinator.Router? let server: SwiftfinStore.State.Server + + @Published + var users: [UserDto] = [] init(server: SwiftfinStore.State.Server) { self.server = server @@ -48,4 +52,20 @@ final class UserSignInViewModel: ViewModel { self.isLoading = false } + + func loadUsers() { + // TODO: this is a hack + JellyfinAPIAPI.basePath = server.currentURI + UserAPI.getPublicUsers() + .sink(receiveCompletion: { completion in + switch completion { + case .finished: () + case .failure: + self.users = [] + } + }, receiveValue: { response in + self.users = response + }) + .store(in: &cancellables) + } } diff --git a/Shared/Views/UserLoginCellView.swift b/Shared/Views/UserLoginCellView.swift new file mode 100644 index 00000000..c4daa49f --- /dev/null +++ b/Shared/Views/UserLoginCellView.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) 2022 Jellyfin & Jellyfin Contributors +// + +import SwiftUI +import JellyfinAPI + +struct UserLoginCellView: View { + + @State + private var expanded = false; + + @State + private var enteredPassword: String = "" + + var user: UserDto + var baseURL: String? + var loginTapped : (String, String) -> Void + var cancelTapped: () -> Void + + var body: some View { + DisclosureGroup() { + VStack(alignment: .leading, spacing: 16) { + SecureField(L10n.password, text: $enteredPassword) + Button { + loginTapped(user.name ?? "", enteredPassword) + } label: { + L10n.signIn.text + } + } + .padding(.leading, -16) + } label: { + HStack(spacing: 4.0) { + AsyncImage( + url: getProfileImageUrl(), + content: { image in + image.resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 50, maxHeight: 50) + .clipShape(Circle()) + }, + placeholder: { + Image(systemName: "person.circle") + .resizable() + .font(.system(size: 40)) + .scaledToFit() + .frame(maxWidth: 50, maxHeight: 50) + }) + .padding(.vertical, 4.0) + + Text(user.name ?? "") + .padding(.leading, 4.0) + Spacer() + } + } + } + + func getProfileImageUrl() -> URL? { + if let userId = user.id, let imageTag = user.primaryImageTag, let server = baseURL { + let url = URL(string: "\(server)/Users/\(userId)/Images/Primary?width=200&tag=\(imageTag)&quality=90") + LogManager.log.debug(url?.absoluteString ?? "") + return url + } + return nil + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 50d35fd9..da969d03 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -249,6 +249,8 @@ 62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; }; 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; }; 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; }; + 631759CF2879DB6A00A621AD /* UserLoginCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631759CE2879DB6A00A621AD /* UserLoginCellView.swift */; }; + 631759D02879DB6A00A621AD /* UserLoginCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631759CE2879DB6A00A621AD /* UserLoginCellView.swift */; }; AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; }; C400DB6A27FE894F007B65FE /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */; }; C400DB6B27FE8C97007B65FE /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; }; @@ -743,6 +745,7 @@ 62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = ""; }; 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; + 631759CE2879DB6A00A621AD /* UserLoginCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLoginCellView.swift; sourceTree = ""; }; AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = ""; }; C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = ""; }; C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemWideElement.swift; sourceTree = ""; }; @@ -1804,6 +1807,7 @@ E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */, 624C21742685CF60007F1390 /* SearchablePickerView.swift */, 53DE4BD1267098F300739748 /* SearchBarView.swift */, + 631759CE2879DB6A00A621AD /* UserLoginCellView.swift */, ); path = Views; sourceTree = ""; @@ -2256,6 +2260,7 @@ E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */, 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */, + 631759D02879DB6A00A621AD /* UserLoginCellView.swift in Sources */, E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, E14B4142279354770016CBE5 /* LocalizedLookup.swift in Sources */, E1B59FD92786AE4600A5287E /* NextUpCard.swift in Sources */, @@ -2485,6 +2490,7 @@ 6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */, 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */, E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */, + 631759CF2879DB6A00A621AD /* UserLoginCellView.swift in Sources */, E1AA33222782648000F6439C /* OverlaySliderColor.swift in Sources */, E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */, E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */, diff --git a/Swiftfin/Views/UserSignInView.swift b/Swiftfin/Views/UserSignInView.swift index eec3f212..d365e92d 100644 --- a/Swiftfin/Views/UserSignInView.swift +++ b/Swiftfin/Views/UserSignInView.swift @@ -19,7 +19,19 @@ struct UserSignInView: View { private var password: String = "" var body: some View { - Form { + List { + #if !os(tvOS) + // DisclosureGroup not available on tvOS + if (viewModel.users.count > 0) { + Section(header: L10n.knownUsers.text) { + ForEach(viewModel.users, id: \.id) { user in + UserLoginCellView(user: user, baseURL: viewModel.server.currentURI, loginTapped: viewModel.login, cancelTapped: viewModel.cancelSignIn) + .disabled(viewModel.isLoading) + } + } + } + #endif + Section { TextField(L10n.username, text: $username) @@ -55,5 +67,6 @@ struct UserSignInView: View { } .navigationTitle(L10n.signIn) .navigationBarBackButtonHidden(viewModel.isLoading) + .onAppear(perform: viewModel.loadUsers) } } diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index c5e7a518..ad118a59 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ