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

View File

@ -14,25 +14,7 @@ struct InstanceSettings: View {
var body: some View { var body: some View {
List { List {
if instance.app.hasFrontendURL { Section(header: Text("Accounts")) {
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) {
if instance.app.supportsAccounts { if instance.app.supportsAccounts {
ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in
#if os(tvOS) #if os(tvOS)
@ -54,15 +36,21 @@ struct InstanceSettings: View {
Spacer() Spacer()
} }
.contextMenu { .contextMenu {
Button("Remove") { removeAccount(account) } Button {
removeAccount(account)
} label: {
Label("Remove", systemImage: "trash")
}
} }
} }
#endif #endif
} }
.redrawOn(change: accountsChanged) .redrawOn(change: accountsChanged)
Button("Add account...") { Button {
presentingAccountForm = true presentingAccountForm = true
} label: {
Label("Add Account...", systemImage: "plus")
} }
.sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) { .sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) {
AccountForm(instance: instance) AccountForm(instance: instance)
@ -75,6 +63,23 @@ struct InstanceSettings: View {
.foregroundColor(.secondary) .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) #if os(tvOS)
.frame(maxWidth: 1000) .frame(maxWidth: 1000)
@ -85,15 +90,6 @@ struct InstanceSettings: View {
.navigationTitle(instance.description) .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) { private func removeAccount(_ account: Account) {
AccountsModel.remove(account) AccountsModel.remove(account)
accountsChanged.toggle() accountsChanged.toggle()

View File

@ -1,10 +1,16 @@
import Defaults import Defaults
import SwiftUI import SwiftUI
struct PlaybackSettings: View { struct PlayerSettings: View {
@Default(.instances) private var instances @Default(.instances) private var instances
@Default(.playerInstanceID) private var playerInstanceID @Default(.playerInstanceID) private var playerInstanceID
@Default(.quality) private var quality @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(.playerSidebar) private var playerSidebar
@Default(.showHistoryInPlayer) private var showHistory @Default(.showHistoryInPlayer) private var showHistory
@Default(.showKeywords) private var showKeywords @Default(.showKeywords) private var showKeywords
@ -29,66 +35,74 @@ struct PlaybackSettings: View {
var body: some View { var body: some View {
Group { Group {
#if os(iOS) #if os(macOS)
Section(header: SettingsHeader(text: "Player")) { sections
sourcePicker
qualityPicker
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 { if idiom == .pad {
sidebarPicker sidebarPicker
} }
#endif
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
}
#if os(macOS) #if os(macOS)
Section(header: SettingsHeader(text: "Sidebar")) { sidebarPicker
sidebarPicker
}
#endif #endif
keywordsToggle keywordsToggle
showHistoryToggle showHistoryToggle
channelSubscribersToggle channelSubscribersToggle
pauseOnHidingPlayerToggle }
Section(header: SettingsHeader(text: "Picture in Picture")) { #if os(iOS)
closePiPOnNavigationToggle Section(header: SettingsHeader(text: "Orientation")) {
closePiPOnOpeningPlayerToggle if idiom == .pad {
#if !os(macOS) enterFullscreenInLandscapeToggle
closePiPAndOpenPlayerOnEnteringForegroundToggle }
#endif honorSystemOrientationLockToggle
lockLandscapeWhenEnteringFullscreenToggle
} }
#endif #endif
}
#if os(macOS) Section(header: SettingsHeader(text: "Picture in Picture")) {
Spacer() closePiPOnNavigationToggle
#endif closePiPOnOpeningPlayerToggle
#if !os(macOS)
closePiPAndOpenPlayerOnEnteringForegroundToggle
#endif
}
}
} }
private var sourcePicker: some View { private var sourcePicker: some View {
@ -122,17 +136,46 @@ struct PlaybackSettings: View {
#endif #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 { private var sidebarPicker: some View {
Picker("Sidebar", selection: $playerSidebar) { Picker("Sidebar", selection: $playerSidebar) {
#if os(macOS) #if os(macOS)
Text("Show").tag(PlayerSidebarSetting.always) Text("Show sidebar").tag(PlayerSidebarSetting.always)
#endif #endif
#if os(iOS) #if os(iOS)
Text("Show sidebar when space permits").tag(PlayerSidebarSetting.whenFits) Text("Show sidebar when space permits").tag(PlayerSidebarSetting.whenFits)
#endif #endif
Text("Hide").tag(PlayerSidebarSetting.never) Text("Hide sidebar").tag(PlayerSidebarSetting.never)
} }
.labelsHidden() .labelsHidden()
@ -144,15 +187,15 @@ struct PlaybackSettings: View {
} }
private var keywordsToggle: some View { private var keywordsToggle: some View {
Toggle("Show video keywords", isOn: $showKeywords) Toggle("Show keywords", isOn: $showKeywords)
} }
private var showHistoryToggle: some View { private var showHistoryToggle: some View {
Toggle("Show history of videos", isOn: $showHistory) Toggle("Show history", isOn: $showHistory)
} }
private var channelSubscribersToggle: some View { private var channelSubscribersToggle: some View {
Toggle("Show channel subscribers count", isOn: $channelSubscribers) Toggle("Show subscribers count", isOn: $channelSubscribers)
} }
private var pauseOnHidingPlayerToggle: some View { private var pauseOnHidingPlayerToggle: some View {
@ -161,7 +204,7 @@ struct PlaybackSettings: View {
#if os(iOS) #if os(iOS)
private var honorSystemOrientationLockToggle: some View { private var honorSystemOrientationLockToggle: some View {
Toggle("Honor system orientation lock", isOn: $honorSystemOrientationLock) Toggle("Honor orientation lock", isOn: $honorSystemOrientationLock)
.disabled(!enterFullscreenInLandscape) .disabled(!enterFullscreenInLandscape)
} }
@ -170,7 +213,7 @@ struct PlaybackSettings: View {
} }
private var lockLandscapeWhenEnteringFullscreenToggle: some View { private var lockLandscapeWhenEnteringFullscreenToggle: some View {
Toggle("Lock landscape orientation when entering fullscreen", isOn: $lockLandscapeWhenEnteringFullscreen) Toggle("Lock landscape on rotation", isOn: $lockLandscapeWhenEnteringFullscreen)
.disabled(!enterFullscreenInLandscape) .disabled(!enterFullscreenInLandscape)
} }
#endif #endif
@ -192,7 +235,7 @@ struct PlaybackSettings: View {
struct PlaybackSettings_Previews: PreviewProvider { struct PlaybackSettings_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
PlaybackSettings() PlayerSettings()
.injectFixtureEnvironmentObjects() .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 { struct SettingsHeader: View {
var text: String var text: String
var secondary = false
var body: some View { var body: some View {
Text(text) Group {
#if os(macOS) || os(tvOS) #if os(iOS)
.font(.title3) if secondary {
.foregroundColor(.secondary) EmptyView()
.focusable(false) } 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 #endif
} }
} }

View File

@ -5,8 +5,10 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
#if os(macOS) #if os(macOS)
private enum Tabs: Hashable { 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 #endif
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@ -24,7 +26,7 @@ struct SettingsView: View {
var body: some View { var body: some View {
#if os(macOS) #if os(macOS)
TabView { TabView(selection: $selection) {
Form { Form {
InstancesSettings() InstancesSettings()
.environmentObject(accounts) .environmentObject(accounts)
@ -42,6 +44,14 @@ struct SettingsView: View {
} }
.tag(Tabs.browsing) .tag(Tabs.browsing)
Form {
PlayerSettings()
}
.tabItem {
Label("Player", systemImage: "play.rectangle")
}
.tag(Tabs.player)
Form { Form {
HistorySettings() HistorySettings()
} }
@ -51,20 +61,12 @@ struct SettingsView: View {
.tag(Tabs.history) .tag(Tabs.history)
Form { Form {
PlaybackSettings() SponsorBlockSettings()
} }
.tabItem { .tabItem {
Label("Playback", systemImage: "play.rectangle") Label("SponsorBlock", systemImage: "dollarsign.circle")
} }
.tag(Tabs.playback) .tag(Tabs.sponsorBlock)
Form {
ServicesSettings()
}
.tabItem {
Label("Services", systemImage: "puzzlepiece")
}
.tag(Tabs.services)
Form { Form {
UpdatesSettings() UpdatesSettings()
@ -73,20 +75,22 @@ struct SettingsView: View {
Label("Updates", systemImage: "gearshape.2") Label("Updates", systemImage: "gearshape.2")
} }
.tag(Tabs.updates) .tag(Tabs.updates)
Form {
Help()
}
.tabItem {
Label("Help", systemImage: "questionmark.circle")
}
.tag(Tabs.help)
} }
.padding(20) .padding(20)
.frame(width: 400, height: 400) .frame(width: 480, height: windowHeight)
#else #else
NavigationView { NavigationView {
List { List {
#if os(tvOS) #if os(tvOS)
AccountSelectionView() AccountSelectionView()
Section(header: SettingsHeader(text: "Favorites")) {
NavigationLink("Edit favorites...") {
EditFavorites()
}
}
#endif #endif
Section(header: Text("Instances")) { Section(header: Text("Instances")) {
@ -96,14 +100,55 @@ struct SettingsView: View {
addInstanceButton addInstanceButton
} }
BrowsingSettings() #if os(tvOS)
HistorySettings() Divider()
PlaybackSettings() #endif
ServicesSettings()
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") .navigationTitle("Settings")
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarLeading) {
#if !os(tvOS) #if !os(tvOS)
Button("Done") { Button("Done") {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
@ -126,9 +171,39 @@ struct SettingsView: View {
#endif #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 { private var addInstanceButton: some View {
Button("Add Instance...") { Button {
presentingInstanceForm = true 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 @main
struct YatteeApp: App { 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) #if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var updater = UpdaterModel() @StateObject private var updater = UpdaterModel()

View File

@ -99,6 +99,8 @@
371F2F1C269B43D300E4A7AB /* NavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationModel.swift */; }; 371F2F1C269B43D300E4A7AB /* NavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationModel.swift */; };
3722AEBC274DA396005EA4D6 /* Badge+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */; }; 3722AEBC274DA396005EA4D6 /* Badge+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */; };
3722AEBE274DA401005EA4D6 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBD274DA401005EA4D6 /* Backport.swift */; }; 3722AEBE274DA401005EA4D6 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBD274DA401005EA4D6 /* Backport.swift */; };
3727B74A27872A920021C15E /* VisualEffectBlur-iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */; };
3727B74B27872B880021C15E /* VisualEffectBlur-macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */; };
3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; }; 3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; };
3729037F2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; }; 3729037F2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; };
372915E42687E33E00F5A35B /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 372915E32687E33E00F5A35B /* Defaults */; }; 372915E42687E33E00F5A35B /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 372915E32687E33E00F5A35B /* Defaults */; };
@ -147,9 +149,9 @@
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; 3748186E26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; };
3748186F26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; 3748186F26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; };
3748187026A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; 3748187026A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; };
37484C1926FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; }; 37484C1926FC837400287258 /* PlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlayerSettings.swift */; };
37484C1A26FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; }; 37484C1A26FC837400287258 /* PlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlayerSettings.swift */; };
37484C1B26FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; }; 37484C1B26FC837400287258 /* PlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlayerSettings.swift */; };
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; }; 37484C2526FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
37484C2626FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; }; 37484C2626FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; }; 37484C2726FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
@ -161,9 +163,9 @@
37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; 37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; 37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
37484C3326FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; 37484C3326FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
374C053527242D9F009BDDBE /* ServicesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* ServicesSettings.swift */; }; 374C053527242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */; };
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* ServicesSettings.swift */; }; 374C053627242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */; };
374C053727242D9F009BDDBE /* ServicesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* ServicesSettings.swift */; }; 374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */; };
374C053B2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; }; 374C053B2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; };
374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; }; 374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; };
374C053D2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; }; 374C053D2724614F009BDDBE /* PlayerTVMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */; };
@ -177,6 +179,9 @@
3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; }; 3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; }; 3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
3751B4B427836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; }; 3751B4B427836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
37579D5D27864F5F00FD0B98 /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37579D5C27864F5F00FD0B98 /* Help.swift */; };
37579D5E27864F5F00FD0B98 /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37579D5C27864F5F00FD0B98 /* Help.swift */; };
37579D5F27864F5F00FD0B98 /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37579D5C27864F5F00FD0B98 /* Help.swift */; };
3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; }; 3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; }; 37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; }; 37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
@ -633,6 +638,8 @@
3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Badge+Backport.swift"; sourceTree = "<group>"; }; 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Badge+Backport.swift"; sourceTree = "<group>"; };
3722AEBD274DA401005EA4D6 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; }; 3722AEBD274DA401005EA4D6 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tint+Backport.swift"; sourceTree = "<group>"; }; 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tint+Backport.swift"; sourceTree = "<group>"; };
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisualEffectBlur-macOS.swift"; sourceTree = "<group>"; };
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisualEffectBlur-iOS.swift"; sourceTree = "<group>"; };
3729037D2739E47400EA99F6 /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; }; 3729037D2739E47400EA99F6 /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; };
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; }; 372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; }; 3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; };
@ -651,18 +658,19 @@
3748186526A7627F0084E870 /* Video+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Video+Fixtures.swift"; sourceTree = "<group>"; }; 3748186526A7627F0084E870 /* Video+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Video+Fixtures.swift"; sourceTree = "<group>"; };
3748186926A764FB0084E870 /* Thumbnail+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Thumbnail+Fixtures.swift"; sourceTree = "<group>"; }; 3748186926A764FB0084E870 /* Thumbnail+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Thumbnail+Fixtures.swift"; sourceTree = "<group>"; };
3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = "<group>"; }; 3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = "<group>"; };
37484C1826FC837400287258 /* PlaybackSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettings.swift; sourceTree = "<group>"; }; 37484C1826FC837400287258 /* PlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSettings.swift; sourceTree = "<group>"; };
37484C2426FC83E000287258 /* InstanceForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceForm.swift; sourceTree = "<group>"; }; 37484C2426FC83E000287258 /* InstanceForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceForm.swift; sourceTree = "<group>"; };
37484C2826FC83FF00287258 /* AccountForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountForm.swift; sourceTree = "<group>"; }; 37484C2826FC83FF00287258 /* AccountForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountForm.swift; sourceTree = "<group>"; };
37484C2C26FC844700287258 /* InstanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSettings.swift; sourceTree = "<group>"; }; 37484C2C26FC844700287258 /* InstanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSettings.swift; sourceTree = "<group>"; };
37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = "<group>"; }; 37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = "<group>"; };
374C053427242D9F009BDDBE /* ServicesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesSettings.swift; sourceTree = "<group>"; }; 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockSettings.swift; sourceTree = "<group>"; };
374C053A2724614F009BDDBE /* PlayerTVMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTVMenu.swift; sourceTree = "<group>"; }; 374C053A2724614F009BDDBE /* PlayerTVMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTVMenu.swift; sourceTree = "<group>"; };
374C053E272472C0009BDDBE /* PlayerSponsorBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSponsorBlock.swift; sourceTree = "<group>"; }; 374C053E272472C0009BDDBE /* PlayerSponsorBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSponsorBlock.swift; sourceTree = "<group>"; };
374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; }; 375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
3751B4B127836902000B7DF4 /* SearchPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPage.swift; sourceTree = "<group>"; }; 3751B4B127836902000B7DF4 /* SearchPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPage.swift; sourceTree = "<group>"; };
37579D5C27864F5F00FD0B98 /* Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help.swift; sourceTree = "<group>"; };
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; }; 37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; }; 37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; }; 37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
@ -1003,6 +1011,8 @@
3722AEBD274DA401005EA4D6 /* Backport.swift */, 3722AEBD274DA401005EA4D6 /* Backport.swift */,
3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */, 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */,
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */, 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
); );
path = Backports; path = Backports;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1068,11 +1078,12 @@
37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */, 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */,
37732FEF2703A26300F04329 /* AccountValidationStatus.swift */, 37732FEF2703A26300F04329 /* AccountValidationStatus.swift */,
376BE50A27349108009AD608 /* BrowsingSettings.swift */, 376BE50A27349108009AD608 /* BrowsingSettings.swift */,
37579D5C27864F5F00FD0B98 /* Help.swift */,
37BC50A72778A84700510953 /* HistorySettings.swift */, 37BC50A72778A84700510953 /* HistorySettings.swift */,
37484C2426FC83E000287258 /* InstanceForm.swift */, 37484C2426FC83E000287258 /* InstanceForm.swift */,
37484C2C26FC844700287258 /* InstanceSettings.swift */, 37484C2C26FC844700287258 /* InstanceSettings.swift */,
37484C1826FC837400287258 /* PlaybackSettings.swift */, 37484C1826FC837400287258 /* PlayerSettings.swift */,
374C053427242D9F009BDDBE /* ServicesSettings.swift */, 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */,
376BE50627347B57009AD608 /* SettingsHeader.swift */, 376BE50627347B57009AD608 /* SettingsHeader.swift */,
37B044B626F7AB9000E1419D /* SettingsView.swift */, 37B044B626F7AB9000E1419D /* SettingsView.swift */,
); );
@ -1903,13 +1914,14 @@
378E50FF26FE8EEE00F49626 /* AccountsMenuView.swift in Sources */, 378E50FF26FE8EEE00F49626 /* AccountsMenuView.swift in Sources */,
37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */, 37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */, 37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */,
3727B74A27872A920021C15E /* VisualEffectBlur-iOS.swift in Sources */,
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
37130A5F277657300033018A /* PersistenceController.swift in Sources */, 37130A5F277657300033018A /* PersistenceController.swift in Sources */,
37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */, 37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */,
37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */, 37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
37484C1926FC837400287258 /* PlaybackSettings.swift in Sources */, 37484C1926FC837400287258 /* PlayerSettings.swift in Sources */,
3711403F26B206A6005B3555 /* SearchModel.swift in Sources */, 3711403F26B206A6005B3555 /* SearchModel.swift in Sources */,
3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */, 3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */,
37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */, 37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
@ -1958,7 +1970,7 @@
377FC7E3267A084A00A6BBAF /* VideoCell.swift in Sources */, 377FC7E3267A084A00A6BBAF /* VideoCell.swift in Sources */,
37C3A251272366440087A57A /* ChannelPlaylistView.swift in Sources */, 37C3A251272366440087A57A /* ChannelPlaylistView.swift in Sources */,
37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */, 37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */,
374C053527242D9F009BDDBE /* ServicesSettings.swift in Sources */, 374C053527242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */,
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */, 37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */,
376A33E42720CB35000C1D6B /* Account.swift in Sources */, 376A33E42720CB35000C1D6B /* Account.swift in Sources */,
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */, 37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
@ -2022,6 +2034,7 @@
371F2F1A269B43D300E4A7AB /* NavigationModel.swift in Sources */, 371F2F1A269B43D300E4A7AB /* NavigationModel.swift in Sources */,
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */, 3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
37579D5D27864F5F00FD0B98 /* Help.swift in Sources */,
3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */, 3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */,
37001563271B1F250049C794 /* AccountsModel.swift in Sources */, 37001563271B1F250049C794 /* AccountsModel.swift in Sources */,
37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */, 37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */,
@ -2043,6 +2056,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3727B74B27872B880021C15E /* VisualEffectBlur-macOS.swift in Sources */,
374710062755291C00CE0F87 /* SearchField.swift in Sources */, 374710062755291C00CE0F87 /* SearchField.swift in Sources */,
378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */, 378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */,
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
@ -2053,7 +2067,7 @@
37C3A24627235DA70087A57A /* ChannelPlaylist.swift in Sources */, 37C3A24627235DA70087A57A /* ChannelPlaylist.swift in Sources */,
37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */, 37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */, 374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */, 374C053627242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */,
37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */, 37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */, 37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
37DD9DA42785BBC900539416 /* NoCommentsView.swift in Sources */, 37DD9DA42785BBC900539416 /* NoCommentsView.swift in Sources */,
@ -2080,7 +2094,7 @@
376B2E0826F920D600B1D64D /* SignInRequiredView.swift in Sources */, 376B2E0826F920D600B1D64D /* SignInRequiredView.swift in Sources */,
37CC3F4D270CFE1700608308 /* PlayerQueueView.swift in Sources */, 37CC3F4D270CFE1700608308 /* PlayerQueueView.swift in Sources */,
37B81B0026D2CA3700675966 /* VideoDetails.swift in Sources */, 37B81B0026D2CA3700675966 /* VideoDetails.swift in Sources */,
37484C1A26FC837400287258 /* PlaybackSettings.swift in Sources */, 37484C1A26FC837400287258 /* PlayerSettings.swift in Sources */,
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */, 37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */, 37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */, 378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */,
@ -2117,6 +2131,7 @@
37AAF29126740715007FC770 /* Channel.swift in Sources */, 37AAF29126740715007FC770 /* Channel.swift in Sources */,
376A33E12720CAD6000C1D6B /* VideosApp.swift in Sources */, 376A33E12720CAD6000C1D6B /* VideosApp.swift in Sources */,
37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */, 37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
37579D5E27864F5F00FD0B98 /* Help.swift in Sources */,
37EF9A77275BEB8E0043B585 /* CommentView.swift in Sources */, 37EF9A77275BEB8E0043B585 /* CommentView.swift in Sources */,
37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */, 37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */,
3748186F26A769D60084E870 /* DetailBadge.swift in Sources */, 3748186F26A769D60084E870 /* DetailBadge.swift in Sources */,
@ -2273,6 +2288,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37579D5F27864F5F00FD0B98 /* Help.swift in Sources */,
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */, 37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
373C8FE6275B955100CB5936 /* CommentsPage.swift in Sources */, 373C8FE6275B955100CB5936 /* CommentsPage.swift in Sources */,
37DD9DBC2785D60300539416 /* FramePreferenceKey.swift in Sources */, 37DD9DBC2785D60300539416 /* FramePreferenceKey.swift in Sources */,
@ -2365,7 +2381,7 @@
37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */, 37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */, 37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */,
37FB2860272225E800A57617 /* ContentItemView.swift in Sources */, 37FB2860272225E800A57617 /* ContentItemView.swift in Sources */,
374C053727242D9F009BDDBE /* ServicesSettings.swift in Sources */, 374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */,
37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */, 37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */,
3711404126B206A6005B3555 /* SearchModel.swift in Sources */, 3711404126B206A6005B3555 /* SearchModel.swift in Sources */,
37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */, 37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */,
@ -2398,7 +2414,7 @@
37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */, 37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
37FB28432721B22200A57617 /* ContentItem.swift in Sources */, 37FB28432721B22200A57617 /* ContentItem.swift in Sources */,
37AAF2A226741C97007FC770 /* SubscriptionsView.swift in Sources */, 37AAF2A226741C97007FC770 /* SubscriptionsView.swift in Sources */,
37484C1B26FC837400287258 /* PlaybackSettings.swift in Sources */, 37484C1B26FC837400287258 /* PlayerSettings.swift in Sources */,
372915E82687E3B900F5A35B /* Defaults.swift in Sources */, 372915E82687E3B900F5A35B /* Defaults.swift in Sources */,
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */, 37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */,
3797758D2689345500DD52A8 /* Store.swift in Sources */, 3797758D2689345500DD52A8 /* Store.swift in Sources */,

View File

@ -22,6 +22,9 @@ struct UpdatesSettings: View {
Spacer() Spacer()
Text("Yattee \(YatteeApp.version) (build \(YatteeApp.build))")
.foregroundColor(.secondary)
CheckForUpdatesView() CheckForUpdatesView()
} }
} }

View File

@ -11,7 +11,7 @@ struct AccountSelectionView: View {
@Default(.instances) private var instances @Default(.instances) private var instances
var body: some View { var body: some View {
Section(header: SettingsHeader(text: showHeader ? "Current Account" : "")) { Section(header: Text(showHeader ? "Current Account" : "")) {
Button(accountButtonTitle(account: accountsModel.current, long: true)) { Button(accountButtonTitle(account: accountsModel.current, long: true)) {
if let account = nextAccount { if let account = nextAccount {
accountsModel.setCurrent(account) accountsModel.setCurrent(account)

View File

@ -71,7 +71,7 @@ struct EditFavorites: View {
} }
.frame(width: 1000, alignment: .leading) .frame(width: 1000, alignment: .leading)
} }
.navigationTitle("Edit Favorites") .navigationTitle("Favorites")
} }
func label(_ item: FavoriteItem) -> String { func label(_ item: FavoriteItem) -> String {