Files
andros e4fdde15aa Full edit fidelity, poll field parity, mention warning, reaction remove
- EditPostView now edits Schedule / Visibility / Language / Mood / Tags; form
  state lives in an @Observable model to prevent init-time @State resets that
  were silently dropping selections. Visibility/Mood rows switched away from
  Menu/confirmationDialog (which refused to propagate selection inside the
  sheet) to an inline segmented Picker and a horizontal emoji strip + free
  text field. Shared across Compose and Edit via PostOptionRows.
- Polls now accept lang/tags/mood/visibility (spec-compliant); ComposeView no
  longer hides those rows when Poll is on. NewPostOptions.poll signature
  extended with the new optional params.
- Compose warns before publishing a mention-only post that has no
  [[org-social:URL][nick]] links in the body (would otherwise be invisible).
- Reactions can be removed: ReactionViewModel gains loadExistingReaction
  (scans own feed on row appear so the toggled state survives relaunches) and
  unreact (deletes the reaction post). Tapping the React button when already
  reacted now removes the reaction.
- PostRowView strips raw `- [ ] Option` checkbox lines from poll body render
  so the poll card is the sole UI for options.
- PostWriter.editPost learns MOOD; OrgSocialPost.mood promoted to var so
  timeline/profile applyEdit can update it.
2026-04-22 11:27:30 +02:00

130 lines
4.6 KiB
Swift

import SwiftUI
import OrgSocialKit
struct PostOptionToggleRow: View {
let icon: String
let label: String
@Binding var isOn: Bool
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon).foregroundStyle(.secondary).frame(width: 24)
Text(label)
Spacer()
Toggle("", isOn: $isOn).labelsHidden()
}
.padding(.horizontal, 16).padding(.vertical, 12)
}
}
struct PostOptionTextFieldRow: View {
let icon: String
let label: String
let placeholder: String
@Binding var text: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon).foregroundStyle(.secondary).frame(width: 24)
Text(label)
Spacer()
TextField(placeholder, text: $text)
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.frame(maxWidth: 160)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.padding(.horizontal, 16).padding(.vertical, 12)
}
}
/// Free-text mood plus a horizontal quick-pick of common emojis. Tap an emoji
/// to set it, or type anything in the text field. Emoji taps use plain
/// `.onTapGesture` because Menu / confirmationDialog fail to propagate the
/// selection back to the @Binding when hosted in the Edit sheet.
struct PostOptionMoodRow: View {
@Binding var mood: String
private static let moods = ["❤️", "👍", "😂", "🎉", "😮", "😢", "🤔", "🙏", "🔥", "😍", "😊", "🚀", ""]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 12) {
Image(systemName: "face.smiling").foregroundStyle(.secondary).frame(width: 24)
Text("Mood")
Spacer()
TextField("free text or pick below", text: $mood)
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.frame(maxWidth: 200)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(Self.moods, id: \.self) { emoji in
let isSelected = mood == emoji
Text(emoji)
.font(.title2)
.padding(6)
.background(
Circle().fill(isSelected ? Color.accentColor.opacity(0.18) : Color.clear)
)
.onTapGesture { mood = emoji }
}
}
.padding(.leading, 52)
.padding(.trailing, 16)
}
}
.padding(.horizontal, 16).padding(.vertical, 12)
}
}
/// Two-state toggle: tap the row to flip between `.public` and `.mention`. A
/// plain tap gesture is used (not Menu / confirmationDialog / Picker.menu)
/// because those all failed to deliver their inner-Button actions when hosted
/// inside the Edit sheet on iOS the dialog would dismiss without the
/// selection being written back to the @Binding.
struct PostOptionVisibilityRow: View {
@Binding var visibility: NewPostOptions.Visibility
var body: some View {
HStack(spacing: 12) {
Image(systemName: visibility == .public ? "globe" : "at")
.foregroundStyle(.secondary).frame(width: 24)
Text("Visibility").foregroundStyle(.primary)
Spacer()
Picker("", selection: $visibility) {
Text("Public").tag(NewPostOptions.Visibility.public)
Text("Mention only").tag(NewPostOptions.Visibility.mention)
}
.pickerStyle(.segmented)
.frame(maxWidth: 220)
}
.padding(.horizontal, 16).padding(.vertical, 12)
}
}
struct PostOptionScheduleSection: View {
@Binding var isScheduled: Bool
@Binding var scheduledDate: Date
var body: some View {
PostOptionToggleRow(icon: "clock", label: "Schedule", isOn: $isScheduled)
if isScheduled {
Divider().padding(.leading, 52)
HStack(spacing: 12) {
Image(systemName: "calendar")
.foregroundStyle(.secondary).frame(width: 24)
Text("Date")
Spacer()
DatePicker("", selection: $scheduledDate, in: Date()...)
.labelsHidden()
}
.padding(.horizontal, 16).padding(.vertical, 12)
}
}
}