Skip to content

Commit

Permalink
[iOS] Admin Dashboard - User Permissions (jellyfin#1313)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* Localization and better planning. Remove the Username as this will end up in another section. Updated planning here: jellyfin#1283 | 5 more views required!

* Initializing an optional variable with nil is redundant line

* Remove Live TV since that will go in another section

* Cleanup Coordinator / Merge with Main

* Remove all 'Allows' from strings

* Fix Merge Issues

* Use CaseIterablePicker, Binding.map

* BackgroundState == updating, change all of the buttons to visible when custom by process of elimination opposed to the default custom value. Make all of the input fields use temp values to make it less jarring.

* Update SessionsSection.swift

* Learn more!

* Validate > 0, don't allow inputs to be less than 1 and reset tempValues when the enum is updated.

* use new binding extensions

* String fixes

* Don't test against adminDefault for users or userDefault for admins.

* Linting indentation

* Default vs UserDefault + no more reason to have temporary variables.

* cleanup

* format

---------

Co-authored-by: Ethan Pippin <[email protected]>
  • Loading branch information
JPKribs and LePips authored Nov 27, 2024
1 parent 34d64bc commit b9ac50c
Show file tree
Hide file tree
Showing 23 changed files with 1,307 additions and 193 deletions.
2 changes: 1 addition & 1 deletion Shared/Components/ChevronAlertButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct ChevronAlertButton<Content>: View where Content: View {
}
}
} message: {
if let description = description {
if let description {
Text(description)
}
}
Expand Down
66 changes: 50 additions & 16 deletions Shared/Coordinators/AdminDashboardCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,53 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
@Root
var start = makeStart

// MARK: - Route: Active Sessions

@Route(.push)
var activeSessions = makeActiveSessions
@Route(.push)
var activeDeviceDetails = makeActiveDeviceDetails
@Route(.push)
var tasks = makeTasks

// MARK: - Route: Devices

@Route(.push)
var devices = makeDevices
@Route(.push)
var deviceDetails = makeDeviceDetails

// MARK: - Route: Server Tasks

@Route(.push)
var editServerTask = makeEditServerTask
@Route(.push)
var tasks = makeTasks
@Route(.modal)
var addServerTaskTrigger = makeAddServerTaskTrigger

// MARK: - Route: Server Logs

@Route(.push)
var serverLogs = makeServerLogs

// MARK: - Route: Users

@Route(.push)
var users = makeUsers
@Route(.push)
var userDetails = makeUserDetails
@Route(.modal)
var userPermissions = makeUserPermissions
@Route(.modal)
var resetUserPassword = makeResetUserPassword
@Route(.modal)
var addServerUser = makeAddServerUser

// MARK: - Route: API Keys

@Route(.push)
var apiKeys = makeAPIKeys

@ViewBuilder
func makeAdminDashboard() -> some View {
AdminDashboardView()
}
// MARK: - Views: Active Sessions

@ViewBuilder
func makeActiveSessions() -> some View {
Expand All @@ -59,21 +75,13 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
ActiveSessionDetailView(box: box)
}

// MARK: - Views: Server Tasks

@ViewBuilder
func makeTasks() -> some View {
ServerTasksView()
}

@ViewBuilder
func makeDevices() -> some View {
DevicesView()
}

@ViewBuilder
func makeDeviceDetails(device: DeviceInfo) -> some View {
DeviceDetailsView(device: device)
}

@ViewBuilder
func makeEditServerTask(observer: ServerTaskObserver) -> some View {
EditServerTaskView(observer: observer)
Expand All @@ -85,11 +93,27 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
}
}

// MARK: - Views: Devices

@ViewBuilder
func makeDevices() -> some View {
DevicesView()
}

@ViewBuilder
func makeDeviceDetails(device: DeviceInfo) -> some View {
DeviceDetailsView(device: device)
}

// MARK: - Views: Server Logs

@ViewBuilder
func makeServerLogs() -> some View {
ServerLogsView()
}

// MARK: - Views: Users

@ViewBuilder
func makeUsers() -> some View {
ServerUsersView()
Expand All @@ -106,17 +130,27 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
}
}

func makeUserPermissions(viewModel: ServerUserAdminViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
ServerUserPermissionsView(viewModel: viewModel)
}
}

func makeResetUserPassword(userID: String) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
ResetUserPasswordView(userID: userID, requiresCurrentPassword: false)
}
}

// MARK: - Views: API Keys

@ViewBuilder
func makeAPIKeys() -> some View {
APIKeysView()
}

// MARK: - Views: Dashboard

@ViewBuilder
func makeStart() -> some View {
AdminDashboardView()
Expand Down
44 changes: 44 additions & 0 deletions Shared/Extensions/Binding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// 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 SwiftUI

extension Binding {

func clamp(min: Value, max: Value) -> Binding<Value> where Value: Comparable {
Binding<Value>(
get: { Swift.min(Swift.max(wrappedValue, min), max) },
set: { wrappedValue = Swift.min(Swift.max($0, min), max) }
)
}

func coalesce<T>(_ defaultValue: T) -> Binding<T> where Value == T? {
Binding<T>(
get: { wrappedValue ?? defaultValue },
set: { wrappedValue = $0 }
)
}

func map<V>(getter: @escaping (Value) -> V, setter: @escaping (V) -> Value) -> Binding<V> {
Binding<V>(
get: { getter(wrappedValue) },
set: { wrappedValue = setter($0) }
)
}

func min(_ minValue: Value) -> Binding<Value> where Value: Comparable {
Binding<Value>(
get: { Swift.max(wrappedValue, minValue) },
set: { wrappedValue = Swift.max($0, minValue) }
)
}

func negate() -> Binding<Bool> where Value == Bool {
map(getter: { !$0 }, setter: { $0 })
}
}
31 changes: 31 additions & 0 deletions Shared/Extensions/JellyfinAPI/ActiveSessionsPolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// 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

enum ActiveSessionsPolicy: Int, Displayable, CaseIterable {

case unlimited = 0
case custom = 1 // Default to 1 Active Session

// MARK: - Display Title

var displayTitle: String {
switch self {
case .unlimited:
return L10n.unlimited
case .custom:
return L10n.custom
}
}

init?(rawValue: Int?) {
guard let rawValue else { return nil }
self.init(rawValue: rawValue)
}
}
27 changes: 27 additions & 0 deletions Shared/Extensions/JellyfinAPI/LoginFailurePolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// 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

enum LoginFailurePolicy: Int, Displayable, CaseIterable {

case unlimited = -1
case userDefault = 0
case custom = 1 // Default to 1

var displayTitle: String {
switch self {
case .unlimited:
return L10n.unlimited
case .userDefault:
return L10n.default
case .custom:
return L10n.custom
}
}
}
31 changes: 31 additions & 0 deletions Shared/Extensions/JellyfinAPI/MaxBitratePolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// 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

enum MaxBitratePolicy: Int, Displayable, CaseIterable {

case unlimited = 0
case custom = 10_000_000 // Default to 10mbps

// MARK: - Display Title

var displayTitle: String {
switch self {
case .unlimited:
return L10n.unlimited
case .custom:
return L10n.custom
}
}

init?(rawValue: Int?) {
guard let rawValue else { return nil }
self.init(rawValue: rawValue)
}
}
24 changes: 24 additions & 0 deletions Shared/Extensions/JellyfinAPI/SyncPlayUserAccessType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// 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 JellyfinAPI

extension SyncPlayUserAccessType: Displayable {

var displayTitle: String {
switch self {
case .createAndJoinGroups:
L10n.createAndJoinGroups
case .joinGroups:
L10n.joinGroups
case .none:
L10n.none
}
}
}
Loading

0 comments on commit b9ac50c

Please sign in to comment.