mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-11-22 21:49:44 +00:00
[iOS] Playback Quality - Learn More (#1316)
* Playback Quality - Learn More * TODO: Fix leading not working on second line. * Remove layoutDirection. * Implement for tvOS. Slightly different spacing. * VStack * WIP - tvOS Implementaiton. SUBJECT TO CHANGE / ELIMINATION. * Background Icon & formatting * wip * Review Changes. Remove unused Strings, clean up comments. * Remove duplicate items used for testing * Remove tvOS scrollIfLargerThanContainer for now. --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
687cfa6b5f
commit
994e99d141
@ -129,3 +129,29 @@ extension FormatStyle where Self == LastSeenFormatStyle {
|
||||
|
||||
static var lastSeen: LastSeenFormatStyle { LastSeenFormatStyle() }
|
||||
}
|
||||
|
||||
struct IntBitRateFormatStyle: FormatStyle {
|
||||
func format(_ value: Int) -> String {
|
||||
let units = [
|
||||
L10n.bitsPerSecond,
|
||||
L10n.kilobitsPerSecond,
|
||||
L10n.megabitsPerSecond,
|
||||
L10n.gigabitsPerSecond,
|
||||
L10n.terabitsPerSecond,
|
||||
]
|
||||
var adjustedValue = Double(value)
|
||||
var unitIndex = 0
|
||||
|
||||
while adjustedValue >= 1000, unitIndex < units.count - 1 {
|
||||
adjustedValue /= 1000
|
||||
unitIndex += 1
|
||||
}
|
||||
|
||||
let formattedValue = String(format: "%.1f", adjustedValue)
|
||||
return "\(formattedValue) \(units[unitIndex])"
|
||||
}
|
||||
}
|
||||
|
||||
extension FormatStyle where Self == IntBitRateFormatStyle {
|
||||
static var bitRate: IntBitRateFormatStyle { IntBitRateFormatStyle() }
|
||||
}
|
||||
|
@ -108,6 +108,8 @@ internal enum L10n {
|
||||
internal static let authorize = L10n.tr("Localizable", "authorize", fallback: "Authorize")
|
||||
/// PlaybackCompatibility Default Category
|
||||
internal static let auto = L10n.tr("Localizable", "auto", fallback: "Auto")
|
||||
/// Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.
|
||||
internal static let autoDescription = L10n.tr("Localizable", "autoDescription", fallback: "Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.")
|
||||
/// Auto Play
|
||||
internal static let autoPlay = L10n.tr("Localizable", "autoPlay", fallback: "Auto Play")
|
||||
/// Back
|
||||
@ -116,12 +118,14 @@ internal enum L10n {
|
||||
internal static let barButtons = L10n.tr("Localizable", "barButtons", fallback: "Bar Buttons")
|
||||
/// Behavior
|
||||
internal static let behavior = L10n.tr("Localizable", "behavior", fallback: "Behavior")
|
||||
/// Tests your server connection to assess internet speed and adjust bandwidth automatically.
|
||||
internal static let birateAutoDescription = L10n.tr("Localizable", "birateAutoDescription", fallback: "Tests your server connection to assess internet speed and adjust bandwidth automatically.")
|
||||
/// Option for automatic bitrate selection
|
||||
internal static let bitrateAuto = L10n.tr("Localizable", "bitrateAuto", fallback: "Auto")
|
||||
/// Default Bitrate
|
||||
internal static let bitrateDefault = L10n.tr("Localizable", "bitrateDefault", fallback: "Default Bitrate")
|
||||
/// Default Bitrate Description
|
||||
internal static let bitrateDefaultDescription = L10n.tr("Localizable", "bitrateDefaultDescription", fallback: "Limits the internet bandwidth used during video playback")
|
||||
internal static let bitrateDefaultDescription = L10n.tr("Localizable", "bitrateDefaultDescription", fallback: "Limits the internet bandwidth used during playback.")
|
||||
/// Option to set the bitrate to 480p quality at 1.5 Mbps
|
||||
internal static let bitrateKbps1500 = L10n.tr("Localizable", "bitrateKbps1500", fallback: "480p - 1.5 Mbps")
|
||||
/// Option to set the bitrate to 360p quality at 420 Kbps
|
||||
@ -130,6 +134,10 @@ internal enum L10n {
|
||||
internal static let bitrateKbps720 = L10n.tr("Localizable", "bitrateKbps720", fallback: "480p - 720 Kbps")
|
||||
/// Option for the maximum bitrate
|
||||
internal static let bitrateMax = L10n.tr("Localizable", "bitrateMax", fallback: "Maximum")
|
||||
/// Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.
|
||||
internal static func bitrateMaxDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "bitrateMaxDescription", String(describing: p1), fallback: "Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.")
|
||||
}
|
||||
/// Option to set the bitrate to 1080p quality at 10 Mbps
|
||||
internal static let bitrateMbps10 = L10n.tr("Localizable", "bitrateMbps10", fallback: "1080p - 10 Mbps")
|
||||
/// Option to set the bitrate to 4K quality at 120 Mbps
|
||||
@ -154,10 +162,10 @@ internal enum L10n {
|
||||
internal static let bitrateMbps80 = L10n.tr("Localizable", "bitrateMbps80", fallback: "4K - 80 Mbps")
|
||||
/// Bitrate Automatic Section Header
|
||||
internal static let bitrateTest = L10n.tr("Localizable", "bitrateTest", fallback: "Bitrate Test")
|
||||
/// Description for bitrate test duration description
|
||||
internal static let bitrateTestDescription = L10n.tr("Localizable", "bitrateTestDescription", fallback: "Determines the length of the 'Auto' bitrate test used to find the available internet bandwidth")
|
||||
/// Description for bitrate test duration indicating longer tests provide more accurate bitrates but may delay playback
|
||||
internal static let bitrateTestDisclaimer = L10n.tr("Localizable", "bitrateTestDisclaimer", fallback: "Longer tests are more accurate but may result in a delayed playback")
|
||||
internal static let bitrateTestDisclaimer = L10n.tr("Localizable", "bitrateTestDisclaimer", fallback: "Longer tests are more accurate but may result in a delayed playback.")
|
||||
/// bps
|
||||
internal static let bitsPerSecond = L10n.tr("Localizable", "bitsPerSecond", fallback: "bps")
|
||||
/// Blue
|
||||
internal static let blue = L10n.tr("Localizable", "blue", fallback: "Blue")
|
||||
/// Bugs and Features
|
||||
@ -220,6 +228,8 @@ internal enum L10n {
|
||||
internal static let compatibility = L10n.tr("Localizable", "compatibility", fallback: "Compatibility")
|
||||
/// PlaybackCompatibility Compatible Category
|
||||
internal static let compatible = L10n.tr("Localizable", "compatible", fallback: "Most Compatible")
|
||||
/// Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.
|
||||
internal static let compatibleDescription = L10n.tr("Localizable", "compatibleDescription", fallback: "Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.")
|
||||
/// Confirm Task Fuction
|
||||
internal static let confirm = L10n.tr("Localizable", "confirm", fallback: "Confirm")
|
||||
/// Confirm Close
|
||||
@ -262,6 +272,8 @@ internal enum L10n {
|
||||
internal static let currentPosition = L10n.tr("Localizable", "currentPosition", fallback: "Current Position")
|
||||
/// PlaybackCompatibility Custom Category
|
||||
internal static let custom = L10n.tr("Localizable", "custom", fallback: "Custom")
|
||||
/// Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.
|
||||
internal static let customDescription = L10n.tr("Localizable", "customDescription", fallback: "Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.")
|
||||
/// Custom Device Name
|
||||
internal static let customDeviceName = L10n.tr("Localizable", "customDeviceName", fallback: "Custom Device Name")
|
||||
/// Your custom device name '%1$@' has been saved.
|
||||
@ -269,11 +281,11 @@ internal enum L10n {
|
||||
return L10n.tr("Localizable", "customDeviceNameSaved", String(describing: p1), fallback: "Your custom device name '%1$@' has been saved.")
|
||||
}
|
||||
/// Custom profile is Added to the Existing Profiles
|
||||
internal static let customDeviceProfileAdd = L10n.tr("Localizable", "customDeviceProfileAdd", fallback: "The custom device profiles will be added to the default Swiftfin device profiles")
|
||||
internal static let customDeviceProfileAdd = L10n.tr("Localizable", "customDeviceProfileAdd", fallback: "The custom device profiles will be added to the default Swiftfin device profiles.")
|
||||
/// Device Profile Section Description
|
||||
internal static let customDeviceProfileDescription = L10n.tr("Localizable", "customDeviceProfileDescription", fallback: "Dictates back to the Jellyfin Server what this device hardware is capable of playing")
|
||||
internal static let customDeviceProfileDescription = L10n.tr("Localizable", "customDeviceProfileDescription", fallback: "Dictates back to the Jellyfin Server what this device hardware is capable of playing.")
|
||||
/// Custom profile will replace the Existing Profiles
|
||||
internal static let customDeviceProfileReplace = L10n.tr("Localizable", "customDeviceProfileReplace", fallback: "The custom device profiles will replace the default Swiftfin device profiles")
|
||||
internal static let customDeviceProfileReplace = L10n.tr("Localizable", "customDeviceProfileReplace", fallback: "The custom device profiles will replace the default Swiftfin device profiles.")
|
||||
/// Settings View - Customize
|
||||
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
||||
/// Section Header for a Custom Device Profile
|
||||
@ -342,10 +354,14 @@ internal enum L10n {
|
||||
internal static let device = L10n.tr("Localizable", "device", fallback: "Device")
|
||||
/// Section Header for Device Profiles
|
||||
internal static let deviceProfile = L10n.tr("Localizable", "deviceProfile", fallback: "Device Profile")
|
||||
/// Decide which media plays natively or requires server transcoding for compatibility.
|
||||
internal static let deviceProfileDescription = L10n.tr("Localizable", "deviceProfileDescription", fallback: "Decide which media plays natively or requires server transcoding for compatibility.")
|
||||
/// Devices
|
||||
internal static let devices = L10n.tr("Localizable", "devices", fallback: "Devices")
|
||||
/// PlaybackCompatibility DirectPlay Category
|
||||
internal static let direct = L10n.tr("Localizable", "direct", fallback: "Direct Play")
|
||||
/// Plays content in its original format. May cause playback issues on unsupported media types.
|
||||
internal static let directDescription = L10n.tr("Localizable", "directDescription", fallback: "Plays content in its original format. May cause playback issues on unsupported media types.")
|
||||
/// DIRECTOR
|
||||
internal static let director = L10n.tr("Localizable", "director", fallback: "DIRECTOR")
|
||||
/// PlayMethod - Direct Play
|
||||
@ -428,6 +444,8 @@ internal enum L10n {
|
||||
internal static let genres = L10n.tr("Localizable", "genres", fallback: "Genres")
|
||||
/// Gestures
|
||||
internal static let gestures = L10n.tr("Localizable", "gestures", fallback: "Gestures")
|
||||
/// Gbps
|
||||
internal static let gigabitsPerSecond = L10n.tr("Localizable", "gigabitsPerSecond", fallback: "Gbps")
|
||||
/// Green
|
||||
internal static let green = L10n.tr("Localizable", "green", fallback: "Green")
|
||||
/// Grid
|
||||
@ -486,6 +504,8 @@ internal enum L10n {
|
||||
}
|
||||
/// Kids
|
||||
internal static let kids = L10n.tr("Localizable", "kids", fallback: "Kids")
|
||||
/// kbps
|
||||
internal static let kilobitsPerSecond = L10n.tr("Localizable", "kilobitsPerSecond", fallback: "kbps")
|
||||
/// Larger
|
||||
internal static let larger = L10n.tr("Localizable", "larger", fallback: "Larger")
|
||||
/// Largest
|
||||
@ -535,9 +555,11 @@ internal enum L10n {
|
||||
/// Option to set the maximum bitrate for playback
|
||||
internal static let maximumBitrate = L10n.tr("Localizable", "maximumBitrate", fallback: "Maximum Bitrate")
|
||||
/// Playback May Fail
|
||||
internal static let mayResultInPlaybackFailure = L10n.tr("Localizable", "mayResultInPlaybackFailure", fallback: "This setting may result in media failing to start playback")
|
||||
internal static let mayResultInPlaybackFailure = L10n.tr("Localizable", "mayResultInPlaybackFailure", fallback: "This setting may result in media failing to start playback.")
|
||||
/// Media
|
||||
internal static let media = L10n.tr("Localizable", "media", fallback: "Media")
|
||||
/// Mbps
|
||||
internal static let megabitsPerSecond = L10n.tr("Localizable", "megabitsPerSecond", fallback: "Mbps")
|
||||
/// Menu Buttons
|
||||
internal static let menuButtons = L10n.tr("Localizable", "menuButtons", fallback: "Menu Buttons")
|
||||
/// Metadata
|
||||
@ -1016,6 +1038,8 @@ internal enum L10n {
|
||||
internal static let taskTriggerInterval = L10n.tr("Localizable", "taskTriggerInterval", fallback: "Sets the duration (in minutes) in between task triggers.")
|
||||
/// Sets the maximum runtime (in hours) for this task trigger.
|
||||
internal static let taskTriggerTimeLimit = L10n.tr("Localizable", "taskTriggerTimeLimit", fallback: "Sets the maximum runtime (in hours) for this task trigger.")
|
||||
/// Tbps
|
||||
internal static let terabitsPerSecond = L10n.tr("Localizable", "terabitsPerSecond", fallback: "Tbps")
|
||||
/// Option to set the test size for bitrate testing
|
||||
internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size")
|
||||
/// Time
|
||||
|
45
Swiftfin tvOS/Views/LearnMoreModal.swift
Normal file
45
Swiftfin tvOS/Views/LearnMoreModal.swift
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LearnMoreModal: View {
|
||||
|
||||
private let items: [TextPair]
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init(@ArrayBuilder<TextPair> items: () -> [TextPair]) {
|
||||
self.items = items()
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
ForEach(items) { content in
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(content.title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text(content.subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Material.regular)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
@ -20,13 +20,22 @@ struct PlaybackQualitySettingsView: View {
|
||||
@EnvironmentObject
|
||||
private var router: PlaybackQualitySettingsCoordinator.Router
|
||||
|
||||
// MARK: - Focus Management
|
||||
|
||||
@FocusState
|
||||
private var focusedItem: FocusableItem?
|
||||
|
||||
private enum FocusableItem: Hashable {
|
||||
case maximumBitrate
|
||||
case compatibility
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
SplitFormWindowView()
|
||||
.descriptionView {
|
||||
Image(systemName: "play.rectangle.on.rectangle")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: 400)
|
||||
descriptionView
|
||||
}
|
||||
.contentView {
|
||||
Section {
|
||||
@ -34,12 +43,11 @@ struct PlaybackQualitySettingsView: View {
|
||||
title: L10n.maximumBitrate,
|
||||
selection: $appMaximumBitrate
|
||||
)
|
||||
.focused($focusedItem, equals: .maximumBitrate)
|
||||
} header: {
|
||||
L10n.bitrateDefault.text
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
L10n.bitrateDefaultDescription.text
|
||||
}
|
||||
L10n.bitrateDefaultDescription.text
|
||||
}
|
||||
.animation(.none, value: appMaximumBitrate)
|
||||
|
||||
@ -49,13 +57,8 @@ struct PlaybackQualitySettingsView: View {
|
||||
title: L10n.testSize,
|
||||
selection: $appMaximumBitrateTest
|
||||
)
|
||||
} header: {
|
||||
L10n.bitrateTest.text
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
L10n.bitrateTestDescription.text
|
||||
L10n.bitrateTestDisclaimer.text
|
||||
}
|
||||
L10n.bitrateTestDisclaimer.text
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +67,7 @@ struct PlaybackQualitySettingsView: View {
|
||||
title: L10n.compatibility,
|
||||
selection: $compatibilityMode
|
||||
)
|
||||
.animation(.none, value: compatibilityMode)
|
||||
.focused($focusedItem, equals: .compatibility)
|
||||
|
||||
if compatibilityMode == .custom {
|
||||
ChevronButton(L10n.profiles)
|
||||
@ -74,8 +77,66 @@ struct PlaybackQualitySettingsView: View {
|
||||
}
|
||||
} header: {
|
||||
L10n.deviceProfile.text
|
||||
} footer: {
|
||||
L10n.deviceProfileDescription.text
|
||||
}
|
||||
}
|
||||
.navigationTitle(L10n.playbackQuality)
|
||||
}
|
||||
|
||||
// MARK: - Description View Icon
|
||||
|
||||
private var descriptionView: some View {
|
||||
ZStack {
|
||||
Image(systemName: "play.rectangle.on.rectangle")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: 400)
|
||||
|
||||
focusedDescription
|
||||
.transition(.opacity.animation(.linear(duration: 0.2)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Description View on Focus
|
||||
|
||||
@ViewBuilder
|
||||
private var focusedDescription: some View {
|
||||
switch focusedItem {
|
||||
case .maximumBitrate:
|
||||
LearnMoreModal {
|
||||
TextPair(
|
||||
title: L10n.auto,
|
||||
subtitle: L10n.birateAutoDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.bitrateMax,
|
||||
subtitle: L10n.bitrateMaxDescription(PlaybackBitrate.max.rawValue.formatted(.bitRate))
|
||||
)
|
||||
}
|
||||
|
||||
case .compatibility:
|
||||
LearnMoreModal {
|
||||
TextPair(
|
||||
title: L10n.auto,
|
||||
subtitle: L10n.autoDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.compatible,
|
||||
subtitle: L10n.compatibleDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.direct,
|
||||
subtitle: L10n.directDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.custom,
|
||||
subtitle: L10n.customDescription
|
||||
)
|
||||
}
|
||||
|
||||
case nil:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@
|
||||
4E73E2A72C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; };
|
||||
4E762AAE2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
|
||||
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
|
||||
4E884C652CEBB301004CF6AD /* LearnMoreModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */; };
|
||||
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
|
||||
4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
|
||||
4E8F74A22CE03C9000CC8969 /* ItemEditorCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8F74A02CE03C8B00CC8969 /* ItemEditorCoordinator.swift */; };
|
||||
@ -1121,6 +1122,7 @@
|
||||
4E71D6882C80910900A0174D /* EditCustomDeviceProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDeviceProfileView.swift; sourceTree = "<group>"; };
|
||||
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
|
||||
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.swift; sourceTree = "<group>"; };
|
||||
4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreModal.swift; sourceTree = "<group>"; };
|
||||
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
|
||||
4E8F74A02CE03C8B00CC8969 /* ItemEditorCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEditorCoordinator.swift; sourceTree = "<group>"; };
|
||||
4E8F74A42CE03D3800CC8969 /* ItemEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEditorView.swift; sourceTree = "<group>"; };
|
||||
@ -3269,6 +3271,7 @@
|
||||
E1A42E4D28CBD3B200A14DCB /* HomeView */,
|
||||
E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */,
|
||||
E193D54E271942C000900D82 /* ItemView */,
|
||||
4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */,
|
||||
E158C8D02A31947500C527C5 /* MediaSourceInfoView.swift */,
|
||||
E103DF932BCF31C5000229B2 /* MediaView */,
|
||||
E10231572BCF8AF8009D71FC /* ProgramsView */,
|
||||
@ -4831,6 +4834,7 @@
|
||||
E14EDEC62B8FB64E000F00A4 /* AnyItemFilter.swift in Sources */,
|
||||
E14EDEC92B8FB65F000F00A4 /* ItemFilterType.swift in Sources */,
|
||||
E1D37F4C2B9CEA5C00343D2B /* ImageSource.swift in Sources */,
|
||||
4E884C652CEBB301004CF6AD /* LearnMoreModal.swift in Sources */,
|
||||
E1B4E4372CA7795200DC49DE /* OrderedDictionary.swift in Sources */,
|
||||
E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
|
||||
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
||||
|
@ -30,32 +30,38 @@ struct LearnMoreButton: View {
|
||||
isPresented = true
|
||||
}
|
||||
.foregroundStyle(Color.accentColor)
|
||||
.font(.subheadline)
|
||||
.buttonStyle(.plain)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.sheet(isPresented: $isPresented) {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
ForEach(items) { content in
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(content.title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(.primary)
|
||||
learnMoreView
|
||||
}
|
||||
}
|
||||
|
||||
Text(content.subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
// MARK: - Learn More View
|
||||
|
||||
Divider()
|
||||
private var learnMoreView: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
ForEach(items) { content in
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(content.title)
|
||||
.font(.headline)
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text(content.subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.edgePadding()
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarCloseButton {
|
||||
isPresented = false
|
||||
}
|
||||
.edgePadding()
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarCloseButton {
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ struct CustomDeviceProfileSettingsView: View {
|
||||
.navigationTitle(L10n.profiles)
|
||||
.topBarTrailing {
|
||||
if customProfiles.isNotEmpty {
|
||||
Button("Add") {
|
||||
Button(L10n.add) {
|
||||
UIDevice.impact(.light)
|
||||
router.route(to: \.createCustomDeviceProfile)
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ struct CustomizeViewsSettings: View {
|
||||
|
||||
if libraryDisplayType == .list, UIDevice.isPad {
|
||||
BasicStepper(
|
||||
title: "Columns",
|
||||
title: L10n.columns,
|
||||
value: $listColumnCount,
|
||||
range: 1 ... 4,
|
||||
step: 1
|
||||
|
@ -31,7 +31,20 @@ struct PlaybackQualitySettingsView: View {
|
||||
} header: {
|
||||
L10n.bitrateDefault.text
|
||||
} footer: {
|
||||
L10n.bitrateDefaultDescription.text
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.bitrateDefaultDescription)
|
||||
LearnMoreButton(L10n.bitrateDefault) {
|
||||
TextPair(
|
||||
title: L10n.auto,
|
||||
subtitle: L10n.birateAutoDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.bitrateMax,
|
||||
subtitle: L10n.bitrateMaxDescription(PlaybackBitrate.max.rawValue.formatted(.bitRate))
|
||||
)
|
||||
}
|
||||
.foregroundStyle(.foreground, .primary)
|
||||
}
|
||||
}
|
||||
.animation(.none, value: appMaximumBitrate)
|
||||
|
||||
@ -44,18 +57,12 @@ struct PlaybackQualitySettingsView: View {
|
||||
} header: {
|
||||
L10n.bitrateTest.text
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
L10n.bitrateTestDescription.text
|
||||
VStack(alignment: .leading) {
|
||||
L10n.bitrateTestDisclaimer.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Have a small description and a "Learn More..."
|
||||
// button that will open a page for longer descriptions
|
||||
// of each option. See: iOS Settings/Accessibility/VoiceOver
|
||||
// for reference
|
||||
|
||||
Section {
|
||||
CaseIterablePicker(
|
||||
L10n.compatibility,
|
||||
@ -70,7 +77,30 @@ struct PlaybackQualitySettingsView: View {
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
L10n.deviceProfile.text
|
||||
Text(L10n.deviceProfile)
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.deviceProfileDescription)
|
||||
LearnMoreButton(L10n.deviceProfile) {
|
||||
TextPair(
|
||||
title: L10n.auto,
|
||||
subtitle: L10n.autoDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.compatible,
|
||||
subtitle: L10n.compatibleDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.direct,
|
||||
subtitle: L10n.directDescription
|
||||
)
|
||||
TextPair(
|
||||
title: L10n.custom,
|
||||
subtitle: L10n.customDescription
|
||||
)
|
||||
}
|
||||
.foregroundStyle(.foreground, .primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.linear, value: appMaximumBitrate)
|
||||
|
@ -364,11 +364,8 @@
|
||||
/* Option to set the bitrate to 360p quality at 420 Kbps */
|
||||
"bitrateKbps420" = "360p - 420 Kbps";
|
||||
|
||||
/* Description for bitrate test duration description */
|
||||
"bitrateTestDescription" = "Determines the length of the 'Auto' bitrate test used to find the available internet bandwidth";
|
||||
|
||||
/* Description for bitrate test duration indicating longer tests provide more accurate bitrates but may delay playback */
|
||||
"bitrateTestDisclaimer" = "Longer tests are more accurate but may result in a delayed playback";
|
||||
"bitrateTestDisclaimer" = "Longer tests are more accurate but may result in a delayed playback.";
|
||||
|
||||
/* Select Server View */
|
||||
"servers" = "Servers";
|
||||
@ -464,10 +461,10 @@
|
||||
"deviceProfile" = "Device Profile";
|
||||
|
||||
/* Custom profile is Added to the Existing Profiles */
|
||||
"customDeviceProfileAdd" = "The custom device profiles will be added to the default Swiftfin device profiles";
|
||||
"customDeviceProfileAdd" = "The custom device profiles will be added to the default Swiftfin device profiles.";
|
||||
|
||||
/* Custom profile will replace the Existing Profiles */
|
||||
"customDeviceProfileReplace" = "The custom device profiles will replace the default Swiftfin device profiles";
|
||||
"customDeviceProfileReplace" = "The custom device profiles will replace the default Swiftfin device profiles.";
|
||||
|
||||
/* Section for Playback Quality Settings */
|
||||
"playbackQuality" = "Playback Quality";
|
||||
@ -503,13 +500,13 @@
|
||||
"bitrateDefault" = "Default Bitrate";
|
||||
|
||||
/* Default Bitrate Description */
|
||||
"bitrateDefaultDescription" = "Limits the internet bandwidth used during video playback";
|
||||
"bitrateDefaultDescription" = "Limits the internet bandwidth used during playback.";
|
||||
|
||||
/* Playback May Fail */
|
||||
"mayResultInPlaybackFailure" = "This setting may result in media failing to start playback";
|
||||
"mayResultInPlaybackFailure" = "This setting may result in media failing to start playback.";
|
||||
|
||||
/* Device Profile Section Description */
|
||||
"customDeviceProfileDescription" = "Dictates back to the Jellyfin Server what this device hardware is capable of playing";
|
||||
"customDeviceProfileDescription" = "Dictates back to the Jellyfin Server what this device hardware is capable of playing.";
|
||||
|
||||
/* Session Device Section Label */
|
||||
"device" = "Device";
|
||||
@ -1322,3 +1319,63 @@
|
||||
/// Replace all metadata and images
|
||||
/// Full refresh that replaces all unlocked metadata and images
|
||||
"replaceAllDescription" = "Replace all unlocked metadata and images with new information.";
|
||||
|
||||
/// Device Profile - Description
|
||||
/// Explains how device profiles control playback and transcoding behavior
|
||||
/// Used in the device profile settings section
|
||||
"deviceProfileDescription" = "Decide which media plays natively or requires server transcoding for compatibility.";
|
||||
|
||||
/// Auto - Description
|
||||
/// Optimizes playback using default settings for most devices
|
||||
/// Some formats may require server transcoding
|
||||
"autoDescription" = "Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.";
|
||||
|
||||
/// Compatible - Description
|
||||
/// Converts media to H.264 video and AAC audio for compatibility
|
||||
/// Requires server transcoding for all content
|
||||
"compatibleDescription" = "Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.";
|
||||
|
||||
/// Direct - Description
|
||||
/// Plays content in its original format without transcoding
|
||||
/// May cause playback issues on unsupported devices
|
||||
"directDescription" = "Plays content in its original format. May cause playback issues on unsupported media types.";
|
||||
|
||||
/// Custom - Description
|
||||
/// Allows customization of device profiles for native playback
|
||||
/// Incorrect settings may result in playback issues
|
||||
"customDescription" = "Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.";
|
||||
|
||||
/// Server Connection Test - Description
|
||||
/// Tests the connection to the server to assess internet speed
|
||||
/// Used to adjust bandwidth settings automatically
|
||||
"birateAutoDescription" = "Tests your server connection to assess internet speed and adjust bandwidth automatically.";
|
||||
|
||||
/// Bandwidth Usage - Description
|
||||
/// Indicates the maximum bandwidth used per playback stream
|
||||
/// Helps to manage data usage during streaming
|
||||
"bitrateMaxDescription" = "Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.";
|
||||
|
||||
// Bits Per Second - Unit
|
||||
// Represents a speed in bits per second
|
||||
// Used for bandwidth display
|
||||
"bitsPerSecond" = "bps";
|
||||
|
||||
// Kilobits Per Second - Unit
|
||||
// Represents a speed in kilobits per second
|
||||
// Used for bandwidth display
|
||||
"kilobitsPerSecond" = "kbps";
|
||||
|
||||
// Megabits Per Second - Unit
|
||||
// Represents a speed in megabits per second
|
||||
// Used for bandwidth display
|
||||
"megabitsPerSecond" = "Mbps";
|
||||
|
||||
// Gigabits Per Second - Unit
|
||||
// Represents a speed in gigabits per second
|
||||
// Used for bandwidth display
|
||||
"gigabitsPerSecond" = "Gbps";
|
||||
|
||||
// Terabits Per Second - Unit
|
||||
// Represents a speed in terabits per second
|
||||
// Used for bandwidth display
|
||||
"terabitsPerSecond" = "Tbps";
|
||||
|
Loading…
Reference in New Issue
Block a user