mana-swift-ui/Sources/ManaFeedbackUI/FeedbackFormModel.swift
Till JS 9844759e86 feat(ManaFeedbackUI): natives Feedback-Sheet + Button (F-4.1)
Neues Library-Produkt ManaFeedbackUI: ManaFeedbackSheet (Kind-Picker/
Titel/Beschreibung, anon + Kontakt-Mail), ManaFeedbackButton +
.manaFeedbackSheet()-Modifier. Postet via ManaCore.FeedbackClient.
Theming via \.manaTheme. swift build grün.
mana/docs/FEEDBACK_NATIVE_PLAN.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:25:05 +02:00

92 lines
2.6 KiB
Swift

import Foundation
import ManaCore
import Observation
/// State-Maschine für ``ManaFeedbackSheet``. Wraps ``FeedbackClient``.
///
/// Bei `isLoggedIn == false` wird zusätzlich ein optionales Kontakt-Mail-Feld
/// angeboten (Rückfrage-Adresse) und der Hinweis gezeigt, dass die
/// Einreichung erst durch die Moderation geht.
@MainActor
@Observable
public final class FeedbackFormModel {
public enum Status: Equatable, Sendable {
case idle
case sending
case sent
case error(String)
}
public var kind: FeedbackKind = .feedback
public var title: String = ""
public var details: String = ""
public var contactEmail: String = ""
public private(set) var status: Status = .idle
private let client: FeedbackClient
public let isLoggedIn: Bool
public init(client: FeedbackClient, isLoggedIn: Bool) {
self.client = client
self.isLoggedIn = isLoggedIn
}
/// Anonyme Einreichung Kontakt-Feld + Moderations-Hinweis zeigen.
public var showsContactField: Bool { !isLoggedIn }
public var canSubmit: Bool {
guard trimmed(title).count >= 3 else { return false }
if case .sending = status { return false }
return true
}
public var isSending: Bool {
if case .sending = status { return true }
return false
}
public var errorMessage: String? {
if case .error(let message) = status { return message }
return nil
}
public func submit() async {
guard canSubmit else { return }
status = .sending
let submission = FeedbackSubmission(
kind: kind,
title: trimmed(title),
description: trimmed(details),
contactEmail: isLoggedIn ? nil : nilIfEmpty(trimmed(contactEmail))
)
do {
_ = try await client.submit(submission)
status = .sent
} catch {
let message = (error as? LocalizedError)?.errorDescription
?? "Senden fehlgeschlagen. Bitte später erneut versuchen."
status = .error(message)
}
}
private func trimmed(_ value: String) -> String {
value.trimmingCharacters(in: .whitespacesAndNewlines)
}
private func nilIfEmpty(_ value: String) -> String? {
value.isEmpty ? nil : value
}
}
extension FeedbackKind {
/// Deutsche Anzeige-Bezeichnung neutral, nicht Wunsch"-lastig.
public var displayName: String {
switch self {
case .wunsch: return "Wunsch"
case .bug: return "Problem"
case .feedback: return "Feedback"
}
}
}