import SwiftUI /// Main "我" (Mine) Tab View for user authentication, profile management, cloud sync status, and BLE device management. struct ProfileView: View { @ObservedObject var authManager: AuthManager = .shared @ObservedObject var bleManager: BLEManager = .shared @State private var showAuthModal = false @State private var showEditProfileModal = false @State private var showChangePasswordModal = false @State private var showScanDeviceModal = false @State private var showSyncAuthPrompt = false @State private var showLogoutConfirmation = false @State private var syncToastMessage: String? var body: some View { NavigationStack { ZStack { Color.spaceBlack.ignoresSafeArea() ScrollView { VStack(spacing: 20) { // 1. Account Profile Header accountHeaderCard .padding(.top, 12) // 2. User Management Actions (if logged in) if authManager.isLoggedIn { userManagementActionsCard } // 3. Audio Cloud Sync Card audioSyncCard // 4. Device Binding & Management DeviceListView( bleManager: bleManager, authManager: authManager, onAddDeviceClicked: handleAddDevice ) // 5. Account Settings & Security accountInfoSection // Footer footer .padding(.top, 12) .padding(.bottom, 40) } .padding(.horizontal, 16) } } .navigationTitle("个人中心") .navigationBarTitleDisplayMode(.inline) .sheet(isPresented: $showAuthModal) { AuthModalView(authManager: authManager) } .sheet(isPresented: $showEditProfileModal) { EditProfileModalView(authManager: authManager) } .sheet(isPresented: $showChangePasswordModal) { ChangePasswordModalView(authManager: authManager) } .sheet(isPresented: $showScanDeviceModal) { DeviceScanView(bleManager: bleManager, authManager: authManager) } .sheet(isPresented: $showSyncAuthPrompt) { SyncAuthPromptModal(onLoginClick: { showAuthModal = true }) } .confirmationDialog("确定要退出登录吗?", isPresented: $showLogoutConfirmation, titleVisibility: .visible) { Button("退出登录", role: .destructive) { authManager.logout() } Button("取消", role: .cancel) {} } } } // MARK: - 1. Account Header private var accountHeaderCard: some View { VStack(spacing: 16) { if let user = authManager.currentUser { // Authenticated Profile State HStack(spacing: 16) { ZStack { Circle() .fill(Color.primary.opacity(0.12)) .frame(width: 60, height: 60) Image(systemName: "person.crop.circle.fill") .font(.system(size: 34)) .foregroundStyle(Color.primary) } VStack(alignment: .leading, spacing: 6) { HStack { Text(user.username) .font(.system(size: 18, weight: .bold)) .foregroundStyle(Color.primary) Text("本地授权") .font(.system(size: 9, weight: .bold, design: .monospaced)) .foregroundStyle(Color.spaceBlack) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.primary) .cornerRadius(4) } Text(user.email ?? user.phoneNumber ?? ("用户 ID: " + String(user.id.prefix(12)))) .font(.system(size: 12)) .foregroundStyle(Color.secondary) } Spacer() Button { showLogoutConfirmation = true } label: { Text("退出") .font(.system(size: 12, weight: .regular)) .foregroundStyle(Color.recordingRed) .padding(.horizontal, 10) .padding(.vertical, 5) .overlay( RoundedRectangle(cornerRadius: 6) .stroke(Color.recordingRed.opacity(0.4), lineWidth: 1) ) } } } else { // Guest / Unauthenticated State VStack(spacing: 14) { HStack(spacing: 14) { ZStack { Circle() .fill(Color.primary.opacity(0.06)) .frame(width: 52, height: 52) Image(systemName: "person.crop.circle.badge.plus") .font(.system(size: 24)) .foregroundStyle(Color.secondary) } VStack(alignment: .leading, spacing: 4) { Text("未登录账号") .font(.system(size: 16, weight: .semibold)) .foregroundStyle(Color.primary) Text("登录/注册后支持全功能体验与 BLE 硬件绑定") .font(.system(size: 12)) .foregroundStyle(Color.secondary) } Spacer() } Button { showAuthModal = true } label: { Text("注册 / 登录账号") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Color.spaceBlack) .frame(maxWidth: .infinity) .padding(.vertical, 11) .background(Color.primary) .cornerRadius(8) } } } } .padding(16) .background(Color.cardBackground.opacity(0.25)) .businessBorder(cornerRadius: 12) } // MARK: - 2. User Management Actions private var userManagementActionsCard: some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 8) { Image(systemName: "slider.horizontal.3") .font(.system(size: 14, weight: .light)) .foregroundStyle(Color.secondary) Text("用户资料管理") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Color.primary) } HStack(spacing: 12) { Button { showEditProfileModal = true } label: { HStack(spacing: 6) { Image(systemName: "square.and.pencil") .font(.system(size: 12)) Text("编辑基本资料") .font(.system(size: 13, weight: .medium)) } .foregroundStyle(Color.primary) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(Color.cardBackground.opacity(0.4)) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.primary.opacity(0.2), lineWidth: 1) ) } Button { showChangePasswordModal = true } label: { HStack(spacing: 6) { Image(systemName: "key.fill") .font(.system(size: 12)) Text("修改安全密码") .font(.system(size: 13, weight: .medium)) } .foregroundStyle(Color.primary) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(Color.cardBackground.opacity(0.4)) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.primary.opacity(0.2), lineWidth: 1) ) } } } .padding(16) .background(Color.cardBackground.opacity(0.25)) .businessBorder(cornerRadius: 12) } // MARK: - 3. Audio Cloud Sync Card private var audioSyncCard: some View { VStack(alignment: .leading, spacing: 14) { HStack { HStack(spacing: 8) { Image(systemName: "icloud.and.arrow.up") .font(.system(size: 14, weight: .light)) .foregroundStyle(Color.secondary) Text("录音云端同步") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Color.primary) } Spacer() if authManager.isLoggedIn { HStack(spacing: 4) { Circle() .fill(Color.green) .frame(width: 6, height: 6) Text("同步功能就绪") .font(.system(size: 11)) .foregroundStyle(Color.secondary) } } else { Text("需要登录") .font(.system(size: 11)) .foregroundStyle(Color.recordingRed) } } VStack(alignment: .leading, spacing: 8) { HStack { Text("上次同步时间") .font(.system(size: 12)) .foregroundStyle(Color.secondary) Spacer() if let lastDate = authManager.lastSyncDate { Text(lastDate.formatted(date: .numeric, time: .shortened)) .font(.system(size: 12, design: .monospaced)) .foregroundStyle(Color.primary) } else { Text("暂无同步记录") .font(.system(size: 12, design: .monospaced)) .foregroundStyle(Color.secondary) } } if let toast = syncToastMessage { HStack(spacing: 6) { Image(systemName: "checkmark.circle.fill") .foregroundStyle(Color.primary) Text(toast) .font(.system(size: 12)) .foregroundStyle(Color.primary) } .padding(.top, 2) } } Button { triggerAudioSync() } label: { HStack(spacing: 8) { if authManager.isSyncing { ProgressView() .tint(Color.primary) } else { Image(systemName: "arrow.triangle.2.circlepath") .font(.system(size: 13)) } Text(authManager.isSyncing ? "正在同步音频与记录..." : "立即同步录音文件") .font(.system(size: 13, weight: .medium)) } .foregroundStyle(Color.primary) .frame(maxWidth: .infinity) .padding(.vertical, 10) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.primary.opacity(0.3), lineWidth: 1) ) } .disabled(authManager.isSyncing) } .padding(16) .background(Color.cardBackground.opacity(0.25)) .businessBorder(cornerRadius: 12) } // MARK: - 5. Account Settings Section private var accountInfoSection: some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 8) { Image(systemName: "shield.checkerboard") .font(.system(size: 14, weight: .light)) .foregroundStyle(Color.secondary) Text("安全与云端合规") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Color.primary) } VStack(spacing: 10) { infoRow(label: "数据存储模式", value: "沙盒 SQLite / UserDefaults") infoRow(label: "后端接口状态", value: "已适配 Local & Remote 两套架构") infoRow(label: "数据加密机制", value: "AES-256 本地私钥保护") } } .padding(16) .background(Color.cardBackground.opacity(0.25)) .businessBorder(cornerRadius: 12) } private func infoRow(label: String, value: String) -> some View { HStack { Text(label) .font(.system(size: 12)) .foregroundStyle(Color.secondary) Spacer() Text(value) .font(.system(size: 12, weight: .regular, design: .monospaced)) .foregroundStyle(Color.primary) } } // MARK: - Actions private func handleAddDevice() { if authManager.isLoggedIn { showScanDeviceModal = true } else { showAuthModal = true } } private func triggerAudioSync() { authManager.syncRecordings { showSyncAuthPrompt = true } onComplete: { success in if success { withAnimation { syncToastMessage = "录音数据与元数据已成功同步!" } DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { syncToastMessage = nil } } } } // MARK: - Footer private var footer: some View { VStack(spacing: 4) { Text("CELESTIA TRACE USER & DEVICE") .font(.system(size: 10, weight: .bold, design: .monospaced)) .foregroundStyle(Color.secondary.opacity(0.4)) .tracking(2) Text("安全本地注册与极简日志空间") .font(.system(size: 9)) .foregroundStyle(Color.secondary.opacity(0.3)) } } } #Preview { ProfileView() }