diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 1ae16f2d..3f18c5d0 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -378,6 +378,8 @@ internal enum L10n { internal static var switchUser: String { return L10n.tr("Localizable", "switchUser") } /// System internal static var system: String { return L10n.tr("Localizable", "system") } + /// System Control Gestures Enabled + internal static var systemControlGesturesEnabled: String { return L10n.tr("Localizable", "systemControlGesturesEnabled") } /// Tags internal static var tags: String { return L10n.tr("Localizable", "tags") } /// Too Many Redirects diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index c94abfc8..864e0033 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -46,6 +46,8 @@ extension Defaults.Keys { // Video player / overlay settings static let overlayType = Key("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite) static let jumpGesturesEnabled = Key("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let systemControlGesturesEnabled = Key("systemControlGesturesEnabled", default: true, + suite: SwiftfinStore.Defaults.generalSuite) static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite) static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .fifteen, diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index a7988db8..bcbe6a11 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -115,6 +115,7 @@ final class VideoPlayerViewModel: ViewModel { let chapters: [ChapterInfo] let overlayType: OverlayType let jumpGesturesEnabled: Bool + let systemControlGesturesEnabled: Bool let resumeOffset: Bool let streamType: ServerStreamType let container: String @@ -241,6 +242,7 @@ final class VideoPlayerViewModel: ViewModel { self.jumpBackwardLength = Defaults[.videoPlayerJumpBackward] self.jumpForwardLength = Defaults[.videoPlayerJumpForward] self.jumpGesturesEnabled = Defaults[.jumpGesturesEnabled] + self.systemControlGesturesEnabled = Defaults[.systemControlGesturesEnabled] self.shouldShowJumpButtonsInOverlayMenu = Defaults[.shouldShowJumpButtonsInOverlayMenu] self.resumeOffset = Defaults[.resumeOffset] diff --git a/Swiftfin tvOS/Views/LibraryListView.swift b/Swiftfin tvOS/Views/LibraryListView.swift index 9001544d..4760e74e 100644 --- a/Swiftfin tvOS/Views/LibraryListView.swift +++ b/Swiftfin tvOS/Views/LibraryListView.swift @@ -38,31 +38,6 @@ struct LibraryListView: View { self.mainCoordinator.root(\.liveTV) } label: { - ZStack { - HStack { - Spacer() - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - Spacer() - }.padding(32) - } - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) - } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) - } - } else { - Button { - self.libraryListRouter.route(to: \.library, - (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) - } - label: { ZStack { HStack { Spacer() @@ -81,6 +56,31 @@ struct LibraryListView: View { .cornerRadius(10) .shadow(radius: 5) .padding(.bottom, 5) + } + } else { + Button { + self.libraryListRouter.route(to: \.library, + (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) + } + label: { + ZStack { + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + } + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) } } } else { diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index 79a134ce..e1a0ffae 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -38,6 +38,8 @@ struct SettingsView: View { var jumpBackwardLength @Default(.jumpGesturesEnabled) var jumpGesturesEnabled + @Default(.systemControlGesturesEnabled) + var systemControlGesturesEnabled @Default(.resumeOffset) var resumeOffset @Default(.subtitleSize) @@ -107,6 +109,8 @@ struct SettingsView: View { Toggle(L10n.jumpGesturesEnabled, isOn: $jumpGesturesEnabled) + Toggle(L10n.systemControlGesturesEnabled, isOn: $systemControlGesturesEnabled) + Toggle(L10n.resume5SecondOffset, isOn: $resumeOffset) Button { diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 61ef1934..128704a5 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -42,12 +42,18 @@ class VLCPlayerViewController: UIViewController { currentChapterOverlayHostingController?.view.alpha ?? 0 > 0 } + private var panBeganBrightness = CGFloat.zero + private var panBeganVolumeValue = Float.zero + private var panBeganPoint = CGPoint.zero + private lazy var videoContentView = makeVideoContentView() private lazy var mainGestureView = makeMainGestureView() private var currentOverlayHostingController: UIHostingController? private var currentChapterOverlayHostingController: UIHostingController? + private var systemControlOverlayLabel = UILabel() private var currentJumpBackwardOverlayView: UIImageView? private var currentJumpForwardOverlayView: UIImageView? + private var volumeView = MPVolumeView() override var keyCommands: [UIKeyCommand]? { var commands = [ @@ -95,6 +101,12 @@ class VLCPlayerViewController: UIViewController { private func setupSubviews() { view.addSubview(videoContentView) view.addSubview(mainGestureView) + + // Setup BrightnessOverlayView + systemControlOverlayLabel.alpha = 0 + systemControlOverlayLabel.translatesAutoresizingMaskIntoConstraints = false + systemControlOverlayLabel.font = .systemFont(ofSize: 48) + view.addSubview(systemControlOverlayLabel) } private func setupConstraints() { @@ -110,6 +122,10 @@ class VLCPlayerViewController: UIViewController { mainGestureView.leftAnchor.constraint(equalTo: videoContentView.leftAnchor), mainGestureView.rightAnchor.constraint(equalTo: videoContentView.rightAnchor), ]) + NSLayoutConstraint.activate([ + systemControlOverlayLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + systemControlOverlayLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) } // MARK: viewWillDisappear @@ -202,6 +218,8 @@ class VLCPlayerViewController: UIViewController { let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(_:))) + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))) + view.addGestureRecognizer(singleTapGesture) view.addGestureRecognizer(pinchGesture) @@ -210,6 +228,10 @@ class VLCPlayerViewController: UIViewController { view.addGestureRecognizer(leftSwipeGesture) } + if viewModel.systemControlGesturesEnabled { + view.addGestureRecognizer(panGesture) + } + return view } @@ -243,6 +265,35 @@ class VLCPlayerViewController: UIViewController { } } + @objc + private func didPan(_ gestureRecognizer: UIPanGestureRecognizer) { + switch gestureRecognizer.state { + case .began: + panBeganBrightness = UIScreen.main.brightness + if let view = volumeView.subviews.first as? UISlider { + panBeganVolumeValue = view.value + } + panBeganPoint = gestureRecognizer.location(in: mainGestureView) + case .changed: + let mainGestureViewHalfWidth = mainGestureView.frame.width * 0.5 + let mainGestureViewHalfHeight = mainGestureView.frame.height * 0.5 + + let pos = gestureRecognizer.location(in: mainGestureView) + let moveDelta = pos.y - panBeganPoint.y + let changedValue = moveDelta / mainGestureViewHalfHeight + + if panBeganPoint.x < mainGestureViewHalfWidth { + UIScreen.main.brightness = panBeganBrightness - changedValue + flashBrightnessOverlay() + } else if let view = volumeView.subviews.first as? UISlider { + view.value = panBeganVolumeValue - Float(changedValue) + flashVolumeOverlay() + } + default: + hideSystemControlOverlay() + } + } + // MARK: setupOverlayHostingController private func setupOverlayHostingController(viewModel: VideoPlayerViewModel) { @@ -560,6 +611,53 @@ extension VLCPlayerViewController { } } +// MARK: Show/Hide System Control + +extension VLCPlayerViewController { + private func flashBrightnessOverlay() { + guard !displayingOverlay else { return } + + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "sun.max", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? + .withTintColor(.white) + + let attributedString = NSMutableAttributedString() + attributedString.append(.init(attachment: imageAttachment)) + attributedString.append(.init(string: " \(String(format: "%.0f", UIScreen.main.brightness * 100))%")) + systemControlOverlayLabel.attributedText = attributedString + systemControlOverlayLabel.layer.removeAllAnimations() + + UIView.animate(withDuration: 0.1) { + self.systemControlOverlayLabel.alpha = 1 + } + } + + private func flashVolumeOverlay() { + guard !displayingOverlay, + let value = (volumeView.subviews.first as? UISlider)?.value else { return } + + let imageAttachment = NSTextAttachment() + imageAttachment.image = UIImage(systemName: "speaker.wave.2", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? + .withTintColor(.white) + + let attributedString = NSMutableAttributedString() + attributedString.append(.init(attachment: imageAttachment)) + attributedString.append(.init(string: " \(String(format: "%.0f", value * 100))%")) + systemControlOverlayLabel.attributedText = attributedString + systemControlOverlayLabel.layer.removeAllAnimations() + + UIView.animate(withDuration: 0.1) { + self.systemControlOverlayLabel.alpha = 1 + } + } + + private func hideSystemControlOverlay() { + UIView.animate(withDuration: 0.75) { + self.systemControlOverlayLabel.alpha = 0 + } + } +} + // MARK: Show/Hide Jump extension VLCPlayerViewController { diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index a76a6d2e..6554c737 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ