Redesigned settings (fixes #47)

This commit is contained in:
Arkadiusz Fal
2022-01-06 16:02:53 +01:00
parent 520d69f37a
commit 3baa7a6893
14 changed files with 608 additions and 324 deletions

View File

@@ -15,10 +15,10 @@ struct AccountsNavigationLink: View {
}
private func removeInstanceButton(_ instance: Instance) -> some View {
if #available(iOS 15.0, *) {
return Button("Remove", role: .destructive) { removeAction(instance) }
} else {
return Button("Remove") { removeAction(instance) }
Button {
removeAction(instance)
} label: {
Label("Remove", systemImage: "trash")
}
}

124
Shared/Settings/Help.swift Normal file
View File

@@ -0,0 +1,124 @@
import Foundation
import SwiftUI
struct Help: View {
static let wikiURL = URL(string: "https://github.com/yattee/yattee/wiki")!
static let matrixURL = URL(string: "https://tinyurl.com/yattee-matrix")!
static let issuesURL = URL(string: "https://github.com/yattee/yattee/issues")!
static let milestonesURL = URL(string: "https://github.com/yattee/yattee/milestones")!
static let donationsURL = URL(string: "https://github.com/yattee/yattee/wiki/Donations")!
static let contributingURL = URL(string: "https://github.com/yattee/yattee/wiki/Contributing")!
@Environment(\.openURL) private var openURL
var body: some View {
Group {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
Section {
header("I am lost")
Text("You can find information about using Yattee in the Wiki pages.")
.padding(.bottom, 8)
helpItemLink("Wiki", url: Self.wikiURL, systemImage: "questionmark.circle")
.padding(.bottom, 8)
}
Spacer()
Section {
header("I want to ask a question")
Text("Discussions take place in Matrix chat channel. It's a good spot for general questions.")
.padding(.bottom, 8)
helpItemLink("Matrix Channel", url: Self.matrixURL, systemImage: "message")
.padding(.bottom, 8)
}
Spacer()
Section {
header("I found a bug /")
header("I have a feature request")
Text("Bugs and great feature ideas can be sent to the GitHub issues tracker. ")
Text("If you are reporting a bug, include all relevant details (especially: app\u{00a0}version, used device and system version, steps to reproduce).")
Text("If you are interested what's coming in future updates, you can track project Milestones.")
.padding(.bottom, 8)
VStack(alignment: .leading, spacing: 8) {
helpItemLink("Issues Tracker", url: Self.issuesURL, systemImage: "ladybug")
helpItemLink("Milestones", url: Self.milestonesURL, systemImage: "list.star")
}
.padding(.bottom, 8)
}
Spacer()
Section {
header("I like this app!")
Text("That's nice to hear. It is fun to deliver apps other people want to use. " +
"You can consider donating to the project or help by contributing to new features development.")
.padding(.bottom, 8)
VStack(alignment: .leading, spacing: 8) {
helpItemLink("Donations", url: Self.donationsURL, systemImage: "dollarsign.circle")
helpItemLink("Contributing", url: Self.contributingURL, systemImage: "hammer")
}
.padding(.bottom, 8)
}
}
#if os(iOS)
.padding(.horizontal)
#endif
}
}
#if os(tvOS)
.frame(maxWidth: 1000)
#else
.frame(maxWidth: .infinity, alignment: .leading)
#endif
.navigationTitle("Help")
}
func header(_ text: String) -> some View {
Text(text)
.fontWeight(.bold)
.font(.title3)
.padding(.bottom, 6)
}
func helpItemLink(_ label: String, url: URL, systemImage: String) -> some View {
Group {
#if os(tvOS)
VStack {
Button {} label: {
HStack(spacing: 8) {
Image(systemName: systemImage)
Text(label)
}
.font(.system(size: 25).bold())
}
Text(url.absoluteString)
}
.frame(maxWidth: .infinity)
#else
Button {
openURL(url)
} label: {
Label(label, systemImage: systemImage)
}
#endif
}
}
}
struct Help_Previews: PreviewProvider {
static var previews: some View {
Help()
}
}

View File

@@ -19,31 +19,66 @@ struct HistorySettings: View {
var body: some View {
Group {
Section(header: SettingsHeader(text: "History")) {
Toggle("Save recent queries and channels", isOn: $saveRecents)
Toggle("Save history of played videos", isOn: $saveHistory)
Toggle("Show progress of watching on thumbnails", isOn: $showWatchingProgress)
.disabled(!saveHistory)
#if os(macOS)
sections
#else
List {
sections
}
#endif
}
#if os(tvOS)
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
.navigationTitle("History")
}
private var sections: some View {
Group {
#if os(tvOS)
Section(header: SettingsHeader(text: "History")) {
Toggle("Save history of searches, channels and playlists", isOn: $saveRecents)
Toggle("Save history of played videos", isOn: $saveHistory)
Toggle("Show progress of watching on thumbnails", isOn: $showWatchingProgress)
.disabled(!saveHistory)
watchedVideoPlayNowBehaviorPicker
#if !os(tvOS)
watchedThresholdPicker
resetWatchedStatusOnPlayingToggle
watchedVideoStylePicker
watchedVideoBadgeColorPicker
}
#else
Section(header: SettingsHeader(text: "History")) {
Toggle("Save history of searches, channels and playlists", isOn: $saveRecents)
Toggle("Save history of played videos", isOn: $saveHistory)
Toggle("Show progress of watching on thumbnails", isOn: $showWatchingProgress)
.disabled(!saveHistory)
}
Section(header: SettingsHeader(text: "Watched")) {
watchedVideoPlayNowBehaviorPicker
#if os(macOS)
.padding(.top, 1)
#endif
watchedThresholdPicker
resetWatchedStatusOnPlayingToggle
}
Section(header: SettingsHeader(text: "Interface")) {
watchedVideoStylePicker
#if os(macOS)
.padding(.top, 1)
#endif
watchedVideoBadgeColorPicker
}
#if os(macOS)
Spacer()
#endif
}
#if os(tvOS)
watchedThresholdPicker
watchedVideoStylePicker
watchedVideoBadgeColorPicker
watchedVideoPlayNowBehaviorPicker
resetWatchedStatusOnPlayingToggle
#endif
#if os(macOS)
Spacer()
#endif
clearHistoryButton
@@ -51,7 +86,7 @@ struct HistorySettings: View {
}
private var watchedThresholdPicker: some View {
Section(header: header("Mark video as watched after playing")) {
Section(header: SettingsHeader(text: "Mark video as watched after playing", secondary: true)) {
Picker("Mark video as watched after playing", selection: $watchedThreshold) {
ForEach(Self.watchedThresholds, id: \.self) { threshold in
Text("\(threshold)%").tag(threshold)
@@ -69,7 +104,7 @@ struct HistorySettings: View {
}
private var watchedVideoStylePicker: some View {
Section(header: header("Mark watched videos with")) {
Section(header: SettingsHeader(text: "Mark watched videos with", secondary: true)) {
Picker("Mark watched videos with", selection: $watchedVideoStyle) {
Text("Nothing").tag(WatchedVideoStyle.nothing)
Text("Badge").tag(WatchedVideoStyle.badge)
@@ -88,7 +123,7 @@ struct HistorySettings: View {
}
private var watchedVideoBadgeColorPicker: some View {
Section(header: header("Badge color")) {
Section(header: SettingsHeader(text: "Badge color", secondary: true)) {
Picker("Badge color", selection: $watchedVideoBadgeColor) {
Text("Based on system color scheme").tag(WatchedVideoBadgeColor.colorSchemeBased)
Text("Blue").tag(WatchedVideoBadgeColor.blue)
@@ -96,6 +131,7 @@ struct HistorySettings: View {
}
.disabled(!saveHistory)
.disabled(watchedVideoStyle == .decreasedOpacity)
.disabled(watchedVideoStyle == .nothing)
.labelsHidden()
#if os(iOS)
@@ -107,7 +143,7 @@ struct HistorySettings: View {
}
private var watchedVideoPlayNowBehaviorPicker: some View {
Section(header: header("When partially watched video is played")) {
Section(header: SettingsHeader(text: "When partially watched video is played", secondary: true)) {
Picker("When partially watched video is played", selection: $watchedVideoPlayNowBehavior) {
Text("Continue").tag(WatchedVideoPlayNowBehavior.continue)
Text("Restart").tag(WatchedVideoPlayNowBehavior.restart)
@@ -125,6 +161,7 @@ struct HistorySettings: View {
private var resetWatchedStatusOnPlayingToggle: some View {
Toggle("Reset watched status when playing again", isOn: $resetWatchedStatusOnPlaying)
.disabled(!saveHistory)
}
private var clearHistoryButton: some View {
@@ -149,19 +186,6 @@ struct HistorySettings: View {
.foregroundColor(.red)
.disabled(!saveHistory)
}
private func header(_ text: String) -> some View {
#if os(iOS)
return EmptyView()
#elseif os(macOS)
return Text(text)
.opacity(saveHistory ? 1 : 0.3)
#else
return Text(text)
.foregroundColor(.secondary)
.opacity(saveHistory ? 1 : 0.2)
#endif
}
}
struct HistorySettings_Previews: PreviewProvider {

View File

@@ -14,25 +14,7 @@ struct InstanceSettings: View {
var body: some View {
List {
if instance.app.hasFrontendURL {
Section(header: Text("Frontend URL")) {
TextField(
"Frontend URL",
text: $frontendURL
)
.onAppear {
frontendURL = instance.frontendURL ?? ""
}
.onChange(of: frontendURL) { newValue in
InstancesModel.setFrontendURL(instance, newValue)
}
.labelsHidden()
.autocapitalization(.none)
.keyboardType(.URL)
}
}
Section(header: Text("Accounts"), footer: sectionFooter) {
Section(header: Text("Accounts")) {
if instance.app.supportsAccounts {
ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in
#if os(tvOS)
@@ -54,15 +36,21 @@ struct InstanceSettings: View {
Spacer()
}
.contextMenu {
Button("Remove") { removeAccount(account) }
Button {
removeAccount(account)
} label: {
Label("Remove", systemImage: "trash")
}
}
}
#endif
}
.redrawOn(change: accountsChanged)
Button("Add account...") {
Button {
presentingAccountForm = true
} label: {
Label("Add Account...", systemImage: "plus")
}
.sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) {
AccountForm(instance: instance)
@@ -75,6 +63,23 @@ struct InstanceSettings: View {
.foregroundColor(.secondary)
}
}
if instance.app.hasFrontendURL {
Section(header: Text("Frontend URL")) {
TextField(
"Frontend URL",
text: $frontendURL
)
.onAppear {
frontendURL = instance.frontendURL ?? ""
}
.onChange(of: frontendURL) { newValue in
InstancesModel.setFrontendURL(instance, newValue)
}
.labelsHidden()
.autocapitalization(.none)
.keyboardType(.URL)
}
}
}
#if os(tvOS)
.frame(maxWidth: 1000)
@@ -85,15 +90,6 @@ struct InstanceSettings: View {
.navigationTitle(instance.description)
}
private var sectionFooter: some View {
if !instance.app.supportsAccounts {
return Text("")
}
return Text("Tap and hold to remove account")
.foregroundColor(.secondary)
}
private func removeAccount(_ account: Account) {
AccountsModel.remove(account)
accountsChanged.toggle()

View File

@@ -1,10 +1,16 @@
import Defaults
import SwiftUI
struct PlaybackSettings: View {
struct PlayerSettings: View {
@Default(.instances) private var instances
@Default(.playerInstanceID) private var playerInstanceID
@Default(.quality) private var quality
@Default(.commentsInstanceID) private var commentsInstanceID
#if !os(tvOS)
@Default(.commentsPlacement) private var commentsPlacement
#endif
@Default(.playerSidebar) private var playerSidebar
@Default(.showHistoryInPlayer) private var showHistory
@Default(.showKeywords) private var showKeywords
@@ -29,66 +35,74 @@ struct PlaybackSettings: View {
var body: some View {
Group {
#if os(iOS)
Section(header: SettingsHeader(text: "Player")) {
sourcePicker
qualityPicker
#if os(macOS)
sections
Spacer()
#else
List {
sections
}
#endif
}
#if os(tvOS)
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
.navigationTitle("Player")
}
private var sections: some View {
Group {
Section(header: SettingsHeader(text: "Playback")) {
sourcePicker
qualityPicker
pauseOnHidingPlayerToggle
}
Section(header: SettingsHeader(text: "Comments")) {
commentsInstancePicker
#if !os(tvOS)
commentsPlacementPicker
.disabled(!CommentsModel.enabled)
#endif
}
Section(header: SettingsHeader(text: "Interface")) {
#if os(iOS)
if idiom == .pad {
sidebarPicker
}
keywordsToggle
showHistoryToggle
channelSubscribersToggle
pauseOnHidingPlayerToggle
if idiom == .pad {
enterFullscreenInLandscapeToggle
}
honorSystemOrientationLockToggle
lockLandscapeWhenEnteringFullscreenToggle
}
Section(header: SettingsHeader(text: "Picture in Picture")) {
closePiPOnNavigationToggle
closePiPOnOpeningPlayerToggle
closePiPAndOpenPlayerOnEnteringForegroundToggle
}
#else
Section(header: SettingsHeader(text: "Source")) {
sourcePicker
}
Section(header: SettingsHeader(text: "Quality")) {
qualityPicker
}
#endif
#if os(macOS)
Section(header: SettingsHeader(text: "Sidebar")) {
sidebarPicker
}
sidebarPicker
#endif
keywordsToggle
showHistoryToggle
channelSubscribersToggle
pauseOnHidingPlayerToggle
}
Section(header: SettingsHeader(text: "Picture in Picture")) {
closePiPOnNavigationToggle
closePiPOnOpeningPlayerToggle
#if !os(macOS)
closePiPAndOpenPlayerOnEnteringForegroundToggle
#endif
#if os(iOS)
Section(header: SettingsHeader(text: "Orientation")) {
if idiom == .pad {
enterFullscreenInLandscapeToggle
}
honorSystemOrientationLockToggle
lockLandscapeWhenEnteringFullscreenToggle
}
#endif
}
#if os(macOS)
Spacer()
#endif
Section(header: SettingsHeader(text: "Picture in Picture")) {
closePiPOnNavigationToggle
closePiPOnOpeningPlayerToggle
#if !os(macOS)
closePiPAndOpenPlayerOnEnteringForegroundToggle
#endif
}
}
}
private var sourcePicker: some View {
@@ -122,17 +136,46 @@ struct PlaybackSettings: View {
#endif
}
private var commentsInstancePicker: some View {
Picker("Source", selection: $commentsInstanceID) {
Text("Disabled").tag(Optional(""))
ForEach(InstancesModel.all.filter { $0.app.supportsComments }) { instance in
Text(instance.description).tag(Optional(instance.id))
}
}
.labelsHidden()
#if os(iOS)
.pickerStyle(.automatic)
#elseif os(tvOS)
.pickerStyle(.inline)
#endif
}
#if !os(tvOS)
private var commentsPlacementPicker: some View {
Picker("Placement", selection: $commentsPlacement) {
Text("Below video description").tag(CommentsPlacement.info)
Text("Separate tab").tag(CommentsPlacement.separate)
}
.labelsHidden()
#if os(iOS)
.pickerStyle(.automatic)
#endif
}
#endif
private var sidebarPicker: some View {
Picker("Sidebar", selection: $playerSidebar) {
#if os(macOS)
Text("Show").tag(PlayerSidebarSetting.always)
Text("Show sidebar").tag(PlayerSidebarSetting.always)
#endif
#if os(iOS)
Text("Show sidebar when space permits").tag(PlayerSidebarSetting.whenFits)
#endif
Text("Hide").tag(PlayerSidebarSetting.never)
Text("Hide sidebar").tag(PlayerSidebarSetting.never)
}
.labelsHidden()
@@ -144,15 +187,15 @@ struct PlaybackSettings: View {
}
private var keywordsToggle: some View {
Toggle("Show video keywords", isOn: $showKeywords)
Toggle("Show keywords", isOn: $showKeywords)
}
private var showHistoryToggle: some View {
Toggle("Show history of videos", isOn: $showHistory)
Toggle("Show history", isOn: $showHistory)
}
private var channelSubscribersToggle: some View {
Toggle("Show channel subscribers count", isOn: $channelSubscribers)
Toggle("Show subscribers count", isOn: $channelSubscribers)
}
private var pauseOnHidingPlayerToggle: some View {
@@ -161,7 +204,7 @@ struct PlaybackSettings: View {
#if os(iOS)
private var honorSystemOrientationLockToggle: some View {
Toggle("Honor system orientation lock", isOn: $honorSystemOrientationLock)
Toggle("Honor orientation lock", isOn: $honorSystemOrientationLock)
.disabled(!enterFullscreenInLandscape)
}
@@ -170,7 +213,7 @@ struct PlaybackSettings: View {
}
private var lockLandscapeWhenEnteringFullscreenToggle: some View {
Toggle("Lock landscape orientation when entering fullscreen", isOn: $lockLandscapeWhenEnteringFullscreen)
Toggle("Lock landscape on rotation", isOn: $lockLandscapeWhenEnteringFullscreen)
.disabled(!enterFullscreenInLandscape)
}
#endif
@@ -192,7 +235,7 @@ struct PlaybackSettings: View {
struct PlaybackSettings_Previews: PreviewProvider {
static var previews: some View {
PlaybackSettings()
PlayerSettings()
.injectFixtureEnvironmentObjects()
}
}

View File

@@ -1,152 +0,0 @@
import Defaults
import SwiftUI
struct ServicesSettings: View {
@Default(.sponsorBlockInstance) private var sponsorBlockInstance
@Default(.sponsorBlockCategories) private var sponsorBlockCategories
@Default(.commentsInstanceID) private var commentsInstanceID
#if !os(tvOS)
@Default(.commentsPlacement) private var commentsPlacement
#endif
var body: some View {
Section(header: SettingsHeader(text: "Comments")) {
commentsInstancePicker
#if !os(tvOS)
commentsPlacementPicker
.disabled(!CommentsModel.enabled)
#endif
}
Section(header: SettingsHeader(text: "SponsorBlock API")) {
TextField(
"SponsorBlock API Instance",
text: $sponsorBlockInstance
)
.labelsHidden()
#if !os(macOS)
.autocapitalization(.none)
.keyboardType(.URL)
#endif
}
Section(header: SettingsHeader(text: "Categories to Skip")) {
#if os(macOS)
let list = ForEach(SponsorBlockAPI.categories, id: \.self) { category in
SponsorBlockCategorySelectionRow(
title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown",
selected: sponsorBlockCategories.contains(category)
) { value in
toggleCategory(category, value: value)
}
}
Group {
if #available(macOS 12.0, *) {
list
.listStyle(.inset(alternatesRowBackgrounds: true))
} else {
list
.listStyle(.inset)
}
}
Spacer()
#else
ForEach(SponsorBlockAPI.categories, id: \.self) { category in
SponsorBlockCategorySelectionRow(
title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown",
selected: sponsorBlockCategories.contains(category)
) { value in
toggleCategory(category, value: value)
}
}
#endif
}
}
private var commentsInstancePicker: some View {
Picker("Source", selection: $commentsInstanceID) {
Text("Disabled").tag(Optional(""))
ForEach(InstancesModel.all.filter { $0.app.supportsComments }) { instance in
Text(instance.description).tag(Optional(instance.id))
}
}
.labelsHidden()
#if os(iOS)
.pickerStyle(.automatic)
#elseif os(tvOS)
.pickerStyle(.inline)
#endif
}
#if !os(tvOS)
private var commentsPlacementPicker: some View {
Picker("Placement", selection: $commentsPlacement) {
Text("Below video description").tag(CommentsPlacement.info)
Text("Separate tab").tag(CommentsPlacement.separate)
}
.labelsHidden()
#if os(iOS)
.pickerStyle(.automatic)
#endif
}
#endif
func toggleCategory(_ category: String, value: Bool) {
if let index = sponsorBlockCategories.firstIndex(where: { $0 == category }), !value {
sponsorBlockCategories.remove(at: index)
} else if value {
sponsorBlockCategories.insert(category)
}
}
struct SponsorBlockCategorySelectionRow: View {
let title: String
let selected: Bool
var action: (Bool) -> Void
@State private var toggleChecked = false
var body: some View {
Button(action: { action(!selected) }) {
HStack {
#if os(macOS)
Toggle(isOn: $toggleChecked) {
Text(self.title)
Spacer()
}
.onAppear {
toggleChecked = selected
}
.onChange(of: toggleChecked) { new in
action(new)
}
#else
Text(self.title)
Spacer()
if selected {
Image(systemName: "checkmark")
#if os(iOS)
.foregroundColor(.accentColor)
#endif
}
#endif
}
.contentShape(Rectangle())
}
#if !os(tvOS)
.buttonStyle(.plain)
#endif
}
}
}
struct ServicesSettings_Previews: PreviewProvider {
static var previews: some View {
VStack {
ServicesSettings()
}
}
}

View File

@@ -2,13 +2,28 @@ import SwiftUI
struct SettingsHeader: View {
var text: String
var secondary = false
var body: some View {
Text(text)
#if os(macOS) || os(tvOS)
.font(.title3)
.foregroundColor(.secondary)
.focusable(false)
Group {
#if os(iOS)
if secondary {
EmptyView()
} else {
Text(text)
}
#else
Text(text)
#endif
}
#if os(tvOS)
.font(secondary ? .footnote : .title3)
.foregroundColor(.secondary)
.focusable(false)
#endif
#if os(macOS)
.font(secondary ? .system(size: 13) : .system(size: 15))
.foregroundColor(secondary ? Color.primary : .secondary)
#endif
}
}

View File

@@ -5,8 +5,10 @@ import SwiftUI
struct SettingsView: View {
#if os(macOS)
private enum Tabs: Hashable {
case instances, browsing, history, playback, services, updates
case instances, browsing, player, history, sponsorBlock, updates, help
}
@State private var selection = Tabs.instances
#endif
@Environment(\.colorScheme) private var colorScheme
@@ -24,7 +26,7 @@ struct SettingsView: View {
var body: some View {
#if os(macOS)
TabView {
TabView(selection: $selection) {
Form {
InstancesSettings()
.environmentObject(accounts)
@@ -42,6 +44,14 @@ struct SettingsView: View {
}
.tag(Tabs.browsing)
Form {
PlayerSettings()
}
.tabItem {
Label("Player", systemImage: "play.rectangle")
}
.tag(Tabs.player)
Form {
HistorySettings()
}
@@ -51,20 +61,12 @@ struct SettingsView: View {
.tag(Tabs.history)
Form {
PlaybackSettings()
SponsorBlockSettings()
}
.tabItem {
Label("Playback", systemImage: "play.rectangle")
Label("SponsorBlock", systemImage: "dollarsign.circle")
}
.tag(Tabs.playback)
Form {
ServicesSettings()
}
.tabItem {
Label("Services", systemImage: "puzzlepiece")
}
.tag(Tabs.services)
.tag(Tabs.sponsorBlock)
Form {
UpdatesSettings()
@@ -73,20 +75,22 @@ struct SettingsView: View {
Label("Updates", systemImage: "gearshape.2")
}
.tag(Tabs.updates)
Form {
Help()
}
.tabItem {
Label("Help", systemImage: "questionmark.circle")
}
.tag(Tabs.help)
}
.padding(20)
.frame(width: 400, height: 400)
.frame(width: 480, height: windowHeight)
#else
NavigationView {
List {
#if os(tvOS)
AccountSelectionView()
Section(header: SettingsHeader(text: "Favorites")) {
NavigationLink("Edit favorites...") {
EditFavorites()
}
}
#endif
Section(header: Text("Instances")) {
@@ -96,14 +100,55 @@ struct SettingsView: View {
addInstanceButton
}
BrowsingSettings()
HistorySettings()
PlaybackSettings()
ServicesSettings()
#if os(tvOS)
Divider()
#endif
Section {
#if os(tvOS)
NavigationLink {
EditFavorites()
} label: {
Label("Favorites", systemImage: "heart.fill")
}
#endif
NavigationLink {
BrowsingSettings()
} label: {
Label("Browsing", systemImage: "list.and.film")
}
NavigationLink {
PlayerSettings()
} label: {
Label("Player", systemImage: "play.rectangle")
}
NavigationLink {
HistorySettings()
} label: {
Label("History", systemImage: "clock.arrow.circlepath")
}
NavigationLink {
SponsorBlockSettings()
} label: {
Label("SponsorBlock", systemImage: "dollarsign.circle")
}
}
Section(footer: versionString) {
NavigationLink {
Help()
} label: {
Label("Help", systemImage: "questionmark.circle")
}
}
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarItem(placement: .navigationBarLeading) {
#if !os(tvOS)
Button("Done") {
presentationMode.wrappedValue.dismiss()
@@ -126,9 +171,39 @@ struct SettingsView: View {
#endif
}
#if os(macOS)
private var windowHeight: Double {
switch selection {
case .instances:
return 390
case .browsing:
return 350
case .player:
return 450
case .history:
return 480
case .sponsorBlock:
return 290
case .updates:
return 200
case .help:
return 570
}
}
#endif
private var versionString: some View {
Text("Yattee \(YatteeApp.version) (build \(YatteeApp.build))")
#if os(tvOS)
.foregroundColor(.secondary)
#endif
}
private var addInstanceButton: some View {
Button("Add Instance...") {
Button {
presentingInstanceForm = true
} label: {
Label("Add Instance...", systemImage: "plus")
}
}
}

View File

@@ -0,0 +1,132 @@
import Defaults
import SwiftUI
struct SponsorBlockSettings: View {
@Default(.sponsorBlockInstance) private var sponsorBlockInstance
@Default(.sponsorBlockCategories) private var sponsorBlockCategories
var body: some View {
Group {
#if os(macOS)
sections
Spacer()
#else
List {
sections
}
#endif
}
#if os(tvOS)
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
.navigationTitle("SponsorBlock")
}
private var sections: some View {
Group {
Section(header: SettingsHeader(text: "SponsorBlock API")) {
TextField(
"SponsorBlock API Instance",
text: $sponsorBlockInstance
)
.labelsHidden()
#if !os(macOS)
.autocapitalization(.none)
.keyboardType(.URL)
#endif
}
Section(header: SettingsHeader(text: "Categories to Skip")) {
#if os(macOS)
let list = ForEach(SponsorBlockAPI.categories, id: \.self) { category in
SponsorBlockCategorySelectionRow(
title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown",
selected: sponsorBlockCategories.contains(category)
) { value in
toggleCategory(category, value: value)
}
}
Group {
if #available(macOS 12.0, *) {
list
.listStyle(.inset(alternatesRowBackgrounds: true))
} else {
list
.listStyle(.inset)
}
}
Spacer()
#else
ForEach(SponsorBlockAPI.categories, id: \.self) { category in
SponsorBlockCategorySelectionRow(
title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown",
selected: sponsorBlockCategories.contains(category)
) { value in
toggleCategory(category, value: value)
}
}
#endif
}
}
}
func toggleCategory(_ category: String, value: Bool) {
if let index = sponsorBlockCategories.firstIndex(where: { $0 == category }), !value {
sponsorBlockCategories.remove(at: index)
} else if value {
sponsorBlockCategories.insert(category)
}
}
struct SponsorBlockCategorySelectionRow: View {
let title: String
let selected: Bool
var action: (Bool) -> Void
@State private var toggleChecked = false
var body: some View {
Button(action: { action(!selected) }) {
HStack {
#if os(macOS)
Toggle(isOn: $toggleChecked) {
Text(self.title)
Spacer()
}
.onAppear {
toggleChecked = selected
}
.onChange(of: toggleChecked) { new in
action(new)
}
#else
Text(self.title)
Spacer()
if selected {
Image(systemName: "checkmark")
#if os(iOS)
.foregroundColor(.accentColor)
#endif
}
#endif
}
.contentShape(Rectangle())
}
#if !os(tvOS)
.buttonStyle(.plain)
#endif
}
}
}
struct ServicesSettings_Previews: PreviewProvider {
static var previews: some View {
VStack {
SponsorBlockSettings()
}
}
}

View File

@@ -3,6 +3,14 @@ import SwiftUI
@main
struct YatteeApp: App {
static var version: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
}
static var build: String {
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown"
}
#if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var updater = UpdaterModel()