Skip to content

[iOS] Admin Dashboard - Parental Ratings #1353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 11, 2024
Merged
23 changes: 10 additions & 13 deletions Shared/Components/SeparatorVStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import SwiftUI

// https://movingparts.io/variadic-views-in-swiftui

/// An `HStack` that inserts an optional `separator` between views.
/// A `VStack` that inserts an optional `separator` between views.
///
/// - Note: Default spacing is removed. The separator view is responsible
/// for spacing.
struct SeparatorVStack<Content: View, Separator: View>: View {

private var content: () -> Content
private var separator: () -> Separator
private let alignment: HorizontalAlignment
private let content: () -> Content
private let separator: () -> Separator

var body: some View {
_VariadicView.Tree(SeparatorVStackLayout(separator: separator)) {
_VariadicView.Tree(SeparatorVStackLayout(alignment: alignment, separator: separator)) {
content()
}
}
Expand All @@ -29,10 +30,12 @@ struct SeparatorVStack<Content: View, Separator: View>: View {
extension SeparatorVStack {

init(
alignment: HorizontalAlignment = .center,
@ViewBuilder separator: @escaping () -> Separator,
@ViewBuilder content: @escaping () -> Content
) {
self.init(
alignment: alignment,
content: content,
separator: separator
)
Expand All @@ -43,14 +46,15 @@ extension SeparatorVStack {

struct SeparatorVStackLayout: _VariadicView_UnaryViewRoot {

var separator: () -> Separator
let alignment: HorizontalAlignment
let separator: () -> Separator

@ViewBuilder
func body(children: _VariadicView.Children) -> some View {

let last = children.last?.id

localHStack {
VStack(alignment: alignment, spacing: 0) {
ForEach(children) { child in
child

Expand All @@ -60,12 +64,5 @@ extension SeparatorVStack {
}
}
}

@ViewBuilder
private func localHStack(@ViewBuilder content: @escaping () -> some View) -> some View {
VStack(spacing: 0) {
content()
}
}
}
}
8 changes: 8 additions & 0 deletions Shared/Coordinators/AdminDashboardCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
@Route(.modal)
var userPermissions = makeUserPermissions
@Route(.modal)
var userParentalRatings = makeUserParentalRatings
@Route(.modal)
var resetUserPassword = makeResetUserPassword
@Route(.modal)
var addServerUser = makeAddServerUser
Expand Down Expand Up @@ -160,6 +162,12 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
}
}

func makeUserParentalRatings(viewModel: ServerUserAdminViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
ServerUserParentalRatingView(viewModel: viewModel)
}
}

func makeResetUserPassword(userID: String) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
ResetUserPasswordView(userID: userID, requiresCurrentPassword: false)
Expand Down
36 changes: 36 additions & 0 deletions Shared/Extensions/JellyfinAPI/ParentalRating.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// 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 UnratedItem: Displayable {

var displayTitle: String {
switch self {
case .movie:
L10n.movies
case .trailer:
L10n.trailers
case .series:
L10n.tvShows
case .music:
L10n.music
case .book:
L10n.books
case .liveTvChannel:
L10n.liveTVChannels
case .liveTvProgram:
L10n.liveTVPrograms
case .channelContent:
L10n.channels
case .other:
L10n.other
}
}
}
38 changes: 38 additions & 0 deletions Shared/Strings/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ internal enum L10n {
internal static let access = L10n.tr("Localizable", "access", fallback: "Access")
/// Accessibility
internal static let accessibility = L10n.tr("Localizable", "accessibility", fallback: "Accessibility")
/// Access schedule
internal static let accessSchedule = L10n.tr("Localizable", "accessSchedule", fallback: "Access schedule")
/// Create an access schedule to limit access to certain hours.
internal static let accessScheduleDescription = L10n.tr("Localizable", "accessScheduleDescription", fallback: "Create an access schedule to limit access to certain hours.")
/// Active
internal static let active = L10n.tr("Localizable", "active", fallback: "Active")
/// Active Devices
Expand Down Expand Up @@ -50,6 +54,10 @@ internal enum L10n {
internal static let administrator = L10n.tr("Localizable", "administrator", fallback: "Administrator")
/// Advanced
internal static let advanced = L10n.tr("Localizable", "advanced", fallback: "Advanced")
/// Age %@
internal static func agesGroup(_ p1: Any) -> String {
return L10n.tr("Localizable", "agesGroup", String(describing: p1), fallback: "Age %@")
}
/// Aired
internal static let aired = L10n.tr("Localizable", "aired", fallback: "Aired")
/// Air Time
Expand All @@ -60,6 +68,8 @@ internal enum L10n {
}
/// Album Artist
internal static let albumArtist = L10n.tr("Localizable", "albumArtist", fallback: "Album Artist")
/// All Audiences
internal static let allAudiences = L10n.tr("Localizable", "allAudiences", fallback: "All Audiences")
/// View all past and present devices that have connected.
internal static let allDevicesDescription = L10n.tr("Localizable", "allDevicesDescription", fallback: "View all past and present devices that have connected.")
/// All Genres
Expand All @@ -68,6 +78,10 @@ internal enum L10n {
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 tags
internal static let allowedTags = L10n.tr("Localizable", "allowedTags", fallback: "Allowed tags")
/// Only show media to this user with at least one of the specified tags.
internal static let allowedTagsDescription = L10n.tr("Localizable", "allowedTagsDescription", fallback: "Only show media to this user with at least one of the specified tags.")
/// Allow media item deletion
internal static let allowItemDeletion = L10n.tr("Localizable", "allowItemDeletion", fallback: "Allow media item deletion")
/// Allow media item editing
Expand Down Expand Up @@ -198,8 +212,18 @@ internal enum L10n {
internal static let bitrateTestDisclaimer = L10n.tr("Localizable", "bitrateTestDisclaimer", fallback: "Longer tests are more accurate but may result in a delayed playback.")
/// bps
internal static let bitsPerSecond = L10n.tr("Localizable", "bitsPerSecond", fallback: "bps")
/// Blocked tags
internal static let blockedTags = L10n.tr("Localizable", "blockedTags", fallback: "Blocked tags")
/// Hide media with at least one of the specified tags.
internal static let blockedTagsDescription = L10n.tr("Localizable", "blockedTagsDescription", fallback: "Hide media with at least one of the specified tags.")
/// Block unrated items
internal static let blockUnratedItems = L10n.tr("Localizable", "blockUnratedItems", fallback: "Block unrated items")
/// Block items from this user with no or unrecognized rating information.
internal static let blockUnratedItemsDescription = L10n.tr("Localizable", "blockUnratedItemsDescription", fallback: "Block items from this user with no or unrecognized rating information.")
/// Blue
internal static let blue = L10n.tr("Localizable", "blue", fallback: "Blue")
/// Books
internal static let books = L10n.tr("Localizable", "books", fallback: "Books")
/// Bugs and Features
internal static let bugsAndFeatures = L10n.tr("Localizable", "bugsAndFeatures", fallback: "Bugs and Features")
/// Buttons
Expand Down Expand Up @@ -736,6 +760,10 @@ internal enum L10n {
internal static let liveTV = L10n.tr("Localizable", "liveTV", fallback: "Live TV")
/// Live TV access
internal static let liveTvAccess = L10n.tr("Localizable", "liveTvAccess", fallback: "Live TV access")
/// Live TV Channels
internal static let liveTVChannels = L10n.tr("Localizable", "liveTVChannels", fallback: "Live TV Channels")
/// Live TV Programs
internal static let liveTVPrograms = L10n.tr("Localizable", "liveTVPrograms", fallback: "Live TV Programs")
/// Live TV recording management
internal static let liveTvRecordingManagement = L10n.tr("Localizable", "liveTvRecordingManagement", fallback: "Live TV recording management")
/// Loading
Expand Down Expand Up @@ -782,6 +810,10 @@ internal enum L10n {
internal static let maximumSessions = L10n.tr("Localizable", "maximumSessions", fallback: "Maximum sessions")
/// Maximum sessions policy
internal static let maximumSessionsPolicy = L10n.tr("Localizable", "maximumSessionsPolicy", fallback: "Maximum sessions policy")
/// Maximum parental rating
internal static let maxParentalRating = L10n.tr("Localizable", "maxParentalRating", fallback: "Maximum parental rating")
/// Content with a higher rating will be hidden from this user.
internal static let maxParentalRatingDescription = L10n.tr("Localizable", "maxParentalRatingDescription", fallback: "Content with a higher rating will be hidden from this user.")
/// This setting may result in media failing to start playback.
internal static let mayResultInPlaybackFailure = L10n.tr("Localizable", "mayResultInPlaybackFailure", fallback: "This setting may result in media failing to start playback.")
/// Media
Expand Down Expand Up @@ -818,6 +850,8 @@ internal enum L10n {
internal static func multipleUsers(_ p1: Int) -> String {
return L10n.tr("Localizable", "multipleUsers", p1, fallback: "%d users")
}
/// Music
internal static let music = L10n.tr("Localizable", "music", fallback: "Music")
/// MVC
internal static let mvc = L10n.tr("Localizable", "mvc", fallback: "MVC")
/// Name
Expand Down Expand Up @@ -926,6 +960,8 @@ internal enum L10n {
internal static func pageOfWithNumbers(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "pageOfWithNumbers", String(describing: p1), String(describing: p2), fallback: "Page %1$@ of %2$@")
}
/// Parental controls
internal static let parentalControls = L10n.tr("Localizable", "parentalControls", fallback: "Parental controls")
/// Parental Rating
internal static let parentalRating = L10n.tr("Localizable", "parentalRating", fallback: "Parental Rating")
/// Password
Expand Down Expand Up @@ -1394,6 +1430,8 @@ internal enum L10n {
internal static let title = L10n.tr("Localizable", "title", fallback: "Title")
/// Too Many Redirects
internal static let tooManyRedirects = L10n.tr("Localizable", "tooManyRedirects", fallback: "Too Many Redirects")
/// Trailers
internal static let trailers = L10n.tr("Localizable", "trailers", fallback: "Trailers")
/// Trailing Value
internal static let trailingValue = L10n.tr("Localizable", "trailingValue", fallback: "Trailing Value")
/// Transcode
Expand Down
18 changes: 18 additions & 0 deletions Swiftfin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */; };
4E2182E52CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
4E2182E62CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
4E2470082D078DD7009139D8 /* ServerUserParentalRatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2470062D078DD7009139D8 /* ServerUserParentalRatingView.swift */; };
4E24ECFB2D076F6200A473A9 /* ListRowCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E24ECFA2D076F2B00A473A9 /* ListRowCheckbox.swift */; };
4E24ECFC2D076F6200A473A9 /* ListRowCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E24ECFA2D076F2B00A473A9 /* ListRowCheckbox.swift */; };
4E2AC4BE2C6C48D200DD600D /* CustomDeviceProfileAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4BD2C6C48D200DD600D /* CustomDeviceProfileAction.swift */; };
Expand Down Expand Up @@ -100,6 +101,8 @@
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
4E63B9FA2C8A5BEF00C25378 /* AdminDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */; };
4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */; };
4E656C302D0798AA00F993F3 /* ParentalRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E656C2F2D0798A900F993F3 /* ParentalRating.swift */; };
4E656C312D0798AA00F993F3 /* ParentalRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E656C2F2D0798A900F993F3 /* ParentalRating.swift */; };
4E6619FC2CEFE2BE00025C99 /* ItemEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6619FB2CEFE2B500025C99 /* ItemEditorViewModel.swift */; };
4E6619FD2CEFE2BE00025C99 /* ItemEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6619FB2CEFE2B500025C99 /* ItemEditorViewModel.swift */; };
4E661A012CEFE39D00025C99 /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E661A002CEFE39900025C99 /* EditMetadataView.swift */; };
Expand Down Expand Up @@ -1181,6 +1184,7 @@
4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskRow.swift; sourceTree = "<group>"; };
4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeSettingsCoordinator.swift; sourceTree = "<group>"; };
4E2182E42CAF67EF0094806B /* PlayMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMethod.swift; sourceTree = "<group>"; };
4E2470062D078DD7009139D8 /* ServerUserParentalRatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerUserParentalRatingView.swift; sourceTree = "<group>"; };
4E24ECFA2D076F2B00A473A9 /* ListRowCheckbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowCheckbox.swift; sourceTree = "<group>"; };
4E2AC4BD2C6C48D200DD600D /* CustomDeviceProfileAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileAction.swift; sourceTree = "<group>"; };
4E2AC4C12C6C491200DD600D /* AudoCodec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudoCodec.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1228,6 +1232,7 @@
4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminDashboardView.swift; sourceTree = "<group>"; };
4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionsViewModel.swift; sourceTree = "<group>"; };
4E656C2F2D0798A900F993F3 /* ParentalRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentalRating.swift; sourceTree = "<group>"; };
4E6619FB2CEFE2B500025C99 /* ItemEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEditorViewModel.swift; sourceTree = "<group>"; };
4E661A002CEFE39900025C99 /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
4E661A042CEFE46300025C99 /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2142,6 +2147,14 @@
path = Components;
sourceTree = "<group>";
};
4E2470072D078DD7009139D8 /* ServerUserParentalRatingView */ = {
isa = PBXGroup;
children = (
4E2470062D078DD7009139D8 /* ServerUserParentalRatingView.swift */,
);
path = ServerUserParentalRatingView;
sourceTree = "<group>";
};
4E2AC4C02C6C48EB00DD600D /* MediaComponents */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2299,6 +2312,7 @@
4E35CE622CBED3FF00DBD886 /* ServerLogsView */,
4E182C9A2C94991800FBEFD5 /* ServerTasksView */,
4EC2B1A72CC9725400D866BE /* ServerUserDetailsView */,
4E2470072D078DD7009139D8 /* ServerUserParentalRatingView */,
4E537A822D03D0FA00659A1A /* ServerUserDeviceAccessView */,
4E537A8C2D04410E00659A1A /* ServerUserLiveTVAccessView */,
4EF3D80A2CF7D6670081AD20 /* ServerUserAccessView */,
Expand Down Expand Up @@ -4234,6 +4248,7 @@
E122A9122788EAAD0060FA63 /* MediaStream.swift */,
4E661A2D2CEFE77700025C99 /* MetadataField.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGuidPair.swift */,
4E656C2F2D0798A900F993F3 /* ParentalRating.swift */,
4EFE0C7C2D0156A500D4834D /* PersonKind.swift */,
E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */,
4E2182E42CAF67EF0094806B /* PlayMethod.swift */,
Expand Down Expand Up @@ -5162,6 +5177,7 @@
E1356E0429A731EB00382563 /* SeparatorHStack.swift in Sources */,
E1575E69293E77B5001665B1 /* ItemSortBy.swift in Sources */,
E1B490482967E2E500D3EDCE /* CoreStore.swift in Sources */,
4E656C312D0798AA00F993F3 /* ParentalRating.swift in Sources */,
E1DC9845296DECB600982F06 /* ProgressIndicator.swift in Sources */,
E1C925F928875647002A7A66 /* LatestInLibraryView.swift in Sources */,
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
Expand Down Expand Up @@ -5384,6 +5400,7 @@
6220D0B426D5ED8000B8E046 /* LibraryCoordinator.swift in Sources */,
E17AC96D2954E9CA003D2BC2 /* DownloadListView.swift in Sources */,
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */,
4E2470082D078DD7009139D8 /* ServerUserParentalRatingView.swift in Sources */,
E1A1528828FD229500600579 /* ChevronButton.swift in Sources */,
E1CB75732C80E71800217C76 /* DirectPlayProfile.swift in Sources */,
E1B490472967E2E500D3EDCE /* CoreStore.swift in Sources */,
Expand Down Expand Up @@ -5546,6 +5563,7 @@
4E2AC4C52C6C492700DD600D /* MediaContainer.swift in Sources */,
4E2AC4CB2C6C494E00DD600D /* VideoCodec.swift in Sources */,
E1EA09692BED78BB004CDE76 /* UserAccessPolicy.swift in Sources */,
4E656C302D0798AA00F993F3 /* ParentalRating.swift in Sources */,
E18E0204288749200022598C /* RowDivider.swift in Sources */,
E18E01DA288747230022598C /* iPadOSEpisodeContentView.swift in Sources */,
E1CB75752C80EAFA00217C76 /* ArrayBuilder.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions Swiftfin/Components/LearnMoreButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ struct LearnMoreButton: View {
private var learnMoreView: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
SeparatorVStack(alignment: .leading) {
Divider()
} content: {
ForEach(items) { content in
VStack(alignment: .leading, spacing: 8) {
Text(content.title)
Expand All @@ -53,7 +55,7 @@ struct LearnMoreButton: View {
.font(.subheadline)
.foregroundStyle(.secondary)
}
Divider()
.padding(.vertical, 16)
}
}
.edgePadding()
Expand Down
Loading