mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
ios: update profile edit sheet design
This commit is contained in:
parent
dfdb4af646
commit
00905ad790
2 changed files with 165 additions and 48 deletions
|
@ -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<String>
|
||||
) -> 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: "")
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue