Implements server selection UI
This commit is contained in:
@@ -17,6 +17,10 @@ struct MediaItem: Codable
|
|||||||
let playing: Bool?
|
let playing: Bool?
|
||||||
let metadata: Metadata?
|
let metadata: Metadata?
|
||||||
|
|
||||||
|
var displayTitle: String {
|
||||||
|
metadata?.title ?? title ?? filename ?? "item \(id)"
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Types
|
// MARK: - Types
|
||||||
|
|
||||||
struct Metadata: Codable
|
struct Metadata: Codable
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ struct ContentView: View
|
|||||||
MainView(model: model)
|
MainView(model: model)
|
||||||
.task { await watchWebsocket() }
|
.task { await watchWebsocket() }
|
||||||
.task { await refresh([.nowPlaying, .playlist, .favorites]) }
|
.task { await refresh([.nowPlaying, .playlist, .favorites]) }
|
||||||
|
.task { await watchForSettingsChanges() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Types
|
// MARK: - Types
|
||||||
@@ -118,7 +119,7 @@ extension ContentView
|
|||||||
model.playlistModel.items = playlist.enumerated().map { (idx, mediaItem) in
|
model.playlistModel.items = playlist.enumerated().map { (idx, mediaItem) in
|
||||||
MediaListItem(
|
MediaListItem(
|
||||||
id: String(mediaItem.id),
|
id: String(mediaItem.id),
|
||||||
title: mediaItem.title ?? mediaItem.filename ?? "<null>",
|
title: mediaItem.displayTitle,
|
||||||
filename: mediaItem.filename ?? "<null>",
|
filename: mediaItem.filename ?? "<null>",
|
||||||
index: idx,
|
index: idx,
|
||||||
isCurrent: mediaItem.current ?? false
|
isCurrent: mediaItem.current ?? false
|
||||||
@@ -131,7 +132,7 @@ extension ContentView
|
|||||||
model.favoritesModel.items = favorites.map { mediaItem in
|
model.favoritesModel.items = favorites.map { mediaItem in
|
||||||
MediaListItem(
|
MediaListItem(
|
||||||
id: String(mediaItem.id),
|
id: String(mediaItem.id),
|
||||||
title: mediaItem.title ?? mediaItem.filename ?? "<null>",
|
title: mediaItem.displayTitle,
|
||||||
filename: mediaItem.filename ?? "<null>"
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwiftUI
|
|||||||
@Observable
|
@Observable
|
||||||
class MainViewModel
|
class MainViewModel
|
||||||
{
|
{
|
||||||
var selectedServer: Server? { Settings.fromDefaults().selectedServer }
|
var selectedServer: Server? = Settings.fromDefaults().selectedServer
|
||||||
|
|
||||||
var connectionError: Error? = nil
|
var connectionError: Error? = nil
|
||||||
var selectedTab: Tab = .playlist
|
var selectedTab: Tab = .playlist
|
||||||
@@ -19,6 +19,7 @@ class MainViewModel
|
|||||||
var favoritesModel = MediaListViewModel(mode: .favorites)
|
var favoritesModel = MediaListViewModel(mode: .favorites)
|
||||||
var nowPlayingViewModel = NowPlayingViewModel()
|
var nowPlayingViewModel = NowPlayingViewModel()
|
||||||
var addMediaViewModel = AddMediaBarViewModel()
|
var addMediaViewModel = AddMediaBarViewModel()
|
||||||
|
var serverSelectionViewModel = ServerSelectionToolbarModifier.ViewModel()
|
||||||
|
|
||||||
enum Tab: String, CaseIterable
|
enum Tab: String, CaseIterable
|
||||||
{
|
{
|
||||||
@@ -40,16 +41,6 @@ struct MainView: View
|
|||||||
if !Settings.fromDefaults().isConfigured {
|
if !Settings.fromDefaults().isConfigured {
|
||||||
model.selectedTab = .settings
|
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 {
|
var body: some View {
|
||||||
@@ -57,11 +48,19 @@ struct MainView: View
|
|||||||
|
|
||||||
TabView(selection: $model.selectedTab) {
|
TabView(selection: $model.selectedTab) {
|
||||||
Tab(.playlist, systemImage: "list.bullet", value: .playlist) {
|
Tab(.playlist, systemImage: "list.bullet", value: .playlist) {
|
||||||
|
NavigationStack {
|
||||||
MediaListView(model: model.playlistModel)
|
MediaListView(model: model.playlistModel)
|
||||||
|
.displayingServerSelectionToolbar(model: $model.serverSelectionViewModel)
|
||||||
|
.navigationTitle(.playlist)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(.favorites, systemImage: "heart.fill", value: .favorites) {
|
Tab(.favorites, systemImage: "heart.fill", value: .favorites) {
|
||||||
|
NavigationStack {
|
||||||
MediaListView(model: model.favoritesModel)
|
MediaListView(model: model.favoritesModel)
|
||||||
|
.displayingServerSelectionToolbar(model: $model.serverSelectionViewModel)
|
||||||
|
.navigationTitle(.favorites)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab(.settings, systemImage: "gear", value: .settings) {
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,18 @@ import SwiftUI
|
|||||||
|
|
||||||
struct MediaListItem: Identifiable
|
struct MediaListItem: Identifiable
|
||||||
{
|
{
|
||||||
let id: String
|
let _id: String
|
||||||
let title: String
|
let title: String
|
||||||
let filename: String
|
let filename: String
|
||||||
let index: Int?
|
let index: Int?
|
||||||
let isCurrent: Bool
|
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) {
|
init(id: String, title: String, filename: String, index: Int? = nil, isCurrent: Bool = false) {
|
||||||
self.id = id
|
self._id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.index = index
|
self.index = index
|
||||||
|
|||||||
Reference in New Issue
Block a user