punchnet-macos/punchnet/Views/Settings/SettingsUserIssueView.swift
2026-03-23 14:58:32 +08:00

189 lines
7.2 KiB
Swift

//
// SettingsUserIssueView.swift
// punchnet
//
// Created by on 2026/1/19.
//
import SwiftUI
// MARK: - ()
struct SettingsUserIssueView: View {
@Environment(UserContext.self) var userContext: UserContext
//
@State private var account: String = ""
@State private var text: String = ""
//
@State private var isSubmitting: Bool = false
@State private var showSuccessToast: Bool = false
//
@State private var showAlert: Bool = false
@State private var errorMessage: String = ""
var body: some View {
ZStack {
//
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 24) {
// 1.
HStack(spacing: 12) {
Image(systemName: "envelope.badge.fill")
.foregroundColor(.blue)
Text("用户反馈")
.font(.system(size: 18, weight: .bold))
}
.padding(.leading, 4)
// 2.
VStack(alignment: .leading, spacing: 20) {
//
VStack(alignment: .leading, spacing: 8) {
Text("联系方式 (选填)")
.font(.caption.bold())
.foregroundColor(.secondary)
TextField("邮箱或用户名", text: $account)
.textFieldStyle(.plain)
.padding(10)
.background(Color.primary.opacity(0.04))
.cornerRadius(8)
}
// ( Placeholder )
VStack(alignment: .leading, spacing: 8) {
Text("问题描述")
.font(.caption.bold())
.foregroundColor(.secondary)
ZStack(alignment: .topLeading) {
if text.isEmpty {
Text("请详细描述您遇到的问题...")
.foregroundColor(.gray.opacity(0.5))
.padding(.horizontal, 12).padding(.vertical, 12)
.font(.system(size: 14))
}
TextEditor(text: $text)
.font(.system(size: 14))
.scrollContentBackground(.hidden) //
.padding(8)
.background(Color.primary.opacity(0.04))
.cornerRadius(8)
}
.frame(minHeight: 160)
}
}
.padding(20)
.background(Color.primary.opacity(0.02))
.cornerRadius(12)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.primary.opacity(0.05), lineWidth: 1))
// 3.
Button {
Task { @MainActor in
await self.submitFeedback()
}
} label: {
HStack {
if isSubmitting {
ProgressView()
.controlSize(.small).brightness(1)
} else {
Image(systemName: "paperplane.fill")
}
Text("发送反馈").fontWeight(.semibold)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(text.isEmpty ? Color.gray : Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.buttonStyle(.plain)
.disabled(text.isEmpty || isSubmitting)
}
.padding(32)
.frame(maxWidth: 600, alignment: .leading)
}
.blur(radius: showSuccessToast ? 8 : 0) //
.disabled(showSuccessToast) //
// 4. Overlay
if showSuccessToast {
successPopup
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("提示"), message: Text(errorMessage))
}
}
// MARK: -
private func submitFeedback() async {
withAnimation {
isSubmitting = true
}
let params: [String: Any] = [
"access_token": self.userContext.networkSession?.accessToken ?? "",
"contact": self.account,
"platform": SystemConfig.systemInfo,
"content": self.text,
"client_id": SystemConfig.getClientId(),
"mac": SystemConfig.macAddressString(mac: SystemConfig.getMacAddress())
]
do {
_ = try await SDLAPIClient.doPost(path: "/app/issue", params: params, as: String.self)
withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
isSubmitting = false
showSuccessToast = true
text = "" //
}
// 2.5
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
withAnimation(.easeOut(duration: 0.3)) {
showSuccessToast = false
}
}
} catch let err as SDLAPIError {
self.showAlert = true
self.errorMessage = err.message
} catch let err {
self.showAlert = true
self.errorMessage = err.localizedDescription
}
self.isSubmitting = false
}
// MARK: -
private var successPopup: some View {
VStack(spacing: 16) {
Image(systemName: "checkmark.seal.fill")
.font(.system(size: 44))
.foregroundStyle(.green.gradient)
Text("发送成功")
.font(.headline)
Text("感谢您的支持!")
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(40)
.background(.ultraThinMaterial) // macOS
.cornerRadius(24)
.shadow(color: .black.opacity(0.15), radius: 20)
.transition(.asymmetric(
insertion: .scale(scale: 0.8).combined(with: .opacity),
removal: .opacity.combined(with: .scale(scale: 1.1))
))
}
}