Implements server selection UI

This commit is contained in:
2025-06-11 13:00:09 -07:00
parent 51048678bb
commit ca829dde4c
4 changed files with 115 additions and 21 deletions

View File

@@ -17,6 +17,10 @@ struct MediaItem: Codable
let playing: Bool?
let metadata: Metadata?
var displayTitle: String {
metadata?.title ?? title ?? filename ?? "item \(id)"
}
// MARK: - Types
struct Metadata: Codable

View File

@@ -78,6 +78,7 @@ struct ContentView: View
MainView(model: model)
.task { await watchWebsocket() }
.task { await refresh([.nowPlaying, .playlist, .favorites]) }
.task { await watchForSettingsChanges() }
}
// MARK: - Types
@@ -118,7 +119,7 @@ extension ContentView
model.playlistModel.items = playlist.enumerated().map { (idx, mediaItem) in
MediaListItem(
id: String(mediaItem.id),
title: mediaItem.title ?? mediaItem.filename ?? "<null>",
title: mediaItem.displayTitle,
filename: mediaItem.filename ?? "<null>",
index: idx,
isCurrent: mediaItem.current ?? false
@@ -131,7 +132,7 @@ extension ContentView
model.favoritesModel.items = favorites.map { mediaItem in
MediaListItem(
id: String(mediaItem.id),
title: mediaItem.title ?? mediaItem.filename ?? "<null>",
title: mediaItem.displayTitle,
filename: mediaItem.filename ?? "<null>"
)
}
@@ -186,4 +187,26 @@ extension ContentView
}
}
}
private func watchForSettingsChanges() async {
let settingsChangedNotifications = NotificationCenter.default.notifications(named: .settingsChanged)
.map({ _ in Optional.none })
for await _ in settingsChangedNotifications {
let newSelectedServer = Settings.fromDefaults().selectedServer
if newSelectedServer != model.selectedServer {
model.selectedServer = newSelectedServer
// Reset view model to defaults
model.playlistModel = MediaListViewModel(mode: .playlist)
model.favoritesModel = MediaListViewModel(mode: .favorites)
model.nowPlayingViewModel = NowPlayingViewModel()
await refresh([.playlist, .nowPlaying, .favorites])
}
// Always reset this
model.serverSelectionViewModel = ServerSelectionToolbarModifier.ViewModel()
}
}
}

View File

@@ -10,15 +10,16 @@ import SwiftUI
@Observable
class MainViewModel
{
var selectedServer: Server? { Settings.fromDefaults().selectedServer }
var selectedServer: Server? = Settings.fromDefaults().selectedServer
var connectionError: Error? = nil
var selectedTab: Tab = .playlist
var playlistModel = MediaListViewModel(mode: .playlist)
var favoritesModel = MediaListViewModel(mode: .favorites)
var nowPlayingViewModel = NowPlayingViewModel()
var addMediaViewModel = AddMediaBarViewModel()
var playlistModel = MediaListViewModel(mode: .playlist)
var favoritesModel = MediaListViewModel(mode: .favorites)
var nowPlayingViewModel = NowPlayingViewModel()
var addMediaViewModel = AddMediaBarViewModel()
var serverSelectionViewModel = ServerSelectionToolbarModifier.ViewModel()
enum Tab: String, CaseIterable
{
@@ -40,16 +41,6 @@ struct MainView: View
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 {
@@ -57,11 +48,19 @@ struct MainView: View
TabView(selection: $model.selectedTab) {
Tab(.playlist, systemImage: "list.bullet", value: .playlist) {
MediaListView(model: model.playlistModel)
NavigationStack {
MediaListView(model: model.playlistModel)
.displayingServerSelectionToolbar(model: $model.serverSelectionViewModel)
.navigationTitle(.playlist)
}
}
Tab(.favorites, systemImage: "heart.fill", value: .favorites) {
MediaListView(model: model.favoritesModel)
NavigationStack {
MediaListView(model: model.favoritesModel)
.displayingServerSelectionToolbar(model: $model.serverSelectionViewModel)
.navigationTitle(.favorites)
}
}
Tab(.settings, systemImage: "gear", value: .settings) {
@@ -107,3 +106,67 @@ struct MainView: View
}
}
struct ServerSelectionToolbarModifier: ViewModifier
{
@Binding var model: ViewModel
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Menu {
Section {
ForEach(model.selectableServers) { server in
Button {
model.selectedServer = server
} label: {
Text(server.displayName)
if model.selectedServer == server {
Image(systemName: "checkmark")
}
}
}
}
#if false
// TODO
Section {
Button(.addServer) {
}
}
#endif
} label: {
Label(model.selectedServer?.displayName ?? "Servers", systemImage: "chevron.down")
.labelStyle(.titleAndIcon)
}
.buttonBorderShape(.capsule)
.buttonStyle(.bordered)
.menuStyle(.button)
}
}
}
// MARK: - Types
@Observable
class ViewModel
{
var selectableServers: [Server] = Settings.fromDefaults().configuredServers
var selectedServer: Server? = Settings.fromDefaults().selectedServer {
didSet {
Settings
.fromDefaults()
.selectedServer(selectedServer)
.save()
}
}
}
}
extension View {
func displayingServerSelectionToolbar(model: Binding<ServerSelectionToolbarModifier.ViewModel>) -> some View {
modifier(ServerSelectionToolbarModifier(model: model))
}
}

View File

@@ -9,14 +9,18 @@ import SwiftUI
struct MediaListItem: Identifiable
{
let id: String
let _id: String
let title: String
let filename: String
let index: Int?
let isCurrent: Bool
var id: String {
_id + filename // temporary: we get duplicate ids from the server sometimes...
}
init(id: String, title: String, filename: String, index: Int? = nil, isCurrent: Bool = false) {
self.id = id
self._id = id
self.title = title
self.filename = filename
self.index = index