import SwiftUI import PhotosUI import UIKit struct ProfileView: View { var appState: AppState var isOwnProfile: Bool var userId: String? @State private var viewModel = ProfileViewModel() @State private var showLogoutConfirm = false @State private var showAvatarPicker = false @State private var showAuthorizeDevice = false @State private var showRotateKeys = false @State private var rotatePassword = "" @State private var isRotating = false @State private var rotateMessage: String? @State private var rotateIsError = false @State private var showChangeUsername = false @State private var newUsername = "" @State private var showChangePassword = false @State private var oldPassword = "" @State private var newPassword = "" @State private var confirmNewPassword = "" @State private var showVerification = false @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { Form { // Avatar Section { HStack { Spacer() VStack(spacing: 8) { if let avatarData = viewModel.avatarData, let uiImage = UIImage(data: avatarData) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 80, height: 80) .clipShape(Circle()) } else { CircularAvatarView( name: viewModel.profile?.username ?? "?", size: 80, isGroup: false ) } if isOwnProfile { Button("Change Photo") { showAvatarPicker = true } .font(.caption) } } Spacer() } .listRowBackground(Color.clear) } // Info Section("Info") { if let username = viewModel.profile?.username { LabeledContent("Username", value: username) } if let email = viewModel.profile?.email { LabeledContent("Email", value: email) } } if isOwnProfile { // Editable fields Section("Contact") { TextField("Phone", text: $viewModel.phone) .keyboardType(.phonePad) Toggle("Phone visible to contacts", isOn: $viewModel.phoneVisible) TextField("Location", text: $viewModel.location) Toggle("Location visible to contacts", isOn: $viewModel.locationVisible) } } else { // Read-only view if let phone = viewModel.profile?.phone, viewModel.profile?.phoneVisible == true { Section("Contact") { LabeledContent("Phone", value: phone) } } if let location = viewModel.profile?.location, viewModel.profile?.locationVisible == true { Section("Location") { LabeledContent("Location", value: location) } } } if !isOwnProfile, let uid = userId { Section("Security") { NavigationLink { SafetyNumberView( peerUserId: uid, peerUsername: viewModel.profile?.username ?? "User", chatClient: appState.chatClient ) } label: { Label("Verify Identity", systemImage: "checkmark.shield") } } } if let error = viewModel.errorMessage { Section { Text(error) .foregroundStyle(.red) } } if isOwnProfile { Section("Account") { Button { newUsername = viewModel.profile?.username ?? "" showChangeUsername = true } label: { Label("Change Username", systemImage: "person.text.rectangle") } Button { showChangePassword = true } label: { Label("Change Password", systemImage: "key") } } Section("Security") { Button { showAuthorizeDevice = true } label: { Label("Authorize New Device", systemImage: "iphone.badge.checkmark") } Button { showRotateKeys = true } label: { Label("Rotate Keys", systemImage: "arrow.triangle.2.circlepath") } } Section { Button(role: .destructive) { showLogoutConfirm = true } label: { HStack { Spacer() Text("Logout") Spacer() } } } } } .navigationTitle(isOwnProfile ? "My Profile" : "Profile") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { if isOwnProfile { Button("Save") { Task { let success = await viewModel.saveProfile(chatClient: appState.chatClient) if success { dismiss() } } } .disabled(viewModel.isSaving) } } ToolbarItem(placement: .topBarLeading) { if !isOwnProfile { Button { dismiss() } label: { Image(systemName: "xmark") } } } } .alert("Logout", isPresented: $showLogoutConfirm) { Button("Cancel", role: .cancel) {} Button("Logout", role: .destructive) { Task { await appState.logout() } } } message: { Text("Are you sure you want to logout?") } .sheet(isPresented: $showAvatarPicker) { ImagePickerView { data in Task { await viewModel.uploadAvatar(imageData: data, chatClient: appState.chatClient) } } } .sheet(isPresented: $showAuthorizeDevice) { AuthorizeDeviceView(appState: appState) } .alert("Rotate Keys", isPresented: $showRotateKeys) { SecureField("Password", text: $rotatePassword) Button("Cancel", role: .cancel) { rotatePassword = "" } Button("Rotate") { Task { isRotating = true let (success, msg) = await appState.chatClient.rotateKeys(password: rotatePassword) rotatePassword = "" isRotating = false rotateMessage = msg rotateIsError = !success } } } message: { Text("Enter your password to generate new keys. All other devices will be disconnected.") } .alert(rotateIsError ? "Error" : "Success", isPresented: Binding( get: { rotateMessage != nil }, set: { if !$0 { rotateMessage = nil } } )) { Button("OK") { rotateMessage = nil } } message: { Text(rotateMessage ?? "") } .alert("Change Username", isPresented: $showChangeUsername) { TextField("New username", text: $newUsername) Button("Cancel", role: .cancel) { newUsername = "" } Button("Change") { Task { let success = await viewModel.changeUsername(newUsername: newUsername, chatClient: appState.chatClient) if success { await viewModel.loadProfile(chatClient: appState.chatClient) } newUsername = "" } } } message: { Text("Enter a new display name.") } .alert("Change Password", isPresented: $showChangePassword) { SecureField("Current password", text: $oldPassword) SecureField("New password", text: $newPassword) SecureField("Confirm new password", text: $confirmNewPassword) Button("Cancel", role: .cancel) { oldPassword = "" newPassword = "" confirmNewPassword = "" } Button("Change") { Task { guard newPassword == confirmNewPassword else { viewModel.errorMessage = "Passwords don't match" return } _ = await viewModel.changePassword( oldPassword: oldPassword, newPassword: newPassword, chatClient: appState.chatClient ) oldPassword = "" newPassword = "" confirmNewPassword = "" } } } message: { Text("Enter your current password and a new password.") } .task { await viewModel.loadProfile(userId: userId, chatClient: appState.chatClient) } } } }