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:
@@ -18,16 +18,10 @@ extension Defaults.Keys {
|
||||
static let defaultForPlayerDetailsPageButtonLabelStyle = UIDevice.current.userInterfaceIdiom == .phone ? PlayerDetailsPageButtonLabelStyle.iconOnly : .iconAndText
|
||||
#endif
|
||||
|
||||
static let kavinPipedInstanceID = "kavin-piped"
|
||||
static let instances = Key<[Instance]>("instances", default: [
|
||||
.init(
|
||||
app: .piped,
|
||||
id: kavinPipedInstanceID,
|
||||
name: "Kavin",
|
||||
apiURL: "https://pipedapi.kavin.rocks",
|
||||
frontendURL: "https://piped.kavin.rocks"
|
||||
)
|
||||
])
|
||||
static let instancesManifest = Key<String>("instancesManifest", default: "")
|
||||
static let countryOfPublicInstances = Key<String?>("countryOfPublicInstances")
|
||||
|
||||
static let instances = Key<[Instance]>("instances", default: [])
|
||||
static let accounts = Key<[Account]>("accounts", default: [])
|
||||
static let lastAccountID = Key<Account.ID?>("lastAccountID")
|
||||
static let lastInstanceID = Key<Instance.ID?>("lastInstanceID")
|
||||
@@ -59,7 +53,7 @@ extension Defaults.Keys {
|
||||
static let playerInstanceID = Key<Instance.ID?>("playerInstance")
|
||||
static let showKeywords = Key<Bool>("showKeywords", default: false)
|
||||
static let showHistoryInPlayer = Key<Bool>("showHistoryInPlayer", default: false)
|
||||
static let commentsInstanceID = Key<Instance.ID?>("commentsInstance", default: kavinPipedInstanceID)
|
||||
static let commentsInstanceID = Key<Instance.ID?>("commentsInstance", default: nil)
|
||||
#if !os(tvOS)
|
||||
static let commentsPlacement = Key<CommentsPlacement>("commentsPlacement", default: .separate)
|
||||
#endif
|
||||
|
@@ -8,45 +8,50 @@ struct AccountsMenuView: View {
|
||||
@Default(.instances) private var instances
|
||||
@Default(.accountPickerDisplaysUsername) private var accountPickerDisplaysUsername
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
ForEach(allAccounts, id: \.id) { account in
|
||||
Button {
|
||||
model.setCurrent(account)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(accountButtonTitle(account: account))
|
||||
@ViewBuilder var body: some View {
|
||||
if !instances.isEmpty {
|
||||
Menu {
|
||||
ForEach(allAccounts, id: \.id) { account in
|
||||
Button {
|
||||
model.setCurrent(account)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(accountButtonTitle(account: account))
|
||||
|
||||
Spacer()
|
||||
Spacer()
|
||||
|
||||
if model.current == account {
|
||||
Image(systemName: "checkmark")
|
||||
if model.current == account {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "person.crop.circle")
|
||||
if accountPickerDisplaysUsername {
|
||||
label
|
||||
.labelStyle(.titleOnly)
|
||||
} label: {
|
||||
HStack {
|
||||
if !accountPickerDisplaysUsername || !(model.current?.isPublic ?? true) {
|
||||
Image(systemName: "globe")
|
||||
}
|
||||
|
||||
if accountPickerDisplaysUsername {
|
||||
label
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(allAccounts.isEmpty)
|
||||
.transaction { t in t.animation = .none }
|
||||
}
|
||||
.disabled(instances.isEmpty)
|
||||
.transaction { t in t.animation = .none }
|
||||
}
|
||||
|
||||
private var label: some View {
|
||||
Label(model.current?.description ?? "Select Account", systemImage: "person.crop.circle")
|
||||
Label(model.current?.description ?? "Select Account", systemImage: "globe")
|
||||
}
|
||||
|
||||
private var allAccounts: [Account] {
|
||||
accounts + instances.map(\.anonymousAccount)
|
||||
accounts + instances.map(\.anonymousAccount) + [model.publicAccount].compactMap { $0 }
|
||||
}
|
||||
|
||||
private func accountButtonTitle(account: Account) -> String {
|
||||
instances.count > 1 ? "\(account.description) — \(account.instance.description)" : account.description
|
||||
account.isPublic ? account.description : "\(account.description) — \(account.instance.shortDescription)"
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ struct ContentView: View {
|
||||
@EnvironmentObject<PlaylistsModel> private var playlists
|
||||
@EnvironmentObject<RecentsModel> private var recents
|
||||
@EnvironmentObject<SearchModel> private var search
|
||||
@EnvironmentObject<SettingsModel> private var settings
|
||||
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
||||
@EnvironmentObject<ThumbnailsModel> private var thumbnailsModel
|
||||
|
||||
@@ -42,6 +43,7 @@ struct ContentView: View {
|
||||
AppSidebarNavigation()
|
||||
#elseif os(tvOS)
|
||||
TVNavigationView()
|
||||
.environmentObject(settings)
|
||||
#endif
|
||||
}
|
||||
.onChange(of: accounts.signedIn) { _ in
|
||||
@@ -105,10 +107,12 @@ struct ContentView: View {
|
||||
}
|
||||
)
|
||||
.background(
|
||||
EmptyView().sheet(isPresented: $navigation.presentingSettings, onDismiss: openWelcomeScreenIfAccountEmpty) {
|
||||
EmptyView().sheet(isPresented: $navigation.presentingSettings) {
|
||||
SettingsView()
|
||||
.environmentObject(accounts)
|
||||
.environmentObject(instances)
|
||||
.environmentObject(settings)
|
||||
.environmentObject(navigation)
|
||||
.environmentObject(player)
|
||||
}
|
||||
)
|
||||
@@ -126,14 +130,6 @@ struct ContentView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
func openWelcomeScreenIfAccountEmpty() {
|
||||
guard Defaults[.instances].isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
navigation.presentingWelcomeScreen = true
|
||||
}
|
||||
|
||||
var videoPlayer: some View {
|
||||
VideoPlayerView()
|
||||
.environmentObject(accounts)
|
||||
|
@@ -66,9 +66,7 @@ final class AppleAVPlayerViewController: UIViewController {
|
||||
|
||||
#if os(tvOS)
|
||||
var infoViewControllers = [UIHostingController<AnyView>]()
|
||||
if CommentsModel.enabled {
|
||||
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
||||
}
|
||||
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
||||
|
||||
var queueSections = [NowPlayingView.ViewSection.playingNext]
|
||||
if Defaults[.showHistoryInPlayer] {
|
||||
|
@@ -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 {
|
||||
|
@@ -1,65 +1,93 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct WelcomeScreen: View {
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
@Default(.accounts) private var allAccounts
|
||||
@State private var store = [ManifestedInstance]()
|
||||
|
||||
var body: some View {
|
||||
let welcomeScreen = VStack {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
|
||||
Text("Welcome")
|
||||
Text("Welcome to Yattee")
|
||||
.frame(maxWidth: .infinity)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
if allAccounts.isEmpty {
|
||||
Text("To start, configure your Instances in Settings")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Text("To start, pick one of your accounts:")
|
||||
.foregroundColor(.secondary)
|
||||
#if os(tvOS)
|
||||
AccountSelectionView(showHeader: false)
|
||||
Text("Select location closest to you to get the best performance")
|
||||
.font(.subheadline)
|
||||
|
||||
ScrollView {
|
||||
ForEach(store.map(\.country).sorted(), id: \.self) { country in
|
||||
Button {
|
||||
Defaults[.countryOfPublicInstances] = country
|
||||
InstancesManifest.shared.setPublicAccount(country, accounts: accounts)
|
||||
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Text("Start")
|
||||
}
|
||||
.opacity(accounts.current.isNil ? 0 : 1)
|
||||
.disabled(accounts.current.isNil)
|
||||
|
||||
#else
|
||||
AccountsMenuView()
|
||||
.onChange(of: accounts.current) { _ in
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
HStack(spacing: 10) {
|
||||
if let flag = flag(country) {
|
||||
Text(flag)
|
||||
}
|
||||
Text(country)
|
||||
#if !os(tvOS)
|
||||
.foregroundColor(.white)
|
||||
#endif
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 280)
|
||||
#if os(tvOS)
|
||||
.padding(8)
|
||||
#else
|
||||
.padding(4)
|
||||
.background(RoundedRectangle(cornerRadius: 4).foregroundColor(Color.accentColor))
|
||||
.padding(.bottom, 2)
|
||||
#endif
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
#if os(tvOS)
|
||||
.padding(.horizontal, 10)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Text("This information will not be collected and it will be saved only on your device. You can change it later in settings.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
OpenSettingsButton()
|
||||
#if !os(tvOS)
|
||||
Spacer()
|
||||
|
||||
Spacer()
|
||||
OpenSettingsButton()
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Spacer()
|
||||
#endif
|
||||
}
|
||||
.onAppear {
|
||||
resource.load().onSuccess { response in
|
||||
if let instances: [ManifestedInstance] = response.typedContent() {
|
||||
store = instances
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
#elseif os(tvOS)
|
||||
.frame(maxWidth: 1000)
|
||||
#endif
|
||||
}
|
||||
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
welcomeScreen
|
||||
.interactiveDismissDisabled()
|
||||
} else {
|
||||
welcomeScreen
|
||||
}
|
||||
func flag(_ country: String) -> String? {
|
||||
store.first { $0.country == country }?.flag
|
||||
}
|
||||
|
||||
var resource: Resource {
|
||||
InstancesManifest.shared.instancesList
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -40,6 +40,7 @@ struct YatteeApp: App {
|
||||
@StateObject private var playlists = PlaylistsModel()
|
||||
@StateObject private var recents = RecentsModel()
|
||||
@StateObject private var search = SearchModel()
|
||||
@StateObject private var settings = SettingsModel()
|
||||
@StateObject private var subscriptions = SubscriptionsModel()
|
||||
@StateObject private var thumbnails = ThumbnailsModel()
|
||||
|
||||
@@ -60,6 +61,7 @@ struct YatteeApp: App {
|
||||
.environmentObject(playerTime)
|
||||
.environmentObject(playlists)
|
||||
.environmentObject(recents)
|
||||
.environmentObject(settings)
|
||||
.environmentObject(subscriptions)
|
||||
.environmentObject(thumbnails)
|
||||
.environmentObject(menu)
|
||||
@@ -144,8 +146,10 @@ struct YatteeApp: App {
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(accounts)
|
||||
.environmentObject(instances)
|
||||
.environmentObject(navigation)
|
||||
.environmentObject(player)
|
||||
.environmentObject(playerControls)
|
||||
.environmentObject(settings)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -170,17 +174,23 @@ struct YatteeApp: App {
|
||||
}
|
||||
#endif
|
||||
|
||||
if let account = accounts.lastUsed ??
|
||||
instances.lastUsed?.anonymousAccount ??
|
||||
InstancesModel.all.first?.anonymousAccount
|
||||
if Defaults[.lastAccountID] != "public",
|
||||
let account = accounts.lastUsed ??
|
||||
instances.lastUsed?.anonymousAccount ??
|
||||
InstancesModel.all.first?.anonymousAccount
|
||||
{
|
||||
accounts.setCurrent(account)
|
||||
}
|
||||
|
||||
if accounts.current.isNil {
|
||||
let countryOfPublicInstances = Defaults[.countryOfPublicInstances]
|
||||
if accounts.current.isNil, countryOfPublicInstances.isNil {
|
||||
navigation.presentingWelcomeScreen = true
|
||||
}
|
||||
|
||||
if !countryOfPublicInstances.isNil {
|
||||
InstancesManifest.shared.setPublicAccount(countryOfPublicInstances!, accounts: accounts, asCurrent: accounts.current.isNil)
|
||||
}
|
||||
|
||||
playlists.accounts = accounts
|
||||
search.accounts = accounts
|
||||
subscriptions.accounts = accounts
|
||||
|
Reference in New Issue
Block a user