Started working on multiple server configuration
This commit is contained in:
@@ -38,15 +38,6 @@ struct API
|
||||
{
|
||||
let baseURL: URL
|
||||
|
||||
static func fromSettings() -> Self? {
|
||||
let settings = Settings.fromDefaults()
|
||||
|
||||
guard let baseURL = settings.serverURL.flatMap({ URL(string: $0) })
|
||||
else { return nil }
|
||||
|
||||
return API(baseURL: baseURL)
|
||||
}
|
||||
|
||||
init(baseURL: URL) {
|
||||
self.baseURL = baseURL
|
||||
}
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Server: Identifiable
|
||||
struct Server: Identifiable, Codable
|
||||
{
|
||||
let serviceName: String?
|
||||
let baseURL: URL
|
||||
|
||||
var id: String { baseURL.absoluteString }
|
||||
|
||||
var api: API { API(baseURL: baseURL) }
|
||||
|
||||
var displayName: String {
|
||||
if let serviceName {
|
||||
return serviceName.queueCubeServiceName
|
||||
|
||||
@@ -9,15 +9,29 @@ import Foundation
|
||||
|
||||
struct Settings
|
||||
{
|
||||
var serverURL: String?
|
||||
var configuredServers: [Server]
|
||||
|
||||
var isConfigured: Bool {
|
||||
!configuredServers.isEmpty
|
||||
}
|
||||
|
||||
static func fromDefaults() -> Settings {
|
||||
let serverURL = UserDefaults.standard.string(forKey: Keys.serverURL.rawValue)
|
||||
return Settings(serverURL: serverURL)
|
||||
let configuredServers: [Server] = {
|
||||
guard let configuredServersData = UserDefaults.standard.data(forKey: Keys.configuredServers.rawValue)
|
||||
else { return [] }
|
||||
|
||||
guard let configuredServers = try? PropertyListDecoder().decode([Server].self, from: configuredServersData)
|
||||
else { return [] }
|
||||
|
||||
return configuredServers
|
||||
}()
|
||||
|
||||
return Settings(configuredServers: configuredServers)
|
||||
}
|
||||
|
||||
func save() {
|
||||
UserDefaults.standard.set(serverURL, forKey: Keys.serverURL.rawValue)
|
||||
let configuredServersData = try! PropertyListEncoder().encode(configuredServers)
|
||||
UserDefaults.standard.set(configuredServersData, forKey: Keys.configuredServers.rawValue)
|
||||
NotificationCenter.default.post(name: .settingsChanged, object: nil)
|
||||
}
|
||||
|
||||
@@ -25,7 +39,7 @@ struct Settings
|
||||
|
||||
enum Keys: String
|
||||
{
|
||||
case serverURL
|
||||
case configuredServers
|
||||
}
|
||||
|
||||
struct Server: Codable
|
||||
|
||||
@@ -125,6 +125,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"NO_SERVERS_CONFIGURED" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No Servers Configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"NOT_CONFIGURED" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
||||
@@ -9,25 +9,26 @@ import SwiftUI
|
||||
|
||||
extension LocalizedStringKey
|
||||
{
|
||||
static let serverURL = LocalizedStringKey("SERVER_URL")
|
||||
static let settings = LocalizedStringKey("SETTINGS")
|
||||
static let settings_ = LocalizedStringKey("SETTINGS_ELLIPSES")
|
||||
static let done = LocalizedStringKey("DONE")
|
||||
static let notConfigured = LocalizedStringKey("NOT_CONFIGURED")
|
||||
static let add = LocalizedStringKey("ADD")
|
||||
static let addAnyURL = LocalizedStringKey("ADD_ANY_URL")
|
||||
static let serverIsOnline = LocalizedStringKey("SERVER_IS_ONLINE")
|
||||
static let unableToConnect = LocalizedStringKey("UNABLE_TO_CONNECT")
|
||||
static let configuration = LocalizedStringKey("CONFIGURATION")
|
||||
static let validating = LocalizedStringKey("VALIDATING")
|
||||
static let general = LocalizedStringKey("GENERAL")
|
||||
static let connectionError = LocalizedStringKey("CONNECTION_ERROR")
|
||||
static let playlist = LocalizedStringKey("PLAYLIST")
|
||||
static let favorites = LocalizedStringKey("FAVORITES")
|
||||
static let servers = LocalizedStringKey("SERVERS")
|
||||
static let addServer = LocalizedStringKey("ADD_SERVER")
|
||||
static let cancel = LocalizedStringKey("CANCEL")
|
||||
static let manual = LocalizedStringKey("ENTER_MANUALLY")
|
||||
static let discovered = LocalizedStringKey("DISCOVERED")
|
||||
static let findingServers = LocalizedStringKey("FINDING_SERVERS")
|
||||
static let serverURL = LocalizedStringKey("SERVER_URL")
|
||||
static let settings = LocalizedStringKey("SETTINGS")
|
||||
static let settings_ = LocalizedStringKey("SETTINGS_ELLIPSES")
|
||||
static let done = LocalizedStringKey("DONE")
|
||||
static let notConfigured = LocalizedStringKey("NOT_CONFIGURED")
|
||||
static let add = LocalizedStringKey("ADD")
|
||||
static let addAnyURL = LocalizedStringKey("ADD_ANY_URL")
|
||||
static let serverIsOnline = LocalizedStringKey("SERVER_IS_ONLINE")
|
||||
static let unableToConnect = LocalizedStringKey("UNABLE_TO_CONNECT")
|
||||
static let configuration = LocalizedStringKey("CONFIGURATION")
|
||||
static let validating = LocalizedStringKey("VALIDATING")
|
||||
static let general = LocalizedStringKey("GENERAL")
|
||||
static let connectionError = LocalizedStringKey("CONNECTION_ERROR")
|
||||
static let playlist = LocalizedStringKey("PLAYLIST")
|
||||
static let favorites = LocalizedStringKey("FAVORITES")
|
||||
static let servers = LocalizedStringKey("SERVERS")
|
||||
static let addServer = LocalizedStringKey("ADD_SERVER")
|
||||
static let cancel = LocalizedStringKey("CANCEL")
|
||||
static let manual = LocalizedStringKey("ENTER_MANUALLY")
|
||||
static let discovered = LocalizedStringKey("DISCOVERED")
|
||||
static let findingServers = LocalizedStringKey("FINDING_SERVERS")
|
||||
static let noServersConfigured = LocalizedStringKey("NO_SERVERS_CONFIGURED")
|
||||
}
|
||||
|
||||
57
QueueCube/Views/ContentPlaceholderView.swift
Normal file
57
QueueCube/Views/ContentPlaceholderView.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// ContentPlaceholderView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 6/10/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentPlaceholderView<Label, Actions>: View
|
||||
where Label: View, Actions: View
|
||||
{
|
||||
let label: Label
|
||||
let actions: Actions
|
||||
|
||||
init(@ViewBuilder label: () -> Label, @ViewBuilder actions: () -> Actions = { EmptyView() }) {
|
||||
self.label = label()
|
||||
self.actions = actions()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Spacer()
|
||||
|
||||
ContentUnavailableView {
|
||||
label
|
||||
.imageScale(.large)
|
||||
.tint(.secondary)
|
||||
} actions: { actions }
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
func contentPlaceholderView<Actions>(
|
||||
title: LocalizedStringKey,
|
||||
systemImage: String, @ViewBuilder actions: () -> Actions = { EmptyView() })
|
||||
-> ContentPlaceholderView<AnyView, Actions>
|
||||
{
|
||||
ContentPlaceholderView(label: {
|
||||
AnyView(erasing: VStack(spacing: 16.0) {
|
||||
Image(systemName: systemImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
|
||||
.frame(width: 50.0, height: 50.0)
|
||||
.foregroundStyle(.secondary)
|
||||
.imageScale(.large)
|
||||
|
||||
|
||||
Text(title)
|
||||
.bold()
|
||||
|
||||
Spacer()
|
||||
.frame(height: 14.0)
|
||||
})
|
||||
}, actions: actions)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ struct ContentView: View
|
||||
init() {
|
||||
self.model = MainViewModel()
|
||||
|
||||
if let api = model.api {
|
||||
if let api = model.selectedServer?.api {
|
||||
let nowPlayingModel = self.model.nowPlayingViewModel
|
||||
nowPlayingModel.onPlayPause = { model in
|
||||
Task { model.isPlaying ? try await api.pause() : try await api.play() }
|
||||
@@ -83,7 +83,7 @@ struct ContentView: View
|
||||
extension ContentView
|
||||
{
|
||||
private func refresh(_ what: RefreshType) async {
|
||||
guard let api = model.api else { return }
|
||||
guard let api = model.selectedServer?.api else { return }
|
||||
|
||||
do {
|
||||
if what.contains(.nowPlaying) {
|
||||
@@ -133,7 +133,7 @@ extension ContentView
|
||||
}
|
||||
|
||||
private func watchWebsocket() async {
|
||||
guard let api = model.api else { return }
|
||||
guard let api = model.selectedServer?.api else { return }
|
||||
|
||||
do {
|
||||
for await streamEvent in try await api.events() {
|
||||
@@ -175,119 +175,3 @@ extension ContentView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Observable
|
||||
class MainViewModel
|
||||
{
|
||||
var api = API.fromSettings()
|
||||
|
||||
var connectionError: Error? = nil
|
||||
var selectedTab: MainTab = .playlist
|
||||
|
||||
var playlistModel = PlaylistViewModel()
|
||||
var favoritesModel = FavoritesViewModel()
|
||||
var nowPlayingViewModel = NowPlayingViewModel()
|
||||
var addMediaViewModel = AddMediaBarViewModel()
|
||||
}
|
||||
|
||||
enum MainTab: String, CaseIterable {
|
||||
case playlist
|
||||
case favorites
|
||||
case settings
|
||||
}
|
||||
|
||||
struct MainView: View
|
||||
{
|
||||
@State var model: MainViewModel
|
||||
@State var isSettingsVisible: Bool = false
|
||||
|
||||
init(model: MainViewModel) {
|
||||
self.model = model
|
||||
|
||||
Task {
|
||||
let settingsChangedNotifications = NotificationCenter.default.notifications(named: .settingsChanged)
|
||||
.map({ _ in Optional.none })
|
||||
|
||||
for await _ in settingsChangedNotifications {
|
||||
model.api = API.fromSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let showConfigurationDialog = model.api == nil
|
||||
|
||||
TabView(selection: $model.selectedTab) {
|
||||
Tab(.playlist, systemImage: "list.bullet", value: .playlist) {
|
||||
PlaylistView(model: model.playlistModel)
|
||||
}
|
||||
|
||||
Tab(.favorites, systemImage: "heart.fill", value: .favorites) {
|
||||
FavoritesView(model: model.favoritesModel)
|
||||
}
|
||||
|
||||
Tab(.settings, systemImage: "gear", value: .settings) {
|
||||
SettingsView(onDone: {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if false
|
||||
VStack {
|
||||
if showConfigurationDialog {
|
||||
ContentPlaceholderView {
|
||||
Image(systemName: "server.rack")
|
||||
Text(.notConfigured)
|
||||
} actions: {
|
||||
Button {
|
||||
isSettingsVisible = true
|
||||
} label: {
|
||||
Text(.settings)
|
||||
}
|
||||
}
|
||||
} else if model.connectionError != nil {
|
||||
ContentPlaceholderView {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
Text(.connectionError)
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $model.selectedTab) {
|
||||
}
|
||||
.frame(maxWidth: 640.0)
|
||||
}
|
||||
|
||||
AddMediaBarView(model: model.addMediaViewModel)
|
||||
.layoutPriority(2.0)
|
||||
.disabled(showConfigurationDialog)
|
||||
}
|
||||
.sheet(isPresented: $isSettingsVisible) {
|
||||
SettingsView(onDone: { isSettingsVisible = false })
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentPlaceholderView<Label, Actions>: View
|
||||
where Label: View, Actions: View
|
||||
{
|
||||
let label: Label
|
||||
let actions: Actions
|
||||
|
||||
init(@ViewBuilder label: () -> Label, @ViewBuilder actions: () -> Actions = { EmptyView() }) {
|
||||
self.label = label()
|
||||
self.actions = actions()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Spacer()
|
||||
|
||||
ContentUnavailableView {
|
||||
label
|
||||
.imageScale(.large)
|
||||
} actions: { actions }
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
109
QueueCube/Views/MainView.swift
Normal file
109
QueueCube/Views/MainView.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// MainView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 6/10/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class MainViewModel
|
||||
{
|
||||
var selectedServer: Server? = nil
|
||||
|
||||
var connectionError: Error? = nil
|
||||
var selectedTab: Tab = .playlist
|
||||
|
||||
var playlistModel = PlaylistViewModel()
|
||||
var favoritesModel = FavoritesViewModel()
|
||||
var nowPlayingViewModel = NowPlayingViewModel()
|
||||
var addMediaViewModel = AddMediaBarViewModel()
|
||||
|
||||
enum Tab: String, CaseIterable
|
||||
{
|
||||
case playlist
|
||||
case favorites
|
||||
case settings
|
||||
}
|
||||
}
|
||||
|
||||
struct MainView: View
|
||||
{
|
||||
@State var model: MainViewModel
|
||||
@State var isSettingsVisible: Bool = false
|
||||
|
||||
init(model: MainViewModel) {
|
||||
self.model = model
|
||||
|
||||
// If no servers are configured, make Settings the default tab.
|
||||
if !Settings.fromDefaults().isConfigured {
|
||||
model.selectedTab = .settings
|
||||
}
|
||||
|
||||
Task {
|
||||
let settingsChangedNotifications = NotificationCenter.default.notifications(named: .settingsChanged)
|
||||
.map({ _ in Optional.none })
|
||||
|
||||
for await _ in settingsChangedNotifications {
|
||||
// TODO
|
||||
// model.api = API.fromSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let showConfigurationDialog = model.selectedServer == nil
|
||||
|
||||
TabView(selection: $model.selectedTab) {
|
||||
Tab(.playlist, systemImage: "list.bullet", value: .playlist) {
|
||||
PlaylistView(model: model.playlistModel)
|
||||
}
|
||||
|
||||
Tab(.favorites, systemImage: "heart.fill", value: .favorites) {
|
||||
FavoritesView(model: model.favoritesModel)
|
||||
}
|
||||
|
||||
Tab(.settings, systemImage: "gear", value: .settings) {
|
||||
SettingsView(onDone: {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if false
|
||||
VStack {
|
||||
if showConfigurationDialog {
|
||||
ContentPlaceholderView {
|
||||
Image(systemName: "server.rack")
|
||||
Text(.notConfigured)
|
||||
} actions: {
|
||||
Button {
|
||||
isSettingsVisible = true
|
||||
} label: {
|
||||
Text(.settings)
|
||||
}
|
||||
}
|
||||
} else if model.connectionError != nil {
|
||||
ContentPlaceholderView {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
Text(.connectionError)
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $model.selectedTab) {
|
||||
}
|
||||
.frame(maxWidth: 640.0)
|
||||
}
|
||||
|
||||
AddMediaBarView(model: model.addMediaViewModel)
|
||||
.layoutPriority(2.0)
|
||||
.disabled(showConfigurationDialog)
|
||||
}
|
||||
.sheet(isPresented: $isSettingsVisible) {
|
||||
SettingsView(onDone: { isSettingsVisible = false })
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +1,13 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// AddServerView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 5/2/25.
|
||||
// Created by James Magahern on 6/10/25.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View
|
||||
{
|
||||
let onDone: () -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
NavigationLink(destination: GeneralSettingsView()) {
|
||||
Image(systemName: "gear")
|
||||
Text(.general)
|
||||
}
|
||||
|
||||
NavigationLink(destination: ServerListSettingsView()) {
|
||||
Image(systemName: "server.rack")
|
||||
Text(.servers)
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(.settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GeneralSettingsView: View
|
||||
{
|
||||
var body: some View {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerListSettingsView: View
|
||||
{
|
||||
@State var model = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
List(model.configuredServers) { server in
|
||||
serverListItem(server)
|
||||
}
|
||||
}
|
||||
|
||||
.navigationTitle(.servers)
|
||||
|
||||
.toolbar {
|
||||
Button {
|
||||
model.isAddServerPresented = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sheet(isPresented: $model.isAddServerPresented) {
|
||||
NavigationView {
|
||||
AddServerView(onAddServer: { model.onAddServer(server: $0) })
|
||||
.navigationTitle(.addServer)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .cancellationAction) {
|
||||
Button(.cancel) { model.isAddServerPresented = false }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func serverListItem(_ server: Server) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "hifispeaker.fill")
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(server.displayName)
|
||||
.lineLimit(1)
|
||||
.bold()
|
||||
|
||||
Text(server.baseURL.absoluteString)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
@Observable
|
||||
class ViewModel
|
||||
{
|
||||
var configuredServers: [Server]
|
||||
var isAddServerPresented = false
|
||||
|
||||
init() {
|
||||
self.configuredServers = []
|
||||
}
|
||||
|
||||
func onAddServer(server: Server) {
|
||||
isAddServerPresented = false
|
||||
configuredServers.append(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddServerView: View
|
||||
{
|
||||
let onAddServer: (Server) -> Void
|
||||
@@ -240,8 +134,6 @@ struct AddServerView: View
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else { return }
|
||||
setNeedsValidation()
|
||||
saveSettings()
|
||||
|
||||
observeForValidation()
|
||||
}
|
||||
}
|
||||
@@ -275,12 +167,7 @@ struct AddServerView: View
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveSettings() {
|
||||
Settings(serverURL: self.serverURL)
|
||||
.save()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
enum ValidationState
|
||||
16
QueueCube/Views/Settings View/GeneralSettingsView.swift
Normal file
16
QueueCube/Views/Settings View/GeneralSettingsView.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// GeneralSettingsView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 6/10/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct GeneralSettingsView: View
|
||||
{
|
||||
var body: some View {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
94
QueueCube/Views/Settings View/ServerListSettingsView.swift
Normal file
94
QueueCube/Views/Settings View/ServerListSettingsView.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// ServerListSettingsView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 6/10/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ServerListSettingsView: View
|
||||
{
|
||||
@State var model = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if model.configuredServers.isEmpty {
|
||||
contentPlaceholderView(title: .noServersConfigured, systemImage: "server.rack") {
|
||||
Button {
|
||||
model.isAddServerPresented = true
|
||||
} label: {
|
||||
Text(.addServer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Form {
|
||||
List(model.configuredServers) { server in
|
||||
serverListItem(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigationTitle(.servers)
|
||||
|
||||
.toolbar {
|
||||
Button {
|
||||
model.isAddServerPresented = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
|
||||
.sheet(isPresented: $model.isAddServerPresented) {
|
||||
NavigationView {
|
||||
AddServerView(onAddServer: { model.onAddServer(server: $0) })
|
||||
.navigationTitle(.addServer)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .cancellationAction) {
|
||||
Button(.cancel) { model.isAddServerPresented = false }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func serverListItem(_ server: Server) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "hifispeaker.fill")
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(server.displayName)
|
||||
.lineLimit(1)
|
||||
.bold()
|
||||
|
||||
Text(server.baseURL.absoluteString)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
@Observable
|
||||
class ViewModel
|
||||
{
|
||||
var configuredServers: [Server]
|
||||
var isAddServerPresented = false
|
||||
|
||||
init() {
|
||||
self.configuredServers = []
|
||||
}
|
||||
|
||||
func onAddServer(server: Server) {
|
||||
isAddServerPresented = false
|
||||
configuredServers.append(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
61
QueueCube/Views/Settings View/SettingsView.swift
Normal file
61
QueueCube/Views/Settings View/SettingsView.swift
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 5/2/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View
|
||||
{
|
||||
let onDone: () -> Void
|
||||
@State private var navigationPath: [SettingsPage]
|
||||
|
||||
init(onDone: @escaping () -> Void) {
|
||||
self.onDone = onDone
|
||||
self.navigationPath = if !Settings.fromDefaults().isConfigured {
|
||||
// Show server settings if not configured.
|
||||
[ .servers ]
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $navigationPath) {
|
||||
List {
|
||||
NavigationLink(value: SettingsPage.general) {
|
||||
Image(systemName: "gear")
|
||||
Text(.general)
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsPage.servers) {
|
||||
Image(systemName: "server.rack")
|
||||
Text(.servers)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: SettingsPage.self, destination: { page in
|
||||
Group {
|
||||
switch page {
|
||||
case .general: GeneralSettingsView()
|
||||
case .servers: ServerListSettingsView()
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
})
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(.settings)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
enum SettingsPage: String, Identifiable
|
||||
{
|
||||
var id: String { rawValue }
|
||||
|
||||
case general
|
||||
case servers
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user