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 */; };
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 */,

View File

@ -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)) {

View File

@ -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() {

View File

@ -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)
}

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