[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:
parent
846aabc868
commit
c113c341bf
|
@ -19,6 +19,8 @@ final class CustomizeSettingsCoordinator: NavigationCoordinatable {
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var indicatorSettings = makeIndicatorSettings
|
var indicatorSettings = makeIndicatorSettings
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
|
var itemViewAttributes = makeItemViewAttributes
|
||||||
|
@Route(.push)
|
||||||
var listColumnSettings = makeListColumnSettings
|
var listColumnSettings = makeListColumnSettings
|
||||||
|
|
||||||
func makeIndicatorSettings() -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
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 {
|
func makeListColumnSettings(selection: Binding<Int>) -> some View {
|
||||||
ListColumnsPickerView(selection: selection)
|
ListColumnsPickerView(selection: selection)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var indicatorSettings = makeIndicatorSettings
|
var indicatorSettings = makeIndicatorSettings
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
var itemViewAttributes = makeItemViewAttributes
|
||||||
|
@Route(.push)
|
||||||
var serverConnection = makeServerConnection
|
var serverConnection = makeServerConnection
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var videoPlayerSettings = makeVideoPlayerSettings
|
var videoPlayerSettings = makeVideoPlayerSettings
|
||||||
|
@ -149,6 +151,12 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
IndicatorSettingsView()
|
IndicatorSettingsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func makeItemViewAttributes(selection: Binding<[ItemViewAttribute]>) -> some View {
|
||||||
|
OrderedSectionSelectorView(selection: selection, sources: ItemViewAttribute.allCases)
|
||||||
|
.navigationTitle(L10n.mediaAttributes.localizedCapitalized)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeServerConnection(server: ServerState) -> some View {
|
func makeServerConnection(server: ServerState) -> some View {
|
||||||
EditServerView(server: server)
|
EditServerView(server: server)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -280,6 +280,8 @@ internal enum L10n {
|
||||||
internal static let columns = L10n.tr("Localizable", "columns", fallback: "Columns")
|
internal static let columns = L10n.tr("Localizable", "columns", fallback: "Columns")
|
||||||
/// Community
|
/// Community
|
||||||
internal static let community = L10n.tr("Localizable", "community", fallback: "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
|
/// Compact
|
||||||
internal static let compact = L10n.tr("Localizable", "compact", fallback: "Compact")
|
internal static let compact = L10n.tr("Localizable", "compact", fallback: "Compact")
|
||||||
/// Compact Logo
|
/// Compact Logo
|
||||||
|
@ -338,12 +340,14 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
/// Creator
|
/// Creator
|
||||||
internal static let creator = L10n.tr("Localizable", "creator", fallback: "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
|
/// Critics
|
||||||
internal static let critics = L10n.tr("Localizable", "critics", fallback: "Critics")
|
internal static let critics = L10n.tr("Localizable", "critics", fallback: "Critics")
|
||||||
/// Current
|
/// Current
|
||||||
internal static let current = L10n.tr("Localizable", "current", fallback: "Current")
|
internal static let current = L10n.tr("Localizable", "current", fallback: "Current")
|
||||||
/// Current Password
|
/// Current password
|
||||||
internal static let currentPassword = L10n.tr("Localizable", "currentPassword", fallback: "Current Password")
|
internal static let currentPassword = L10n.tr("Localizable", "currentPassword", fallback: "Current password")
|
||||||
/// Custom
|
/// Custom
|
||||||
internal static let custom = L10n.tr("Localizable", "custom", fallback: "Custom")
|
internal static let custom = L10n.tr("Localizable", "custom", fallback: "Custom")
|
||||||
/// Custom bitrate
|
/// Custom bitrate
|
||||||
|
@ -368,10 +372,10 @@ internal enum L10n {
|
||||||
internal static let customFailedLogins = L10n.tr("Localizable", "customFailedLogins", fallback: "Custom failed logins")
|
internal static let customFailedLogins = L10n.tr("Localizable", "customFailedLogins", fallback: "Custom failed logins")
|
||||||
/// Customize
|
/// Customize
|
||||||
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
||||||
/// Custom Profile
|
/// Custom profile
|
||||||
internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom Profile")
|
internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom profile")
|
||||||
/// Custom Rating
|
/// Custom rating
|
||||||
internal static let customRating = L10n.tr("Localizable", "customRating", fallback: "Custom Rating")
|
internal static let customRating = L10n.tr("Localizable", "customRating", fallback: "Custom rating")
|
||||||
/// Custom sessions
|
/// Custom sessions
|
||||||
internal static let customSessions = L10n.tr("Localizable", "customSessions", fallback: "Custom sessions")
|
internal static let customSessions = L10n.tr("Localizable", "customSessions", fallback: "Custom sessions")
|
||||||
/// Daily
|
/// Daily
|
||||||
|
@ -384,10 +388,10 @@ internal enum L10n {
|
||||||
internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.")
|
internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.")
|
||||||
/// Date Added
|
/// Date Added
|
||||||
internal static let dateAdded = L10n.tr("Localizable", "dateAdded", fallback: "Date Added")
|
internal static let dateAdded = L10n.tr("Localizable", "dateAdded", fallback: "Date Added")
|
||||||
/// Date Created
|
/// Date created
|
||||||
internal static let dateCreated = L10n.tr("Localizable", "dateCreated", fallback: "Date Created")
|
internal static let dateCreated = L10n.tr("Localizable", "dateCreated", fallback: "Date created")
|
||||||
/// Date Modified
|
/// Date modified
|
||||||
internal static let dateModified = L10n.tr("Localizable", "dateModified", fallback: "Date Modified")
|
internal static let dateModified = L10n.tr("Localizable", "dateModified", fallback: "Date modified")
|
||||||
/// Date of death
|
/// Date of death
|
||||||
internal static let dateOfDeath = L10n.tr("Localizable", "dateOfDeath", fallback: "Date of death")
|
internal static let dateOfDeath = L10n.tr("Localizable", "dateOfDeath", fallback: "Date of death")
|
||||||
/// Dates
|
/// Dates
|
||||||
|
@ -788,6 +792,8 @@ internal enum L10n {
|
||||||
internal static let media = L10n.tr("Localizable", "media", fallback: "Media")
|
internal static let media = L10n.tr("Localizable", "media", fallback: "Media")
|
||||||
/// Media Access
|
/// Media Access
|
||||||
internal static let mediaAccess = L10n.tr("Localizable", "mediaAccess", fallback: "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
|
/// Media downloads
|
||||||
internal static let mediaDownloads = L10n.tr("Localizable", "mediaDownloads", fallback: "Media downloads")
|
internal static let mediaDownloads = L10n.tr("Localizable", "mediaDownloads", fallback: "Media downloads")
|
||||||
/// Media playback
|
/// Media playback
|
||||||
|
@ -872,8 +878,8 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
/// No title
|
/// No title
|
||||||
internal static let noTitle = L10n.tr("Localizable", "noTitle", fallback: "No title")
|
internal static let noTitle = L10n.tr("Localizable", "noTitle", fallback: "No title")
|
||||||
/// Official Rating
|
/// Official rating
|
||||||
internal static let officialRating = L10n.tr("Localizable", "officialRating", fallback: "Official Rating")
|
internal static let officialRating = L10n.tr("Localizable", "officialRating", fallback: "Official rating")
|
||||||
/// Offset
|
/// Offset
|
||||||
internal static let offset = L10n.tr("Localizable", "offset", fallback: "Offset")
|
internal static let offset = L10n.tr("Localizable", "offset", fallback: "Offset")
|
||||||
/// OK
|
/// OK
|
||||||
|
@ -902,8 +908,8 @@ internal enum L10n {
|
||||||
internal static let overview = L10n.tr("Localizable", "overview", fallback: "Overview")
|
internal static let overview = L10n.tr("Localizable", "overview", fallback: "Overview")
|
||||||
/// Parental controls
|
/// Parental controls
|
||||||
internal static let parentalControls = L10n.tr("Localizable", "parentalControls", fallback: "Parental controls")
|
internal static let parentalControls = L10n.tr("Localizable", "parentalControls", fallback: "Parental controls")
|
||||||
/// Parental Rating
|
/// Parental rating
|
||||||
internal static let parentalRating = L10n.tr("Localizable", "parentalRating", fallback: "Parental Rating")
|
internal static let parentalRating = L10n.tr("Localizable", "parentalRating", fallback: "Parental rating")
|
||||||
/// Password
|
/// Password
|
||||||
internal static let password = L10n.tr("Localizable", "password", fallback: "Password")
|
internal static let password = L10n.tr("Localizable", "password", fallback: "Password")
|
||||||
/// User password has been changed.
|
/// 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.")
|
internal static let quickConnectSuccessMessage = L10n.tr("Localizable", "quickConnectSuccessMessage", fallback: "Authorizing Quick Connect successful. Please continue on your other device.")
|
||||||
/// Random
|
/// Random
|
||||||
internal static let random = L10n.tr("Localizable", "random", fallback: "Random")
|
internal static let random = L10n.tr("Localizable", "random", fallback: "Random")
|
||||||
/// Random Image
|
/// Random image
|
||||||
internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random Image")
|
internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random image")
|
||||||
/// Rating
|
/// Rating
|
||||||
internal static let rating = L10n.tr("Localizable", "rating", fallback: "Rating")
|
internal static let rating = L10n.tr("Localizable", "rating", fallback: "Rating")
|
||||||
/// %@ rating on a scale from 1 to 10.
|
/// %@ rating on a scale from 1 to 10.
|
||||||
|
|
|
@ -172,5 +172,13 @@ extension StoredValues.Keys {
|
||||||
default: false
|
default: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var itemViewAttributes: Key<[ItemViewAttribute]> {
|
||||||
|
CurrentUserKey(
|
||||||
|
"itemViewAttributes",
|
||||||
|
domain: "itemViewAttributes",
|
||||||
|
default: ItemViewAttribute.allCases
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,49 +9,81 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct AttributesHStack: View {
|
struct AttributesHStack: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
var viewModel: ItemViewModel
|
||||||
|
|
||||||
|
@StoredValue(.User.itemViewAttributes)
|
||||||
|
private var itemViewAttributes
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 25) {
|
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 {
|
if let officialRating = viewModel.item.officialRating {
|
||||||
Text(officialRating)
|
Text(officialRating)
|
||||||
.asAttributeStyle(.outline)
|
.asAttributeStyle(.outline)
|
||||||
}
|
}
|
||||||
|
case .videoQuality:
|
||||||
if let mediaStreams = viewModel.selectedMediaSource?.mediaStreams {
|
if viewModel.selectedMediaSource?.mediaStreams?.hasHDVideo == true {
|
||||||
|
Text("HD")
|
||||||
if mediaStreams.hasHDVideo {
|
.asAttributeStyle(.fill)
|
||||||
Text("HD")
|
}
|
||||||
.asAttributeStyle(.fill)
|
if viewModel.selectedMediaSource?.mediaStreams?.has4KVideo == true {
|
||||||
}
|
Text("4K")
|
||||||
|
.asAttributeStyle(.fill)
|
||||||
if mediaStreams.has4KVideo {
|
}
|
||||||
Text("4K")
|
case .audioChannels:
|
||||||
.asAttributeStyle(.fill)
|
if viewModel.selectedMediaSource?.mediaStreams?.has51AudioChannelLayout == true {
|
||||||
}
|
Text("5.1")
|
||||||
|
.asAttributeStyle(.fill)
|
||||||
if mediaStreams.has51AudioChannelLayout {
|
}
|
||||||
Text("5.1")
|
if viewModel.selectedMediaSource?.mediaStreams?.has71AudioChannelLayout == true {
|
||||||
.asAttributeStyle(.fill)
|
Text("7.1")
|
||||||
}
|
.asAttributeStyle(.fill)
|
||||||
|
}
|
||||||
if mediaStreams.has71AudioChannelLayout {
|
case .subtitles:
|
||||||
Text("7.1")
|
if viewModel.selectedMediaSource?.mediaStreams?.hasSubtitles == true {
|
||||||
.asAttributeStyle(.fill)
|
Text("CC")
|
||||||
}
|
.asAttributeStyle(.outline)
|
||||||
|
|
||||||
if mediaStreams.hasSubtitles {
|
|
||||||
Text("CC")
|
|
||||||
.asAttributeStyle(.outline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(Color(UIColor.darkGray))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,12 @@ extension CustomizeViewsSettings {
|
||||||
@Injected(\.currentUserSession)
|
@Injected(\.currentUserSession)
|
||||||
private var userSession
|
private var userSession
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: CustomizeSettingsCoordinator.Router
|
||||||
|
|
||||||
|
@StoredValue(.User.itemViewAttributes)
|
||||||
|
private var itemViewAttributes
|
||||||
|
|
||||||
@StoredValue(.User.enableItemEditing)
|
@StoredValue(.User.enableItemEditing)
|
||||||
private var enableItemEditing
|
private var enableItemEditing
|
||||||
@StoredValue(.User.enableItemDeletion)
|
@StoredValue(.User.enableItemDeletion)
|
||||||
|
@ -25,24 +31,24 @@ extension CustomizeViewsSettings {
|
||||||
private var enableCollectionManagement
|
private var enableCollectionManagement
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if userSession?.user.permissions.items.canEditMetadata ?? false ||
|
Section(L10n.items) {
|
||||||
userSession?.user.permissions.items.canDelete ?? false ||
|
|
||||||
userSession?.user.permissions.items.canManageCollections ?? false
|
|
||||||
{
|
|
||||||
|
|
||||||
Section(L10n.items) {
|
ChevronButton(L10n.mediaAttributes)
|
||||||
/// Enable Refreshing Items from All Visible LIbraries
|
.onSelect {
|
||||||
if userSession?.user.permissions.items.canEditMetadata ?? false {
|
router.route(to: \.itemViewAttributes, $itemViewAttributes)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
4E17498F2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
4E17498F2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
||||||
4E182C9C2C94993200FBEFD5 /* ServerTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */; };
|
4E182C9C2C94993200FBEFD5 /* ServerTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */; };
|
||||||
4E182C9F2C94A1E000FBEFD5 /* ServerTaskRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.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 */; };
|
4E1AA0042D0640AA00524970 /* RemoteImageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1AA0032D0640A400524970 /* RemoteImageInfo.swift */; };
|
||||||
4E1AA0052D0640AA00524970 /* 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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4E2182E42CAF67EF0094806B /* PlayMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMethod.swift; sourceTree = "<group>"; };
|
||||||
|
@ -3315,6 +3318,7 @@
|
||||||
4EFE0C7F2D02054300D4834D /* ItemArrayElements.swift */,
|
4EFE0C7F2D02054300D4834D /* ItemArrayElements.swift */,
|
||||||
E14EDECA2B8FB66F000F00A4 /* ItemFilter */,
|
E14EDECA2B8FB66F000F00A4 /* ItemFilter */,
|
||||||
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
||||||
|
4E1A39322D56C83E00BAC1C7 /* ItemViewAttributes.swift */,
|
||||||
E13F05EB28BC9000003499D2 /* LibraryDisplayType.swift */,
|
E13F05EB28BC9000003499D2 /* LibraryDisplayType.swift */,
|
||||||
E1DE2B4E2B983F3200F6715F /* LibraryParent */,
|
E1DE2B4E2B983F3200F6715F /* LibraryParent */,
|
||||||
4E2AC4C02C6C48EB00DD600D /* MediaComponents */,
|
4E2AC4C02C6C48EB00DD600D /* MediaComponents */,
|
||||||
|
@ -6158,6 +6162,7 @@
|
||||||
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
||||||
E148128628C15475003B8787 /* SortOrder+ItemSortOrder.swift in Sources */,
|
E148128628C15475003B8787 /* SortOrder+ItemSortOrder.swift in Sources */,
|
||||||
E1CB75722C80E71800217C76 /* DirectPlayProfile.swift in Sources */,
|
E1CB75722C80E71800217C76 /* DirectPlayProfile.swift in Sources */,
|
||||||
|
4E1A39332D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */,
|
||||||
E1E1E24E28DF8A2E000DF5FD /* PreferenceKeys.swift in Sources */,
|
E1E1E24E28DF8A2E000DF5FD /* PreferenceKeys.swift in Sources */,
|
||||||
E1575E9B293E7B1E001665B1 /* EnvironmentValue+Keys.swift in Sources */,
|
E1575E9B293E7B1E001665B1 /* EnvironmentValue+Keys.swift in Sources */,
|
||||||
E133328929538D8D00EE76AB /* Files.swift in Sources */,
|
E133328929538D8D00EE76AB /* Files.swift in Sources */,
|
||||||
|
@ -6882,6 +6887,7 @@
|
||||||
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
|
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
|
||||||
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
|
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
|
||||||
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
|
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
|
||||||
|
4E1A39342D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */,
|
||||||
4EC1C8522C7FDFA300E2879E /* PlaybackDeviceProfile.swift in Sources */,
|
4EC1C8522C7FDFA300E2879E /* PlaybackDeviceProfile.swift in Sources */,
|
||||||
4EA09DE12CC4E4F100CB27E4 /* APIKeysView.swift in Sources */,
|
4EA09DE12CC4E4F100CB27E4 /* APIKeysView.swift in Sources */,
|
||||||
DFB7C3DF2C7AA43A00CE7CDC /* UserSignInState.swift in Sources */,
|
DFB7C3DF2C7AA43A00CE7CDC /* UserSignInState.swift in Sources */,
|
||||||
|
|
|
@ -9,14 +9,26 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct AttributesHStack: View {
|
struct AttributesHStack: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
var viewModel: ItemViewModel
|
||||||
|
|
||||||
|
@StoredValue(.User.itemViewAttributes)
|
||||||
|
private var itemViewAttributes
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
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 {
|
if let criticRating = viewModel.item.criticRating {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Group {
|
Group {
|
||||||
|
@ -33,7 +45,7 @@ extension ItemView {
|
||||||
}
|
}
|
||||||
.asAttributeStyle(.outline)
|
.asAttributeStyle(.outline)
|
||||||
}
|
}
|
||||||
|
case .ratingCommunity:
|
||||||
if let communityRating = viewModel.item.communityRating {
|
if let communityRating = viewModel.item.communityRating {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Image(systemName: "star.fill")
|
Image(systemName: "star.fill")
|
||||||
|
@ -43,41 +55,35 @@ extension ItemView {
|
||||||
}
|
}
|
||||||
.asAttributeStyle(.outline)
|
.asAttributeStyle(.outline)
|
||||||
}
|
}
|
||||||
|
case .ratingOfficial:
|
||||||
if let officialRating = viewModel.item.officialRating {
|
if let officialRating = viewModel.item.officialRating {
|
||||||
Text(officialRating)
|
Text(officialRating)
|
||||||
.asAttributeStyle(.outline)
|
.asAttributeStyle(.outline)
|
||||||
}
|
}
|
||||||
|
case .videoQuality:
|
||||||
if let mediaStreams = viewModel.selectedMediaSource?.mediaStreams {
|
if viewModel.selectedMediaSource?.mediaStreams?.hasHDVideo == true {
|
||||||
|
Text("HD")
|
||||||
if mediaStreams.hasHDVideo {
|
.asAttributeStyle(.fill)
|
||||||
Text("HD")
|
}
|
||||||
.asAttributeStyle(.fill)
|
if viewModel.selectedMediaSource?.mediaStreams?.has4KVideo == true {
|
||||||
}
|
Text("4K")
|
||||||
|
.asAttributeStyle(.fill)
|
||||||
if mediaStreams.has4KVideo {
|
}
|
||||||
Text("4K")
|
case .audioChannels:
|
||||||
.asAttributeStyle(.fill)
|
if viewModel.selectedMediaSource?.mediaStreams?.has51AudioChannelLayout == true {
|
||||||
}
|
Text("5.1")
|
||||||
|
.asAttributeStyle(.fill)
|
||||||
if mediaStreams.has51AudioChannelLayout {
|
}
|
||||||
Text("5.1")
|
if viewModel.selectedMediaSource?.mediaStreams?.has71AudioChannelLayout == true {
|
||||||
.asAttributeStyle(.fill)
|
Text("7.1")
|
||||||
}
|
.asAttributeStyle(.fill)
|
||||||
|
}
|
||||||
if mediaStreams.has71AudioChannelLayout {
|
case .subtitles:
|
||||||
Text("7.1")
|
if viewModel.selectedMediaSource?.mediaStreams?.hasSubtitles == true {
|
||||||
.asAttributeStyle(.fill)
|
Text("CC")
|
||||||
}
|
.asAttributeStyle(.outline)
|
||||||
|
|
||||||
if mediaStreams.hasSubtitles {
|
|
||||||
Text("CC")
|
|
||||||
.asAttributeStyle(.outline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(Color(UIColor.darkGray))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,12 @@ extension CustomizeViewsSettings {
|
||||||
@Injected(\.currentUserSession)
|
@Injected(\.currentUserSession)
|
||||||
private var userSession
|
private var userSession
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: SettingsCoordinator.Router
|
||||||
|
|
||||||
|
@StoredValue(.User.itemViewAttributes)
|
||||||
|
private var itemViewAttributes
|
||||||
|
|
||||||
@StoredValue(.User.enableItemEditing)
|
@StoredValue(.User.enableItemEditing)
|
||||||
private var enableItemEditing
|
private var enableItemEditing
|
||||||
@StoredValue(.User.enableItemDeletion)
|
@StoredValue(.User.enableItemDeletion)
|
||||||
|
@ -25,39 +31,37 @@ extension CustomizeViewsSettings {
|
||||||
private var enableCollectionManagement
|
private var enableCollectionManagement
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if userSession?.user.permissions.items.canEditMetadata ?? false
|
Section(L10n.items) {
|
||||||
|| userSession?.user.permissions.items.canDelete ?? false
|
|
||||||
// || userSession?.user.permissions.items.canDownload ?? false
|
ChevronButton(L10n.mediaAttributes)
|
||||||
|| userSession?.user.permissions.items.canManageCollections ?? false
|
.onSelect {
|
||||||
// || userSession?.user.permissions.items.canManageLyrics ?? false
|
router.route(to: \.itemViewAttributes, $itemViewAttributes)
|
||||||
// || 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 {
|
/// Enable Editing Items from All Visible LIbraries
|
||||||
Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion)
|
if userSession?.user.permissions.items.canEditMetadata ?? false {
|
||||||
}
|
Toggle(L10n.allowItemEditing, isOn: $enableItemEditing)
|
||||||
/// 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 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)
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,6 +394,9 @@
|
||||||
/// Community
|
/// Community
|
||||||
"community" = "Community";
|
"community" = "Community";
|
||||||
|
|
||||||
|
/// Community rating
|
||||||
|
"communityRating" = "Community rating";
|
||||||
|
|
||||||
/// Compact
|
/// Compact
|
||||||
"compact" = "Compact";
|
"compact" = "Compact";
|
||||||
|
|
||||||
|
@ -478,14 +481,17 @@
|
||||||
/// Creator
|
/// Creator
|
||||||
"creator" = "Creator";
|
"creator" = "Creator";
|
||||||
|
|
||||||
|
/// Critic rating
|
||||||
|
"criticRating" = "Critic rating";
|
||||||
|
|
||||||
/// Critics
|
/// Critics
|
||||||
"critics" = "Critics";
|
"critics" = "Critics";
|
||||||
|
|
||||||
/// Current
|
/// Current
|
||||||
"current" = "Current";
|
"current" = "Current";
|
||||||
|
|
||||||
/// Current Password
|
/// Current password
|
||||||
"currentPassword" = "Current Password";
|
"currentPassword" = "Current password";
|
||||||
|
|
||||||
/// Custom
|
/// Custom
|
||||||
"custom" = "Custom";
|
"custom" = "Custom";
|
||||||
|
@ -520,11 +526,11 @@
|
||||||
/// Customize
|
/// Customize
|
||||||
"customize" = "Customize";
|
"customize" = "Customize";
|
||||||
|
|
||||||
/// Custom Profile
|
/// Custom profile
|
||||||
"customProfile" = "Custom Profile";
|
"customProfile" = "Custom profile";
|
||||||
|
|
||||||
/// Custom Rating
|
/// Custom rating
|
||||||
"customRating" = "Custom Rating";
|
"customRating" = "Custom rating";
|
||||||
|
|
||||||
/// Custom sessions
|
/// Custom sessions
|
||||||
"customSessions" = "Custom sessions";
|
"customSessions" = "Custom sessions";
|
||||||
|
@ -544,11 +550,11 @@
|
||||||
/// Date Added
|
/// Date Added
|
||||||
"dateAdded" = "Date Added";
|
"dateAdded" = "Date Added";
|
||||||
|
|
||||||
/// Date Created
|
/// Date created
|
||||||
"dateCreated" = "Date Created";
|
"dateCreated" = "Date created";
|
||||||
|
|
||||||
/// Date Modified
|
/// Date modified
|
||||||
"dateModified" = "Date Modified";
|
"dateModified" = "Date modified";
|
||||||
|
|
||||||
/// Date of death
|
/// Date of death
|
||||||
"dateOfDeath" = "Date of death";
|
"dateOfDeath" = "Date of death";
|
||||||
|
@ -1117,6 +1123,9 @@
|
||||||
/// Media Access
|
/// Media Access
|
||||||
"mediaAccess" = "Media Access";
|
"mediaAccess" = "Media Access";
|
||||||
|
|
||||||
|
/// Media attributes
|
||||||
|
"mediaAttributes" = "Media attributes";
|
||||||
|
|
||||||
/// Media downloads
|
/// Media downloads
|
||||||
"mediaDownloads" = "Media downloads";
|
"mediaDownloads" = "Media downloads";
|
||||||
|
|
||||||
|
@ -1240,8 +1249,8 @@
|
||||||
/// No title
|
/// No title
|
||||||
"noTitle" = "No title";
|
"noTitle" = "No title";
|
||||||
|
|
||||||
/// Official Rating
|
/// Official rating
|
||||||
"officialRating" = "Official Rating";
|
"officialRating" = "Official rating";
|
||||||
|
|
||||||
/// Offset
|
/// Offset
|
||||||
"offset" = "Offset";
|
"offset" = "Offset";
|
||||||
|
@ -1285,8 +1294,8 @@
|
||||||
/// Parental controls
|
/// Parental controls
|
||||||
"parentalControls" = "Parental controls";
|
"parentalControls" = "Parental controls";
|
||||||
|
|
||||||
/// Parental Rating
|
/// Parental rating
|
||||||
"parentalRating" = "Parental Rating";
|
"parentalRating" = "Parental rating";
|
||||||
|
|
||||||
/// Password
|
/// Password
|
||||||
"password" = "Password";
|
"password" = "Password";
|
||||||
|
@ -1423,8 +1432,8 @@
|
||||||
/// Random
|
/// Random
|
||||||
"random" = "Random";
|
"random" = "Random";
|
||||||
|
|
||||||
/// Random Image
|
/// Random image
|
||||||
"randomImage" = "Random Image";
|
"randomImage" = "Random image";
|
||||||
|
|
||||||
/// Rating
|
/// Rating
|
||||||
"rating" = "Rating";
|
"rating" = "Rating";
|
||||||
|
|
Loading…
Reference in New Issue