diff --git a/Shared/Coordinators/SettingsCoordinator.swift b/Shared/Coordinators/SettingsCoordinator.swift index 431d88938..5641371b3 100644 --- a/Shared/Coordinators/SettingsCoordinator.swift +++ b/Shared/Coordinators/SettingsCoordinator.swift @@ -20,8 +20,6 @@ final class SettingsCoordinator: NavigationCoordinatable { @Route(.push) var log = makeLog @Route(.push) - var nativePlayerSettings = makeNativePlayerSettings - @Route(.push) var maximumBitrateSettings = makeMaximumBitrateSettings @Route(.push) var quickConnect = makeQuickConnectAuthorize @@ -69,10 +67,6 @@ final class SettingsCoordinator: NavigationCoordinatable { #endif #if os(iOS) - @ViewBuilder - func makeNativePlayerSettings() -> some View { - NativeVideoPlayerSettingsView() - } @ViewBuilder func makeMaximumBitrateSettings() -> some View { diff --git a/Shared/Coordinators/VideoPlayerSettingsCoordinator.swift b/Shared/Coordinators/VideoPlayerSettingsCoordinator.swift index 0133f055b..fbcba2096 100644 --- a/Shared/Coordinators/VideoPlayerSettingsCoordinator.swift +++ b/Shared/Coordinators/VideoPlayerSettingsCoordinator.swift @@ -19,7 +19,12 @@ final class VideoPlayerSettingsCoordinator: NavigationCoordinatable { @Route(.push) var fontPicker = makeFontPicker - #if os(iOS) + #if os(tvOS) + @Route(.push) + var resumeOffset = makeResumeOffset + @Route(.push) + var subtitleSize = makeSubtitleSize + #elseif os(iOS) @Route(.push) var gestureSettings = makeGestureSettings @Route(.push) @@ -30,7 +35,17 @@ final class VideoPlayerSettingsCoordinator: NavigationCoordinatable { FontPickerView(selection: selection) } - #if os(iOS) + #if os(tvOS) + + func makeResumeOffset(selection: Binding) -> some View { + ResumeOffsetPickerView(selection: selection) + } + + func makeSubtitleSize(selection: Binding) -> some View { + SubtitleSizePickerView(selection: selection) + } + + #elseif os(iOS) @ViewBuilder func makeGestureSettings() -> some View { diff --git a/Shared/Strings/Strings.swift b/Shared/Strings/Strings.swift index 1fa91a3fc..407372891 100644 --- a/Shared/Strings/Strings.swift +++ b/Shared/Strings/Strings.swift @@ -98,6 +98,8 @@ internal enum L10n { internal static let bugsAndFeatures = L10n.tr("Localizable", "bugsAndFeatures", fallback: "Bugs and Features") /// Buttons internal static let buttons = L10n.tr("Localizable", "buttons", fallback: "Buttons") + /// Customize which playback buttons are active and adjust their size + internal static let buttonsDescription = L10n.tr("Localizable", "buttonsDescription", fallback: "Customize which playback buttons are active and adjust their size") /// Cancel internal static let cancel = L10n.tr("Localizable", "cancel", fallback: "Cancel") /// Cannot connect to host @@ -384,8 +386,8 @@ internal enum L10n { } /// Password internal static let password = L10n.tr("Localizable", "password", fallback: "Password") - /// Pause on background - internal static let pauseOnBackground = L10n.tr("Localizable", "pauseOnBackground", fallback: "Pause on background") + /// Pause on Background + internal static let pauseOnBackground = L10n.tr("Localizable", "pauseOnBackground", fallback: "Pause on Background") /// People internal static let people = L10n.tr("Localizable", "people", fallback: "People") /// Play @@ -404,6 +406,10 @@ internal enum L10n { internal static let playbackSpeed = L10n.tr("Localizable", "playbackSpeed", fallback: "Playback Speed") /// Played internal static let played = L10n.tr("Localizable", "played", fallback: "Played") + /// Player Controls + internal static let playerControls = L10n.tr("Localizable", "playerControls", fallback: "Player Controls") + /// Customize playback controls and gestures + internal static let playerControlsDescription = L10n.tr("Localizable", "playerControlsDescription", fallback: "Customize playback controls and gestures") /// Player Gestures Lock Gesture Enabled internal static let playerGesturesLockGestureEnabled = L10n.tr("Localizable", "playerGesturesLockGestureEnabled", fallback: "Player Gestures Lock Gesture Enabled") /// Play From Beginning @@ -412,8 +418,8 @@ internal enum L10n { internal static let playNext = L10n.tr("Localizable", "playNext", fallback: "Play Next") /// Play Next Item internal static let playNextItem = L10n.tr("Localizable", "playNextItem", fallback: "Play Next Item") - /// Play on active - internal static let playOnActive = L10n.tr("Localizable", "playOnActive", fallback: "Play on active") + /// Play on Active + internal static let playOnActive = L10n.tr("Localizable", "playOnActive", fallback: "Play on Active") /// Play Previous Item internal static let playPreviousItem = L10n.tr("Localizable", "playPreviousItem", fallback: "Play Previous Item") /// Posters @@ -498,8 +504,8 @@ internal enum L10n { internal static let resume5SecondOffset = L10n.tr("Localizable", "resume5SecondOffset", fallback: "Resume 5 Second Offset") /// Resume Offset internal static let resumeOffset = L10n.tr("Localizable", "resumeOffset", fallback: "Resume Offset") - /// Resume content seconds before the recorded resume time - internal static let resumeOffsetDescription = L10n.tr("Localizable", "resumeOffsetDescription", fallback: "Resume content seconds before the recorded resume time") + /// Set the media playback to rewind by the offset amount in seconds when resuming + internal static let resumeOffsetDescription = L10n.tr("Localizable", "resumeOffsetDescription", fallback: "Set the media playback to rewind by the offset amount in seconds when resuming") /// Resume Offset internal static let resumeOffsetTitle = L10n.tr("Localizable", "resumeOffsetTitle", fallback: "Resume Offset") /// Retrieving media information @@ -598,6 +604,8 @@ internal enum L10n { internal static let slider = L10n.tr("Localizable", "slider", fallback: "Slider") /// Slider Color internal static let sliderColor = L10n.tr("Localizable", "sliderColor", fallback: "Slider Color") + /// Customize the timeline slider for media playback + internal static let sliderDescription = L10n.tr("Localizable", "sliderDescription", fallback: "Customize the timeline slider for media playback") /// Slider Type internal static let sliderType = L10n.tr("Localizable", "sliderType", fallback: "Slider Type") /// Smaller @@ -622,6 +630,10 @@ internal enum L10n { internal static let subtitle = L10n.tr("Localizable", "subtitle", fallback: "Subtitle") /// Subtitle Color internal static let subtitleColor = L10n.tr("Localizable", "subtitleColor", fallback: "Subtitle Color") + /// Customize text-based subtitles font and color + internal static let subtitleDescriptionIOS = L10n.tr("Localizable", "subtitleDescriptionIOS", fallback: "Customize text-based subtitles font and color") + /// Customize text-based subtitles font + internal static let subtitleDescriptionTVOS = L10n.tr("Localizable", "subtitleDescriptionTVOS", fallback: "Customize text-based subtitles font") /// Subtitle Font internal static let subtitleFont = L10n.tr("Localizable", "subtitleFont", fallback: "Subtitle Font") /// Subtitle Offset @@ -646,6 +658,8 @@ internal enum L10n { internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size") /// Timestamp internal static let timestamp = L10n.tr("Localizable", "timestamp", fallback: "Timestamp") + /// Adjust how the current timestamp is shown during playback + internal static let timestampDescription = L10n.tr("Localizable", "timestampDescription", fallback: "Adjust how the current timestamp is shown during playback") /// Timestamp Type internal static let timestampType = L10n.tr("Localizable", "timestampType", fallback: "Timestamp Type") /// Too Many Redirects @@ -654,6 +668,8 @@ internal enum L10n { internal static let trailingValue = L10n.tr("Localizable", "trailingValue", fallback: "Trailing Value") /// Transition internal static let transition = L10n.tr("Localizable", "transition", fallback: "Transition") + /// Configure how Swiftfin manages transitions between foreground and background while media is playing + internal static let transitionDescription = L10n.tr("Localizable", "transitionDescription", fallback: "Configure how Swiftfin manages transitions between foreground and background while media is playing") /// Try again internal static let tryAgain = L10n.tr("Localizable", "tryAgain", fallback: "Try again") /// TV Shows diff --git a/Swiftfin tvOS/Components/StepperView.swift b/Swiftfin tvOS/Components/StepperView.swift index e193243bc..f72084f6a 100644 --- a/Swiftfin tvOS/Components/StepperView.swift +++ b/Swiftfin tvOS/Components/StepperView.swift @@ -13,6 +13,12 @@ struct StepperView: View { @Binding private var value: Value + @State + private var updatedValue: Value + + @Environment(\.presentationMode) + private var presentationMode + private var title: String private var description: String? private var range: ClosedRange @@ -36,16 +42,17 @@ struct StepperView: View { } .frame(maxHeight: .infinity) - formatter(value).text + formatter(updatedValue).text .font(.title) .frame(height: 250) VStack { - HStack { Button { - guard value >= range.lowerBound else { return } - value = value.advanced(by: -step) + if updatedValue > range.lowerBound { + updatedValue = max(updatedValue.advanced(by: -step), range.lowerBound) + value = updatedValue + } } label: { Image(systemName: "minus") .font(.title2.weight(.bold)) @@ -54,8 +61,10 @@ struct StepperView: View { .buttonStyle(.card) Button { - guard value <= range.upperBound else { return } - value = value.advanced(by: step) + if updatedValue < range.upperBound { + updatedValue = min(updatedValue.advanced(by: step), range.upperBound) + value = updatedValue + } } label: { Image(systemName: "plus") .font(.title2.weight(.bold)) @@ -64,10 +73,8 @@ struct StepperView: View { .buttonStyle(.card) } - Button { - onCloseSelected() - } label: { - Text("Close") + Button(L10n.close) { + presentationMode.wrappedValue.dismiss() } Spacer() @@ -86,15 +93,14 @@ extension StepperView { range: ClosedRange, step: Value.Stride ) { - self.init( - value: value, - title: title, - description: description, - range: range, - step: step, - formatter: { $0.description }, - onCloseSelected: {} - ) + self._value = value + self._updatedValue = State(initialValue: value.wrappedValue) + self.title = title + self.description = description + self.range = range + self.step = step + self.formatter = { $0.description } + self.onCloseSelected = {} } func valueFormatter(_ formatter: @escaping (Value) -> String) -> Self { diff --git a/Swiftfin tvOS/Views/FontPickerView.swift b/Swiftfin tvOS/Views/FontPickerView.swift index 8b76365cc..befb76210 100644 --- a/Swiftfin tvOS/Views/FontPickerView.swift +++ b/Swiftfin tvOS/Views/FontPickerView.swift @@ -50,5 +50,6 @@ struct FontPickerView: View { } } .withDescriptionTopPadding() + .navigationTitle(L10n.subtitleFont) } } diff --git a/Swiftfin tvOS/Views/ResumeOffsetPickerView.swift b/Swiftfin tvOS/Views/ResumeOffsetPickerView.swift new file mode 100644 index 000000000..59c27b1d3 --- /dev/null +++ b/Swiftfin tvOS/Views/ResumeOffsetPickerView.swift @@ -0,0 +1,28 @@ +// +// 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 Defaults +import SwiftUI + +struct ResumeOffsetPickerView: View { + + @Binding + var selection: Int + + var body: some View { + StepperView( + title: L10n.resumeOffset, + value: $selection, + range: 0 ... 30, + step: 1 + ) + .valueFormatter { + $0.secondLabel + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift deleted file mode 100644 index 264409779..000000000 --- a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// 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 Defaults -import SwiftUI - -struct VideoPlayerSettingsView: View { - - @Default(.VideoPlayer.Subtitle.subtitleFontName) - private var subtitleFontName - - @Default(.VideoPlayer.jumpBackwardLength) - private var jumpBackwardLength - @Default(.VideoPlayer.jumpForwardLength) - private var jumpForwardLength - @Default(.VideoPlayer.resumeOffset) - private var resumeOffset - - @Default(.VideoPlayer.Transition.pauseOnBackground) - private var pauseOnBackground - @Default(.VideoPlayer.Transition.playOnActive) - private var playOnActive - - @EnvironmentObject - private var router: VideoPlayerSettingsCoordinator.Router - - @State - private var isPresentingResumeOffsetStepper: Bool = false - - var body: some View { - SplitFormWindowView() - .descriptionView { - Image(systemName: "tv") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 400) - } - .contentView { - - Section { - - ChevronButton( - L10n.offset, - subtitle: resumeOffset.secondLabel - ) - .onSelect { - isPresentingResumeOffsetStepper = true - } - } header: { - L10n.resume.text - } footer: { - L10n.resumeOffsetDescription.text - } - - Section { - - ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName) - .onSelect { - router.route(to: \.fontPicker, $subtitleFontName) - } - } header: { - L10n.subtitles.text - } footer: { - L10n.subtitlesDisclaimer.text - } - - Section(L10n.playback) { - Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) - Toggle(L10n.playOnActive, isOn: $playOnActive) - } - .navigationTitle(L10n.videoPlayer.text) - .blurFullScreenCover(isPresented: $isPresentingResumeOffsetStepper) { - StepperView( - title: L10n.resumeOffsetTitle, - description: L10n.resumeOffsetDescription, - value: $resumeOffset, - range: 0 ... 30, - step: 1 - ) - .valueFormatter { - $0.secondLabel - } - .onCloseSelected { - isPresentingResumeOffsetStepper = false - } - } - } - } -} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift new file mode 100644 index 000000000..55ec44b95 --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift @@ -0,0 +1,34 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct PlayerControlsSection: View { + @Default(.VideoPlayer.jumpBackwardLength) + private var jumpBackwardLength + @Default(.VideoPlayer.jumpForwardLength) + private var jumpForwardLength + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section { + InlineEnumToggle(title: L10n.jumpBackwardLength, selection: $jumpBackwardLength) + + InlineEnumToggle(title: L10n.jumpForwardLength, selection: $jumpForwardLength) + } header: { + L10n.playerControls.text + } footer: { + L10n.playerControlsDescription.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift new file mode 100644 index 000000000..ddb163864 --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift @@ -0,0 +1,36 @@ +// +// 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 Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct ResumeOffsetSection: View { + @Default(.VideoPlayer.resumeOffset) + private var resumeOffset + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section { + ChevronButton( + L10n.offset, + subtitle: resumeOffset.secondLabel + ) + .onSelect { + router.route(to: \.resumeOffset, $resumeOffset) + } + } header: { + L10n.resume.text + } footer: { + L10n.resumeOffsetDescription.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift new file mode 100644 index 000000000..39a334dec --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift @@ -0,0 +1,27 @@ +// +// 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 Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct SliderSection: View { + @Default(.VideoPlayer.Overlay.chapterSlider) + private var chapterSlider + + var body: some View { + Section { + Toggle(L10n.chapterSlider, isOn: $chapterSlider) + } header: { + L10n.slider.text + } footer: { + L10n.sliderDescription.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift new file mode 100644 index 000000000..9c9a375ee --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift @@ -0,0 +1,43 @@ +// +// 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 Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct SubtitleSection: View { + @Default(.VideoPlayer.Subtitle.subtitleFontName) + private var subtitleFontName + @Default(.VideoPlayer.Subtitle.subtitleSize) + private var subtitleSize + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section { + ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName) + .onSelect { + router.route(to: \.fontPicker, $subtitleFontName) + } + + ChevronButton( + L10n.subtitleSize, + subtitle: subtitleSize.description + ) + .onSelect { + router.route(to: \.subtitleSize, $subtitleSize) + } + } header: { + L10n.subtitle.text + } footer: { + L10n.subtitleDescriptionTVOS.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift new file mode 100644 index 000000000..91db16086 --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift @@ -0,0 +1,31 @@ +// +// 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 Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct TransitionSection: View { + @Default(.VideoPlayer.Transition.pauseOnBackground) + private var pauseOnBackground + @Default(.VideoPlayer.Transition.playOnActive) + private var playOnActive + + var body: some View { + Section { + Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) + + Toggle(L10n.playOnActive, isOn: $playOnActive) + } header: { + L10n.transition.text + } footer: { + L10n.transitionDescription.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift new file mode 100644 index 000000000..40b5bf5e2 --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift @@ -0,0 +1,46 @@ +// +// 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 Defaults +import SwiftUI + +struct VideoPlayerSettingsView: View { + @Default(.VideoPlayer.videoPlayerType) + private var videoPlayerType + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + SplitFormWindowView() + .descriptionView { + Image(systemName: "tv") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 400) + } + .contentView { + switch videoPlayerType { + case .native: + ResumeOffsetSection() + + case .swiftfin: + PlayerControlsSection() + + ResumeOffsetSection() + + SliderSection() + + SubtitleSection() + + TransitionSection() + } + } + .navigationTitle(L10n.videoPlayer.text) + } +} diff --git a/Swiftfin tvOS/Views/SubtitleSizePickerView.swift b/Swiftfin tvOS/Views/SubtitleSizePickerView.swift new file mode 100644 index 000000000..b7c09839e --- /dev/null +++ b/Swiftfin tvOS/Views/SubtitleSizePickerView.swift @@ -0,0 +1,25 @@ +// +// 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 Defaults +import SwiftUI + +struct SubtitleSizePickerView: View { + + @Binding + var selection: Int + + var body: some View { + StepperView( + title: L10n.subtitleSize, + value: $selection, + range: 8 ... 24, + step: 1 + ) + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 584379c3e..3c0f4236a 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -14,6 +14,16 @@ 4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; }; 4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; }; 4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */; }; + 4E23D9552C711C81004B6FE5 /* ResumeOffsetSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9542C711C81004B6FE5 /* ResumeOffsetSection.swift */; }; + 4E23D9572C711E16004B6FE5 /* PlayerControlsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9562C711E16004B6FE5 /* PlayerControlsSection.swift */; }; + 4E23D9762C71547F004B6FE5 /* ResumeOffsetSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D96A2C71547F004B6FE5 /* ResumeOffsetSection.swift */; }; + 4E23D9842C71B81F004B6FE5 /* ResumeOffsetPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9832C71B81F004B6FE5 /* ResumeOffsetPickerView.swift */; }; + 4E23D9862C71C006004B6FE5 /* SubtitleSizePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9852C71C006004B6FE5 /* SubtitleSizePickerView.swift */; }; + 4E23D9902C71C8CC004B6FE5 /* PlayerControlsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9882C71C8CC004B6FE5 /* PlayerControlsSection.swift */; }; + 4E23D9912C71C8CC004B6FE5 /* ResumeOffsetSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D9892C71C8CC004B6FE5 /* ResumeOffsetSection.swift */; }; + 4E23D9922C71C8CC004B6FE5 /* SliderSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D98A2C71C8CC004B6FE5 /* SliderSection.swift */; }; + 4E23D9932C71C8CC004B6FE5 /* SubtitleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D98B2C71C8CC004B6FE5 /* SubtitleSection.swift */; }; + 4E23D9952C71C8CC004B6FE5 /* TransitionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E23D98D2C71C8CC004B6FE5 /* TransitionSection.swift */; }; 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; 4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; }; 4E73E2A72C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; }; @@ -453,7 +463,6 @@ E1559A76294D960C00C1FFBC /* MainOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1559A75294D960C00C1FFBC /* MainOverlay.swift */; }; E157563029355B7900976E1F /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E157562F29355B7900976E1F /* UpdateView.swift */; }; E15756322935642A00976E1F /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15756312935642A00976E1F /* Double.swift */; }; - E15756342936851D00976E1F /* NativeVideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15756332936851D00976E1F /* NativeVideoPlayerSettingsView.swift */; }; E15756362936856700976E1F /* VideoPlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15756352936856700976E1F /* VideoPlayerType.swift */; }; E1575E3C293C6B15001665B1 /* Files in Frameworks */ = {isa = PBXBuildFile; productRef = E1575E3B293C6B15001665B1 /* Files */; }; E1575E56293E7650001665B1 /* VLCUI in Frameworks */ = {isa = PBXBuildFile; productRef = E1575E55293E7650001665B1 /* VLCUI */; }; @@ -938,6 +947,16 @@ 4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = ""; }; 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOrientation.swift; sourceTree = ""; }; 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeSettingsCoordinator.swift; sourceTree = ""; }; + 4E23D9542C711C81004B6FE5 /* ResumeOffsetSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeOffsetSection.swift; sourceTree = ""; }; + 4E23D9562C711E16004B6FE5 /* PlayerControlsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSection.swift; sourceTree = ""; }; + 4E23D96A2C71547F004B6FE5 /* ResumeOffsetSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResumeOffsetSection.swift; sourceTree = ""; }; + 4E23D9832C71B81F004B6FE5 /* ResumeOffsetPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeOffsetPickerView.swift; sourceTree = ""; }; + 4E23D9852C71C006004B6FE5 /* SubtitleSizePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSizePickerView.swift; sourceTree = ""; }; + 4E23D9882C71C8CC004B6FE5 /* PlayerControlsSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerControlsSection.swift; sourceTree = ""; }; + 4E23D9892C71C8CC004B6FE5 /* ResumeOffsetSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResumeOffsetSection.swift; sourceTree = ""; }; + 4E23D98A2C71C8CC004B6FE5 /* SliderSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderSection.swift; sourceTree = ""; }; + 4E23D98B2C71C8CC004B6FE5 /* SubtitleSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubtitleSection.swift; sourceTree = ""; }; + 4E23D98D2C71C8CC004B6FE5 /* TransitionSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionSection.swift; sourceTree = ""; }; 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = ""; }; 4E73E2AD2C420207002D2A78 /* MaximumBitrateSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaximumBitrateSettingsView.swift; sourceTree = ""; }; @@ -1256,7 +1275,6 @@ E1559A75294D960C00C1FFBC /* MainOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainOverlay.swift; sourceTree = ""; }; E157562F29355B7900976E1F /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; E15756312935642A00976E1F /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; - E15756332936851D00976E1F /* NativeVideoPlayerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoPlayerSettingsView.swift; sourceTree = ""; }; E15756352936856700976E1F /* VideoPlayerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerType.swift; sourceTree = ""; }; E1575EA5293E7D40001665B1 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; E1579EA62B97DC1500A31CA1 /* Eventful.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Eventful.swift; sourceTree = ""; }; @@ -1683,6 +1701,36 @@ path = Components; sourceTree = ""; }; + 4E23D96F2C71547F004B6FE5 /* Sections */ = { + isa = PBXGroup; + children = ( + 4E23D9882C71C8CC004B6FE5 /* PlayerControlsSection.swift */, + 4E23D9892C71C8CC004B6FE5 /* ResumeOffsetSection.swift */, + 4E23D98A2C71C8CC004B6FE5 /* SliderSection.swift */, + 4E23D98B2C71C8CC004B6FE5 /* SubtitleSection.swift */, + 4E23D98D2C71C8CC004B6FE5 /* TransitionSection.swift */, + 4E23D96A2C71547F004B6FE5 /* ResumeOffsetSection.swift */, + ); + path = Sections; + sourceTree = ""; + }; + 4E23D9712C71547F004B6FE5 /* Components */ = { + isa = PBXGroup; + children = ( + 4E23D96F2C71547F004B6FE5 /* Sections */, + ); + path = Components; + sourceTree = ""; + }; + 4E23D9732C71547F004B6FE5 /* VideoPlayerSettingsView */ = { + isa = PBXGroup; + children = ( + 4E23D9712C71547F004B6FE5 /* Components */, + E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */, + ); + path = VideoPlayerSettingsView; + sourceTree = ""; + }; 4E3A785D2C3B87A400D33C11 /* PlaybackBitrate */ = { isa = PBXGroup; children = ( @@ -2221,6 +2269,8 @@ isa = PBXGroup; children = ( BD3957742C112A330078CEF8 /* ButtonSection.swift */, + 4E23D9562C711E16004B6FE5 /* PlayerControlsSection.swift */, + 4E23D9542C711C81004B6FE5 /* ResumeOffsetSection.swift */, BD3957762C112AD30078CEF8 /* SliderSection.swift */, BD3957782C113EC40078CEF8 /* SubtitleSection.swift */, BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */, @@ -2604,11 +2654,13 @@ E103DF932BCF31C5000229B2 /* MediaView */, E10231572BCF8AF8009D71FC /* ProgramsView */, E10B1E8C2BD7708900A92EAF /* QuickConnectView.swift */, + 4E23D9832C71B81F004B6FE5 /* ResumeOffsetPickerView.swift */, E1E1643928BAC2EF00323B0A /* SearchView.swift */, E193D54A271941D300900D82 /* SelectServerView.swift */, E164A8122BE4995200A54B18 /* SelectUserView */, E193D54F2719430400900D82 /* ServerDetailView.swift */, E1E5D54D2783E66600692DFE /* SettingsView */, + 4E23D9852C71C006004B6FE5 /* SubtitleSizePickerView.swift */, E1763A672BF3D168004DF6AB /* UserSignInView */, 5310694F2684E7EE00CFFDBA /* VideoPlayer */, ); @@ -3483,7 +3535,6 @@ E104C86F296E087200C1C3F9 /* IndicatorSettingsView.swift */, E16AF11B292C98A7001422A8 /* GestureSettingsView.swift */, 4E73E2AD2C420207002D2A78 /* MaximumBitrateSettingsView.swift */, - E15756332936851D00976E1F /* NativeVideoPlayerSettingsView.swift */, E1545BD62BDC559500D9578F /* UserProfileSettingsView */, E1BE1CEB2BDB68BC008176A9 /* SettingsView */, E1BDF2E7295148F400CC0294 /* VideoPlayerSettingsView */, @@ -3499,7 +3550,7 @@ E104C872296E0D0A00C1C3F9 /* IndicatorSettingsView.swift */, 4E73E2AF2C4211CA002D2A78 /* MaximumBitrateSettingsView.swift */, 5398514426B64DA100101B49 /* SettingsView.swift */, - E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */, + 4E23D9732C71547F004B6FE5 /* VideoPlayerSettingsView */, ); path = SettingsView; sourceTree = ""; @@ -3901,6 +3952,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4E23D9842C71B81F004B6FE5 /* ResumeOffsetPickerView.swift in Sources */, E1AEFA392BE36C4C00CFAFD8 /* SwiftfinStore+ServerState.swift in Sources */, E15D4F0B2B1BD88900442DB8 /* Edge.swift in Sources */, E193D53627193F8500900D82 /* LibraryCoordinator.swift in Sources */, @@ -3917,6 +3969,7 @@ E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */, E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */, C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */, + 4E23D9952C71C8CC004B6FE5 /* TransitionSection.swift in Sources */, C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */, E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */, E1575E6F293E77B5001665B1 /* GestureAction.swift in Sources */, @@ -4054,6 +4107,7 @@ E1579EA82B97DC1500A31CA1 /* Eventful.swift in Sources */, E185920828CDAAA200326F80 /* SimilarItemsHStack.swift in Sources */, E10E842C29A589860064EA49 /* NonePosterButton.swift in Sources */, + 4E23D9902C71C8CC004B6FE5 /* PlayerControlsSection.swift in Sources */, E1575E5C293E77B5001665B1 /* PlaybackSpeed.swift in Sources */, E1DC9842296DEBD800982F06 /* WatchedIndicator.swift in Sources */, E1575E6C293E77B5001665B1 /* SliderType.swift in Sources */, @@ -4125,12 +4179,14 @@ E1DABAFA2A270E62008AC34A /* OverviewCard.swift in Sources */, E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */, E129428628F080B500796AC6 /* OnReceiveNotificationModifier.swift in Sources */, + 4E23D9762C71547F004B6FE5 /* ResumeOffsetSection.swift in Sources */, 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, 62E632E4267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, E149CCAE2BE6ECC8008B9331 /* Storable.swift in Sources */, C46DD8D92A8DC2990046A504 /* LiveNativeVideoPlayer.swift in Sources */, E1575E9F293E7B1E001665B1 /* Int.swift in Sources */, E1D9F475296E86D400129AF3 /* NativeVideoPlayer.swift in Sources */, + 4E23D9912C71C8CC004B6FE5 /* ResumeOffsetSection.swift in Sources */, E145EB462BE0AD4E003BF6F3 /* Set.swift in Sources */, E1575E7D293E77B5001665B1 /* PosterDisplayType.swift in Sources */, E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */, @@ -4150,6 +4206,7 @@ E12CC1CD28D135C700678D5D /* NextUpView.swift in Sources */, E18E02232887492B0022598C /* ImageView.swift in Sources */, E1575E7F293E77B5001665B1 /* AppAppearance.swift in Sources */, + 4E23D9862C71C006004B6FE5 /* SubtitleSizePickerView.swift in Sources */, E1575E5D293E77B5001665B1 /* ItemViewType.swift in Sources */, E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */, E1575E7A293E77B5001665B1 /* TimeStampType.swift in Sources */, @@ -4214,6 +4271,7 @@ E1D37F4F2B9CEDC400343D2B /* DeviceProfile.swift in Sources */, E1575E94293E7B1E001665B1 /* VerticalAlignment.swift in Sources */, E1575EA3293E7B1E001665B1 /* UIDevice.swift in Sources */, + 4E23D9922C71C8CC004B6FE5 /* SliderSection.swift in Sources */, E193D547271941C500900D82 /* SelectUserView.swift in Sources */, E1BDF2E62951475300CC0294 /* VideoPlayerActionButton.swift in Sources */, E10231592BCF8AF8009D71FC /* ChannelLibraryView.swift in Sources */, @@ -4239,6 +4297,7 @@ E1B90C8A2BC475E7007027C8 /* ScalingButtonStyle.swift in Sources */, E1DABAFE2A27B982008AC34A /* RatingsCard.swift in Sources */, E1C9261B288756BD002A7A66 /* DotHStack.swift in Sources */, + 4E23D9932C71C8CC004B6FE5 /* SubtitleSection.swift in Sources */, E104C873296E0D0A00C1C3F9 /* IndicatorSettingsView.swift in Sources */, E18ACA8D2A14773500BB4F35 /* (null) in Sources */, E10B1E8E2BD7708900A92EAF /* QuickConnectView.swift in Sources */, @@ -4307,6 +4366,7 @@ E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */, E129429828F4785200796AC6 /* CaseIterablePicker.swift in Sources */, E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */, + 4E23D9552C711C81004B6FE5 /* ResumeOffsetSection.swift in Sources */, E154965E296CA2EF00C4EF88 /* DownloadTask.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, E1E2F8422B757E0900B75998 /* OnFirstAppearModifier.swift in Sources */, @@ -4567,12 +4627,12 @@ E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */, E1C925F72887504B002A7A66 /* PanDirectionGestureRecognizer.swift in Sources */, E18E01E9288747230022598C /* SeriesItemView.swift in Sources */, - E15756342936851D00976E1F /* NativeVideoPlayerSettingsView.swift in Sources */, E1D4BF7C2719D05000A11E64 /* AppSettingsView.swift in Sources */, 4E16FD512C0183DB00110147 /* LetterPickerButton.swift in Sources */, E19D41AE2BF288320082B8B2 /* ServerCheckViewModel.swift in Sources */, E1BDF2F329524C3B00CC0294 /* ChaptersActionButton.swift in Sources */, E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */, + 4E23D9572C711E16004B6FE5 /* PlayerControlsSection.swift in Sources */, E1BE1CF02BDB6C97008176A9 /* UserProfileSettingsView.swift in Sources */, E1DC7ACA2C63337C00AEE368 /* iOS15View.swift in Sources */, E1CFE28028FA606800B7D34C /* ChapterTrack.swift in Sources */, diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 38f911da8..a201177ee 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "651194fc1966b57201a0de2cba27dc40798bbdf515febdc83f00d634d916fea4", + "originHash" : "c3c005943de15b4f098b3401fea9ea92a16063e9fb79137ea4cfd816903c993a", "pins" : [ { "identity" : "blurhashkit", diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeFilterSection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeFilterSection.swift new file mode 100644 index 000000000..9fabcf051 --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeFilterSection.swift @@ -0,0 +1,54 @@ +// +// 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 Defaults +import SwiftUI + +extension CustomizeViewsSettings { + struct CustomizeFilterSection: View { + @Default(.Customization.Library.letterPickerEnabled) + private var letterPickerEnabled + @Default(.Customization.Library.letterPickerOrientation) + private var letterPickerOrientation + @Default(.Customization.Library.enabledDrawerFilters) + private var libraryEnabledDrawerFilters + @Default(.Customization.Search.enabledDrawerFilters) + private var searchEnabledDrawerFilters + + @EnvironmentObject + private var router: SettingsCoordinator.Router + + var body: some View { + Section { + Toggle(L10n.letterPicker, isOn: $letterPickerEnabled) + + if letterPickerEnabled { + CaseIterablePicker( + L10n.orientation, + selection: $letterPickerOrientation + ) + } + + ChevronButton(L10n.library) + .onSelect { + router.route(to: \.itemFilterDrawerSelector, $libraryEnabledDrawerFilters) + } + + ChevronButton(L10n.search) + .onSelect { + router.route(to: \.itemFilterDrawerSelector, $searchEnabledDrawerFilters) + } + + } header: { + L10n.filters.text + } footer: { + L10n.filters.text + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeItemSection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeItemSection.swift new file mode 100644 index 000000000..44cd5b533 --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeItemSection.swift @@ -0,0 +1,64 @@ +// +// 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 Defaults +import SwiftUI + +extension CustomizeViewsSettings { + struct CustomizeItemSection: View { + @Default(.Customization.itemViewType) + private var itemViewType + @Default(.Customization.CinematicItemViewType.usePrimaryImage) + private var cinematicItemViewTypeUsePrimaryImage + + @Default(.Customization.Episodes.useSeriesLandscapeBackdrop) + private var useSeriesLandscapeBackdrop + + @Default(.Customization.shouldShowMissingSeasons) + private var shouldShowMissingSeasons + @Default(.Customization.shouldShowMissingEpisodes) + private var shouldShowMissingEpisodes + + var body: some View { + if UIDevice.isPhone { + Section { + CaseIterablePicker(L10n.items, selection: $itemViewType) + } header: { + Text("Media Items") + } footer: { + // L10n.itemsDescription.text + } + + if itemViewType == .cinematic { + Section { + Toggle(L10n.usePrimaryImage, isOn: $cinematicItemViewTypeUsePrimaryImage) + } footer: { + L10n.usePrimaryImageDescription.text + } + } + } + + Section { + Toggle(L10n.seriesBackdrop, isOn: $useSeriesLandscapeBackdrop) + } footer: { + // TODO: think of a better name + L10n.episodeLandscapePoster.text + } + + Section { + Toggle(L10n.showMissingSeasons, isOn: $shouldShowMissingSeasons) + + Toggle(L10n.showMissingEpisodes, isOn: $shouldShowMissingEpisodes) + } header: { + L10n.missingItems.text + } footer: { + // L10n.missingItemsDescription.text + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeLibrarySection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeLibrarySection.swift new file mode 100644 index 000000000..e1b65e870 --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizeLibrarySection.swift @@ -0,0 +1,80 @@ +// +// 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 Defaults +import SwiftUI + +extension CustomizeViewsSettings { + struct CustomizeLibrarySection: View { + @Default(.Customization.Library.cinematicBackground) + private var cinematicBackground + @Default(.Customization.Library.randomImage) + private var libraryRandomImage + @Default(.Customization.Library.showFavorites) + private var showFavorites + @Default(.Customization.showRecentlyAdded) + private var showRecentlyAdded + + @Default(.Customization.Library.displayType) + private var libraryDisplayType + @Default(.Customization.Library.posterType) + private var libraryPosterType + @Default(.Customization.Library.listColumnCount) + private var listColumnCount + + @Default(.Customization.Library.rememberLayout) + private var rememberLibraryLayout + @Default(.Customization.Library.rememberSort) + private var rememberLibrarySort + + var body: some View { + Section { + Toggle(L10n.cinematicBackground, isOn: $cinematicBackground) + + Toggle(L10n.randomImage, isOn: $libraryRandomImage) + + Toggle(L10n.showFavorites, isOn: $showFavorites) + + Toggle(L10n.showRecentlyAdded, isOn: $showRecentlyAdded) + } header: { + L10n.library.text + } footer: { + // L10n.libraryDescription.text + } + + Section { + CaseIterablePicker(L10n.library, selection: $libraryDisplayType) + + CaseIterablePicker(L10n.posters, selection: $libraryPosterType) + + if libraryDisplayType == .list, UIDevice.isPad { + BasicStepper( + title: "Columns", + value: $listColumnCount, + range: 1 ... 4, + step: 1 + ) + } + } footer: { + Text("Customize The library layouts") // L10n.libraryDescription.text + } + + Section { + Toggle("Remember layout", isOn: $rememberLibraryLayout) + } footer: { + Text("Remember layout for individual libraries") + } + + Section { + Toggle("Remember sorting", isOn: $rememberLibrarySort) + } footer: { + Text("Remember sorting for individual libraries") + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizePosterSection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizePosterSection.swift new file mode 100644 index 000000000..5f2523cb1 --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/CustomizePosterSection.swift @@ -0,0 +1,59 @@ +// +// 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 Defaults +import SwiftUI + +extension CustomizeViewsSettings { + struct CustomizePosterSection: View { + @Default(.Customization.showPosterLabels) + private var showPosterLabels + @Default(.Customization.nextUpPosterType) + private var nextUpPosterType + @Default(.Customization.recentlyAddedPosterType) + private var recentlyAddedPosterType + @Default(.Customization.latestInLibraryPosterType) + private var latestInLibraryPosterType + @Default(.Customization.similarPosterType) + private var similarPosterType + @Default(.Customization.searchPosterType) + private var searchPosterType + @Default(.Customization.Library.displayType) + private var libraryViewType + + @EnvironmentObject + private var router: SettingsCoordinator.Router + + var body: some View { + Section { + ChevronButton(L10n.indicators) + .onSelect { + router.route(to: \.indicatorSettings) + } + + Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) + + CaseIterablePicker(L10n.next, selection: $nextUpPosterType) + + CaseIterablePicker(L10n.recentlyAdded, selection: $recentlyAddedPosterType) + + CaseIterablePicker(L10n.latestWithString(L10n.library), selection: $latestInLibraryPosterType) + + CaseIterablePicker(L10n.recommended, selection: $similarPosterType) + + CaseIterablePicker(L10n.search, selection: $searchPosterType) + + CaseIterablePicker(L10n.library, selection: $libraryViewType) + } header: { + L10n.posters.text + } footer: { + // L10n.postersDescription.text + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift new file mode 100644 index 000000000..a1c01b0e1 --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift @@ -0,0 +1,34 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +// TODO: will be entirely re-organized + +struct CustomizeViewsSettings: View { + @Default(.Customization.showRecentlyAdded) + private var showRecentlyAdded + + var body: some View { + List { + CustomizeItemSection() + + CustomizeFilterSection() + + CustomizePosterSection() + + CustomizeLibrarySection() + + Section("Home") { + Toggle("Show recently added", isOn: $showRecentlyAdded) + } + } + .navigationTitle(L10n.customize) + } +} diff --git a/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift index b70eec14c..14c4afcb7 100644 --- a/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift @@ -62,11 +62,6 @@ struct SettingsView: View { selection: $videoPlayerType ) - ChevronButton(L10n.nativePlayer) - .onSelect { - router.route(to: \.nativePlayerSettings) - } - ChevronButton(L10n.videoPlayer) .onSelect { router.route(to: \.videoPlayerSettings) diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/ActionButtonSelectorView.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/ActionButtonSelectorView.swift index fc92ba9ef..05d598ea2 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/ActionButtonSelectorView.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/ActionButtonSelectorView.swift @@ -10,7 +10,6 @@ import Defaults import SwiftUI struct ActionButtonSelectorView: View { - @Binding var selection: [VideoPlayerActionButton] diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift index 05e2f6ee1..e5227853b 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift @@ -11,19 +11,14 @@ import SwiftUI extension VideoPlayerSettingsView { struct ButtonSection: View { - @Default(.VideoPlayer.Overlay.playbackButtonType) private var playbackButtonType - @Default(.VideoPlayer.showJumpButtons) private var showJumpButtons - @Default(.VideoPlayer.barActionButtons) private var barActionButtons - @Default(.VideoPlayer.menuActionButtons) private var menuActionButtons - @Default(.VideoPlayer.autoPlayEnabled) private var autoPlayEnabled @@ -31,8 +26,7 @@ extension VideoPlayerSettingsView { private var router: VideoPlayerSettingsCoordinator.Router var body: some View { - Section(L10n.buttons) { - + Section { CaseIterablePicker(L10n.playbackButtons, selection: $playbackButtonType) Toggle(isOn: $showJumpButtons) { @@ -51,6 +45,10 @@ extension VideoPlayerSettingsView { .onSelect { router.route(to: \.actionButtonSelector, $menuActionButtons) } + } header: { + L10n.buttons.text + } footer: { + L10n.buttonsDescription.text } .onChange(of: barActionButtons) { newValue in autoPlayEnabled = newValue.contains(.autoPlay) || menuActionButtons.contains(.autoPlay) diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift new file mode 100644 index 000000000..f86dfabd6 --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/PlayerControlsSection.swift @@ -0,0 +1,39 @@ +// +// 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 Defaults +import SwiftUI + +extension VideoPlayerSettingsView { + struct PlayerControlsSection: View { + @Default(.VideoPlayer.jumpBackwardLength) + private var jumpBackwardLength + @Default(.VideoPlayer.jumpForwardLength) + private var jumpForwardLength + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section { + ChevronButton(L10n.gestures) + .onSelect { + router.route(to: \.gestureSettings) + } + + CaseIterablePicker(L10n.jumpBackwardLength, selection: $jumpBackwardLength) + + CaseIterablePicker(L10n.jumpForwardLength, selection: $jumpForwardLength) + } header: { + L10n.playerControls.text + } footer: { + L10n.playerControlsDescription.text + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/NativeVideoPlayerSettingsView.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift similarity index 61% rename from Swiftfin/Views/SettingsView/NativeVideoPlayerSettingsView.swift rename to Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift index 0f64516c2..b87c57fe6 100644 --- a/Swiftfin/Views/SettingsView/NativeVideoPlayerSettingsView.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ResumeOffsetSection.swift @@ -9,18 +9,15 @@ import Defaults import SwiftUI -struct NativeVideoPlayerSettingsView: View { - - @Default(.VideoPlayer.resumeOffset) - private var resumeOffset - - var body: some View { - Form { +extension VideoPlayerSettingsView { + struct ResumeOffsetSection: View { + @Default(.VideoPlayer.resumeOffset) + private var resumeOffset + var body: some View { Section { - BasicStepper( - title: "Resume Offset", + title: L10n.resumeOffset, value: $resumeOffset, range: 0 ... 30, step: 1 @@ -28,10 +25,12 @@ struct NativeVideoPlayerSettingsView: View { .valueFormatter { $0.secondLabel } - } footer: { - Text("Resume content seconds before the recorded resume time") + } header: { + L10n.resume.text + } + footer: { + L10n.resumeOffsetDescription.text } } - .navigationTitle("Native Player") } } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift index e70365ad8..fad2f4089 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift @@ -11,19 +11,15 @@ import SwiftUI extension VideoPlayerSettingsView { struct SliderSection: View { - @Default(.VideoPlayer.Overlay.chapterSlider) private var chapterSlider - @Default(.VideoPlayer.Overlay.sliderColor) private var sliderColor - @Default(.VideoPlayer.Overlay.sliderType) private var sliderType var body: some View { - Section(L10n.slider) { - + Section { Toggle(L10n.chapterSlider, isOn: $chapterSlider) ColorPicker(selection: $sliderColor, supportsOpacity: false) { @@ -31,6 +27,10 @@ extension VideoPlayerSettingsView { } CaseIterablePicker(L10n.sliderType, selection: $sliderType) + } header: { + L10n.slider.text + } footer: { + L10n.sliderDescription.text } } } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift index 10b9a63fd..8e09748dd 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift @@ -39,10 +39,9 @@ extension VideoPlayerSettingsView { Text(L10n.subtitleColor) } } header: { - Text(L10n.subtitle) + L10n.subtitle.text } footer: { - // TODO: better wording - Text("Settings only affect some subtitle types") + L10n.subtitleDescriptionIOS.text } } } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift index 3ad3a5479..c2761fad6 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift @@ -11,7 +11,6 @@ import SwiftUI extension VideoPlayerSettingsView { struct TimestampSection: View { - @Default(.VideoPlayer.Overlay.trailingTimestampType) private var trailingTimestampType @Default(.VideoPlayer.Overlay.showCurrentTimeWhileScrubbing) @@ -20,13 +19,16 @@ extension VideoPlayerSettingsView { private var timestampType var body: some View { - Section(L10n.timestamp) { - + Section { Toggle(L10n.scrubCurrentTime, isOn: $showCurrentTimeWhileScrubbing) CaseIterablePicker(L10n.timestampType, selection: $timestampType) CaseIterablePicker(L10n.trailingValue, selection: $trailingTimestampType) + } header: { + L10n.timestamp.text + } footer: { + L10n.timestampDescription.text } } } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift index 3c307a527..91db16086 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift @@ -17,10 +17,14 @@ extension VideoPlayerSettingsView { private var playOnActive var body: some View { - Section(L10n.transition) { - + Section { Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) + Toggle(L10n.playOnActive, isOn: $playOnActive) + } header: { + L10n.transition.text + } footer: { + L10n.transitionDescription.text } } } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift index 885f046a8..6562461da 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift @@ -10,53 +10,30 @@ import Defaults import SwiftUI struct VideoPlayerSettingsView: View { - - @Default(.VideoPlayer.jumpBackwardLength) - private var jumpBackwardLength - @Default(.VideoPlayer.jumpForwardLength) - private var jumpForwardLength - @Default(.VideoPlayer.resumeOffset) - private var resumeOffset - - @EnvironmentObject - private var router: VideoPlayerSettingsCoordinator.Router + @Default(.VideoPlayer.videoPlayerType) + private var videoPlayerType var body: some View { Form { + switch videoPlayerType { + case .native: + ResumeOffsetSection() - ChevronButton(L10n.gestures) - .onSelect { - router.route(to: \.gestureSettings) - } + case .swiftfin: + PlayerControlsSection() - CaseIterablePicker(L10n.jumpBackwardLength, selection: $jumpBackwardLength) + ResumeOffsetSection() - CaseIterablePicker(L10n.jumpForwardLength, selection: $jumpForwardLength) + ButtonSection() - Section { - - BasicStepper( - title: L10n.resumeOffset, - value: $resumeOffset, - range: 0 ... 30, - step: 1 - ) - .valueFormatter { - $0.secondLabel - } - } footer: { - Text(L10n.resumeOffsetDescription) - } + SliderSection() - ButtonSection() + SubtitleSection() - SliderSection() + TimestampSection() - SubtitleSection() - - TimestampSection() - - TransitionSection() + TransitionSection() + } } .navigationTitle(L10n.videoPlayer) } diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 077a43620..47dc52c69 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ