ios_client
This commit is contained in:
277
ios_client 0.8.5/Kecalek/Views/Profile/ProfileView.swift
Normal file
277
ios_client 0.8.5/Kecalek/Views/Profile/ProfileView.swift
Normal file
@@ -0,0 +1,277 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user