Remove images, select tags
This commit is contained in:
parent
7e91b4b08c
commit
64b6d88a41
@ -18,6 +18,7 @@
|
||||
E218501B2CEE59EC0090B18B /* Tag+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501A2CEE59E80090B18B /* Tag+Storage.swift */; };
|
||||
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501C2CEE6CB30090B18B /* VerticalCenter.swift */; };
|
||||
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */; };
|
||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850222CF10C840090B18B /* TagSelectionView.swift */; };
|
||||
E24252012C50E0A40029FF16 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = E24252002C50E0A40029FF16 /* HighlightedTextEditor */; };
|
||||
E24252032C5163CF0029FF16 /* Importer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252022C5163CF0029FF16 /* Importer.swift */; };
|
||||
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252052C51684E0029FF16 /* GenericMetadata.swift */; };
|
||||
@ -89,6 +90,7 @@
|
||||
E218501A2CEE59E80090B18B /* Tag+Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tag+Storage.swift"; sourceTree = "<group>"; };
|
||||
E218501C2CEE6CB30090B18B /* VerticalCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalCenter.swift; sourceTree = "<group>"; };
|
||||
E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
|
||||
E21850222CF10C840090B18B /* TagSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagSelectionView.swift; sourceTree = "<group>"; };
|
||||
E24252022C5163CF0029FF16 /* Importer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Importer.swift; sourceTree = "<group>"; };
|
||||
E24252052C51684E0029FF16 /* GenericMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericMetadata.swift; sourceTree = "<group>"; };
|
||||
E24252072C5168750029FF16 /* GenericMetadata+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GenericMetadata+Localized.swift"; sourceTree = "<group>"; };
|
||||
@ -300,6 +302,7 @@
|
||||
E2B85F4B2C4B8B7F0047CD0C /* Posts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E21850222CF10C840090B18B /* TagSelectionView.swift */,
|
||||
E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */,
|
||||
E21850082CEE01BF0090B18B /* PagePickerView.swift */,
|
||||
E2A21C112CB18D520060935B /* DatePickerView.swift */,
|
||||
@ -481,6 +484,7 @@
|
||||
E2A37D292CED2C6A0000979F /* TagsListView.swift in Sources */,
|
||||
E24252032C5163CF0029FF16 /* Importer.swift in Sources */,
|
||||
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
|
||||
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */,
|
||||
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */,
|
||||
E2A21C122CB18D560060935B /* DatePickerView.swift in Sources */,
|
||||
|
@ -30,14 +30,25 @@ struct PostImageGalleryView: View {
|
||||
@State
|
||||
private var showImagePicker = false
|
||||
|
||||
private var imageAtCurrentIndex: Image? {
|
||||
guard !post.images.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
guard currentIndex < post.images.count else {
|
||||
return post.images.last?.imageToDisplay
|
||||
}
|
||||
return post.images[currentIndex].imageToDisplay
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ZStack(alignment: .bottom) {
|
||||
post.images[currentIndex]
|
||||
.imageToDisplay
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
if let imageAtCurrentIndex {
|
||||
imageAtCurrentIndex
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
if post.images.count > 1 {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(0..<post.images.count, id: \.self) { index in
|
||||
@ -60,6 +71,9 @@ struct PostImageGalleryView: View {
|
||||
Button(action: { showImagePicker = true }) {
|
||||
NavigationIcon(symbol: .plus, edge: .all)
|
||||
}
|
||||
Button(action: removeImage) {
|
||||
NavigationIcon(symbol: .trash, edge: .all)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@ -120,6 +134,13 @@ struct PostImageGalleryView: View {
|
||||
post.images.swapAt(currentIndex, currentIndex+1)
|
||||
currentIndex += 1
|
||||
}
|
||||
|
||||
private func removeImage() {
|
||||
post.images.remove(at: currentIndex)
|
||||
if currentIndex >= post.images.count {
|
||||
currentIndex = post.images.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(traits: .fixedLayout(width: 300, height: 250)) {
|
||||
|
@ -1,22 +1,5 @@
|
||||
import SwiftUI
|
||||
|
||||
|
||||
private struct CenteredPost<Content>: View where Content: View {
|
||||
|
||||
let content: Content
|
||||
|
||||
init(@ViewBuilder content: () -> Content) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HorizontalCenter {
|
||||
content
|
||||
}
|
||||
.listRowBackground(ColorPalette.listBackground)
|
||||
}
|
||||
}
|
||||
|
||||
struct PostList: View {
|
||||
|
||||
@EnvironmentObject
|
||||
@ -25,13 +8,13 @@ struct PostList: View {
|
||||
var body: some View {
|
||||
List {
|
||||
if content.posts.isEmpty {
|
||||
CenteredPost {
|
||||
HorizontalCenter {
|
||||
Text("No posts yet.")
|
||||
.padding()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
CenteredPost {
|
||||
HorizontalCenter {
|
||||
Button(action: addNewPost) {
|
||||
Text("Add post")
|
||||
}
|
||||
@ -39,7 +22,7 @@ struct PostList: View {
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
ForEach(content.posts) { post in
|
||||
CenteredPost {
|
||||
HorizontalCenter {
|
||||
PostView(post: post)
|
||||
.frame(maxWidth: 600)
|
||||
}
|
||||
@ -48,8 +31,7 @@ struct PostList: View {
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.background(ColorPalette.listBackground)
|
||||
.scrollContentBackground(.hidden)
|
||||
//.scrollContentBackground(.hidden)
|
||||
}
|
||||
|
||||
private func addNewPost() {
|
||||
|
@ -1,13 +1,28 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct PostView: View {
|
||||
|
||||
@ObservedObject
|
||||
var post: Post
|
||||
|
||||
@Environment(\.language)
|
||||
var language
|
||||
private var language
|
||||
|
||||
var body: some View {
|
||||
LocalizedPostView(post: post, localized: post.localized(in: language))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct LocalizedPostView: View {
|
||||
|
||||
@ObservedObject
|
||||
var post: Post
|
||||
|
||||
@ObservedObject
|
||||
var localized: LocalizedPost
|
||||
|
||||
@State
|
||||
private var showDatePicker = false
|
||||
|
||||
@ -20,6 +35,12 @@ struct PostView: View {
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
private var linkedPageText: String {
|
||||
if let page = post.linkedPage {
|
||||
return page.localized(in: language).title
|
||||
@ -29,7 +50,7 @@ struct PostView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
if post.localized(in: language).images.isEmpty {
|
||||
if localized.images.isEmpty {
|
||||
Button(action: { showImagePicker = true }) {
|
||||
Text("Add image")
|
||||
}
|
||||
@ -37,7 +58,7 @@ struct PostView: View {
|
||||
.foregroundStyle(.blue)
|
||||
.padding(.top)
|
||||
} else {
|
||||
PostImageGalleryView(post: post.localized(in: language))
|
||||
PostImageGalleryView(post: localized)
|
||||
.aspectRatio(1.33, contentMode: .fill)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
@ -49,7 +70,7 @@ struct PostView: View {
|
||||
Toggle("Draft", isOn: $post.isDraft)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
TextField("", text: post.localized(in: language).editableTitle())
|
||||
TextField("", text: $localized.title)
|
||||
.font(.system(size: 24, weight: .bold))
|
||||
.foregroundStyle(Color.primary)
|
||||
.textFieldStyle(.plain)
|
||||
@ -60,22 +81,21 @@ struct PostView: View {
|
||||
en: tag.english.name,
|
||||
de: tag.german.name)
|
||||
)
|
||||
.onTapGesture {
|
||||
remove(tag: tag)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Button(action: { showTagPicker = true }) {
|
||||
SwiftUI.Image(systemSymbol: .plusCircleFill)
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 18)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.blue)
|
||||
//.opacity(0.7)
|
||||
.padding(.top, 3)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
TextEditor(text: post.localized(in: language).editableContent())
|
||||
TextEditor(text: $localized.content)
|
||||
.font(.body)
|
||||
.foregroundStyle(.secondary)
|
||||
.textEditorStyle(.plain)
|
||||
@ -91,7 +111,7 @@ struct PostView: View {
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color.secondary.colorInvert())
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
.cornerRadius(8)
|
||||
.sheet(isPresented: $showDatePicker) {
|
||||
DatePickerView(
|
||||
@ -106,8 +126,13 @@ struct PostView: View {
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
ImagePickerView(
|
||||
showImagePicker: $showImagePicker,
|
||||
post: post.localized(in: language)
|
||||
)
|
||||
post: localized)
|
||||
}
|
||||
.sheet(isPresented: $showTagPicker) {
|
||||
TagSelectionView(
|
||||
presented: $showTagPicker,
|
||||
selected: $post.tags,
|
||||
tags: $content.tags)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,11 +145,10 @@ struct PostView: View {
|
||||
List {
|
||||
PostView(post: .fullMock)
|
||||
.listRowSeparator(.hidden)
|
||||
//.listRowBackground(ColorPalette.listBackground)
|
||||
.environment(\.language, ContentLanguage.german)
|
||||
PostView(post: .mock)
|
||||
.listRowSeparator(.hidden)
|
||||
//.listRowBackground(ColorPalette.listBackground)
|
||||
}
|
||||
.environmentObject(Content.mock)
|
||||
//.listStyle(.plain)
|
||||
}
|
||||
|
95
CHDataManagement/Views/Posts/TagSelectionView.swift
Normal file
95
CHDataManagement/Views/Posts/TagSelectionView.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct TagSelectionView: View {
|
||||
|
||||
@Binding
|
||||
var presented: Bool
|
||||
|
||||
@Binding
|
||||
private var selected: [Tag]
|
||||
|
||||
@Binding
|
||||
private var tags: [Tag]
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
init(presented: Binding<Bool>, selected: Binding<[Tag]>, tags: Binding<[Tag]>) {
|
||||
self._presented = presented
|
||||
self._selected = selected
|
||||
self._tags = tags
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
List {
|
||||
Section("Selected tags") {
|
||||
ForEach(selected) { tag in
|
||||
HStack {
|
||||
Image(systemSymbol: .minusCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
.onTapGesture {
|
||||
deselect(tag: tag)
|
||||
}
|
||||
Text(tag.localized(in: language).name)
|
||||
}
|
||||
}
|
||||
.onMove(perform: moveTag)
|
||||
}
|
||||
Section("Available tags") {
|
||||
ForEach(tags.filter({ !selected.contains($0) })) { tag in
|
||||
HStack {
|
||||
Image(systemSymbol: .plusCircleFill)
|
||||
.foregroundStyle(.green)
|
||||
Text(tag.localized(in: language).name)
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
select(tag: tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 300)
|
||||
.padding()
|
||||
Button(action: dismiss) {
|
||||
Text("Save")
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
||||
private func moveTag(from source: IndexSet, to destination: Int) {
|
||||
selected.move(fromOffsets: source, toOffset: destination)
|
||||
}
|
||||
|
||||
private func deselect(tag: Tag) {
|
||||
guard let index = selected.firstIndex(of: tag) else {
|
||||
return
|
||||
}
|
||||
selected.remove(at: index)
|
||||
|
||||
let insertIndex = tags.firstIndex(where: { $0 > tag }) ?? tags.endIndex
|
||||
tags.insert(tag, at: insertIndex)
|
||||
}
|
||||
|
||||
private func select(tag: Tag) {
|
||||
guard let index = tags.firstIndex(of: tag) else {
|
||||
return
|
||||
}
|
||||
tags.remove(at: index)
|
||||
selected.append(tag)
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
presented = false
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TagSelectionView(
|
||||
presented: .constant(true),
|
||||
selected: .constant([.hiking, .nature]),
|
||||
tags: .constant([.sports, .mock]))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user