diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 06128eb4..01263003 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -150,6 +150,11 @@ BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */; }; BD0BA22E2AD6508C00306A8D /* DownloadVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */; }; BD0BA22F2AD6508C00306A8D /* DownloadVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */; }; + BD3957752C112A330078CEF8 /* ButtonSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3957742C112A330078CEF8 /* ButtonSection.swift */; }; + BD3957772C112AD30078CEF8 /* SliderSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3957762C112AD30078CEF8 /* SliderSection.swift */; }; + BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3957782C113EC40078CEF8 /* SubtitleSection.swift */; }; + BD39577C2C113FAA0078CEF8 /* TimestampSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */; }; + BD39577E2C1140810078CEF8 /* TransitionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577D2C1140810078CEF8 /* TransitionSection.swift */; }; C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; }; C44FA6E02AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */; }; C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */; }; @@ -1047,6 +1052,11 @@ AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = ""; }; BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineVideoPlayerManager.swift; sourceTree = ""; }; BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadVideoPlayerManager.swift; sourceTree = ""; }; + BD3957742C112A330078CEF8 /* ButtonSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonSection.swift; sourceTree = ""; }; + BD3957762C112AD30078CEF8 /* SliderSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderSection.swift; sourceTree = ""; }; + BD3957782C113EC40078CEF8 /* SubtitleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSection.swift; sourceTree = ""; }; + BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampSection.swift; sourceTree = ""; }; + BD39577D2C1140810078CEF8 /* TransitionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionSection.swift; sourceTree = ""; }; C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = ""; }; C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = ""; }; C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = ""; }; @@ -2194,6 +2204,18 @@ path = VideoPlayerManager; sourceTree = ""; }; + BD39577A2C113F780078CEF8 /* Sections */ = { + isa = PBXGroup; + children = ( + BD3957742C112A330078CEF8 /* ButtonSection.swift */, + BD3957762C112AD30078CEF8 /* SliderSection.swift */, + BD3957782C113EC40078CEF8 /* SubtitleSection.swift */, + BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */, + BD39577D2C1140810078CEF8 /* TransitionSection.swift */, + ); + path = Sections; + sourceTree = ""; + }; C44FA6DD2AACD15300EDEB56 /* PlaybackButtons */ = { isa = PBXGroup; children = ( @@ -3241,6 +3263,7 @@ isa = PBXGroup; children = ( E1BDF2E82951490400CC0294 /* ActionButtonSelectorView.swift */, + BD39577A2C113F780078CEF8 /* Sections */, ); path = Components; sourceTree = ""; @@ -4227,6 +4250,7 @@ E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */, 621338932660107500A81A2A /* String.swift in Sources */, E17AC96F2954EE4B003D2BC2 /* DownloadListViewModel.swift in Sources */, + BD39577C2C113FAA0078CEF8 /* TimestampSection.swift in Sources */, 62C83B08288C6A630004ED0C /* FontPickerView.swift in Sources */, E122A9132788EAAD0060FA63 /* MediaStream.swift in Sources */, E1E9017F28DAB15F001B1594 /* BarActionButtons.swift in Sources */, @@ -4290,6 +4314,7 @@ E133328829538D8D00EE76AB /* Files.swift in Sources */, C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */, E1401CA02937DFF500E8B599 /* AppIconSelectorView.swift in Sources */, + BD39577E2C1140810078CEF8 /* TransitionSection.swift in Sources */, E1092F4C29106F9F00163F57 /* GestureAction.swift in Sources */, E11BDF772B8513B40045C54A /* ItemGenre.swift in Sources */, E16DEAC228EFCF590058F196 /* EnvironmentValue+Keys.swift in Sources */, @@ -4515,6 +4540,7 @@ BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */, E14EA1672BF70F9C00DE757A /* SquareImageCropView.swift in Sources */, E1BDF2F529524E6400CC0294 /* PlayNextItemActionButton.swift in Sources */, + BD3957772C112AD30078CEF8 /* SliderSection.swift in Sources */, E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */, E14EA1692BF7330A00DE757A /* UserProfileImageViewModel.swift in Sources */, E18ACA952A15A3E100BB4F35 /* (null) in Sources */, @@ -4557,6 +4583,7 @@ E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */, E1D5C39628DF90C100CDBEFB /* Slider.swift in Sources */, E187A60229AB28F0008387E6 /* RotateContentView.swift in Sources */, + BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */, E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */, @@ -4592,6 +4619,7 @@ E11BDF972B865F550045C54A /* ItemTag.swift in Sources */, E1D4BF8A2719D3D000A11E64 /* AppSettingsCoordinator.swift in Sources */, E1D37F482B9C648E00343D2B /* MaxHeightText.swift in Sources */, + BD3957752C112A330078CEF8 /* ButtonSection.swift in Sources */, E1ED91182B95993300802036 /* TitledLibraryParent.swift in Sources */, E13DD3F92717E961009D4DAF /* SelectUserViewModel.swift in Sources */, E1194F502BEB1E3000888DB6 /* StoredValues+Temp.swift in Sources */, diff --git a/Swiftfin/Views/AboutAppView.swift b/Swiftfin/Views/AboutAppView.swift index 94c1c683..75ce5ca2 100644 --- a/Swiftfin/Views/AboutAppView.swift +++ b/Swiftfin/Views/AboutAppView.swift @@ -23,8 +23,7 @@ struct AboutAppView: View { .aspectRatio(1, contentMode: .fit) .frame(height: 150) - // App name, not to be localized - Text("Swiftfin") + Text(verbatim: "Swiftfin") .fontWeight(.semibold) .font(.title2) } diff --git a/Swiftfin/Views/ConnectToServerView.swift b/Swiftfin/Views/ConnectToServerView.swift index 57eece5f..dda161da 100644 --- a/Swiftfin/Views/ConnectToServerView.swift +++ b/Swiftfin/Views/ConnectToServerView.swift @@ -37,6 +37,27 @@ struct ConnectToServerView: View { private let timer = Timer.publish(every: 12, on: .main, in: .common).autoconnect() + private func handleConnection(_ event: ConnectToServerViewModel.Event) { + switch event { + case let .connected(server): + UIDevice.feedback(.success) + + Notifications[.didConnectToServer].post(object: server) + router.popLast() + case let .duplicateServer(server): + UIDevice.feedback(.warning) + + duplicateServer = server + isPresentingDuplicateServer = true + case let .error(eventError): + UIDevice.feedback(.error) + + error = eventError + isPresentingError = true + isURLFocused = true + } + } + @ViewBuilder private var connectSection: some View { Section(L10n.connectToServer) { @@ -126,24 +147,7 @@ struct ConnectToServerView: View { viewModel.send(.searchForServers) } .onReceive(viewModel.events) { event in - switch event { - case let .connected(server): - UIDevice.feedback(.success) - - Notifications[.didConnectToServer].post(object: server) - router.popLast() - case let .duplicateServer(server): - UIDevice.feedback(.warning) - - duplicateServer = server - isPresentingDuplicateServer = true - case let .error(eventError): - UIDevice.feedback(.error) - - error = eventError - isPresentingError = true - isURLFocused = true - } + handleConnection(event) } .onReceive(timer) { _ in guard viewModel.state != .connecting else { return } diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift new file mode 100644 index 00000000..05e2f6ee --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/ButtonSection.swift @@ -0,0 +1,63 @@ +// +// 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 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 + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section(L10n.buttons) { + + CaseIterablePicker(L10n.playbackButtons, selection: $playbackButtonType) + + Toggle(isOn: $showJumpButtons) { + HStack { + Image(systemName: "goforward") + Text(L10n.jump) + } + } + + ChevronButton(L10n.barButtons) + .onSelect { + router.route(to: \.actionButtonSelector, $barActionButtons) + } + + ChevronButton(L10n.menuButtons) + .onSelect { + router.route(to: \.actionButtonSelector, $menuActionButtons) + } + } + .onChange(of: barActionButtons) { newValue in + autoPlayEnabled = newValue.contains(.autoPlay) || menuActionButtons.contains(.autoPlay) + } + .onChange(of: menuActionButtons) { newValue in + autoPlayEnabled = newValue.contains(.autoPlay) || barActionButtons.contains(.autoPlay) + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift new file mode 100644 index 00000000..e70365ad --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SliderSection.swift @@ -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) 2024 Jellyfin & Jellyfin Contributors +// + +import Defaults +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) { + + Toggle(L10n.chapterSlider, isOn: $chapterSlider) + + ColorPicker(selection: $sliderColor, supportsOpacity: false) { + Text(L10n.sliderColor) + } + + CaseIterablePicker(L10n.sliderType, selection: $sliderType) + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift new file mode 100644 index 00000000..10b9a63f --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/SubtitleSection.swift @@ -0,0 +1,49 @@ +// +// 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 + @Default(.VideoPlayer.Subtitle.subtitleColor) + private var subtitleColor + + @EnvironmentObject + private var router: VideoPlayerSettingsCoordinator.Router + + var body: some View { + Section { + ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName) + .onSelect { + router.route(to: \.fontPicker, $subtitleFontName) + } + + BasicStepper( + title: L10n.subtitleSize, + value: $subtitleSize, + range: 8 ... 24, + step: 1 + ) + + ColorPicker(selection: $subtitleColor, supportsOpacity: false) { + Text(L10n.subtitleColor) + } + } header: { + Text(L10n.subtitle) + } footer: { + // TODO: better wording + Text("Settings only affect some subtitle types") + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift new file mode 100644 index 00000000..3ad3a547 --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TimestampSection.swift @@ -0,0 +1,33 @@ +// +// 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 TimestampSection: View { + + @Default(.VideoPlayer.Overlay.trailingTimestampType) + private var trailingTimestampType + @Default(.VideoPlayer.Overlay.showCurrentTimeWhileScrubbing) + private var showCurrentTimeWhileScrubbing + @Default(.VideoPlayer.Overlay.timestampType) + private var timestampType + + var body: some View { + Section(L10n.timestamp) { + + Toggle(L10n.scrubCurrentTime, isOn: $showCurrentTimeWhileScrubbing) + + CaseIterablePicker(L10n.timestampType, selection: $timestampType) + + CaseIterablePicker(L10n.trailingValue, selection: $trailingTimestampType) + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift new file mode 100644 index 00000000..3c307a52 --- /dev/null +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.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 TransitionSection: View { + @Default(.VideoPlayer.Transition.pauseOnBackground) + private var pauseOnBackground + @Default(.VideoPlayer.Transition.playOnActive) + private var playOnActive + + var body: some View { + Section(L10n.transition) { + + Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) + Toggle(L10n.playOnActive, isOn: $playOnActive) + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift index 1b6411e3..885f046a 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift @@ -11,11 +11,6 @@ import SwiftUI struct VideoPlayerSettingsView: View { - // TODO: Organize - - @Default(.VideoPlayer.autoPlayEnabled) - private var autoPlayEnabled - @Default(.VideoPlayer.jumpBackwardLength) private var jumpBackwardLength @Default(.VideoPlayer.jumpForwardLength) @@ -23,42 +18,6 @@ struct VideoPlayerSettingsView: View { @Default(.VideoPlayer.resumeOffset) private var resumeOffset - @Default(.VideoPlayer.showJumpButtons) - private var showJumpButtons - - @Default(.VideoPlayer.barActionButtons) - private var barActionButtons - @Default(.VideoPlayer.menuActionButtons) - private var menuActionButtons - - @Default(.VideoPlayer.Subtitle.subtitleFontName) - private var subtitleFontName - @Default(.VideoPlayer.Subtitle.subtitleSize) - private var subtitleSize - @Default(.VideoPlayer.Subtitle.subtitleColor) - private var subtitleColor - - @Default(.VideoPlayer.Overlay.chapterSlider) - private var chapterSlider - @Default(.VideoPlayer.Overlay.playbackButtonType) - private var playbackButtonType - @Default(.VideoPlayer.Overlay.sliderColor) - private var sliderColor - @Default(.VideoPlayer.Overlay.sliderType) - private var sliderType - - @Default(.VideoPlayer.Overlay.trailingTimestampType) - private var trailingTimestampType - @Default(.VideoPlayer.Overlay.showCurrentTimeWhileScrubbing) - private var showCurrentTimeWhileScrubbing - @Default(.VideoPlayer.Overlay.timestampType) - private var timestampType - - @Default(.VideoPlayer.Transition.pauseOnBackground) - private var pauseOnBackground - @Default(.VideoPlayer.Transition.playOnActive) - private var playOnActive - @EnvironmentObject private var router: VideoPlayerSettingsCoordinator.Router @@ -89,84 +48,16 @@ struct VideoPlayerSettingsView: View { Text(L10n.resumeOffsetDescription) } - Section(L10n.buttons) { + ButtonSection() - CaseIterablePicker(L10n.playbackButtons, selection: $playbackButtonType) + SliderSection() - Toggle(isOn: $showJumpButtons) { - HStack { - Image(systemName: "goforward") - Text(L10n.jump) - } - } + SubtitleSection() - ChevronButton(L10n.barButtons) - .onSelect { - router.route(to: \.actionButtonSelector, $barActionButtons) - } + TimestampSection() - ChevronButton(L10n.menuButtons) - .onSelect { - router.route(to: \.actionButtonSelector, $menuActionButtons) - } - } - - Section(L10n.slider) { - - Toggle(L10n.chapterSlider, isOn: $chapterSlider) - - ColorPicker(selection: $sliderColor, supportsOpacity: false) { - Text(L10n.sliderColor) - } - - CaseIterablePicker(L10n.sliderType, selection: $sliderType) - } - - Section { - - ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName) - .onSelect { - router.route(to: \.fontPicker, $subtitleFontName) - } - - BasicStepper( - title: L10n.subtitleSize, - value: $subtitleSize, - range: 8 ... 24, - step: 1 - ) - - ColorPicker(selection: $subtitleColor, supportsOpacity: false) { - Text(L10n.subtitleColor) - } - } header: { - Text(L10n.subtitle) - } footer: { - // TODO: better wording - Text("Settings only affect some subtitle types") - } - - Section(L10n.timestamp) { - - Toggle(L10n.scrubCurrentTime, isOn: $showCurrentTimeWhileScrubbing) - - CaseIterablePicker(L10n.timestampType, selection: $timestampType) - - CaseIterablePicker(L10n.trailingValue, selection: $trailingTimestampType) - } - - Section(L10n.transition) { - - Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) - Toggle(L10n.playOnActive, isOn: $playOnActive) - } + TransitionSection() } .navigationTitle(L10n.videoPlayer) - .onChange(of: barActionButtons) { newValue in - autoPlayEnabled = newValue.contains(.autoPlay) || menuActionButtons.contains(.autoPlay) - } - .onChange(of: menuActionButtons) { newValue in - autoPlayEnabled = newValue.contains(.autoPlay) || barActionButtons.contains(.autoPlay) - } } }