mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 16:42:01 +00:00
419 lines
15 KiB
Swift
419 lines
15 KiB
Swift
//
|
|
// SettingsViewPlayer.swift
|
|
// Sora
|
|
//
|
|
// Created by Francesco on 31/01/25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
fileprivate struct SettingsSection<Content: View>: View {
|
|
let title: String
|
|
let footer: String?
|
|
let content: Content
|
|
|
|
init(title: String, footer: String? = nil, @ViewBuilder content: () -> Content) {
|
|
self.title = title
|
|
self.footer = footer
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(title.uppercased())
|
|
.font(.footnote)
|
|
.foregroundStyle(.gray)
|
|
.padding(.horizontal, 20)
|
|
|
|
VStack(spacing: 0) {
|
|
content
|
|
}
|
|
.background(.ultraThinMaterial)
|
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.strokeBorder(
|
|
LinearGradient(
|
|
gradient: Gradient(stops: [
|
|
.init(color: Color.accentColor.opacity(0.3), location: 0),
|
|
.init(color: Color.accentColor.opacity(0), location: 1)
|
|
]),
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
),
|
|
lineWidth: 0.5
|
|
)
|
|
)
|
|
.padding(.horizontal, 20)
|
|
|
|
if let footer = footer {
|
|
Text(footer)
|
|
.font(.footnote)
|
|
.foregroundStyle(.gray)
|
|
.padding(.horizontal, 20)
|
|
.padding(.top, 4)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate struct SettingsToggleRow: View {
|
|
let icon: String
|
|
let title: String
|
|
@Binding var isOn: Bool
|
|
var showDivider: Bool = true
|
|
|
|
init(icon: String, title: String, isOn: Binding<Bool>, showDivider: Bool = true) {
|
|
self.icon = icon
|
|
self.title = title
|
|
self._isOn = isOn
|
|
self.showDivider = showDivider
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
Image(systemName: icon)
|
|
.frame(width: 24, height: 24)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text(title)
|
|
.foregroundStyle(.primary)
|
|
|
|
Spacer()
|
|
|
|
Toggle("", isOn: $isOn)
|
|
.labelsHidden()
|
|
.tint(.accentColor.opacity(0.7))
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
|
|
if showDivider {
|
|
Divider()
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate struct SettingsPickerRow<T: Hashable>: View {
|
|
let icon: String
|
|
let title: String
|
|
let options: [T]
|
|
let optionToString: (T) -> String
|
|
@Binding var selection: T
|
|
var showDivider: Bool = true
|
|
|
|
init(icon: String, title: String, options: [T], optionToString: @escaping (T) -> String, selection: Binding<T>, showDivider: Bool = true) {
|
|
self.icon = icon
|
|
self.title = title
|
|
self.options = options
|
|
self.optionToString = optionToString
|
|
self._selection = selection
|
|
self.showDivider = showDivider
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
Image(systemName: icon)
|
|
.frame(width: 24, height: 24)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text(title)
|
|
.foregroundStyle(.primary)
|
|
|
|
Spacer()
|
|
|
|
Menu {
|
|
ForEach(options, id: \.self) { option in
|
|
Button(action: { selection = option }) {
|
|
Text(optionToString(option))
|
|
}
|
|
}
|
|
} label: {
|
|
Text(optionToString(selection))
|
|
.foregroundStyle(.gray)
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
|
|
if showDivider {
|
|
Divider()
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate struct SettingsStepperRow: View {
|
|
let icon: String
|
|
let title: String
|
|
@Binding var value: Double
|
|
let range: ClosedRange<Double>
|
|
let step: Double
|
|
var formatter: (Double) -> String = { "\(Int($0))" }
|
|
var showDivider: Bool = true
|
|
|
|
init(icon: String, title: String, value: Binding<Double>, range: ClosedRange<Double>, step: Double, formatter: @escaping (Double) -> String = { "\(Int($0))" }, showDivider: Bool = true) {
|
|
self.icon = icon
|
|
self.title = title
|
|
self._value = value
|
|
self.range = range
|
|
self.step = step
|
|
self.formatter = formatter
|
|
self.showDivider = showDivider
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
Image(systemName: icon)
|
|
.frame(width: 24, height: 24)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text(title)
|
|
.foregroundStyle(.primary)
|
|
|
|
Spacer()
|
|
|
|
Stepper(formatter(value), value: $value, in: range, step: step)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
|
|
if showDivider {
|
|
Divider()
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SettingsViewPlayer: View {
|
|
@AppStorage("externalPlayer") private var externalPlayer: String = "Sora"
|
|
@AppStorage("alwaysLandscape") private var isAlwaysLandscape = false
|
|
@AppStorage("rememberPlaySpeed") private var isRememberPlaySpeed = false
|
|
@AppStorage("holdSpeedPlayer") private var holdSpeedPlayer: Double = 2.0
|
|
@AppStorage("skipIncrement") private var skipIncrement: Double = 10.0
|
|
@AppStorage("skipIncrementHold") private var skipIncrementHold: Double = 30.0
|
|
@AppStorage("holdForPauseEnabled") private var holdForPauseEnabled = false
|
|
@AppStorage("skip85Visible") private var skip85Visible: Bool = true
|
|
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
|
|
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
|
|
@AppStorage("pipButtonVisible") private var pipButtonVisible: Bool = true
|
|
|
|
private let mediaPlayers = ["Default", "Sora", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "IINA", "TracyPlayer"]
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 24) {
|
|
SettingsSection(
|
|
title: "Media Player",
|
|
footer: "Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments."
|
|
) {
|
|
SettingsPickerRow(
|
|
icon: "play.circle",
|
|
title: "Media Player",
|
|
options: mediaPlayers,
|
|
optionToString: { $0 },
|
|
selection: $externalPlayer
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "rotate.right",
|
|
title: "Force Landscape",
|
|
isOn: $isAlwaysLandscape
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "hand.tap",
|
|
title: "Two Finger Hold for Pause",
|
|
isOn: $holdForPauseEnabled,
|
|
showDivider: true
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "pip",
|
|
title: "Show PiP Button",
|
|
isOn: $pipButtonVisible,
|
|
showDivider: false
|
|
)
|
|
}
|
|
|
|
SettingsSection(title: "Speed Settings") {
|
|
SettingsToggleRow(
|
|
icon: "speedometer",
|
|
title: "Remember Playback speed",
|
|
isOn: $isRememberPlaySpeed
|
|
)
|
|
|
|
SettingsStepperRow(
|
|
icon: "forward.fill",
|
|
title: "Hold Speed",
|
|
value: $holdSpeedPlayer,
|
|
range: 0.25...2.5,
|
|
step: 0.25,
|
|
formatter: { String(format: "%.2f", $0) },
|
|
showDivider: false
|
|
)
|
|
}
|
|
|
|
SettingsSection(title: "Progress bar Marker Color") {
|
|
ColorPicker("Segments Color", selection: Binding(
|
|
get: {
|
|
if let data = UserDefaults.standard.data(forKey: "segmentsColorData"),
|
|
let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
|
|
return Color(uiColor)
|
|
}
|
|
return .yellow
|
|
},
|
|
set: { newColor in
|
|
let uiColor = UIColor(newColor)
|
|
if let data = try? NSKeyedArchiver.archivedData(
|
|
withRootObject: uiColor,
|
|
requiringSecureCoding: false
|
|
) {
|
|
UserDefaults.standard.set(data, forKey: "segmentsColorData")
|
|
}
|
|
}
|
|
))
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
}
|
|
|
|
SettingsSection(
|
|
title: "Skip Settings",
|
|
footer: "Double tapping the screen on it's sides will skip with the short tap setting."
|
|
) {
|
|
SettingsStepperRow(
|
|
icon: "goforward",
|
|
title: "Tap Skip",
|
|
value: $skipIncrement,
|
|
range: 5...300,
|
|
step: 5,
|
|
formatter: { "\(Int($0))s" }
|
|
)
|
|
|
|
SettingsStepperRow(
|
|
icon: "goforward.plus",
|
|
title: "Long press Skip",
|
|
value: $skipIncrementHold,
|
|
range: 5...300,
|
|
step: 5,
|
|
formatter: { "\(Int($0))s" }
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "hand.tap.fill",
|
|
title: "Double Tap to Seek",
|
|
isOn: $doubleTapSeekEnabled
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "forward.end",
|
|
title: "Show Skip 85s Button",
|
|
isOn: $skip85Visible
|
|
)
|
|
|
|
SettingsToggleRow(
|
|
icon: "forward.frame",
|
|
title: "Show Skip Intro / Outro Buttons",
|
|
isOn: $skipIntroOutroVisible,
|
|
showDivider: false
|
|
)
|
|
}
|
|
|
|
SubtitleSettingsSection()
|
|
}
|
|
.padding(.vertical, 20)
|
|
}
|
|
.scrollViewBottomPadding()
|
|
.navigationTitle("Player")
|
|
}
|
|
}
|
|
|
|
struct SubtitleSettingsSection: View {
|
|
@State private var foregroundColor: String = SubtitleSettingsManager.shared.settings.foregroundColor
|
|
@State private var fontSize: Double = SubtitleSettingsManager.shared.settings.fontSize
|
|
@State private var shadowRadius: Double = SubtitleSettingsManager.shared.settings.shadowRadius
|
|
@State private var backgroundEnabled: Bool = SubtitleSettingsManager.shared.settings.backgroundEnabled
|
|
@State private var bottomPadding: Double = Double(SubtitleSettingsManager.shared.settings.bottomPadding)
|
|
@State private var subtitleDelay: Double = SubtitleSettingsManager.shared.settings.subtitleDelay
|
|
|
|
private let colors = ["white", "yellow", "green", "blue", "red", "purple"]
|
|
private let shadowOptions = [0, 1, 3, 6]
|
|
|
|
var body: some View {
|
|
SettingsSection(title: "Subtitle Settings") {
|
|
SettingsPickerRow(
|
|
icon: "paintbrush",
|
|
title: "Subtitle Color",
|
|
options: colors,
|
|
optionToString: { $0.capitalized },
|
|
selection: $foregroundColor
|
|
)
|
|
.onChange(of: foregroundColor) { newValue in
|
|
SubtitleSettingsManager.shared.update { settings in
|
|
settings.foregroundColor = newValue
|
|
}
|
|
}
|
|
|
|
SettingsPickerRow(
|
|
icon: "shadow",
|
|
title: "Shadow",
|
|
options: shadowOptions,
|
|
optionToString: { "\($0)" },
|
|
selection: Binding(
|
|
get: { Int(shadowRadius) },
|
|
set: { shadowRadius = Double($0) }
|
|
)
|
|
)
|
|
.onChange(of: shadowRadius) { newValue in
|
|
SubtitleSettingsManager.shared.update { settings in
|
|
settings.shadowRadius = newValue
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow(
|
|
icon: "rectangle.fill",
|
|
title: "Background Enabled",
|
|
isOn: $backgroundEnabled
|
|
)
|
|
.onChange(of: backgroundEnabled) { newValue in
|
|
SubtitleSettingsManager.shared.update { settings in
|
|
settings.backgroundEnabled = newValue
|
|
}
|
|
}
|
|
|
|
SettingsStepperRow(
|
|
icon: "textformat.size",
|
|
title: "Font Size",
|
|
value: $fontSize,
|
|
range: 12...36,
|
|
step: 1
|
|
)
|
|
.onChange(of: fontSize) { newValue in
|
|
SubtitleSettingsManager.shared.update { settings in
|
|
settings.fontSize = newValue
|
|
}
|
|
}
|
|
|
|
SettingsStepperRow(
|
|
icon: "arrow.up.and.down",
|
|
title: "Bottom Padding",
|
|
value: $bottomPadding,
|
|
range: 0...50,
|
|
step: 1,
|
|
showDivider: false
|
|
)
|
|
.onChange(of: bottomPadding) { newValue in
|
|
SubtitleSettingsManager.shared.update { settings in
|
|
settings.bottomPadding = CGFloat(newValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|