Skip to content

Commit

Permalink
Much bigger changes to allow attribute customization.
Browse files Browse the repository at this point in the history
  • Loading branch information
JPKribs committed Feb 8, 2025
1 parent 8e3e638 commit 83ccc7a
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 198 deletions.
11 changes: 11 additions & 0 deletions Shared/Coordinators/CustomizeSettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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)
}
Expand Down
8 changes: 8 additions & 0 deletions Shared/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions Shared/Objects/ItemViewAttributes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// 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
//

import Defaults
import Foundation

enum ItemViewAttribute: String, CaseIterable, Codable, Displayable, Defaults.Serializable {

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
}
}
}
42 changes: 22 additions & 20 deletions Shared/Strings/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +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 ratings
internal static let communityRatings = L10n.tr("Localizable", "communityRatings", fallback: "Community ratings")
/// 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
Expand Down Expand Up @@ -340,14 +340,14 @@ internal enum L10n {
}
/// Creator
internal static let creator = L10n.tr("Localizable", "creator", fallback: "Creator")
/// Critic ratings
internal static let criticRatings = L10n.tr("Localizable", "criticRatings", fallback: "Critic ratings")
/// 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
Expand All @@ -372,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
Expand All @@ -388,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
Expand Down Expand Up @@ -792,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
Expand Down Expand Up @@ -876,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
Expand Down Expand Up @@ -906,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.
Expand Down Expand Up @@ -998,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.
Expand Down
16 changes: 4 additions & 12 deletions Shared/SwiftfinStore/StoredValue/StoredValues+User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,11 @@ extension StoredValues.Keys {
)
}

static var enableCriticRatings: Key<Bool> {
static var itemViewAttributes: Key<[ItemViewAttribute]> {
CurrentUserKey(
"enableCriticRatings",
domain: "enableCriticRatings",
default: true
)
}

static var enableCommunityRatings: Key<Bool> {
CurrentUserKey(
"enableCommunityRatings",
domain: "enableCommunityRatings",
default: true
"itemViewAttributes",
domain: "itemViewAttributes",
default: ItemViewAttribute.allCases
)
}
}
Expand Down
78 changes: 38 additions & 40 deletions Swiftfin tvOS/Views/ItemView/Components/AttributeHStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,30 @@
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
//

import Defaults
import SwiftUI

extension ItemView {

struct AttributesHStack: View {

@ObservedObject
var viewModel: ItemViewModel

@StoredValue(.User.enableCriticRatings)
private var enableCriticRatings

@StoredValue(.User.enableCommunityRatings)
private var enableCommunityRatings
@StoredValue(.User.itemViewAttributes)
private var itemViewAttributes

var body: some View {
HStack(spacing: 25) {
ForEach(itemViewAttributes, id: \.self) { attribute in
getAttribute(attribute)
}
}
.foregroundColor(Color(UIColor.darkGray))
}

if let criticRating = viewModel.item.criticRating, enableCriticRatings {
@ViewBuilder
func getAttribute(_ attribute: ItemViewAttribute) -> some View {
switch attribute {
case .ratingCritics:
if let criticRating = viewModel.item.criticRating {
HStack(spacing: 2) {
Group {
if criticRating >= 60 {
Expand All @@ -41,8 +45,8 @@ extension ItemView {
}
.asAttributeStyle(.outline)
}

if let communityRating = viewModel.item.communityRating, enableCommunityRatings {
case .ratingCommunity:
if let communityRating = viewModel.item.communityRating {
HStack(spacing: 2) {
Image(systemName: "star.fill")
.font(.caption2)
Expand All @@ -51,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))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ extension CustomizeViewsSettings {
@Injected(\.currentUserSession)
private var userSession

@StoredValue(.User.enableCriticRatings)
private var enableCriticRatings
@StoredValue(.User.enableCommunityRatings)
private var enableCommunityRatings
@EnvironmentObject
private var router: CustomizeSettingsCoordinator.Router

@StoredValue(.User.itemViewAttributes)
private var itemViewAttributes

@StoredValue(.User.enableItemEditing)
private var enableItemEditing
Expand All @@ -32,29 +33,22 @@ extension CustomizeViewsSettings {
var body: some View {
Section(L10n.items) {

Toggle(L10n.criticRatings, isOn: $enableCriticRatings)

Toggle(L10n.communityRatings, isOn: $enableCommunityRatings)
}

if userSession?.user.permissions.items.canEditMetadata ?? false ||
userSession?.user.permissions.items.canDelete ?? false ||
userSession?.user.permissions.items.canManageCollections ?? false
{
Section(L10n.management) {

/// 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)
}
}
}
Expand Down
Loading

0 comments on commit 83ccc7a

Please sign in to comment.