ios: update profile edit sheet design

This commit is contained in:
Levitating Pineapple 2024-09-12 14:31:27 +03:00
parent dfdb4af646
commit 00905ad790
No known key found for this signature in database
GPG key ID: 6EAADA587E14B73A
2 changed files with 165 additions and 48 deletions

View file

@ -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: "")

View file

@ -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
}