diff --git a/Shared/Coordinators/CustomizeSettingsCoordinator.swift b/Shared/Coordinators/CustomizeSettingsCoordinator.swift index 91cc4869a..824f4288f 100644 --- a/Shared/Coordinators/CustomizeSettingsCoordinator.swift +++ b/Shared/Coordinators/CustomizeSettingsCoordinator.swift @@ -19,6 +19,8 @@ final class CustomizeSettingsCoordinator: NavigationCoordinatable { @Route(.modal) var indicatorSettings = makeIndicatorSettings @Route(.modal) + var itemViewAttributes = makeItemViewAttributes + @Route(.push) var listColumnSettings = makeListColumnSettings func makeIndicatorSettings() -> NavigationViewCoordinator { @@ -27,6 +29,15 @@ final class CustomizeSettingsCoordinator: NavigationCoordinatable { } } + func makeItemViewAttributes(selection: Binding<[ItemViewAttribute]>) -> NavigationViewCoordinator { + NavigationViewCoordinator { + OrderedSectionSelectorView(selection: selection, sources: ItemViewAttribute.allCases) + .systemImage("list.bullet.rectangle.fill") + .navigationTitle(L10n.mediaAttributes.localizedCapitalized) + } + } + + @ViewBuilder func makeListColumnSettings(selection: Binding) -> some View { ListColumnsPickerView(selection: selection) } diff --git a/Shared/Coordinators/SettingsCoordinator.swift b/Shared/Coordinators/SettingsCoordinator.swift index 56b5cd170..57167b74f 100644 --- a/Shared/Coordinators/SettingsCoordinator.swift +++ b/Shared/Coordinators/SettingsCoordinator.swift @@ -44,6 +44,8 @@ final class SettingsCoordinator: NavigationCoordinatable { @Route(.push) var indicatorSettings = makeIndicatorSettings @Route(.push) + var itemViewAttributes = makeItemViewAttributes + @Route(.push) var serverConnection = makeServerConnection @Route(.push) var videoPlayerSettings = makeVideoPlayerSettings @@ -149,6 +151,12 @@ final class SettingsCoordinator: NavigationCoordinatable { IndicatorSettingsView() } + @ViewBuilder + func makeItemViewAttributes(selection: Binding<[ItemViewAttribute]>) -> some View { + OrderedSectionSelectorView(selection: selection, sources: ItemViewAttribute.allCases) + .navigationTitle(L10n.mediaAttributes.localizedCapitalized) + } + @ViewBuilder func makeServerConnection(server: ServerState) -> some View { EditServerView(server: server) diff --git a/Shared/Objects/ItemViewAttributes.swift b/Shared/Objects/ItemViewAttributes.swift new file mode 100644 index 000000000..96578944e --- /dev/null +++ b/Shared/Objects/ItemViewAttributes.swift @@ -0,0 +1,34 @@ +// +// 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) 2025 Jellyfin & Jellyfin Contributors +// + +enum ItemViewAttribute: String, CaseIterable, Displayable, Storable { + + case ratingCritics + case ratingCommunity + case ratingOfficial + case videoQuality + case audioChannels + case subtitles + + var displayTitle: String { + switch self { + case .ratingCritics: + return L10n.criticRating + case .ratingCommunity: + return L10n.communityRating + case .ratingOfficial: + return L10n.parentalRating + case .videoQuality: + return L10n.video + case .audioChannels: + return L10n.audio + case .subtitles: + return L10n.subtitles + } + } +} diff --git a/Shared/Strings/Strings.swift b/Shared/Strings/Strings.swift index 2239cd641..aac334e96 100644 --- a/Shared/Strings/Strings.swift +++ b/Shared/Strings/Strings.swift @@ -280,6 +280,8 @@ internal enum L10n { internal static let columns = L10n.tr("Localizable", "columns", fallback: "Columns") /// Community internal static let community = L10n.tr("Localizable", "community", fallback: "Community") + /// Community rating + internal static let communityRating = L10n.tr("Localizable", "communityRating", fallback: "Community rating") /// Compact internal static let compact = L10n.tr("Localizable", "compact", fallback: "Compact") /// Compact Logo @@ -338,12 +340,14 @@ internal enum L10n { } /// Creator internal static let creator = L10n.tr("Localizable", "creator", fallback: "Creator") + /// Critic rating + internal static let criticRating = L10n.tr("Localizable", "criticRating", fallback: "Critic rating") /// Critics internal static let critics = L10n.tr("Localizable", "critics", fallback: "Critics") /// Current internal static let current = L10n.tr("Localizable", "current", fallback: "Current") - /// Current Password - internal static let currentPassword = L10n.tr("Localizable", "currentPassword", fallback: "Current Password") + /// Current password + internal static let currentPassword = L10n.tr("Localizable", "currentPassword", fallback: "Current password") /// Custom internal static let custom = L10n.tr("Localizable", "custom", fallback: "Custom") /// Custom bitrate @@ -368,10 +372,10 @@ internal enum L10n { internal static let customFailedLogins = L10n.tr("Localizable", "customFailedLogins", fallback: "Custom failed logins") /// Customize internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize") - /// Custom Profile - internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom Profile") - /// Custom Rating - internal static let customRating = L10n.tr("Localizable", "customRating", fallback: "Custom Rating") + /// Custom profile + internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom profile") + /// Custom rating + internal static let customRating = L10n.tr("Localizable", "customRating", fallback: "Custom rating") /// Custom sessions internal static let customSessions = L10n.tr("Localizable", "customSessions", fallback: "Custom sessions") /// Daily @@ -384,10 +388,10 @@ internal enum L10n { internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.") /// Date Added internal static let dateAdded = L10n.tr("Localizable", "dateAdded", fallback: "Date Added") - /// Date Created - internal static let dateCreated = L10n.tr("Localizable", "dateCreated", fallback: "Date Created") - /// Date Modified - internal static let dateModified = L10n.tr("Localizable", "dateModified", fallback: "Date Modified") + /// Date created + internal static let dateCreated = L10n.tr("Localizable", "dateCreated", fallback: "Date created") + /// Date modified + internal static let dateModified = L10n.tr("Localizable", "dateModified", fallback: "Date modified") /// Date of death internal static let dateOfDeath = L10n.tr("Localizable", "dateOfDeath", fallback: "Date of death") /// Dates @@ -788,6 +792,8 @@ internal enum L10n { internal static let media = L10n.tr("Localizable", "media", fallback: "Media") /// Media Access internal static let mediaAccess = L10n.tr("Localizable", "mediaAccess", fallback: "Media Access") + /// Media attributes + internal static let mediaAttributes = L10n.tr("Localizable", "mediaAttributes", fallback: "Media attributes") /// Media downloads internal static let mediaDownloads = L10n.tr("Localizable", "mediaDownloads", fallback: "Media downloads") /// Media playback @@ -872,8 +878,8 @@ internal enum L10n { } /// No title internal static let noTitle = L10n.tr("Localizable", "noTitle", fallback: "No title") - /// Official Rating - internal static let officialRating = L10n.tr("Localizable", "officialRating", fallback: "Official Rating") + /// Official rating + internal static let officialRating = L10n.tr("Localizable", "officialRating", fallback: "Official rating") /// Offset internal static let offset = L10n.tr("Localizable", "offset", fallback: "Offset") /// OK @@ -902,8 +908,8 @@ internal enum L10n { internal static let overview = L10n.tr("Localizable", "overview", fallback: "Overview") /// 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") + /// Parental rating + internal static let parentalRating = L10n.tr("Localizable", "parentalRating", fallback: "Parental rating") /// Password internal static let password = L10n.tr("Localizable", "password", fallback: "Password") /// User password has been changed. @@ -994,8 +1000,8 @@ internal enum L10n { internal static let quickConnectSuccessMessage = L10n.tr("Localizable", "quickConnectSuccessMessage", fallback: "Authorizing Quick Connect successful. Please continue on your other device.") /// Random internal static let random = L10n.tr("Localizable", "random", fallback: "Random") - /// Random Image - internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random Image") + /// Random image + internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random image") /// Rating internal static let rating = L10n.tr("Localizable", "rating", fallback: "Rating") /// %@ rating on a scale from 1 to 10. diff --git a/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift b/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift index 7e12270d3..7a0d2e702 100644 --- a/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift +++ b/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift @@ -172,5 +172,13 @@ extension StoredValues.Keys { default: false ) } + + static var itemViewAttributes: Key<[ItemViewAttribute]> { + CurrentUserKey( + "itemViewAttributes", + domain: "itemViewAttributes", + default: ItemViewAttribute.allCases + ) + } } } diff --git a/Swiftfin tvOS/Views/ItemView/Components/AttributeHStack.swift b/Swiftfin tvOS/Views/ItemView/Components/AttributeHStack.swift index d553901bd..31cfc3795 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/AttributeHStack.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/AttributeHStack.swift @@ -9,49 +9,81 @@ import SwiftUI extension ItemView { - struct AttributesHStack: View { - @ObservedObject var viewModel: ItemViewModel + @StoredValue(.User.itemViewAttributes) + private var itemViewAttributes + var body: some View { HStack(spacing: 25) { - - if let officialRating = viewModel.item.officialRating { - Text(officialRating) - .asAttributeStyle(.outline) + ForEach(itemViewAttributes, id: \.self) { attribute in + getAttribute(attribute) } + } + .foregroundStyle(Color(UIColor.darkGray)) + } - if let mediaStreams = viewModel.selectedMediaSource?.mediaStreams { - - if mediaStreams.hasHDVideo { - Text("HD") - .asAttributeStyle(.fill) - } - - if mediaStreams.has4KVideo { - Text("4K") - .asAttributeStyle(.fill) - } - - if mediaStreams.has51AudioChannelLayout { - Text("5.1") - .asAttributeStyle(.fill) - } + @ViewBuilder + func getAttribute(_ attribute: ItemViewAttribute) -> some View { + switch attribute { + case .ratingCritics: + if let criticRating = viewModel.item.criticRating { + HStack(spacing: 2) { + Group { + if criticRating >= 60 { + Image(.tomatoFresh) + .symbolRenderingMode(.hierarchical) + } else { + Image(.tomatoRotten) + } + } + .font(.caption2) - if mediaStreams.has71AudioChannelLayout { - Text("7.1") - .asAttributeStyle(.fill) + Text("\(criticRating, specifier: "%.0f")") } + .asAttributeStyle(.outline) + } + case .ratingCommunity: + if let communityRating = viewModel.item.communityRating { + HStack(spacing: 2) { + Image(systemName: "star.fill") + .font(.caption2) - if mediaStreams.hasSubtitles { - Text("CC") - .asAttributeStyle(.outline) + Text("\(communityRating, specifier: "%.1f")") } + .asAttributeStyle(.outline) + } + case .ratingOfficial: + if let officialRating = viewModel.item.officialRating { + Text(officialRating) + .asAttributeStyle(.outline) + } + case .videoQuality: + if viewModel.selectedMediaSource?.mediaStreams?.hasHDVideo == true { + Text("HD") + .asAttributeStyle(.fill) + } + if viewModel.selectedMediaSource?.mediaStreams?.has4KVideo == true { + Text("4K") + .asAttributeStyle(.fill) + } + case .audioChannels: + if viewModel.selectedMediaSource?.mediaStreams?.has51AudioChannelLayout == true { + Text("5.1") + .asAttributeStyle(.fill) + } + if viewModel.selectedMediaSource?.mediaStreams?.has71AudioChannelLayout == true { + Text("7.1") + .asAttributeStyle(.fill) + } + case .subtitles: + if viewModel.selectedMediaSource?.mediaStreams?.hasSubtitles == true { + Text("CC") + .asAttributeStyle(.outline) } } - .foregroundColor(Color(UIColor.darkGray)) } } } diff --git a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift index 5ea483ddf..861d8f008 100644 --- a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift +++ b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift @@ -17,6 +17,12 @@ extension CustomizeViewsSettings { @Injected(\.currentUserSession) private var userSession + @EnvironmentObject + private var router: CustomizeSettingsCoordinator.Router + + @StoredValue(.User.itemViewAttributes) + private var itemViewAttributes + @StoredValue(.User.enableItemEditing) private var enableItemEditing @StoredValue(.User.enableItemDeletion) @@ -25,24 +31,24 @@ extension CustomizeViewsSettings { private var enableCollectionManagement var body: some View { - if userSession?.user.permissions.items.canEditMetadata ?? false || - userSession?.user.permissions.items.canDelete ?? false || - userSession?.user.permissions.items.canManageCollections ?? false - { - - Section(L10n.items) { - /// Enable Refreshing Items from All Visible LIbraries - if userSession?.user.permissions.items.canEditMetadata ?? false { - Toggle(L10n.allowItemEditing, isOn: $enableItemEditing) - } - /// Enable Deleting Items from Approved Libraries - if userSession?.user.permissions.items.canDelete ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - } - /// Enable Refreshing & Deleting Collections - if userSession?.user.permissions.items.canManageCollections ?? false { - Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement) + Section(L10n.items) { + + ChevronButton(L10n.mediaAttributes) + .onSelect { + router.route(to: \.itemViewAttributes, $itemViewAttributes) } + + /// Enable Refreshing Items from All Visible LIbraries + if userSession?.user.permissions.items.canEditMetadata ?? false { + Toggle(L10n.allowItemEditing, isOn: $enableItemEditing) + } + /// Enable Deleting Items from Approved Libraries + if userSession?.user.permissions.items.canDelete ?? false { + Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) + } + /// Enable Refreshing & Deleting Collections + if userSession?.user.permissions.items.canManageCollections ?? false { + Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement) } } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 0ef417b36..fe457af8a 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ 4E17498F2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; }; 4E182C9C2C94993200FBEFD5 /* ServerTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */; }; 4E182C9F2C94A1E000FBEFD5 /* ServerTaskRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */; }; + 4E1A39332D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */; }; + 4E1A39342D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */; }; 4E1AA0042D0640AA00524970 /* RemoteImageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1AA0032D0640A400524970 /* RemoteImageInfo.swift */; }; 4E1AA0052D0640AA00524970 /* RemoteImageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1AA0032D0640A400524970 /* RemoteImageInfo.swift */; }; 4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */; }; @@ -1285,6 +1287,7 @@ 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; 4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksView.swift; sourceTree = ""; }; 4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskRow.swift; sourceTree = ""; }; + 4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewAttributes.swift; sourceTree = ""; }; 4E1AA0032D0640A400524970 /* RemoteImageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImageInfo.swift; sourceTree = ""; }; 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeSettingsCoordinator.swift; sourceTree = ""; }; 4E2182E42CAF67EF0094806B /* PlayMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMethod.swift; sourceTree = ""; }; @@ -3315,6 +3318,7 @@ 4EFE0C7F2D02054300D4834D /* ItemArrayElements.swift */, E14EDECA2B8FB66F000F00A4 /* ItemFilter */, E1C925F328875037002A7A66 /* ItemViewType.swift */, + 4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */, E13F05EB28BC9000003499D2 /* LibraryDisplayType.swift */, E1DE2B4E2B983F3200F6715F /* LibraryParent */, 4E2AC4C02C6C48EB00DD600D /* MediaComponents */, @@ -6158,6 +6162,7 @@ 53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */, E148128628C15475003B8787 /* SortOrder+ItemSortOrder.swift in Sources */, E1CB75722C80E71800217C76 /* DirectPlayProfile.swift in Sources */, + 4E1A39332D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */, E1E1E24E28DF8A2E000DF5FD /* PreferenceKeys.swift in Sources */, E1575E9B293E7B1E001665B1 /* EnvironmentValue+Keys.swift in Sources */, E133328929538D8D00EE76AB /* Files.swift in Sources */, @@ -6882,6 +6887,7 @@ E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */, 4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */, E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */, + 4E1A39342D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */, 4EC1C8522C7FDFA300E2879E /* PlaybackDeviceProfile.swift in Sources */, 4EA09DE12CC4E4F100CB27E4 /* APIKeysView.swift in Sources */, DFB7C3DF2C7AA43A00CE7CDC /* UserSignInState.swift in Sources */, diff --git a/Swiftfin/Views/ItemView/Components/AttributeHStack.swift b/Swiftfin/Views/ItemView/Components/AttributeHStack.swift index 59a64414b..db586a394 100644 --- a/Swiftfin/Views/ItemView/Components/AttributeHStack.swift +++ b/Swiftfin/Views/ItemView/Components/AttributeHStack.swift @@ -9,14 +9,26 @@ import SwiftUI extension ItemView { - struct AttributesHStack: View { - @ObservedObject var viewModel: ItemViewModel + @StoredValue(.User.itemViewAttributes) + private var itemViewAttributes + var body: some View { HStack { + ForEach(itemViewAttributes, id: \.self) { attribute in + getAttribute(attribute) + } + } + .foregroundStyle(Color(UIColor.darkGray)) + } + + @ViewBuilder + func getAttribute(_ attribute: ItemViewAttribute) -> some View { + switch attribute { + case .ratingCritics: if let criticRating = viewModel.item.criticRating { HStack(spacing: 2) { Group { @@ -33,7 +45,7 @@ extension ItemView { } .asAttributeStyle(.outline) } - + case .ratingCommunity: if let communityRating = viewModel.item.communityRating { HStack(spacing: 2) { Image(systemName: "star.fill") @@ -43,41 +55,35 @@ extension ItemView { } .asAttributeStyle(.outline) } - + case .ratingOfficial: if let officialRating = viewModel.item.officialRating { Text(officialRating) .asAttributeStyle(.outline) } - - if let mediaStreams = viewModel.selectedMediaSource?.mediaStreams { - - if mediaStreams.hasHDVideo { - Text("HD") - .asAttributeStyle(.fill) - } - - if mediaStreams.has4KVideo { - Text("4K") - .asAttributeStyle(.fill) - } - - if mediaStreams.has51AudioChannelLayout { - Text("5.1") - .asAttributeStyle(.fill) - } - - if mediaStreams.has71AudioChannelLayout { - Text("7.1") - .asAttributeStyle(.fill) - } - - if mediaStreams.hasSubtitles { - Text("CC") - .asAttributeStyle(.outline) - } + case .videoQuality: + if viewModel.selectedMediaSource?.mediaStreams?.hasHDVideo == true { + Text("HD") + .asAttributeStyle(.fill) + } + if viewModel.selectedMediaSource?.mediaStreams?.has4KVideo == true { + Text("4K") + .asAttributeStyle(.fill) + } + case .audioChannels: + if viewModel.selectedMediaSource?.mediaStreams?.has51AudioChannelLayout == true { + Text("5.1") + .asAttributeStyle(.fill) + } + if viewModel.selectedMediaSource?.mediaStreams?.has71AudioChannelLayout == true { + Text("7.1") + .asAttributeStyle(.fill) + } + case .subtitles: + if viewModel.selectedMediaSource?.mediaStreams?.hasSubtitles == true { + Text("CC") + .asAttributeStyle(.outline) } } - .foregroundColor(Color(UIColor.darkGray)) } } } diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift index 0e08a81c5..713871c34 100644 --- a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift @@ -17,6 +17,12 @@ extension CustomizeViewsSettings { @Injected(\.currentUserSession) private var userSession + @EnvironmentObject + private var router: SettingsCoordinator.Router + + @StoredValue(.User.itemViewAttributes) + private var itemViewAttributes + @StoredValue(.User.enableItemEditing) private var enableItemEditing @StoredValue(.User.enableItemDeletion) @@ -25,39 +31,37 @@ extension CustomizeViewsSettings { private var enableCollectionManagement var body: some View { - if userSession?.user.permissions.items.canEditMetadata ?? false - || userSession?.user.permissions.items.canDelete ?? false - // || userSession?.user.permissions.items.canDownload ?? false - || userSession?.user.permissions.items.canManageCollections ?? false - // || userSession?.user.permissions.items.canManageLyrics ?? false - // || userSession?.user.permissions.items.canManageSubtitles - { - Section(L10n.items) { - /// Enable Editing Items from All Visible LIbraries - if userSession?.user.permissions.items.canEditMetadata ?? false { - Toggle(L10n.allowItemEditing, isOn: $enableItemEditing) - } - /// Enable Deleting Items from Approved Libraries - if userSession?.user.permissions.items.canDelete ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - } - /// Enable Downloading All Items - /* if userSession?.user.permissions.items.canDownload ?? false { - Toggle(L10n.allowItemDownloading, isOn: $enableItemDownloads) - } */ - /// Enable Deleting or Editing Collections - if userSession?.user.permissions.items.canManageCollections ?? false { - Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement) + Section(L10n.items) { + + ChevronButton(L10n.mediaAttributes) + .onSelect { + router.route(to: \.itemViewAttributes, $itemViewAttributes) } - /// Manage Item Lyrics - /* if userSession?.user.permissions.items.canManageLyrics ?? false { - Toggle(L10n.allowLyricsManagement isOn: $enableLyricsManagement) - } */ - /// Manage Item Subtitles - /* if userSession?.user.items.canManageSubtitles ?? false { - Toggle(L10n.allowSubtitleManagement, isOn: $enableSubtitleManagement) - } */ + + /// Enable Editing Items from All Visible LIbraries + if userSession?.user.permissions.items.canEditMetadata ?? false { + Toggle(L10n.allowItemEditing, isOn: $enableItemEditing) + } + /// Enable Deleting Items from Approved Libraries + if userSession?.user.permissions.items.canDelete ?? false { + Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) + } + /// Enable Downloading All Items + /* if userSession?.user.permissions.items.canDownload ?? false { + Toggle(L10n.allowItemDownloading, isOn: $enableItemDownloads) + } */ + /// Enable Deleting or Editing Collections + if userSession?.user.permissions.items.canManageCollections ?? false { + Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement) } + /// Manage Item Lyrics + /* if userSession?.user.permissions.items.canManageLyrics ?? false { + Toggle(L10n.allowLyricsManagement isOn: $enableLyricsManagement) + } */ + /// Manage Item Subtitles + /* if userSession?.user.items.canManageSubtitles ?? false { + Toggle(L10n.allowSubtitleManagement, isOn: $enableSubtitleManagement) + } */ } } } diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 2bea7ad19..f8fb06726 100644 --- a/Translations/en.lproj/Localizable.strings +++ b/Translations/en.lproj/Localizable.strings @@ -394,6 +394,9 @@ /// Community "community" = "Community"; +/// Community rating +"communityRating" = "Community rating"; + /// Compact "compact" = "Compact"; @@ -478,14 +481,17 @@ /// Creator "creator" = "Creator"; +/// Critic rating +"criticRating" = "Critic rating"; + /// Critics "critics" = "Critics"; /// Current "current" = "Current"; -/// Current Password -"currentPassword" = "Current Password"; +/// Current password +"currentPassword" = "Current password"; /// Custom "custom" = "Custom"; @@ -520,11 +526,11 @@ /// Customize "customize" = "Customize"; -/// Custom Profile -"customProfile" = "Custom Profile"; +/// Custom profile +"customProfile" = "Custom profile"; -/// Custom Rating -"customRating" = "Custom Rating"; +/// Custom rating +"customRating" = "Custom rating"; /// Custom sessions "customSessions" = "Custom sessions"; @@ -544,11 +550,11 @@ /// Date Added "dateAdded" = "Date Added"; -/// Date Created -"dateCreated" = "Date Created"; +/// Date created +"dateCreated" = "Date created"; -/// Date Modified -"dateModified" = "Date Modified"; +/// Date modified +"dateModified" = "Date modified"; /// Date of death "dateOfDeath" = "Date of death"; @@ -1117,6 +1123,9 @@ /// Media Access "mediaAccess" = "Media Access"; +/// Media attributes +"mediaAttributes" = "Media attributes"; + /// Media downloads "mediaDownloads" = "Media downloads"; @@ -1240,8 +1249,8 @@ /// No title "noTitle" = "No title"; -/// Official Rating -"officialRating" = "Official Rating"; +/// Official rating +"officialRating" = "Official rating"; /// Offset "offset" = "Offset"; @@ -1285,8 +1294,8 @@ /// Parental controls "parentalControls" = "Parental controls"; -/// Parental Rating -"parentalRating" = "Parental Rating"; +/// Parental rating +"parentalRating" = "Parental rating"; /// Password "password" = "Password"; @@ -1423,8 +1432,8 @@ /// Random "random" = "Random"; -/// Random Image -"randomImage" = "Random Image"; +/// Random image +"randomImage" = "Random image"; /// Rating "rating" = "Rating";