From 394cf7a2e4916b42e47f74009605cb5f95a44411 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sat, 7 Dec 2024 00:09:35 +0100 Subject: [PATCH] Improve page and post detail views --- CHDataManagement.xcodeproj/project.pbxproj | 20 +++ CHDataManagement/Model/Content+Load.swift | 12 +- CHDataManagement/Model/Content+Save.swift | 4 +- CHDataManagement/Model/LocalizedPage.swift | 4 +- .../Views/Generic/DescriptionField.swift | 40 ++++++ .../Views/Generic/IconButton.swift | 36 ++++++ .../Views/Generic/OptionalTextField.swift | 7 +- .../Views/Pages/LocalizedPageDetailView.swift | 70 ++++++++++ .../Views/Pages/PageDetailView.swift | 72 +++++++++++ .../Views/Pages/PageListView.swift | 4 +- .../Views/Posts/LocalizedPostDetailView.swift | 64 +++++++++ .../Views/Posts/PostDetailView.swift | 121 +++++------------- .../LocalizedPostFeedSettingsView.swift | 10 +- 13 files changed, 355 insertions(+), 109 deletions(-) create mode 100644 CHDataManagement/Views/Generic/DescriptionField.swift create mode 100644 CHDataManagement/Views/Generic/IconButton.swift create mode 100644 CHDataManagement/Views/Pages/LocalizedPageDetailView.swift create mode 100644 CHDataManagement/Views/Pages/PageDetailView.swift create mode 100644 CHDataManagement/Views/Posts/LocalizedPostDetailView.swift diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 117dc1a..f8a7a01 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -88,6 +88,11 @@ E29D31262D0370A80051B7F4 /* VideoOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31252D0370A50051B7F4 /* VideoOption.swift */; }; E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31272D0371870051B7F4 /* ContentPageVideo.swift */; }; E29D312A2D039B090051B7F4 /* ImageDescriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31292D039B050051B7F4 /* ImageDescriptions.swift */; }; + E29D312C2D039DB80051B7F4 /* PageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312B2D039DB30051B7F4 /* PageDetailView.swift */; }; + E29D312E2D03A0D70051B7F4 /* LocalizedPageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312D2D03A0CF0051B7F4 /* LocalizedPageDetailView.swift */; }; + E29D31302D03A2C50051B7F4 /* DescriptionField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */; }; + E29D31322D03B5680051B7F4 /* LocalizedPostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31312D03B5610051B7F4 /* LocalizedPostDetailView.swift */; }; + E29D31342D03B5D50051B7F4 /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31332D03B5D30051B7F4 /* IconButton.swift */; }; E2A21C012CB16A820060935B /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C002CB16A820060935B /* PostView.swift */; }; E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C022CB16C220060935B /* Environment+Language.swift */; }; E2A21C052CB1766C0060935B /* LocalizedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C042CB176670060935B /* LocalizedText.swift */; }; @@ -218,6 +223,11 @@ E29D31252D0370A50051B7F4 /* VideoOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoOption.swift; sourceTree = ""; }; E29D31272D0371870051B7F4 /* ContentPageVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPageVideo.swift; sourceTree = ""; }; E29D31292D039B050051B7F4 /* ImageDescriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDescriptions.swift; sourceTree = ""; }; + E29D312B2D039DB30051B7F4 /* PageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailView.swift; sourceTree = ""; }; + E29D312D2D03A0CF0051B7F4 /* LocalizedPageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageDetailView.swift; sourceTree = ""; }; + E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionField.swift; sourceTree = ""; }; + E29D31312D03B5610051B7F4 /* LocalizedPostDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostDetailView.swift; sourceTree = ""; }; + E29D31332D03B5D30051B7F4 /* IconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = ""; }; E2A21C002CB16A820060935B /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = ""; }; E2A21C022CB16C220060935B /* Environment+Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Language.swift"; sourceTree = ""; }; E2A21C042CB176670060935B /* LocalizedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedText.swift; sourceTree = ""; }; @@ -376,6 +386,8 @@ E2A21C322CB5BCAC0060935B /* Pages */ = { isa = PBXGroup; children = ( + E29D312D2D03A0CF0051B7F4 /* LocalizedPageDetailView.swift */, + E29D312B2D039DB30051B7F4 /* PageDetailView.swift */, E2A21C312CB5BCAC0060935B /* PageContentView.swift */, E2A37D242CEBD7A10000979F /* PageListView.swift */, ); @@ -401,6 +413,8 @@ E2A21C372CB9A4F10060935B /* Generic */ = { isa = PBXGroup; children = ( + E29D31332D03B5D30051B7F4 /* IconButton.swift */, + E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */, E25DA5902D023A7E00AEF16D /* IntegerField.swift */, E218501C2CEE6CB30090B18B /* VerticalCenter.swift */, E2A37D2C2CED2EEE0000979F /* OptionalTextField.swift */, @@ -533,6 +547,7 @@ E2A21C072CB17B810060935B /* TagView.swift */, E21850242CF38BCE0090B18B /* TextEntrySheet.swift */, E21850262CF3B42D0090B18B /* PostDetailView.swift */, + E29D31312D03B5610051B7F4 /* LocalizedPostDetailView.swift */, E218502C2CF791440090B18B /* PostImagesView.swift */, ); path = Posts; @@ -733,9 +748,11 @@ E25DA5952D023BD100AEF16D /* PageSettingsView.swift in Sources */, E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */, E2A21C2A2CB2AA4F0060935B /* Post+Mock.swift in Sources */, + E29D312E2D03A0D70051B7F4 /* LocalizedPageDetailView.swift in Sources */, E24252082C5168750029FF16 /* GenericMetadata+Localized.swift in Sources */, E2581DED2C75202400F1F079 /* Tag.swift in Sources */, E2A21C4F2CBB29E50060935B /* ImageDetailsView.swift in Sources */, + E29D31302D03A2C50051B7F4 /* DescriptionField.swift in Sources */, E25DA53D2D0043E600AEF16D /* LocalizedSettings.swift in Sources */, E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */, E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */, @@ -744,6 +761,7 @@ E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */, E2A37D172CE73F1A0000979F /* TagFile.swift in Sources */, E2A37D292CED2C6A0000979F /* TagsListView.swift in Sources */, + E29D312C2D039DB80051B7F4 /* PageDetailView.swift in Sources */, E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */, E24252032C5163CF0029FF16 /* Importer.swift in Sources */, E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */, @@ -783,6 +801,7 @@ E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */, E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */, E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */, + E29D31322D03B5680051B7F4 /* LocalizedPostDetailView.swift in Sources */, E2A37D1F2CEA94370000979F /* Optional+Extensions.swift in Sources */, E25DA5292CFFBFBB00AEF16D /* ImageType.swift in Sources */, E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */, @@ -798,6 +817,7 @@ E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */, E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */, E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */, + E29D31342D03B5D50051B7F4 /* IconButton.swift in Sources */, E25DA5712D01015400AEF16D /* GenerationSettingsView.swift in Sources */, E25DA5872D01CA9300AEF16D /* GenerationResultsHandler.swift in Sources */, E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */, diff --git a/CHDataManagement/Model/Content+Load.swift b/CHDataManagement/Model/Content+Load.swift index 7a76390..8a97cd4 100644 --- a/CHDataManagement/Model/Content+Load.swift +++ b/CHDataManagement/Model/Content+Load.swift @@ -23,7 +23,7 @@ extension Content { linkPreviewDescription: post.linkPreviewDescription) } - private func convert(_ page: LocalizedPageFile) -> LocalizedPage { + private func convert(_ page: LocalizedPageFile, images: [String : ImageResource]) -> LocalizedPage { LocalizedPage( urlString: page.url, title: page.title, @@ -32,7 +32,7 @@ extension Content { files: Set(page.files), externalFiles: Set(page.externalFiles), requiredFiles: Set(page.requiredFiles), - linkPreviewImage: page.linkPreviewImage, + linkPreviewImage: page.linkPreviewImage.map { images[$0] }, linkPreviewTitle: page.linkPreviewTitle, linkPreviewDescription: page.linkPreviewDescription) } @@ -84,7 +84,7 @@ extension Content { english: convert(data.value.english, images: images)) } - let pages: [String : Page] = loadPages(pagesData, tags: tags) + let pages: [String : Page] = loadPages(pagesData, tags: tags, images: images) let posts = postsData.map { postId, post in let linkedPage = post.linkedPageId.map { pages[$0] } @@ -134,7 +134,7 @@ extension Content { english: convert(settings.english)) } - private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag]) -> [String : Page] { + private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag], images: [String : ImageResource]) -> [String : Page] { pagesData.reduce(into: [:]) { pages, data in let (pageId, page) = data pages[pageId] = Page( @@ -143,8 +143,8 @@ extension Content { createdDate: page.createdDate, startDate: page.startDate, endDate: page.endDate, - german: convert(page.german), - english: convert(page.english), + german: convert(page.german, images: images), + english: convert(page.english, images: images), tags: page.tags.map { tags[$0]! }) } } diff --git a/CHDataManagement/Model/Content+Save.swift b/CHDataManagement/Model/Content+Save.swift index 54e0f99..58071ad 100644 --- a/CHDataManagement/Model/Content+Save.swift +++ b/CHDataManagement/Model/Content+Save.swift @@ -26,7 +26,7 @@ extension Content { german: image.germanDescription.nonEmpty, english: image.englishDescription.nonEmpty) } - + storage.save(imageDescriptions: imageDescriptions) do { @@ -65,7 +65,7 @@ private extension LocalizedPage { externalFiles: externalFiles.sorted(), requiredFiles: requiredFiles.sorted(), title: title, - linkPreviewImage: linkPreviewImage, + linkPreviewImage: linkPreviewImage?.id, linkPreviewTitle: linkPreviewTitle, linkPreviewDescription: linkPreviewDescription, lastModifiedDate: lastModified, diff --git a/CHDataManagement/Model/LocalizedPage.swift b/CHDataManagement/Model/LocalizedPage.swift index a72914d..d30b3fe 100644 --- a/CHDataManagement/Model/LocalizedPage.swift +++ b/CHDataManagement/Model/LocalizedPage.swift @@ -56,7 +56,7 @@ final class LocalizedPage: ObservableObject { var requiredFiles: Set = [] @Published - var linkPreviewImage: String? + var linkPreviewImage: ImageResource? @Published var linkPreviewTitle: String? @@ -71,7 +71,7 @@ final class LocalizedPage: ObservableObject { files: Set = [], externalFiles: Set = [], requiredFiles: Set = [], - linkPreviewImage: String? = nil, + linkPreviewImage: ImageResource? = nil, linkPreviewTitle: String? = nil, linkPreviewDescription: String? = nil) { self.urlString = urlString diff --git a/CHDataManagement/Views/Generic/DescriptionField.swift b/CHDataManagement/Views/Generic/DescriptionField.swift new file mode 100644 index 0000000..6db4fe8 --- /dev/null +++ b/CHDataManagement/Views/Generic/DescriptionField.swift @@ -0,0 +1,40 @@ +import SwiftUI + +struct DescriptionField: View { + + @Binding + var text: String + + var body: some View { + TextEditor(text: $text) + .font(.body) + .lineLimit(5, reservesSpace: true) + .frame(maxWidth: 400, minHeight: 50, maxHeight: 500) + .textEditorStyle(.plain) + .padding(.vertical, 8) + .padding(.leading, 3) + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + } +} + +struct OptionalDescriptionField: View { + + @Binding + var text: String? + + var body: some View { + TextEditor(text: Binding( + get: { text ?? "" }, + set: { text = $0.isEmpty ? nil : $0 } + )) + .font(.body) + .lineLimit(5, reservesSpace: true) + .frame(maxWidth: 400, minHeight: 50, maxHeight: 500) + .textEditorStyle(.plain) + .padding(.vertical, 8) + .padding(.leading, 3) + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + } +} diff --git a/CHDataManagement/Views/Generic/IconButton.swift b/CHDataManagement/Views/Generic/IconButton.swift new file mode 100644 index 0000000..6e44b6b --- /dev/null +++ b/CHDataManagement/Views/Generic/IconButton.swift @@ -0,0 +1,36 @@ +import SwiftUI +import SFSafeSymbols + +struct IconButton: View { + + let symbol: SFSymbol + + let action: () -> Void + + let size: CGFloat + + let color: Color + + let background: Color + + init(symbol: SFSymbol, size: CGFloat, color: Color, background: Color = .white, action: @escaping () -> Void) { + self.symbol = symbol + self.action = action + self.size = size + self.color = color + self.background = background + } + + var body: some View { + Button(action: action) { + Image(systemSymbol: symbol) + .resizable() + .aspectRatio(1, contentMode: .fit) + .frame(width: size, height: size) + .foregroundStyle(color) + .background(Circle() + .fill(background) + .padding(1)) + } + } +} diff --git a/CHDataManagement/Views/Generic/OptionalTextField.swift b/CHDataManagement/Views/Generic/OptionalTextField.swift index 68c9fbf..f854c23 100644 --- a/CHDataManagement/Views/Generic/OptionalTextField.swift +++ b/CHDataManagement/Views/Generic/OptionalTextField.swift @@ -8,9 +8,12 @@ struct OptionalTextField: View { // The optional text that will be passed in and out of the component @Binding var text: String? - init(_ titleKey: LocalizedStringKey, text: Binding) { + let prompt: String? + + init(_ titleKey: LocalizedStringKey, text: Binding, prompt: String? = nil) { self.titleKey = titleKey self._text = text + self.prompt = prompt } var body: some View { @@ -23,6 +26,6 @@ struct OptionalTextField: View { // Convert an empty string to `nil` text = newValue.isEmpty ? nil : newValue } - )) + ), prompt: prompt.map(Text.init)) } } diff --git a/CHDataManagement/Views/Pages/LocalizedPageDetailView.swift b/CHDataManagement/Views/Pages/LocalizedPageDetailView.swift new file mode 100644 index 0000000..2a83335 --- /dev/null +++ b/CHDataManagement/Views/Pages/LocalizedPageDetailView.swift @@ -0,0 +1,70 @@ +import SwiftUI +import SFSafeSymbols + +struct LocalizedPageDetailView: View { + + @ObservedObject + private var item: LocalizedPage + + init(page: LocalizedPage, showImagePicker: Bool = false) { + self.item = page + self.showImagePicker = showImagePicker + } + + @State + private var showImagePicker = false + + var body: some View { + VStack(alignment: .leading) { + Text("Link Preview Title") + .font(.headline) + OptionalTextField("", text: $item.linkPreviewTitle, + prompt: item.title) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + + HStack { + Text("Link Preview Image") + .font(.headline) + IconButton(symbol: .squareAndPencilCircleFill, + size: 22, + color: .blue) { + showImagePicker = true + } + + IconButton(symbol: .trashCircleFill, + size: 22, + color: .red) { + item.linkPreviewImage = nil + } + Spacer() + } + + .buttonStyle(.plain) + if let image = item.linkPreviewImage { + image.imageToDisplay + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 400, maxHeight: 300) + .cornerRadius(8) + } + + Text("Link Preview Description") + .font(.headline) + .padding(.top) + OptionalDescriptionField(text: $item.linkPreviewDescription) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + } + .sheet(isPresented: $showImagePicker) { + ImagePickerView(showImagePicker: $showImagePicker) { image in + item.linkPreviewImage = image + } + } + } +} + +#Preview { + LocalizedPageDetailView(page: .english) + .environmentObject(Content.mock) +} diff --git a/CHDataManagement/Views/Pages/PageDetailView.swift b/CHDataManagement/Views/Pages/PageDetailView.swift new file mode 100644 index 0000000..72da1bd --- /dev/null +++ b/CHDataManagement/Views/Pages/PageDetailView.swift @@ -0,0 +1,72 @@ +import SwiftUI + +struct PageDetailView: View { + + @Environment(\.language) + private var language + + @ObservedObject + private var item: Page + + init(page: Page) { + self.item = page + } + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + Text("ID") + .font(.headline) + TextField("", text: $item.id) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + + HStack { + Text("Draft") + .font(.headline) + Spacer() + Toggle("", isOn: $item.isDraft) + .toggleStyle(.switch) + } + .padding(.bottom) + + HStack { + Text("Start") + .font(.headline) + Spacer() + DatePicker("", selection: $item.startDate, displayedComponents: .date) + .datePickerStyle(.compact) + .padding(.bottom) + } + + HStack(alignment: .firstTextBaseline) { + Text("Has end date") + .font(.headline) + Spacer() + Toggle("", isOn: $item.hasEndDate) + .toggleStyle(.switch) + .padding(.bottom) + } + + if item.hasEndDate { + HStack(alignment: .firstTextBaseline) { + Text("End date") + .font(.headline) + Spacer() + DatePicker("", selection: $item.endDate, displayedComponents: .date) + .datePickerStyle(.compact) + .padding(.bottom) + } + } + + LocalizedPageDetailView(page: item.localized(in: language)) + + } + .padding() + } + } +} + +#Preview { + PageDetailView(page: .empty) +} diff --git a/CHDataManagement/Views/Pages/PageListView.swift b/CHDataManagement/Views/Pages/PageListView.swift index c0cd290..159b69e 100644 --- a/CHDataManagement/Views/Pages/PageListView.swift +++ b/CHDataManagement/Views/Pages/PageListView.swift @@ -53,7 +53,7 @@ struct PageListView: View { } } detail: { if let selected { - EmptyView() + PageDetailView(page: selected) .frame(maxWidth: 350) } else { EmptyView() @@ -111,5 +111,5 @@ struct PageListView: View { #Preview { PageListView() - .environmentObject(Content()) + .environmentObject(Content.mock) } diff --git a/CHDataManagement/Views/Posts/LocalizedPostDetailView.swift b/CHDataManagement/Views/Posts/LocalizedPostDetailView.swift new file mode 100644 index 0000000..ce92121 --- /dev/null +++ b/CHDataManagement/Views/Posts/LocalizedPostDetailView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +struct LocalizedPostDetailView: View { + + @ObservedObject + private var item: LocalizedPost + + init(post: LocalizedPost, showImagePicker: Bool = false) { + self.item = post + self.showImagePicker = showImagePicker + } + + @State + private var showImagePicker = false + + var body: some View { + VStack(alignment: .leading) { + Text("Link Preview Title") + .font(.headline) + OptionalTextField("", text: $item.linkPreviewTitle, + prompt: item.title) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + + HStack { + Text("Link Preview Image") + .font(.headline) + IconButton(symbol: .squareAndPencilCircleFill, + size: 22, + color: .blue) { + showImagePicker = true + } + + IconButton(symbol: .trashCircleFill, + size: 22, + color: .red) { + item.linkPreviewImage = nil + } + Spacer() + } + + .buttonStyle(.plain) + if let image = item.linkPreviewImage { + image.imageToDisplay + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 400, maxHeight: 300) + .cornerRadius(8) + } + + Text("Link Preview Description") + .font(.headline) + .padding(.top) + OptionalDescriptionField(text: $item.linkPreviewDescription) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + } + .sheet(isPresented: $showImagePicker) { + ImagePickerView(showImagePicker: $showImagePicker) { image in + item.linkPreviewImage = image + } + } + } +} diff --git a/CHDataManagement/Views/Posts/PostDetailView.swift b/CHDataManagement/Views/Posts/PostDetailView.swift index 95f5725..bef536e 100644 --- a/CHDataManagement/Views/Posts/PostDetailView.swift +++ b/CHDataManagement/Views/Posts/PostDetailView.swift @@ -34,115 +34,64 @@ struct PostDetailView: View { private var language @ObservedObject - var post: Post - - @State - private var showPagePicker = false + private var item: Post init(post: Post) { - self.post = post - } - - private var linkedPageText: String { - if let page = post.linkedPage { - return page.localized(in: language).title - } - return "Add" + self.item = post } var body: some View { ScrollView { VStack(alignment: .leading) { - Text("Post data") + Text("ID") .font(.headline) - DetailListItem { - Text("ID") - .foregroundStyle(.primary) - TextField("ID", text: $post.id) - .multilineTextAlignment(.trailing) - } - DetailListItem { + TextField("", text: $item.id) + .textFieldStyle(.roundedBorder) + .padding(.bottom) + + HStack { Text("Draft") + .font(.headline) Spacer() - Toggle(isOn: $post.isDraft) { - Text("") - }.toggleStyle(.switch) + Toggle("", isOn: $item.isDraft) + .toggleStyle(.switch) } - DetailListItem { + .padding(.bottom) + + HStack { Text("Start") + .font(.headline) Spacer() - DatePicker("", selection: $post.startDate, displayedComponents: .date) + DatePicker("", selection: $item.startDate, displayedComponents: .date) .datePickerStyle(.compact) + .padding(.bottom) } - DetailListItem { - Text("End") + + HStack(alignment: .firstTextBaseline) { + Text("Has end date") + .font(.headline) Spacer() - Toggle(isOn: $post.hasEndDate) { - Text("") - }.toggleStyle(.switch) - DatePicker("", selection: $post.endDate, displayedComponents: .date) - .datePickerStyle(.compact) - .disabled(!post.hasEndDate) + Toggle("", isOn: $item.hasEndDate) + .toggleStyle(.switch) + .padding(.bottom) } - DetailListItem { - Text("Linked page") - Spacer() - Button(action: { showPagePicker = true }) { - Text(linkedPageText) + + if item.hasEndDate { + HStack(alignment: .firstTextBaseline) { + Text("End date") + .font(.headline) + Spacer() + DatePicker("", selection: $item.endDate, displayedComponents: .date) + .datePickerStyle(.compact) + .padding(.bottom) } - .buttonStyle(.plain) - .foregroundStyle(.blue) } - LocalizedPostDetailView(post: post.localized(in: language)) + + LocalizedPostDetailView(post: item.localized(in: language)) } .padding() } - .sheet(isPresented: $showPagePicker) { - PagePickerView( - showPagePicker: $showPagePicker, - selectedPage: $post.linkedPage) - } - } -} - -struct LocalizedPostDetailView: View { - - @ObservedObject - var post: LocalizedPost - - @State - private var showImagePicker = false - - var body: some View { - VStack(alignment: .leading) { - Text("Link Preview") - .font(.headline) - DetailListItem { - Text("Title") - Spacer() - OptionalTextField("", text: $post.linkPreviewTitle) - } - DetailListItem { - Text("Image") - Spacer() - Button(action: { showImagePicker = true }) { - Text(post.linkPreviewImage?.id ?? "Select") - } - .buttonStyle(.plain) - .foregroundStyle(.blue) - } - DetailListItem { - Text("Description") - Spacer() - OptionalTextField("", text: $post.linkPreviewDescription) - } - } - .sheet(isPresented: $showImagePicker) { - ImagePickerView(showImagePicker: $showImagePicker) { image in - post.linkPreviewImage = image - } - } } } diff --git a/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift b/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift index 0463909..64609d2 100644 --- a/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift +++ b/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift @@ -27,15 +27,7 @@ struct LocalizedPostFeedSettingsView: View { Text("Description") .font(.headline) - TextEditor(text: $settings.description) - .font(.body) - .lineLimit(5, reservesSpace: true) - .frame(maxWidth: 400, minHeight: 50, maxHeight: 500) - .textEditorStyle(.plain) - .padding(.vertical, 8) - .padding(.leading, 3) - .background(Color.gray.opacity(0.1)) - .cornerRadius(8) + DescriptionField(text: $settings.description) Text("The description of all post feed pages.") .foregroundStyle(.secondary) .padding(.bottom)