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>
92 lines
2.6 KiB
Swift
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"
|
|
}
|
|
}
|
|
}
|