feat: feedback with photo support + web dashboard feedback button
iOS: - Photo picker in feedback sheet (screenshot/photo attachment) - Image sent as base64, uploaded to Gitea issue as attachment Web: - Feedback button in sidebar rail - Modal with text area + send - Auto-labels same as iOS Gateway: - Multipart image upload to Gitea issue assets API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
struct FeedbackButton: View {
|
||||
@State private var showSheet = false
|
||||
@@ -15,7 +16,7 @@ struct FeedbackButton: View {
|
||||
}
|
||||
.sheet(isPresented: $showSheet) {
|
||||
FeedbackSheet()
|
||||
.presentationDetents([.medium])
|
||||
.presentationDetents([.large, .medium])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +27,9 @@ struct FeedbackSheet: View {
|
||||
@State private var isSending = false
|
||||
@State private var sent = false
|
||||
@State private var error: String?
|
||||
@State private var selectedPhoto: PhotosPickerItem?
|
||||
@State private var photoData: Data?
|
||||
@State private var photoPreview: UIImage?
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
@@ -64,6 +68,43 @@ struct FeedbackSheet: View {
|
||||
.stroke(Color.textTertiary.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
|
||||
// Photo attachment
|
||||
HStack {
|
||||
PhotosPicker(selection: $selectedPhoto, matching: .screenshots) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "camera.fill")
|
||||
.font(.caption)
|
||||
Text(photoPreview != nil ? "Change photo" : "Add screenshot")
|
||||
.font(.caption.weight(.medium))
|
||||
}
|
||||
.foregroundStyle(Color.accentWarm)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color.accentWarm.opacity(0.1))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
if let preview = photoPreview {
|
||||
Image(uiImage: preview)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
|
||||
Button {
|
||||
photoData = nil
|
||||
photoPreview = nil
|
||||
selectedPhoto = nil
|
||||
} label: {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.textTertiary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if let error {
|
||||
Text(error)
|
||||
.font(.caption)
|
||||
@@ -90,6 +131,15 @@ struct FeedbackSheet: View {
|
||||
}
|
||||
.padding()
|
||||
.background(Color.surfaceCard)
|
||||
.onChange(of: selectedPhoto) {
|
||||
Task {
|
||||
if let item = selectedPhoto,
|
||||
let data = try? await item.loadTransferable(type: Data.self) {
|
||||
photoData = data
|
||||
photoPreview = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sendFeedback() {
|
||||
@@ -98,7 +148,10 @@ struct FeedbackSheet: View {
|
||||
|
||||
Task {
|
||||
do {
|
||||
let body: [String: String] = ["text": text.trimmingCharacters(in: .whitespaces), "source": "ios"]
|
||||
var body: [String: Any] = ["text": text.trimmingCharacters(in: .whitespaces), "source": "ios"]
|
||||
if let imgData = photoData {
|
||||
body["image"] = imgData.base64EncodedString()
|
||||
}
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: body)
|
||||
let data = try await APIClient.shared.rawPost(path: "/api/feedback", data: jsonData)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user