2022-01-31 21:28:07 +00:00
|
|
|
//
|
2022-02-01 17:34:06 +00:00
|
|
|
// UserProfile.swift
|
2022-01-31 21:28:07 +00:00
|
|
|
// SimpleX
|
|
|
|
|
//
|
|
|
|
|
// Created by Evgeny Poberezkin on 31/01/2022.
|
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import SwiftUI
|
2022-05-31 07:55:13 +01:00
|
|
|
import SimpleXChat
|
2022-01-31 21:28:07 +00:00
|
|
|
|
2022-02-01 17:34:06 +00:00
|
|
|
struct UserProfile: View {
|
2022-01-31 21:28:07 +00:00
|
|
|
@EnvironmentObject var chatModel: ChatModel
|
|
|
|
|
@State private var profile = Profile(displayName: "", fullName: "")
|
2022-03-25 22:13:01 +04:00
|
|
|
@State private var editProfile = false
|
|
|
|
|
@State private var showChooseSource = false
|
|
|
|
|
@State private var showImagePicker = false
|
2022-05-18 21:32:30 +04:00
|
|
|
@State private var showTakePhoto = false
|
2022-04-04 19:19:54 +01:00
|
|
|
@State private var chosenImage: UIImage? = nil
|
2022-01-31 21:28:07 +00:00
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
let user: User = chatModel.currentUser!
|
|
|
|
|
|
|
|
|
|
return VStack(alignment: .leading) {
|
|
|
|
|
Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.")
|
|
|
|
|
.padding(.bottom)
|
2022-03-25 22:13:01 +04:00
|
|
|
|
2022-01-31 21:28:07 +00:00
|
|
|
if editProfile {
|
2022-03-25 22:13:01 +04:00
|
|
|
ZStack(alignment: .center) {
|
|
|
|
|
ZStack(alignment: .topTrailing) {
|
|
|
|
|
profileImageView(profile.image)
|
|
|
|
|
if user.image != nil {
|
|
|
|
|
Button {
|
|
|
|
|
profile.image = nil
|
|
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "multiply")
|
|
|
|
|
.resizable()
|
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
|
.frame(width: 12)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editImageButton { showChooseSource = true }
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
|
|
|
|
2022-01-31 21:28:07 +00:00
|
|
|
VStack(alignment: .leading) {
|
2022-05-12 15:07:28 +01:00
|
|
|
ZStack(alignment: .leading) {
|
|
|
|
|
if !validDisplayName(profile.displayName) {
|
|
|
|
|
Image(systemName: "exclamationmark.circle")
|
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
.padding(.bottom, 10)
|
|
|
|
|
}
|
|
|
|
|
profileNameTextEdit("Display name", $profile.displayName)
|
|
|
|
|
}
|
2022-03-25 22:13:01 +04:00
|
|
|
profileNameTextEdit("Full name (optional)", $profile.fullName)
|
2022-01-31 21:28:07 +00:00
|
|
|
HStack(spacing: 20) {
|
|
|
|
|
Button("Cancel") { editProfile = false }
|
2022-02-02 12:51:39 +00:00
|
|
|
Button("Save (and notify contacts)") { saveProfile() }
|
2022-05-12 15:07:28 +01:00
|
|
|
.disabled(profile.displayName == "" || !validDisplayName(profile.displayName))
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
|
|
|
|
|
} else {
|
2022-03-25 22:13:01 +04:00
|
|
|
ZStack(alignment: .center) {
|
|
|
|
|
profileImageView(user.image)
|
|
|
|
|
.onTapGesture { startEditingImage(user) }
|
|
|
|
|
|
|
|
|
|
if user.image == nil {
|
|
|
|
|
editImageButton { startEditingImage(user) }
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
2022-03-25 22:13:01 +04:00
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
|
|
|
|
|
|
|
|
VStack(alignment: .leading) {
|
|
|
|
|
profileNameView("Display name:", user.profile.displayName)
|
|
|
|
|
profileNameView("Full name:", user.profile.fullName)
|
2022-01-31 21:28:07 +00:00
|
|
|
Button("Edit") {
|
2022-08-23 18:18:12 +04:00
|
|
|
profile = fromLocalProfile(user.profile)
|
2022-01-31 21:28:07 +00:00
|
|
|
editProfile = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.padding()
|
2022-02-03 07:16:29 +00:00
|
|
|
.frame(maxHeight: .infinity, alignment: .top)
|
2022-03-25 22:13:01 +04:00
|
|
|
.confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
|
|
|
|
Button("Take picture") {
|
2022-05-18 21:32:30 +04:00
|
|
|
showTakePhoto = true
|
2022-03-25 22:13:01 +04:00
|
|
|
}
|
|
|
|
|
Button("Choose from library") {
|
|
|
|
|
showImagePicker = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-18 21:32:30 +04:00
|
|
|
.fullScreenCover(isPresented: $showTakePhoto) {
|
|
|
|
|
ZStack {
|
|
|
|
|
Color.black.edgesIgnoringSafeArea(.all)
|
2022-04-04 19:19:54 +01:00
|
|
|
CameraImagePicker(image: $chosenImage)
|
|
|
|
|
}
|
2022-03-25 22:13:01 +04:00
|
|
|
}
|
2022-05-18 21:32:30 +04:00
|
|
|
.sheet(isPresented: $showImagePicker) {
|
|
|
|
|
LibraryImagePicker(image: $chosenImage) {
|
|
|
|
|
didSelectItem in showImagePicker = false
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-04 19:19:54 +01:00
|
|
|
.onChange(of: chosenImage) { image in
|
2022-04-08 18:17:10 +01:00
|
|
|
if let image = image {
|
2022-04-25 12:44:24 +04:00
|
|
|
profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
2022-03-25 22:13:01 +04:00
|
|
|
} else {
|
|
|
|
|
profile.image = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-26 20:32:24 +01:00
|
|
|
func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding<String>) -> some View {
|
2022-03-25 22:13:01 +04:00
|
|
|
TextField(label, text: name)
|
|
|
|
|
.textInputAutocapitalization(.never)
|
|
|
|
|
.disableAutocorrection(true)
|
|
|
|
|
.padding(.bottom)
|
2022-05-12 15:07:28 +01:00
|
|
|
.padding(.leading, 28)
|
2022-03-25 22:13:01 +04:00
|
|
|
}
|
|
|
|
|
|
2022-09-26 20:32:24 +01:00
|
|
|
func profileNameView(_ label: LocalizedStringKey, _ name: String) -> some View {
|
2022-03-25 22:13:01 +04:00
|
|
|
HStack {
|
|
|
|
|
Text(label)
|
|
|
|
|
Text(name).fontWeight(.bold)
|
|
|
|
|
}
|
|
|
|
|
.padding(.bottom)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func startEditingImage(_ user: User) {
|
2022-08-23 18:18:12 +04:00
|
|
|
profile = fromLocalProfile(user.profile)
|
2022-03-25 22:13:01 +04:00
|
|
|
editProfile = true
|
|
|
|
|
showChooseSource = true
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-02-02 12:51:39 +00:00
|
|
|
func saveProfile() {
|
2022-02-24 17:16:41 +00:00
|
|
|
Task {
|
|
|
|
|
do {
|
|
|
|
|
if let newProfile = try await apiUpdateProfile(profile: profile) {
|
|
|
|
|
DispatchQueue.main.async {
|
2022-08-23 18:18:12 +04:00
|
|
|
if let profileId = chatModel.currentUser?.profile.profileId {
|
2022-08-25 17:36:26 +04:00
|
|
|
chatModel.currentUser?.profile = toLocalProfile(profileId, newProfile, "")
|
2022-08-23 18:18:12 +04:00
|
|
|
}
|
2022-02-24 17:16:41 +00:00
|
|
|
profile = newProfile
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-30 13:03:44 +01:00
|
|
|
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
2022-02-24 17:16:41 +00:00
|
|
|
editProfile = false
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-28 11:49:36 +01:00
|
|
|
func profileImageView(_ imageStr: String?) -> some View {
|
|
|
|
|
ProfileImage(imageStr: imageStr)
|
|
|
|
|
.aspectRatio(1, contentMode: .fit)
|
|
|
|
|
.frame(maxWidth: 192, maxHeight: 192)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func editImageButton(action: @escaping () -> Void) -> some View {
|
|
|
|
|
Button {
|
|
|
|
|
action()
|
|
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "camera")
|
|
|
|
|
.resizable()
|
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
|
.frame(width: 48)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-01 17:34:06 +00:00
|
|
|
struct UserProfile_Previews: PreviewProvider {
|
2022-01-31 21:28:07 +00:00
|
|
|
static var previews: some View {
|
2022-03-25 22:13:01 +04:00
|
|
|
let chatModel1 = ChatModel()
|
|
|
|
|
chatModel1.currentUser = User.sampleData
|
|
|
|
|
let chatModel2 = ChatModel()
|
|
|
|
|
chatModel2.currentUser = User.sampleData
|
|
|
|
|
chatModel2.currentUser?.profile.image = "data:image/jpg;base64,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
|
|
|
|
|
return Group {
|
|
|
|
|
UserProfile()
|
|
|
|
|
.environmentObject(chatModel1)
|
|
|
|
|
UserProfile()
|
|
|
|
|
.environmentObject(chatModel2)
|
|
|
|
|
}
|
2022-01-31 21:28:07 +00:00
|
|
|
}
|
|
|
|
|
}
|