mirror of
https://github.com/yattee/yattee.git
synced 2025-08-09 20:24:06 +00:00
Locations manifest, reorganized instances settings
This commit is contained in:
@@ -118,6 +118,7 @@ struct AccountForm: View {
|
||||
var footer: some View {
|
||||
HStack {
|
||||
AccountValidationStatus(
|
||||
app: .constant(instance.app),
|
||||
isValid: $isValid,
|
||||
isValidated: $isValidated,
|
||||
isValidating: $isValidating,
|
||||
|
@@ -2,6 +2,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct AccountValidationStatus: View {
|
||||
@Binding var app: VideosApp?
|
||||
@Binding var isValid: Bool
|
||||
@Binding var isValidated: Bool
|
||||
@Binding var isValidating: Bool
|
||||
@@ -16,7 +17,7 @@ struct AccountValidationStatus: View {
|
||||
.opacity(isValidating ? 1 : (isValidated ? 1 : 0))
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(isValid ? "Connected successfully" : "Connection failed")
|
||||
Text(isValid ? "Connected successfully (\(app?.name ?? "Unknown"))" : "Connection failed")
|
||||
if let error = error, !isValid {
|
||||
Text(error)
|
||||
.font(.caption2)
|
||||
|
@@ -11,6 +11,10 @@ struct AccountsNavigationLink: View {
|
||||
.buttonStyle(.plain)
|
||||
.contextMenu {
|
||||
removeInstanceButton(instance)
|
||||
|
||||
#if os(tvOS)
|
||||
Button("Cancel", role: .cancel) {}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
57
Shared/Settings/AdvancedSettings.swift
Normal file
57
Shared/Settings/AdvancedSettings.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct AdvancedSettings: View {
|
||||
@Default(.instancesManifest) private var instancesManifest
|
||||
@Default(.showMPVPlaybackStats) private var showMPVPlaybackStats
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
#if os(macOS)
|
||||
advancedSettings
|
||||
Spacer()
|
||||
#else
|
||||
List {
|
||||
advancedSettings
|
||||
}
|
||||
#if os(iOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#if os(tvOS)
|
||||
.frame(maxWidth: 1000)
|
||||
#endif
|
||||
.navigationTitle("Advanced")
|
||||
}
|
||||
|
||||
@ViewBuilder var advancedSettings: some View {
|
||||
Section(header: manifestHeader, footer: manifestFooter) {
|
||||
TextField("URL", text: $instancesManifest)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
|
||||
Section(header: SettingsHeader(text: "Debugging")) {
|
||||
showMPVPlaybackStatsToggle
|
||||
}
|
||||
}
|
||||
|
||||
var manifestHeader: some View {
|
||||
SettingsHeader(text: "Public Manifest")
|
||||
}
|
||||
|
||||
var manifestFooter: some View {
|
||||
Text("You can create your own locations manifest and set its URL here to replace the built-in one")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
var showMPVPlaybackStatsToggle: some View {
|
||||
Toggle("Show MPV playback statistics", isOn: $showMPVPlaybackStats)
|
||||
}
|
||||
}
|
||||
|
||||
struct AdvancedSettings_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AdvancedSettings()
|
||||
}
|
||||
}
|
@@ -5,8 +5,8 @@ struct InstanceForm: View {
|
||||
|
||||
@State private var name = ""
|
||||
@State private var url = ""
|
||||
@State private var app = VideosApp.invidious
|
||||
|
||||
@State private var app: VideosApp?
|
||||
@State private var isValid = false
|
||||
@State private var isValidated = false
|
||||
@State private var isValidating = false
|
||||
@@ -27,7 +27,6 @@ struct InstanceForm: View {
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
}
|
||||
.onChange(of: app) { _ in validate() }
|
||||
.onChange(of: url) { _ in validate() }
|
||||
#if os(iOS)
|
||||
.padding(.vertical)
|
||||
@@ -35,13 +34,13 @@ struct InstanceForm: View {
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(Color.background(scheme: colorScheme))
|
||||
#else
|
||||
.frame(width: 400, height: 190)
|
||||
.frame(width: 400, height: 150)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack(alignment: .center) {
|
||||
Text("Add Instance")
|
||||
Text("Add Location")
|
||||
.font(.title2.bold())
|
||||
|
||||
Spacer()
|
||||
@@ -71,17 +70,9 @@ struct InstanceForm: View {
|
||||
|
||||
private var formFields: some View {
|
||||
Group {
|
||||
Picker("Application", selection: $app) {
|
||||
ForEach(VideosApp.allCases, id: \.self) { app in
|
||||
Text(app.rawValue.capitalized).tag(app)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.labelsHidden()
|
||||
|
||||
TextField("Name", text: $name)
|
||||
|
||||
TextField("API URL", text: $url)
|
||||
TextField("URL", text: $url)
|
||||
|
||||
#if !os(macOS)
|
||||
.autocapitalization(.none)
|
||||
@@ -92,7 +83,13 @@ struct InstanceForm: View {
|
||||
|
||||
private var footer: some View {
|
||||
HStack(alignment: .center) {
|
||||
AccountValidationStatus(isValid: $isValid, isValidated: $isValidated, isValidating: $isValidating, error: $validationError)
|
||||
AccountValidationStatus(
|
||||
app: $app,
|
||||
isValid: $isValid,
|
||||
isValidated: $isValidated,
|
||||
isValidating: $isValidating,
|
||||
error: $validationError
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -137,7 +134,7 @@ struct InstanceForm: View {
|
||||
}
|
||||
|
||||
func submitForm() {
|
||||
guard isValid else {
|
||||
guard isValid, let app = app else {
|
||||
return
|
||||
}
|
||||
|
||||
|
119
Shared/Settings/LocationsSettings.swift
Normal file
119
Shared/Settings/LocationsSettings.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct LocationsSettings: View {
|
||||
@State private var countries = [String]()
|
||||
@State private var presentingInstanceForm = false
|
||||
@State private var savedFormInstanceID: Instance.ID?
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@EnvironmentObject<SettingsModel> private var model
|
||||
|
||||
@Default(.countryOfPublicInstances) private var countryOfPublicInstances
|
||||
@Default(.instances) private var instances
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
#if os(macOS)
|
||||
settings
|
||||
Spacer()
|
||||
#else
|
||||
List {
|
||||
settings
|
||||
}
|
||||
#if os(iOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
.onAppear(perform: loadCountries)
|
||||
.onChange(of: countryOfPublicInstances) { newCountry in
|
||||
InstancesManifest.shared.setPublicAccount(newCountry, accounts: accounts, asCurrent: accounts.current?.isPublic ?? true)
|
||||
}
|
||||
.sheet(isPresented: $presentingInstanceForm) {
|
||||
InstanceForm(savedInstanceID: $savedFormInstanceID)
|
||||
}
|
||||
#if os(tvOS)
|
||||
.frame(maxWidth: 1000)
|
||||
#endif
|
||||
.navigationTitle("Locations")
|
||||
}
|
||||
|
||||
@ViewBuilder var settings: some View {
|
||||
Section(header: SettingsHeader(text: "Public Locations"), footer: countryFooter) {
|
||||
Picker("Country", selection: $countryOfPublicInstances) {
|
||||
Text("Don't use public locations").tag(String?.none)
|
||||
ForEach(countries, id: \.self) { country in
|
||||
Text(country).tag(Optional(country))
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.pickerStyle(.inline)
|
||||
#endif
|
||||
.disabled(countries.isEmpty)
|
||||
|
||||
Button {
|
||||
InstancesManifest.shared.changePublicAccount(accounts, settings: model)
|
||||
} label: {
|
||||
if let account = accounts.current, account.isPublic {
|
||||
Text("Switch to other public location")
|
||||
} else {
|
||||
Text("Switch to public locations")
|
||||
}
|
||||
}
|
||||
.disabled(countryOfPublicInstances.isNil)
|
||||
}
|
||||
|
||||
Section(header: SettingsHeader(text: "Custom Locations")) {
|
||||
#if os(macOS)
|
||||
InstancesSettings()
|
||||
#else
|
||||
ForEach(instances) { instance in
|
||||
AccountsNavigationLink(instance: instance)
|
||||
}
|
||||
addInstanceButton
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var countryFooter: some View {
|
||||
if let account = accounts.current {
|
||||
let locationType = account.isPublic ? (account.country ?? "Unknown") : "Custom"
|
||||
let description = account.isPublic ? account.url : account.instance?.description ?? "unknown"
|
||||
|
||||
Text("Current: \(locationType)\n\(description)")
|
||||
.foregroundColor(.secondary)
|
||||
#if os(macOS)
|
||||
.padding(.bottom, 10)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func loadCountries() {
|
||||
InstancesManifest.shared.instancesList.load()
|
||||
.onSuccess { response in
|
||||
if let instances: [ManifestedInstance] = response.typedContent() {
|
||||
self.countries = instances.map(\.country).unique().sorted()
|
||||
}
|
||||
}.onFailure { _ in
|
||||
model.presentAlert(title: "Could not load locations manifest")
|
||||
}
|
||||
}
|
||||
|
||||
private var addInstanceButton: some View {
|
||||
Button {
|
||||
presentingInstanceForm = true
|
||||
} label: {
|
||||
Label("Add Location...", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocationsSettings_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LocationsSettings()
|
||||
.environmentObject(AccountsModel())
|
||||
.environmentObject(NavigationModel())
|
||||
}
|
||||
}
|
@@ -26,8 +26,6 @@ struct PlayerSettings: View {
|
||||
|
||||
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
|
||||
|
||||
@Default(.showMPVPlaybackStats) private var showMPVPlaybackStats
|
||||
|
||||
#if os(iOS)
|
||||
private var idiom: UIUserInterfaceIdiom {
|
||||
UIDevice.current.userInterfaceIdiom
|
||||
@@ -103,10 +101,6 @@ struct PlayerSettings: View {
|
||||
lockOrientationInFullScreenToggle
|
||||
}
|
||||
#endif
|
||||
|
||||
Section(header: SettingsHeader(text: "Debugging")) {
|
||||
showMPVPlaybackStatsToggle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,10 +227,6 @@ struct PlayerSettings: View {
|
||||
Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground)
|
||||
}
|
||||
#endif
|
||||
|
||||
private var showMPVPlaybackStatsToggle: some View {
|
||||
Toggle("Show MPV playback statistics", isOn: $showMPVPlaybackStats)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaybackSettings_Previews: PreviewProvider {
|
||||
|
@@ -5,10 +5,10 @@ import SwiftUI
|
||||
struct SettingsView: View {
|
||||
#if os(macOS)
|
||||
private enum Tabs: Hashable {
|
||||
case instances, browsing, player, history, sponsorBlock, help
|
||||
case browsing, player, history, sponsorBlock, locations, advanced, help
|
||||
}
|
||||
|
||||
@State private var selection = Tabs.instances
|
||||
@State private var selection = Tabs.browsing
|
||||
#endif
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@@ -18,24 +18,20 @@ struct SettingsView: View {
|
||||
#endif
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
@State private var presentingInstanceForm = false
|
||||
@State private var savedFormInstanceID: Instance.ID?
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@EnvironmentObject<SettingsModel> private var model
|
||||
|
||||
@Default(.instances) private var instances
|
||||
|
||||
var body: some View {
|
||||
settings
|
||||
.environmentObject(model)
|
||||
.alert(isPresented: $model.presentingAlert) { model.alert }
|
||||
}
|
||||
|
||||
var settings: some View {
|
||||
#if os(macOS)
|
||||
TabView(selection: $selection) {
|
||||
Form {
|
||||
InstancesSettings()
|
||||
.environmentObject(accounts)
|
||||
}
|
||||
.tabItem {
|
||||
Label("Instances", systemImage: "server.rack")
|
||||
}
|
||||
.tag(Tabs.instances)
|
||||
|
||||
Form {
|
||||
BrowsingSettings()
|
||||
}
|
||||
@@ -68,6 +64,22 @@ struct SettingsView: View {
|
||||
}
|
||||
.tag(Tabs.sponsorBlock)
|
||||
|
||||
Form {
|
||||
LocationsSettings()
|
||||
}
|
||||
.tabItem {
|
||||
Label("Locations", systemImage: "globe")
|
||||
}
|
||||
.tag(Tabs.locations)
|
||||
|
||||
Group {
|
||||
AdvancedSettings()
|
||||
}
|
||||
.tabItem {
|
||||
Label("Advanced", systemImage: "wrench.and.screwdriver")
|
||||
}
|
||||
.tag(Tabs.advanced)
|
||||
|
||||
Form {
|
||||
Help()
|
||||
}
|
||||
@@ -88,9 +100,7 @@ struct SettingsView: View {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.sheet(isPresented: $presentingInstanceForm) {
|
||||
InstanceForm(savedInstanceID: $savedFormInstanceID)
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -99,16 +109,6 @@ struct SettingsView: View {
|
||||
List {
|
||||
#if os(tvOS)
|
||||
AccountSelectionView()
|
||||
#endif
|
||||
|
||||
Section(header: Text("Instances")) {
|
||||
ForEach(instances) { instance in
|
||||
AccountsNavigationLink(instance: instance)
|
||||
}
|
||||
addInstanceButton
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
Divider()
|
||||
#endif
|
||||
|
||||
@@ -144,6 +144,18 @@ struct SettingsView: View {
|
||||
} label: {
|
||||
Label("SponsorBlock", systemImage: "dollarsign.circle")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
LocationsSettings()
|
||||
} label: {
|
||||
Label("Locations", systemImage: "globe")
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
AdvancedSettings()
|
||||
} label: {
|
||||
Label("Advanced", systemImage: "wrench.and.screwdriver")
|
||||
}
|
||||
}
|
||||
|
||||
Section(footer: versionString) {
|
||||
@@ -175,8 +187,6 @@ struct SettingsView: View {
|
||||
#if os(macOS)
|
||||
private var windowHeight: Double {
|
||||
switch selection {
|
||||
case .instances:
|
||||
return 390
|
||||
case .browsing:
|
||||
return 390
|
||||
case .player:
|
||||
@@ -185,6 +195,10 @@ struct SettingsView: View {
|
||||
return 480
|
||||
case .sponsorBlock:
|
||||
return 660
|
||||
case .locations:
|
||||
return 480
|
||||
case .advanced:
|
||||
return 300
|
||||
case .help:
|
||||
return 570
|
||||
}
|
||||
@@ -197,14 +211,6 @@ struct SettingsView: View {
|
||||
.foregroundColor(.secondary)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var addInstanceButton: some View {
|
||||
Button {
|
||||
presentingInstanceForm = true
|
||||
} label: {
|
||||
Label("Add Instance...", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
|
Reference in New Issue
Block a user