feat: 设置页面
This commit is contained in:
parent
2f0d487dc4
commit
b6178896ec
@ -3,7 +3,7 @@ import Foundation
|
||||
/// API 配置信息
|
||||
public enum APIConfig {
|
||||
/// API 基础 URL
|
||||
public static let baseURL = "https://api.memorywake.com/api/v1"
|
||||
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
|
||||
|
||||
/// 认证 token - 从 Keychain 中获取
|
||||
public static var authToken: String {
|
||||
|
||||
@ -1,40 +1,117 @@
|
||||
import SwiftUI
|
||||
|
||||
// User profile model
|
||||
struct UserProfile: Codable {
|
||||
let userId: String
|
||||
let nickname: String
|
||||
let avatarUrl: String?
|
||||
let account: String
|
||||
let email: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case userId = "user_id"
|
||||
case nickname
|
||||
case avatarUrl = "avatar_file_url"
|
||||
case account
|
||||
case email
|
||||
}
|
||||
}
|
||||
|
||||
// API Response wrapper
|
||||
struct APIResponse<T: Codable>: Codable {
|
||||
let code: Int
|
||||
let data: T
|
||||
}
|
||||
|
||||
struct UserProfileModal: View {
|
||||
@Binding var showModal: Bool
|
||||
@Binding var showSettings: Bool
|
||||
@State private var userProfile: UserProfile?
|
||||
@State private var isLoading = false
|
||||
@State private var errorMessage: String?
|
||||
@State private var isCopied = false
|
||||
|
||||
var body: some View {
|
||||
// Modal content with transparent background
|
||||
VStack(spacing: 20) {
|
||||
Spacer()
|
||||
.frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
||||
// 用户信息区域
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.padding()
|
||||
} else if let error = errorMessage {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
.padding()
|
||||
}
|
||||
|
||||
HStack(alignment: .center, spacing: 16) {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundColor(.blue)
|
||||
.clipShape(Circle())
|
||||
if let avatarUrl = userProfile?.avatarUrl, !avatarUrl.isEmpty, let url = URL(string: avatarUrl) {
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 60, height: 60)
|
||||
.clipShape(Circle())
|
||||
default:
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 60, height: 60)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Name")
|
||||
Text(userProfile?.nickname ?? "Name")
|
||||
.font(Typography.font(for: .body))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
HStack(spacing: 4) {
|
||||
Text("ID: 12345678")
|
||||
Text("ID: \(userProfile?.userId ?? "")")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: 120)
|
||||
|
||||
Button(action: {
|
||||
UIPasteboard.general.string = "12345678"
|
||||
print("Copy ID button tapped")
|
||||
UIPasteboard.general.string = userProfile?.userId
|
||||
print("Copied to clipboard:", userProfile?.userId ?? "nil")
|
||||
withAnimation {
|
||||
isCopied = true
|
||||
// Reset after 2 seconds
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
withAnimation {
|
||||
isCopied = false
|
||||
print("Reset copy button state")
|
||||
}
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
if isCopied {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.themePrimary)
|
||||
} else {
|
||||
Image(systemName: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.animation(.easeInOut, value: isCopied)
|
||||
.contentShape(Rectangle()) // Make the entire button area tappable
|
||||
.frame(width: 24, height: 24) // Ensure minimum touch target size
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,8 +129,8 @@ struct UserProfileModal: View {
|
||||
ZStack(alignment: .center) {
|
||||
// SVG背景 - 确保铺满整个区域
|
||||
SVGImage(svgName: "Pioneer")
|
||||
.scaledToFill()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.scaledToFill()
|
||||
|
||||
// 内容区域
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
@ -80,8 +157,8 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.frame(height: 112)
|
||||
.frame(maxWidth: .infinity)
|
||||
.cornerRadius(16)
|
||||
.clipped() // 确保内容不会超出边界
|
||||
|
||||
|
||||
VStack(spacing: 12) {
|
||||
// upload
|
||||
@ -102,9 +179,9 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.contentShape(Rectangle()) // 使整个区域可点击
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// memories
|
||||
Button(action: {
|
||||
@ -124,9 +201,9 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.contentShape(Rectangle()) // 使整个区域可点击
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// Box
|
||||
Button(action: {
|
||||
@ -146,9 +223,9 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.contentShape(Rectangle()) // 使整个区域可点击
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// setting
|
||||
Button(action: {
|
||||
@ -170,10 +247,9 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.contentShape(Rectangle()) // 使整个区域可点击
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
@ -189,6 +265,32 @@ struct UserProfileModal: View {
|
||||
.background(Color.themeTextWhiteSecondary)
|
||||
.cornerRadius(20, corners: [.topRight, .bottomRight])
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.onAppear {
|
||||
fetchUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchUserInfo() {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
NetworkService.shared.get(
|
||||
path: "/iam/user-info",
|
||||
parameters: nil
|
||||
) { (result: Result<APIResponse<UserProfile>, NetworkError>) in
|
||||
DispatchQueue.main.async {
|
||||
isLoading = false
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self.userProfile = response.data
|
||||
print("✅ Successfully fetched user info:", response.data)
|
||||
case .failure(let error):
|
||||
self.errorMessage = error.localizedDescription
|
||||
print("❌ Failed to fetch user info:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user