Implements add media page
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Optional
|
||||
{
|
||||
@@ -91,3 +92,8 @@ struct RequestBuilder
|
||||
case delete = "DELETE"
|
||||
}
|
||||
}
|
||||
|
||||
extension Color
|
||||
{
|
||||
static let label = Color(uiColor: .label)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD_MEDIA" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add Media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ADD_SERVER" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@@ -178,6 +188,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SEARCH_FOR_MEDIA" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search YouTube for Media…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SERVER_IS_ONLINE" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
||||
@@ -33,4 +33,6 @@ extension LocalizedStringKey
|
||||
static let noServersConfigured = LocalizedStringKey("NO_SERVERS_CONFIGURED")
|
||||
static let playlistEmpty = LocalizedStringKey("PLAYLIST_IS_EMPTY")
|
||||
static let favoritesEmpty = LocalizedStringKey("FAVORITES_IS_EMPTY")
|
||||
static let addMedia = LocalizedStringKey("ADD_MEDIA")
|
||||
static let searchForMedia = LocalizedStringKey("SEARCH_FOR_MEDIA")
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// AddMediaBarView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 3/3/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class AddMediaBarViewModel
|
||||
{
|
||||
var fieldContents: String = ""
|
||||
|
||||
var onAdd: (String) -> Void = { _ in }
|
||||
var onSearch: () -> Void = {}
|
||||
}
|
||||
|
||||
struct AddMediaBarView: View
|
||||
{
|
||||
@State var model: AddMediaBarViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Button(action: model.onSearch) { Image(systemName: "magnifyingglass") }
|
||||
|
||||
TextField(.addAnyURL, text: $model.fieldContents)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
||||
Button(action: { model.onAdd(model.fieldContents) }) { Text(.add) }
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color.black.opacity(0.4))
|
||||
}
|
||||
}
|
||||
110
QueueCube/Views/AddMediaView.swift
Normal file
110
QueueCube/Views/AddMediaView.swift
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// AddMediaView.swift
|
||||
// QueueCube
|
||||
//
|
||||
// Created by James Magahern on 6/11/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AddMediaView: View
|
||||
{
|
||||
@Binding var model: ViewModel
|
||||
@FocusState var fieldFocused: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
// Add URL
|
||||
Section {
|
||||
TextField(.addAnyURL, text: $model.fieldContents)
|
||||
.autocapitalization(.none)
|
||||
.autocorrectionDisabled()
|
||||
.focused($fieldFocused)
|
||||
}
|
||||
|
||||
if model.supportsSearch {
|
||||
Section {
|
||||
NavigationLink {
|
||||
SearchMediaView(model: $model)
|
||||
} label: {
|
||||
Image(systemName: "magnifyingglass")
|
||||
Button(.searchForMedia, action: model.onSearch)
|
||||
}
|
||||
.tint(.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
.task { fieldFocused = true }
|
||||
.onAppear { model.activeDetent = ViewModel.Detent.collapsed.value }
|
||||
.navigationTitle(.addMedia)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||
Button(.add, action: model.addButtonTapped)
|
||||
.disabled(model.fieldContents.isEmpty)
|
||||
.bold()
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: .topBarLeading) {
|
||||
Button(.cancel, action: model.onCancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
enum Page: String, Identifiable
|
||||
{
|
||||
case addURL
|
||||
case searchMedia
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
@Observable
|
||||
class ViewModel
|
||||
{
|
||||
var fieldContents: String = ""
|
||||
var onAdd: (String) -> Void = { _ in }
|
||||
var onCancel: () -> Void = { }
|
||||
var onSearch: () -> Void = { }
|
||||
var supportsSearch: Bool = true
|
||||
|
||||
var activeDetent: PresentationDetent = Detent.collapsed.value
|
||||
|
||||
enum Detent: CaseIterable
|
||||
{
|
||||
case collapsed
|
||||
case expanded
|
||||
|
||||
var value: PresentationDetent {
|
||||
switch self {
|
||||
case .collapsed: .height(320.0)
|
||||
case .expanded: .large
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func addButtonTapped() {
|
||||
onAdd(fieldContents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchMediaView: View
|
||||
{
|
||||
@Binding var model: AddMediaView.ViewModel
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
|
||||
}
|
||||
.navigationTitle(.searchForMedia)
|
||||
.presentationBackground(.regularMaterial)
|
||||
.onAppear {
|
||||
model.activeDetent = AddMediaView.ViewModel.Detent.expanded.value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,14 @@ struct ContentView: View
|
||||
.presentationBackground(.regularMaterial)
|
||||
.presentationDetents([ .height(320.0) ])
|
||||
}
|
||||
.sheet(isPresented: $model.isAddMediaSheetPresented) {
|
||||
AddMediaView(model: $model.addMediaViewModel)
|
||||
.presentationBackground(.regularMaterial)
|
||||
.presentationDetents(
|
||||
Set(AddMediaView.ViewModel.Detent.allCases.map { $0.value }),
|
||||
selection: $model.addMediaViewModel.activeDetent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
@@ -16,11 +16,12 @@ class MainViewModel
|
||||
var selectedTab: Tab = .playlist
|
||||
|
||||
var isNowPlayingSheetPresented: Bool = false
|
||||
var isAddMediaSheetPresented: Bool = false
|
||||
|
||||
var playlistModel = MediaListViewModel(mode: .playlist)
|
||||
var favoritesModel = MediaListViewModel(mode: .favorites)
|
||||
var nowPlayingViewModel = NowPlayingViewModel()
|
||||
var addMediaViewModel = AddMediaBarViewModel()
|
||||
var addMediaViewModel = AddMediaView.ViewModel()
|
||||
var serverSelectionViewModel = ServerSelectionToolbarModifier.ViewModel()
|
||||
|
||||
private var refreshingFromAPIDepth: UInt8 = 0
|
||||
@@ -39,7 +40,7 @@ class MainViewModel
|
||||
}
|
||||
|
||||
func onAddButtonTapped() {
|
||||
|
||||
isAddMediaSheetPresented = true
|
||||
}
|
||||
|
||||
func onNowPlayingMiniTapped() {
|
||||
@@ -101,6 +102,7 @@ class MainViewModel
|
||||
let strippedURL = mediaURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !strippedURL.isEmpty {
|
||||
addMediaViewModel.fieldContents = ""
|
||||
isAddMediaSheetPresented = false
|
||||
|
||||
switch selectedTab {
|
||||
case .playlist:
|
||||
@@ -112,6 +114,10 @@ class MainViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addMediaViewModel.onCancel = { [weak self] in
|
||||
self?.isAddMediaSheetPresented = false
|
||||
}
|
||||
}
|
||||
|
||||
func withModificationsViaAPI(_ modificationBlock: (API) async throws -> Void) async {
|
||||
@@ -341,7 +347,7 @@ struct ErrorDisplayModifier: ViewModifier
|
||||
.fill(.background)
|
||||
|
||||
contentPlaceholderView(title: .connectionError, systemImage: "exclamationmark.triangle.fill")
|
||||
.tint(Color(uiColor: .label))
|
||||
.tint(.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ struct NowPlayingView: View
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.tint(Color(uiColor: .label))
|
||||
.imageScale(.large)
|
||||
.frame(height: 34.0)
|
||||
.tint(.label)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user