[tvOS] Mirror iOS Ratings + Attribute Settings (#1422)

* Copy + Paste + Settings

* Much bigger changes to allow attribute customization.

* wip

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
Joe Kribs 2025-02-15 15:27:34 -07:00 committed by GitHub
parent 846aabc868
commit c113c341bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 271 additions and 141 deletions

View File

@ -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<BasicNavigationViewCoordinator> {
@ -27,6 +29,15 @@ final class CustomizeSettingsCoordinator: NavigationCoordinatable {
}
}
func makeItemViewAttributes(selection: Binding<[ItemViewAttribute]>) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
OrderedSectionSelectorView(selection: selection, sources: ItemViewAttribute.allCases)
.systemImage("list.bullet.rectangle.fill")
.navigationTitle(L10n.mediaAttributes.localizedCapitalized)
}
}
@ViewBuilder
func makeListColumnSettings(selection: Binding<Int>) -> some View {
ListColumnsPickerView(selection: selection)
}

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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.

View File

@ -172,5 +172,13 @@ extension StoredValues.Keys {
default: false
)
}
static var itemViewAttributes: Key<[ItemViewAttribute]> {
CurrentUserKey(
"itemViewAttributes",
domain: "itemViewAttributes",
default: ItemViewAttribute.allCases
)
}
}
}

View File

@ -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) {
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 {
if criticRating >= 60 {
Image(.tomatoFresh)
.symbolRenderingMode(.hierarchical)
} else {
Image(.tomatoRotten)
}
}
.font(.caption2)
Text("\(criticRating, specifier: "%.0f")")
}
.asAttributeStyle(.outline)
}
case .ratingCommunity:
if let communityRating = viewModel.item.communityRating {
HStack(spacing: 2) {
Image(systemName: "star.fill")
.font(.caption2)
Text("\(communityRating, specifier: "%.1f")")
}
.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))
}
}
}

View File

@ -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) {
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)
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)
}
}
}

View File

@ -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 = "<group>"; };
4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksView.swift; sourceTree = "<group>"; };
4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskRow.swift; sourceTree = "<group>"; };
4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewAttributes.swift; sourceTree = "<group>"; };
4E1AA0032D0640A400524970 /* RemoteImageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImageInfo.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>"; };
@ -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 */,

View File

@ -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))
}
}
}

View File

@ -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)
Section(L10n.items) {
ChevronButton(L10n.mediaAttributes)
.onSelect {
router.route(to: \.itemViewAttributes, $itemViewAttributes)
}
/// 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)
} */
/// 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)
} */
}
}
}

View File

@ -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";