2022-08-14 17:06:22 +00:00
|
|
|
import Defaults
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
struct QualitySettings: View {
|
|
|
|
@State private var presentingProfileForm = false
|
2022-08-14 22:15:18 +00:00
|
|
|
@State private var editedProfileID: QualityProfile.ID?
|
2022-08-14 17:06:22 +00:00
|
|
|
|
2022-08-16 22:34:25 +00:00
|
|
|
@EnvironmentObject<SettingsModel> private var settings
|
2022-08-14 17:06:22 +00:00
|
|
|
|
2022-08-16 22:34:25 +00:00
|
|
|
@Default(.qualityProfiles) private var qualityProfiles
|
2022-08-14 17:06:22 +00:00
|
|
|
@Default(.batteryCellularProfile) private var batteryCellularProfile
|
|
|
|
@Default(.batteryNonCellularProfile) private var batteryNonCellularProfile
|
|
|
|
@Default(.chargingCellularProfile) private var chargingCellularProfile
|
|
|
|
@Default(.chargingNonCellularProfile) private var chargingNonCellularProfile
|
2022-08-22 21:14:27 +00:00
|
|
|
@Default(.forceAVPlayerForLiveStreams) private var forceAVPlayerForLiveStreams
|
2022-08-14 17:06:22 +00:00
|
|
|
|
|
|
|
var body: some View {
|
2022-08-22 21:14:27 +00:00
|
|
|
VStack(alignment: .leading) {
|
2022-08-14 17:06:22 +00:00
|
|
|
#if os(macOS)
|
|
|
|
sections
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
#else
|
|
|
|
List {
|
|
|
|
sections
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
.sheet(isPresented: $presentingProfileForm) {
|
2022-08-14 22:15:18 +00:00
|
|
|
QualityProfileForm(qualityProfileID: $editedProfileID)
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
#if os(tvOS)
|
|
|
|
.frame(maxWidth: 1000)
|
|
|
|
#elseif os(iOS)
|
|
|
|
.listStyle(.insetGrouped)
|
|
|
|
#endif
|
|
|
|
.navigationTitle("Quality")
|
|
|
|
}
|
|
|
|
|
|
|
|
var sections: some View {
|
|
|
|
Group {
|
|
|
|
Group {
|
|
|
|
#if os(tvOS)
|
|
|
|
Section(header: Text("Default Profile")) {
|
|
|
|
Text("\(QualityProfilesModel.shared.tvOSProfile?.description ?? "None")")
|
|
|
|
}
|
|
|
|
#elseif os(iOS)
|
|
|
|
if UIDevice.current.hasCellularCapabilites {
|
|
|
|
Section(header: Text("Battery")) {
|
|
|
|
Picker("Wi-Fi", selection: $batteryNonCellularProfile) { profilePickerOptions }
|
|
|
|
Picker("Cellular", selection: $batteryCellularProfile) { profilePickerOptions }
|
|
|
|
}
|
|
|
|
Section(header: Text("Charging")) {
|
|
|
|
Picker("Wi-Fi", selection: $chargingNonCellularProfile) { profilePickerOptions }
|
|
|
|
Picker("Cellular", selection: $chargingCellularProfile) { profilePickerOptions }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
nonCellularBatteryDevicesProfilesPickers
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if Power.hasInternalBattery {
|
|
|
|
nonCellularBatteryDevicesProfilesPickers
|
|
|
|
} else {
|
|
|
|
Picker("Default", selection: $chargingNonCellularProfile) { profilePickerOptions }
|
|
|
|
}
|
|
|
|
#endif
|
2022-08-22 21:14:27 +00:00
|
|
|
|
|
|
|
forceAVPlayerForLiveStreamsToggle
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
.disabled(qualityProfiles.isEmpty)
|
|
|
|
Section(header: SettingsHeader(text: "Profiles"), footer: profilesFooter) {
|
|
|
|
profilesList
|
|
|
|
|
|
|
|
Button {
|
2022-08-14 22:15:18 +00:00
|
|
|
editedProfileID = nil
|
2022-08-14 17:06:22 +00:00
|
|
|
presentingProfileForm = true
|
|
|
|
} label: {
|
|
|
|
Label("Add profile...", systemImage: "plus")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
2022-08-16 22:34:25 +00:00
|
|
|
|
|
|
|
HStack {
|
|
|
|
Button {
|
|
|
|
settings.presentAlert(
|
|
|
|
Alert(
|
|
|
|
title: Text("Are you sure you want to restore default quality profiles?"),
|
|
|
|
message: Text("This will remove all your custom profiles and return their default values. This cannot be reverted."),
|
|
|
|
primaryButton: .destructive(Text("Reset")) {
|
|
|
|
QualityProfilesModel.shared.reset()
|
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} label: {
|
|
|
|
Text("Restore default profiles...")
|
|
|
|
.foregroundColor(.red)
|
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
}
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder var nonCellularBatteryDevicesProfilesPickers: some View {
|
|
|
|
Picker("Battery", selection: $batteryNonCellularProfile) { profilePickerOptions }
|
|
|
|
Picker("Charging", selection: $chargingNonCellularProfile) { profilePickerOptions }
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:14:27 +00:00
|
|
|
@ViewBuilder var forceAVPlayerForLiveStreamsToggle: some View {
|
|
|
|
Toggle("Always use AVPlayer for live videos", isOn: $forceAVPlayerForLiveStreams)
|
|
|
|
}
|
|
|
|
|
2022-08-14 17:06:22 +00:00
|
|
|
@ViewBuilder func profileControl(_ qualityProfile: QualityProfile) -> some View {
|
|
|
|
#if os(tvOS)
|
|
|
|
Button {
|
|
|
|
QualityProfilesModel.shared.applyToAll(qualityProfile)
|
|
|
|
} label: {
|
|
|
|
Text(qualityProfile.description)
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
Text(qualityProfile.description)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
var profilePickerOptions: some View {
|
|
|
|
ForEach(qualityProfiles) { qualityProfile in
|
|
|
|
Text(qualityProfile.description).tag(qualityProfile.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var profilesFooter: some View {
|
|
|
|
#if os(tvOS)
|
|
|
|
Text("You can switch between profiles in playback settings controls.")
|
|
|
|
#else
|
|
|
|
Text("You can use automatic profile selection based on current device status or switch it in video playback settings controls.")
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder var profilesList: some View {
|
|
|
|
let list = ForEach(qualityProfiles) { qualityProfile in
|
|
|
|
profileControl(qualityProfile)
|
|
|
|
.contextMenu {
|
|
|
|
Button {
|
|
|
|
QualityProfilesModel.shared.applyToAll(qualityProfile)
|
|
|
|
} label: {
|
|
|
|
#if os(tvOS)
|
|
|
|
Text("Make default")
|
|
|
|
#elseif os(iOS)
|
|
|
|
Label("Apply to all", systemImage: "wand.and.stars")
|
|
|
|
#else
|
|
|
|
if Power.hasInternalBattery {
|
|
|
|
Text("Apply to all")
|
|
|
|
} else {
|
|
|
|
Text("Make default")
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
Button {
|
2022-08-14 22:15:18 +00:00
|
|
|
editedProfileID = qualityProfile.id
|
2022-08-14 17:06:22 +00:00
|
|
|
presentingProfileForm = true
|
|
|
|
} label: {
|
|
|
|
Label("Edit...", systemImage: "pencil")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
QualityProfilesModel.shared.remove(qualityProfile)
|
|
|
|
} label: {
|
|
|
|
Label("Remove", systemImage: "trash")
|
|
|
|
}
|
|
|
|
|
|
|
|
#if os(tvOS)
|
|
|
|
Button("Cancel", role: .cancel) {}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if #available(macOS 12.0, *) {
|
|
|
|
#if os(macOS)
|
|
|
|
List {
|
|
|
|
list
|
|
|
|
}
|
|
|
|
.listStyle(.inset(alternatesRowBackgrounds: true))
|
|
|
|
#else
|
|
|
|
list
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
#if os(macOS)
|
|
|
|
List {
|
|
|
|
list
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
list
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct QualitySettings_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
|
|
|
#if os(macOS)
|
|
|
|
QualitySettings()
|
|
|
|
#else
|
|
|
|
NavigationView {
|
|
|
|
EmptyView()
|
|
|
|
QualitySettings()
|
|
|
|
}
|
|
|
|
.navigationViewStyle(.stack)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|