Remove images, select tags

This commit is contained in:
Christoph Hagen 2024-11-24 17:04:01 +01:00
parent 7e91b4b08c
commit 64b6d88a41
5 changed files with 169 additions and 43 deletions

View File

@ -18,6 +18,7 @@
E218501B2CEE59EC0090B18B /* Tag+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501A2CEE59E80090B18B /* Tag+Storage.swift */; }; E218501B2CEE59EC0090B18B /* Tag+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501A2CEE59E80090B18B /* Tag+Storage.swift */; };
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501C2CEE6CB30090B18B /* VerticalCenter.swift */; }; E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501C2CEE6CB30090B18B /* VerticalCenter.swift */; };
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501E2CEE6DAC0090B18B /* ImagePickerView.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 */; }; E24252012C50E0A40029FF16 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = E24252002C50E0A40029FF16 /* HighlightedTextEditor */; };
E24252032C5163CF0029FF16 /* Importer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252022C5163CF0029FF16 /* Importer.swift */; }; E24252032C5163CF0029FF16 /* Importer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252022C5163CF0029FF16 /* Importer.swift */; };
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252052C51684E0029FF16 /* GenericMetadata.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; E24252072C5168750029FF16 /* GenericMetadata+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GenericMetadata+Localized.swift"; sourceTree = "<group>"; };
@ -300,6 +302,7 @@
E2B85F4B2C4B8B7F0047CD0C /* Posts */ = { E2B85F4B2C4B8B7F0047CD0C /* Posts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E21850222CF10C840090B18B /* TagSelectionView.swift */,
E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */, E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */,
E21850082CEE01BF0090B18B /* PagePickerView.swift */, E21850082CEE01BF0090B18B /* PagePickerView.swift */,
E2A21C112CB18D520060935B /* DatePickerView.swift */, E2A21C112CB18D520060935B /* DatePickerView.swift */,
@ -481,6 +484,7 @@
E2A37D292CED2C6A0000979F /* TagsListView.swift in Sources */, E2A37D292CED2C6A0000979F /* TagsListView.swift in Sources */,
E24252032C5163CF0029FF16 /* Importer.swift in Sources */, E24252032C5163CF0029FF16 /* Importer.swift in Sources */,
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */, E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */, E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */,
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */, E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */,
E2A21C122CB18D560060935B /* DatePickerView.swift in Sources */, E2A21C122CB18D560060935B /* DatePickerView.swift in Sources */,

View File

@ -30,14 +30,25 @@ struct PostImageGalleryView: View {
@State @State
private var showImagePicker = false 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 { var body: some View {
ZStack(alignment: .center) { ZStack(alignment: .center) {
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
post.images[currentIndex] if let imageAtCurrentIndex {
.imageToDisplay imageAtCurrentIndex
.resizable() .resizable()
.scaledToFit() .scaledToFit()
}
if post.images.count > 1 { if post.images.count > 1 {
HStack(spacing: 8) { HStack(spacing: 8) {
ForEach(0..<post.images.count, id: \.self) { index in ForEach(0..<post.images.count, id: \.self) { index in
@ -60,6 +71,9 @@ struct PostImageGalleryView: View {
Button(action: { showImagePicker = true }) { Button(action: { showImagePicker = true }) {
NavigationIcon(symbol: .plus, edge: .all) NavigationIcon(symbol: .plus, edge: .all)
} }
Button(action: removeImage) {
NavigationIcon(symbol: .trash, edge: .all)
}
}.padding() }.padding()
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@ -120,6 +134,13 @@ struct PostImageGalleryView: View {
post.images.swapAt(currentIndex, currentIndex+1) post.images.swapAt(currentIndex, currentIndex+1)
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)) { #Preview(traits: .fixedLayout(width: 300, height: 250)) {

View File

@ -1,22 +1,5 @@
import SwiftUI 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 { struct PostList: View {
@EnvironmentObject @EnvironmentObject
@ -25,13 +8,13 @@ struct PostList: View {
var body: some View { var body: some View {
List { List {
if content.posts.isEmpty { if content.posts.isEmpty {
CenteredPost { HorizontalCenter {
Text("No posts yet.") Text("No posts yet.")
.padding() .padding()
} }
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
CenteredPost { HorizontalCenter {
Button(action: addNewPost) { Button(action: addNewPost) {
Text("Add post") Text("Add post")
} }
@ -39,7 +22,7 @@ struct PostList: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
ForEach(content.posts) { post in ForEach(content.posts) { post in
CenteredPost { HorizontalCenter {
PostView(post: post) PostView(post: post)
.frame(maxWidth: 600) .frame(maxWidth: 600)
} }
@ -48,8 +31,7 @@ struct PostList: View {
} }
} }
.listStyle(.plain) .listStyle(.plain)
.background(ColorPalette.listBackground) //.scrollContentBackground(.hidden)
.scrollContentBackground(.hidden)
} }
private func addNewPost() { private func addNewPost() {

View File

@ -1,13 +1,28 @@
import SwiftUI import SwiftUI
import SFSafeSymbols
struct PostView: View { struct PostView: View {
@ObservedObject
var post: Post
@Environment(\.language) @Environment(\.language)
var language private var language
var body: some View {
LocalizedPostView(post: post, localized: post.localized(in: language))
}
}
struct LocalizedPostView: View {
@ObservedObject @ObservedObject
var post: Post var post: Post
@ObservedObject
var localized: LocalizedPost
@State @State
private var showDatePicker = false private var showDatePicker = false
@ -20,6 +35,12 @@ struct PostView: View {
@State @State
private var showTagPicker = false private var showTagPicker = false
@Environment(\.language)
private var language
@EnvironmentObject
private var content: Content
private var linkedPageText: String { private var linkedPageText: String {
if let page = post.linkedPage { if let page = post.linkedPage {
return page.localized(in: language).title return page.localized(in: language).title
@ -29,7 +50,7 @@ struct PostView: View {
var body: some View { var body: some View {
VStack(alignment: .center) { VStack(alignment: .center) {
if post.localized(in: language).images.isEmpty { if localized.images.isEmpty {
Button(action: { showImagePicker = true }) { Button(action: { showImagePicker = true }) {
Text("Add image") Text("Add image")
} }
@ -37,7 +58,7 @@ struct PostView: View {
.foregroundStyle(.blue) .foregroundStyle(.blue)
.padding(.top) .padding(.top)
} else { } else {
PostImageGalleryView(post: post.localized(in: language)) PostImageGalleryView(post: localized)
.aspectRatio(1.33, contentMode: .fill) .aspectRatio(1.33, contentMode: .fill)
} }
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -49,7 +70,7 @@ struct PostView: View {
Toggle("Draft", isOn: $post.isDraft) Toggle("Draft", isOn: $post.isDraft)
} }
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
TextField("", text: post.localized(in: language).editableTitle()) TextField("", text: $localized.title)
.font(.system(size: 24, weight: .bold)) .font(.system(size: 24, weight: .bold))
.foregroundStyle(Color.primary) .foregroundStyle(Color.primary)
.textFieldStyle(.plain) .textFieldStyle(.plain)
@ -60,22 +81,21 @@ struct PostView: View {
en: tag.english.name, en: tag.english.name,
de: tag.german.name) de: tag.german.name)
) )
.onTapGesture { .foregroundStyle(.white)
remove(tag: tag)
}
} }
Button(action: { showTagPicker = true }) { Button(action: { showTagPicker = true }) {
SwiftUI.Image(systemSymbol: .plusCircleFill) Image(systemSymbol: .squareAndPencilCircleFill)
.resizable() .resizable()
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
.frame(height: 18) .frame(height: 22)
.foregroundColor(Color.blue) .foregroundColor(Color.blue)
//.opacity(0.7) .background(Circle()
.padding(.top, 3) .fill(Color.white)
.padding(1))
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }
TextEditor(text: post.localized(in: language).editableContent()) TextEditor(text: $localized.content)
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.textEditorStyle(.plain) .textEditorStyle(.plain)
@ -91,7 +111,7 @@ struct PostView: View {
} }
.padding() .padding()
} }
.background(Color.secondary.colorInvert()) .background(Color(NSColor.windowBackgroundColor))
.cornerRadius(8) .cornerRadius(8)
.sheet(isPresented: $showDatePicker) { .sheet(isPresented: $showDatePicker) {
DatePickerView( DatePickerView(
@ -106,8 +126,13 @@ struct PostView: View {
.sheet(isPresented: $showImagePicker) { .sheet(isPresented: $showImagePicker) {
ImagePickerView( ImagePickerView(
showImagePicker: $showImagePicker, 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 { List {
PostView(post: .fullMock) PostView(post: .fullMock)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
//.listRowBackground(ColorPalette.listBackground)
.environment(\.language, ContentLanguage.german) .environment(\.language, ContentLanguage.german)
PostView(post: .mock) PostView(post: .mock)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
//.listRowBackground(ColorPalette.listBackground)
} }
.environmentObject(Content.mock)
//.listStyle(.plain) //.listStyle(.plain)
} }

View 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]))
}