Unified detail views, model

This commit is contained in:
Christoph Hagen
2024-12-16 09:54:21 +01:00
parent 1e67a99866
commit 31d1ecb8bd
57 changed files with 853 additions and 954 deletions

View File

@ -0,0 +1,26 @@
import SwiftUI
struct BoolPropertyView: View {
let title: LocalizedStringKey
@Binding
var value: Bool
let footer: LocalizedStringKey
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(title)
.font(.headline)
Spacer()
Toggle("", isOn: $value)
.toggleStyle(.switch)
}
Text(footer)
.foregroundStyle(.secondary)
.padding(.bottom)
}
}
}

View File

@ -0,0 +1,50 @@
import SwiftUI
struct DatePropertyView: View {
let title: String
@Binding
var value: Date
let footer: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
DatePicker("", selection: $value, displayedComponents: .date)
.datePickerStyle(.compact)
Text(footer)
.foregroundStyle(.secondary)
.padding(.bottom)
}
}
}
struct OptionalDatePropertyView: View {
let title: LocalizedStringKey
@Binding
var isEnabled: Bool
@Binding
var date: Date
let footer: LocalizedStringKey
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack(alignment: .firstTextBaseline) {
Toggle("", isOn: $isEnabled)
.toggleStyle(.switch)
DatePicker("", selection: $date, displayedComponents: .date)
.datePickerStyle(.compact)
.padding(.bottom)
.disabled(!isEnabled)
Spacer()
}
}
}
}

View File

@ -2,9 +2,9 @@ import SwiftUI
struct FilePropertyView: View {
let title: String
let title: LocalizedStringKey
let description: String
let footer: LocalizedStringKey
@Binding
var selectedFile: FileResource?
@ -13,9 +13,7 @@ struct FilePropertyView: View {
private var showFileSelectionSheet = false
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
GenericPropertyView(title: title, footer: footer) {
HStack {
Text(selectedFile?.id ?? "No file selected")
Spacer()
@ -23,9 +21,6 @@ struct FilePropertyView: View {
showFileSelectionSheet = true
}
}
Text(description)
.foregroundStyle(.secondary)
.padding(.bottom)
}
.sheet(isPresented: $showFileSelectionSheet) {
FileSelectionView(selectedFile: $selectedFile)

View File

@ -0,0 +1,57 @@
import SwiftUI
struct FolderOnDiskPropertyView: View {
let title: LocalizedStringKey
@Binding
var folder: String
let footer: LocalizedStringKey
let update: (URL) -> Void
init(title: LocalizedStringKey, folder: Binding<String>, footer: LocalizedStringKey, update: @escaping (URL) -> Void) {
self.title = title
self._folder = folder
self.footer = footer
self.update = update
}
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack(alignment: .firstTextBaseline) {
Text(folder)
Spacer()
Button("Select") {
guard let url = openFolderSelectionPanel() else {
return
}
DispatchQueue.main.async {
update(url)
}
}
}
}
}
private func openFolderSelectionPanel() -> URL? {
let panel = NSOpenPanel()
// Sets up so user can only select a single directory
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
panel.showsHiddenFiles = false
panel.title = "Select directory"
//panel.prompt = "Select Directory"
let response = panel.runModal()
guard response == .OK else {
return nil
}
guard let url = panel.url else {
return nil
}
return url
}
}

View File

@ -0,0 +1,27 @@
import SwiftUI
struct GenericPropertyView<Content>: View where Content: View {
let title: LocalizedStringKey
let footer: LocalizedStringKey
let content: Content
public init(title: LocalizedStringKey, footer: LocalizedStringKey, @ViewBuilder content: () -> Content) {
self.title = title
self.footer = footer
self.content = content()
}
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
content
Text(footer)
.foregroundStyle(.secondary)
.padding(.bottom)
}
}
}

View File

@ -0,0 +1,56 @@
import SwiftUI
struct IdPropertyView: View {
@Binding
var id: String
let title: LocalizedStringKey
let footer: LocalizedStringKey
let validation: (String) -> Bool
let update: (String) -> Void
@State
private var newId: String
init(id: Binding<String>,
title: LocalizedStringKey = "ID",
footer: LocalizedStringKey,
validation: @escaping (String) -> Bool = { _ in true },
update: @escaping (String) -> Void) {
self._id = id
self.title = title
self.footer = footer
self.validation = validation
self.update = update
self.newId = id.wrappedValue
}
private var isValid: Bool {
validation(id)
}
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack {
TextField("", text: $newId)
.textFieldStyle(.roundedBorder)
Spacer()
Button("Update", action: setNewId)
.disabled(!isValid)
}
}
}
private func setNewId() {
update(newId)
// In case of failure, resets the id
// In case of update, sets to potentially modified id
DispatchQueue.main.async {
newId = id
}
}
}

View File

@ -2,22 +2,17 @@ import SwiftUI
struct IntegerPropertyView: View {
let title: LocalizedStringKey
@Binding
var value: Int
let title: String
let footer: String
let footer: LocalizedStringKey
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
GenericPropertyView(title: title, footer: footer) {
IntegerField("", number: $value)
.textFieldStyle(.roundedBorder)
Text(footer)
.foregroundStyle(.secondary)
.padding(.bottom)
}
}
}

View File

@ -0,0 +1,36 @@
import SwiftUI
struct OptionalImagePropertyView: View {
let title: LocalizedStringKey
@Binding
var selectedImage: FileResource?
let footer: LocalizedStringKey
@State
private var showSelectionSheet = false
var body: some View {
GenericPropertyView(title: title, footer: footer) {
if let image = selectedImage {
image.imageToDisplay
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxHeight: 300)
.cornerRadius(8)
}
HStack {
Text(selectedImage?.id ?? "No file selected")
Spacer()
Button("Select") {
showSelectionSheet = true
}
}
}
.sheet(isPresented: $showSelectionSheet) {
FileSelectionView(selectedFile: $selectedImage, allowedType: .images)
}
}
}

View File

@ -0,0 +1,27 @@
import SwiftUI
struct OptionalStringPropertyView: View {
let title: LocalizedStringKey
@Binding
var text: String?
let prompt: String?
let footer: LocalizedStringKey
init(title: LocalizedStringKey, text: Binding<String?>, prompt: String? = nil, footer: LocalizedStringKey) {
self.title = title
self._text = text
self.prompt = prompt
self.footer = footer
}
var body: some View {
GenericPropertyView(title: title, footer: footer) {
OptionalTextField(title, text: $text, prompt: prompt)
.textFieldStyle(.roundedBorder)
}
}
}

View File

@ -0,0 +1,27 @@
import SwiftUI
struct OptionalTextFieldPropertyView: View {
let title: LocalizedStringKey
@Binding
var text: String?
let prompt: String?
let footer: LocalizedStringKey
init(title: LocalizedStringKey, text: Binding<String?>, prompt: String? = nil, footer: LocalizedStringKey) {
self.title = title
self._text = text
self.prompt = prompt
self.footer = footer
}
var body: some View {
GenericPropertyView(title: title, footer: footer) {
OptionalDescriptionField(text: $text)
.textFieldStyle(.roundedBorder)
}
}
}

View File

@ -0,0 +1,30 @@
import SwiftUI
struct PagePropertyView: View {
let title: LocalizedStringKey
@Binding
var selectedPage: Page?
let footer: LocalizedStringKey
@State
private var showPageSelectionSheet = false
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack {
Text(selectedPage?.id ?? "No page selected")
Spacer()
Button("Select") {
showPageSelectionSheet = true
}
}
}
.sheet(isPresented: $showPageSelectionSheet) {
PagePickerView(selectedPage: $selectedPage)
}
}
}

View File

@ -0,0 +1,27 @@
import SwiftUI
struct StringPropertyView: View {
let title: LocalizedStringKey
@Binding
var text: String
let prompt: String?
let footer: LocalizedStringKey
init(title: LocalizedStringKey, text: Binding<String>, prompt: String? = nil, footer: LocalizedStringKey) {
self.title = title
self._text = text
self.prompt = prompt
self.footer = footer
}
var body: some View {
GenericPropertyView(title: title, footer: footer) {
TextField(title, text: $text, prompt: prompt.map(Text.init))
.textFieldStyle(.roundedBorder)
}
}
}