diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 198fd495bd..986164a1fd 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -9,6 +9,171 @@ import SwiftUI import SimpleXChat +struct NewUserProfile: View { + @State private var profile = Profile(displayName: "", fullName: "") + // Modals + @State private var showChooseSource = false + @State private var showImagePicker = false + @State private var showFilePicker = false + @State private var showTakePhoto = false + @State private var chosenImage: UIImage? = nil + @State private var alert: UserProfileAlert? + + var body: some View { + List { + Section { + Text(""" +Your profile is stored on your device and shared only with your contacts. +SimpleX servers cannot see your profile. +""" + ) + } + Section { + ProfileImage(imageStr: profile.image, size: 128) + .padding(8) + .overlay { + if profile.image != nil { + overlayButton("xmark", color: .red, alignment: .topTrailing) { profile.image = nil } + } + overlayButton("pencil", color: .accentColor, alignment: .bottomTrailing) { + showChooseSource = true + } + } + .frame(maxWidth: .infinity, alignment: .center) + if showFullName { + nameField("Full name", text: $profile.fullName) + } + nameField("Profile name", text: $profile.displayName) + Button("Save and notify contacts", action: saveProfile).disabled(!canSaveProfile) + } + } + // Lifecycle + .task { + if let user = ChatModel.shared.currentUser { + profile = fromLocalProfile(user.profile) + } + } + .onChange(of: chosenImage) { image in + if let image { + profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + profile.image = nil + } + } + // Modals + .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { + Button("Take picture") { + showTakePhoto = true + } + Button("Choose from library") { + showImagePicker = true + } + Button("Choose file") { + showFilePicker = true + } + if UIPasteboard.general.hasImages { + Button("Paste image") { + chosenImage = UIPasteboard.general.image + } + } + } + .fullScreenCover(isPresented: $showTakePhoto) { + ZStack { + Color.black.edgesIgnoringSafeArea(.all) + CameraImagePicker(image: $chosenImage) + } + } + .sheet(isPresented: $showImagePicker) { + LibraryImagePicker(image: $chosenImage) { _ in + await MainActor.run { + showImagePicker = false + } + } + } + .fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.image]) { url in + + } + .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } + } + + @ViewBuilder + private func overlayButton( + _ systemName: String, + color: Color, + alignment: Alignment, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .foregroundStyle(color) + .imageScale(.large) + .padding(8) + .background(.bar) + .clipShape(Circle()) + .onTapGesture(perform: action) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment) + } + + @ViewBuilder + private func nameField( + _ title: LocalizedStringKey, + text: Binding + ) -> some View { + let isValid = validDisplayName(text.wrappedValue) + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(title).foregroundStyle(.secondary).font(.caption) + Spacer() + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .opacity(isValid ? 0 : 1) + .onTapGesture { + alert = .invalidNameError(validName: mkValidName(profile.displayName)) + } + } + TextField(title, text: text) + .padding(.vertical, 4) + .padding(.horizontal, 4) + .overlay { + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(isValid ? Color(.tertiaryLabel) : Color.red) + } + }.listRowSeparator(.hidden) + } + + // MARK: Computed + private func validNewProfileName(_ user: User) -> Bool { + profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces)) + } + + private var showFullName: Bool { + profile.fullName != "" && + profile.fullName != profile.displayName + } + + private var canSaveProfile: Bool { + profile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces)) + } + + private func saveProfile() { + Task { + do { + profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) + if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { + DispatchQueue.main.async { + ChatModel.shared.updateCurrentUser(newProfile) + profile = newProfile + } + } else { + alert = .duplicateUserError + } + } catch { + logger.error("UserProfile apiUpdateProfile error: \(responseError(error))") + } + } + } +} + struct UserProfile: View { @EnvironmentObject var chatModel: ChatModel @State private var profile = Profile(displayName: "", fullName: "") diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index c8623a95cb..0000000000 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,48 +0,0 @@ -{ - "originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115", - "pins" : [ - { - "identity" : "codescanner", - "kind" : "remoteSourceControl", - "location" : "https://github.com/twostraws/CodeScanner", - "state" : { - "revision" : "34da57fb63b47add20de8a85da58191523ccce57", - "version" : "2.5.0" - } - }, - { - "identity" : "lzstring-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Ibrahimhass/lzstring-swift", - "state" : { - "revision" : "7f62f21de5b18582a950e1753b775cc614722407" - } - }, - { - "identity" : "swiftygif", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kirualex/SwiftyGif", - "state" : { - "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" - } - }, - { - "identity" : "webrtc", - "kind" : "remoteSourceControl", - "location" : "https://github.com/simplex-chat/WebRTC.git", - "state" : { - "revision" : "34bedc50f9c58dccf4967ea59c7e6a47d620803b" - } - }, - { - "identity" : "yams", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/Yams", - "state" : { - "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", - "version" : "5.1.2" - } - } - ], - "version" : 3 -}