[iOS] Admin Dashboard - QuickConnect Other User (#1488)
* Allow other user authorization. * Show user being logged in. * Fix localizations & update screenshot * Cleanup Locales * mirror lable changes on tvOS * cleanup * fix strings * adjust sizes --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
93033262d6
commit
8c4fde87f1
|
@ -71,6 +71,8 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
|
|||
var userLiveTVAccess = makeUserLiveTVAccess
|
||||
@Route(.modal)
|
||||
var userPermissions = makeUserPermissions
|
||||
@Route(.push)
|
||||
var quickConnectAuthorize = makeQuickConnectAuthorize
|
||||
@Route(.modal)
|
||||
var userParentalRatings = makeUserParentalRatings
|
||||
@Route(.modal)
|
||||
|
@ -234,6 +236,11 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeQuickConnectAuthorize(user: UserDto) -> some View {
|
||||
QuickConnectAuthorizeView(user: user)
|
||||
}
|
||||
|
||||
// MARK: - Views: API Keys
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
@ -112,8 +112,8 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeQuickConnectAuthorize() -> some View {
|
||||
QuickConnectAuthorizeView()
|
||||
func makeQuickConnectAuthorize(user: UserDto) -> some View {
|
||||
QuickConnectAuthorizeView(user: user)
|
||||
}
|
||||
|
||||
func makeResetUserPassword(userID: String) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
||||
|
|
|
@ -94,14 +94,8 @@ internal enum L10n {
|
|||
internal static let allLanguages = L10n.tr("Localizable", "allLanguages", fallback: "All languages")
|
||||
/// All Media
|
||||
internal static let allMedia = L10n.tr("Localizable", "allMedia", fallback: "All Media")
|
||||
/// Allow collection management
|
||||
internal static let allowCollectionManagement = L10n.tr("Localizable", "allowCollectionManagement", fallback: "Allow collection management")
|
||||
/// Allowed
|
||||
internal static let allowed = L10n.tr("Localizable", "allowed", fallback: "Allowed")
|
||||
/// Allow media item deletion
|
||||
internal static let allowItemDeletion = L10n.tr("Localizable", "allowItemDeletion", fallback: "Allow media item deletion")
|
||||
/// Allow media item editing
|
||||
internal static let allowItemEditing = L10n.tr("Localizable", "allowItemEditing", fallback: "Allow media item editing")
|
||||
/// All Servers
|
||||
internal static let allServers = L10n.tr("Localizable", "allServers", fallback: "All Servers")
|
||||
/// View and manage all registered users on the server, including their permissions and activity status.
|
||||
|
@ -440,6 +434,8 @@ internal enum L10n {
|
|||
internal static let deleteItemConfirmation = L10n.tr("Localizable", "deleteItemConfirmation", fallback: "Are you sure you want to delete this item?")
|
||||
/// Are you sure you want to delete this item? This action cannot be undone.
|
||||
internal static let deleteItemConfirmationMessage = L10n.tr("Localizable", "deleteItemConfirmationMessage", fallback: "Are you sure you want to delete this item? This action cannot be undone.")
|
||||
/// Delete media
|
||||
internal static let deleteMedia = L10n.tr("Localizable", "deleteMedia", fallback: "Delete media")
|
||||
/// Delete Schedule
|
||||
internal static let deleteSchedule = L10n.tr("Localizable", "deleteSchedule", fallback: "Delete Schedule")
|
||||
/// Are you sure you wish to delete this schedule?
|
||||
|
@ -544,6 +540,10 @@ internal enum L10n {
|
|||
internal static let dvd = L10n.tr("Localizable", "dvd", fallback: "DVD")
|
||||
/// Edit
|
||||
internal static let edit = L10n.tr("Localizable", "edit", fallback: "Edit")
|
||||
/// Edit Collections
|
||||
internal static let editCollections = L10n.tr("Localizable", "editCollections", fallback: "Edit Collections")
|
||||
/// Edit media
|
||||
internal static let editMedia = L10n.tr("Localizable", "editMedia", fallback: "Edit media")
|
||||
/// Editor
|
||||
internal static let editor = L10n.tr("Localizable", "editor", fallback: "Editor")
|
||||
/// Edit Server
|
||||
|
@ -1056,6 +1056,8 @@ internal enum L10n {
|
|||
internal static let quickConnectStep3 = L10n.tr("Localizable", "quickConnectStep3", fallback: "Enter the following code:")
|
||||
/// Authorizing Quick Connect successful. Please continue on your other device.
|
||||
internal static let quickConnectSuccessMessage = L10n.tr("Localizable", "quickConnectSuccessMessage", fallback: "Authorizing Quick Connect successful. Please continue on your other device.")
|
||||
/// This user will be authenticated to the other device.
|
||||
internal static let quickConnectUserDisclaimer = L10n.tr("Localizable", "quickConnectUserDisclaimer", fallback: "This user will be authenticated to the other device.")
|
||||
/// Random
|
||||
internal static let random = L10n.tr("Localizable", "random", fallback: "Random")
|
||||
/// Random image
|
||||
|
|
|
@ -22,7 +22,7 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful {
|
|||
// MARK: Action
|
||||
|
||||
enum Action: Equatable {
|
||||
case authorize(String)
|
||||
case authorize(code: String)
|
||||
case cancel
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,12 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful {
|
|||
private var authorizeTask: AnyCancellable?
|
||||
private var eventSubject: PassthroughSubject<Event, Never> = .init()
|
||||
|
||||
let user: UserDto
|
||||
|
||||
init(user: UserDto) {
|
||||
self.user = user
|
||||
}
|
||||
|
||||
func respond(to action: Action) -> State {
|
||||
switch action {
|
||||
case let .authorize(code):
|
||||
|
@ -53,7 +59,7 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful {
|
|||
try? await Task.sleep(nanoseconds: 10_000_000_000)
|
||||
|
||||
do {
|
||||
try await authorize(code: code)
|
||||
try await authorize(code: code, userID: user.id)
|
||||
|
||||
await MainActor.run {
|
||||
self.eventSubject.send(.authorized)
|
||||
|
@ -76,8 +82,8 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful {
|
|||
}
|
||||
}
|
||||
|
||||
private func authorize(code: String) async throws {
|
||||
let request = Paths.authorize(code: code)
|
||||
private func authorize(code: String, userID: String? = nil) async throws {
|
||||
let request = Paths.authorizeQuickConnect(code: code, userID: userID)
|
||||
let response = try await userSession.client.send(request)
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
|
|
@ -43,15 +43,15 @@ extension CustomizeViewsSettings {
|
|||
|
||||
/// Enable Refreshing Items from All Visible LIbraries
|
||||
if userSession?.user.permissions.items.canEditMetadata ?? false {
|
||||
Toggle(L10n.allowItemEditing, isOn: $enableItemEditing)
|
||||
Toggle(L10n.editMedia, isOn: $enableItemEditing)
|
||||
}
|
||||
/// Enable Deleting Items from Approved Libraries
|
||||
if userSession?.user.permissions.items.canDelete ?? false {
|
||||
Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion)
|
||||
Toggle(L10n.deleteMedia, isOn: $enableItemDeletion)
|
||||
}
|
||||
/// Enable Refreshing & Deleting Collections
|
||||
if userSession?.user.permissions.items.canManageCollections ?? false {
|
||||
Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement)
|
||||
Toggle(L10n.editCollections, isOn: $enableCollectionManagement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ private struct ListRowButtonStyle: ButtonStyle {
|
|||
}
|
||||
|
||||
private func secondaryStyle(configuration: Configuration) -> some ShapeStyle {
|
||||
if configuration.role == .destructive {
|
||||
if configuration.role == .destructive || configuration.role == .cancel {
|
||||
return AnyShapeStyle(Color.red.opacity(0.2))
|
||||
} else {
|
||||
return isEnabled ? AnyShapeStyle(HierarchicalShapeStyle.secondary) : AnyShapeStyle(Color.gray)
|
||||
|
|
|
@ -81,13 +81,16 @@ struct ServerUserDetailsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
ChevronButton(L10n.permissions) {
|
||||
router.route(to: \.userPermissions, viewModel)
|
||||
}
|
||||
if let userId = viewModel.user.id {
|
||||
ChevronButton(L10n.password) {
|
||||
router.route(to: \.resetUserPassword, userId)
|
||||
}
|
||||
ChevronButton(L10n.quickConnect) {
|
||||
router.route(to: \.quickConnectAuthorize, viewModel.user)
|
||||
}
|
||||
ChevronButton(L10n.permissions) {
|
||||
router.route(to: \.userPermissions, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,19 +46,19 @@ extension CustomizeViewsSettings {
|
|||
|
||||
/// Enable Editing Items from All Visible LIbraries
|
||||
if userSession?.user.permissions.items.canEditMetadata ?? false {
|
||||
Toggle(L10n.allowItemEditing, isOn: $enableItemEditing)
|
||||
Toggle(L10n.editMedia, isOn: $enableItemEditing)
|
||||
}
|
||||
/// Enable Deleting Items from Approved Libraries
|
||||
if userSession?.user.permissions.items.canDelete ?? false {
|
||||
Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion)
|
||||
Toggle(L10n.deleteMedia, isOn: $enableItemDeletion)
|
||||
}
|
||||
/// Enable Downloading All Items
|
||||
/* if userSession?.user.permissions.items.canDownload ?? false {
|
||||
Toggle(L10n.allowItemDownloading, isOn: $enableItemDownloads)
|
||||
Toggle(L10n.itemDownloading, isOn: $enableItemDownloads)
|
||||
} */
|
||||
/// Enable Deleting or Editing Collections
|
||||
if userSession?.user.permissions.items.canManageCollections ?? false {
|
||||
Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement)
|
||||
Toggle(L10n.editCollections, isOn: $enableCollectionManagement)
|
||||
}
|
||||
/// Manage Item Lyrics
|
||||
/* if userSession?.user.permissions.items.canManageLyrics ?? false {
|
||||
|
|
|
@ -8,10 +8,16 @@
|
|||
|
||||
import Defaults
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct QuickConnectAuthorizeView: View {
|
||||
|
||||
// MARK: - Dismiss Environment
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
// MARK: - Defaults
|
||||
|
||||
@Default(.accentColor)
|
||||
|
@ -24,11 +30,8 @@ struct QuickConnectAuthorizeView: View {
|
|||
|
||||
// MARK: - State & Environment Objects
|
||||
|
||||
@EnvironmentObject
|
||||
private var router: SettingsCoordinator.Router
|
||||
|
||||
@StateObject
|
||||
private var viewModel = QuickConnectAuthorizeViewModel()
|
||||
private var viewModel: QuickConnectAuthorizeViewModel
|
||||
|
||||
// MARK: - Quick Connect Variables
|
||||
|
||||
|
@ -45,10 +48,46 @@ struct QuickConnectAuthorizeView: View {
|
|||
@State
|
||||
private var error: Error? = nil
|
||||
|
||||
// MARK: - Initialize
|
||||
|
||||
init(user: UserDto) {
|
||||
self._viewModel = StateObject(wrappedValue: QuickConnectAuthorizeViewModel(user: user))
|
||||
}
|
||||
|
||||
// MARK: Display the User Being Authenticated
|
||||
|
||||
@ViewBuilder
|
||||
private var loginUserRow: some View {
|
||||
HStack {
|
||||
UserProfileImage(
|
||||
userID: viewModel.user.id,
|
||||
source: viewModel.user.profileImageSource(
|
||||
client: viewModel.userSession.client,
|
||||
maxWidth: 120
|
||||
)
|
||||
)
|
||||
.frame(width: 50, height: 50)
|
||||
|
||||
Text(viewModel.user.name ?? L10n.unknown)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
loginUserRow
|
||||
} header: {
|
||||
Text(L10n.user)
|
||||
} footer: {
|
||||
Text(L10n.quickConnectUserDisclaimer)
|
||||
}
|
||||
|
||||
Section {
|
||||
TextField(L10n.quickConnectCode, text: $code)
|
||||
.keyboardType(.numberPad)
|
||||
|
@ -59,14 +98,13 @@ struct QuickConnectAuthorizeView: View {
|
|||
}
|
||||
|
||||
if viewModel.state == .authorizing {
|
||||
ListRowButton(L10n.cancel) {
|
||||
ListRowButton(L10n.cancel, role: .cancel) {
|
||||
viewModel.send(.cancel)
|
||||
isCodeFocused = true
|
||||
}
|
||||
.foregroundStyle(.red, .red.opacity(0.2))
|
||||
} else {
|
||||
ListRowButton(L10n.authorize) {
|
||||
viewModel.send(.authorize(code))
|
||||
viewModel.send(.authorize(code: code))
|
||||
}
|
||||
.disabled(code.count != 6 || viewModel.state == .authorizing)
|
||||
.foregroundStyle(
|
||||
|
@ -107,7 +145,7 @@ struct QuickConnectAuthorizeView: View {
|
|||
isPresented: $isPresentingSuccess
|
||||
) {
|
||||
Button(L10n.dismiss, role: .cancel) {
|
||||
router.pop()
|
||||
dismiss()
|
||||
}
|
||||
} message: {
|
||||
L10n.quickConnectSuccessMessage.text
|
||||
|
|
|
@ -45,7 +45,7 @@ struct UserProfileSettingsView: View {
|
|||
|
||||
Section {
|
||||
ChevronButton(L10n.quickConnect) {
|
||||
router.route(to: \.quickConnect)
|
||||
router.route(to: \.quickConnect, viewModel.userSession.user.data)
|
||||
}
|
||||
|
||||
ChevronButton(L10n.password) {
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue