| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- 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()
- }
|