Unified detail views, model
This commit is contained in:
26
CHDataManagement/Views/Generic/BoolPropertyView.swift
Normal file
26
CHDataManagement/Views/Generic/BoolPropertyView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
50
CHDataManagement/Views/Generic/DatePropertyView.swift
Normal file
50
CHDataManagement/Views/Generic/DatePropertyView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
27
CHDataManagement/Views/Generic/GenericPropertyView.swift
Normal file
27
CHDataManagement/Views/Generic/GenericPropertyView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
56
CHDataManagement/Views/Generic/IdPropertyView.swift
Normal file
56
CHDataManagement/Views/Generic/IdPropertyView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
30
CHDataManagement/Views/Generic/PagePropertyView.swift
Normal file
30
CHDataManagement/Views/Generic/PagePropertyView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
CHDataManagement/Views/Generic/StringPropertyView.swift
Normal file
27
CHDataManagement/Views/Generic/StringPropertyView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user