From b7446f6d26c936793216ebc0cd1dd5f6a243f7ed Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Fri, 20 Dec 2024 16:59:39 +0100 Subject: [PATCH] Fix avatar and displayname in ConversationInfoFragment --- Linphone/UI/Main/ContentView.swift | 57 ++++-- .../Fragments/ConversationInfoFragment.swift | 2 +- .../Model/ConversationModel.swift | 2 +- .../Main/Fragments/SideMenuAccountRow.swift | 2 +- .../Fragments/AccountProfileFragment.swift | 178 +++++++++--------- .../ViewModel/AccountProfileViewModel.swift | 32 ++-- Linphone/UI/Main/Viewmodel/AccountModel.swift | 30 +++ 7 files changed, 177 insertions(+), 126 deletions(-) diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 294231aa5..0278368ee 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -79,10 +79,13 @@ struct ContentView: View { @State private var isShowLoginFragment: Bool = false private let avatarSize = 45.0 + @State private var imagePath: URL? var body: some View { let pub = NotificationCenter.default .publisher(for: NSNotification.Name("ContactLoaded")) + let imageChanged = NotificationCenter.default + .publisher(for: NSNotification.Name("ImageChanged")) GeometryReader { geometry in VStack(spacing: 0) { if (telecomManager.callInProgress && !fullscreenVideo && ((!telecomManager.callDisplayed && callViewModel.callsCounter == 1) || callViewModel.callsCounter > 1)) || isShowConversationFragment { @@ -308,30 +311,41 @@ struct ContentView: View { VStack(spacing: 0) { if searchIsActive == false { HStack { - AsyncImage(url: accountProfileViewModel.getImagePath()) { image in - switch image { - case .empty: - ProgressView() - .frame(width: avatarSize, height: avatarSize) - case .success(let image): - image + if (accountProfileViewModel.accountModelIndex ?? 0) < CoreContext.shared.accounts.count { + AsyncImage(url: imagePath) { image in + switch image { + case .empty: + ProgressView() + .frame(width: avatarSize, height: avatarSize) + case .success(let image): + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + case .failure: + Image(uiImage: contactsManager.textToImage( + firstName: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex ?? 0].avatarModel?.name ?? "", + lastName: "")) .resizable() - .aspectRatio(contentMode: .fill) .frame(width: avatarSize, height: avatarSize) .clipShape(Circle()) - case .failure: - Image(uiImage: contactsManager.textToImage( - firstName: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex ?? 0].avatarModel?.name ?? "", - lastName: "")) - .resizable() - .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - @unknown default: - EmptyView() + @unknown default: + EmptyView() + } + } + .onTapGesture { + openMenu() + } + .onAppear { + imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath() + } + .onChange(of: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].usernaneAvatar) { _ in + imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath() + } + .onReceive(imageChanged) { _ in + imagePath = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].getImagePath() } - } - .onTapGesture { - openMenu() } Text(String(localized: index == 0 ? "bottom_navigation_contacts_label" : (index == 1 ? "bottom_navigation_calls_label" : (index == 2 ? "bottom_navigation_conversations_label" : "bottom_navigation_meetings_label")))) @@ -1338,6 +1352,9 @@ struct ContentView: View { .onChange(of: scenePhase) { newPhase in CoreContext.shared.enteredForeground = newPhase == .active orientation = UIDevice.current.orientation + if newPhase == .active { + accountProfileViewModel.setAvatarModel() + } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift index 26f4bf35d..5fdcd0f5c 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift @@ -293,7 +293,7 @@ struct ConversationInfoFragment: View { Avatar(contactAvatarModel: participantConversationModel, avatarSize: 50) } else { let avatarSize = 50.0 - AsyncImage(url: accountProfileViewModel.getImagePath()) { image in + AsyncImage(url: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].imagePathAvatar) { image in switch image { case .empty: ProgressView() diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index 38a1f4c1e..77d7362f3 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -265,7 +265,7 @@ class ConversationModel: ObservableObject, Identifiable { subjectTmp = self.chatRoom.participants.first!.address!.displayName != nil ? self.chatRoom.participants.first!.address!.displayName! - : (self.chatRoom.participants.first!.address!.username ?? self.chatRoom.participants.first!.address!.asStringUriOnly()) + : (self.chatRoom.participants.first!.address!.username ?? String(self.chatRoom.participants.first!.address!.asStringUriOnly().dropFirst(4))) } } diff --git a/Linphone/UI/Main/Fragments/SideMenuAccountRow.swift b/Linphone/UI/Main/Fragments/SideMenuAccountRow.swift index ca37292fd..89c9bd195 100644 --- a/Linphone/UI/Main/Fragments/SideMenuAccountRow.swift +++ b/Linphone/UI/Main/Fragments/SideMenuAccountRow.swift @@ -35,7 +35,7 @@ struct SideMenuAccountRow: View { var body: some View { HStack { - AsyncImage(url: accountProfileViewModel.getImagePath()) { image in + AsyncImage(url: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].imagePathAvatar) { image in switch image { case .empty: ProgressView() diff --git a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift index f2d6da14e..6c76755f7 100644 --- a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift +++ b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift @@ -99,7 +99,7 @@ struct AccountProfileFragment: View { && !accountModel.photoAvatarModel!.isEmpty && selectedImage == nil && !removedImage { - AsyncImage(url: accountProfileViewModel.getImagePath()) { image in + AsyncImage(url: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!].imagePathAvatar) { image in switch image { case .empty: ProgressView() @@ -265,39 +265,40 @@ struct AccountProfileFragment: View { } if detailIsOpen { - VStack(spacing: 0) { - VStack(spacing: 30) { - HStack { - Text(String(localized: "sip_address") + ":") - .default_text_style_700(styleSize: 15) - - Text(accountModel.avatarModel!.address) - .foregroundStyle(Color.grayMain2c700) - .default_text_style(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - - Button(action: { - UIPasteboard.general.setValue( - accountModel.avatarModel!.address, - forPasteboardType: UTType.plainText.identifier - ) + if accountModel.avatarModel != nil { + VStack(spacing: 0) { + VStack(spacing: 30) { + HStack { + Text(String(localized: "sip_address") + ":") + .default_text_style_700(styleSize: 15) - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() - }, label: { - Image("copy") - .resizable() - .frame(width: 20, height: 20) - }) - } - - VStack(alignment: .leading) { - Text("sip_address_display_name") - .default_text_style_700(styleSize: 15) - .padding(.bottom, -5) + Text(accountModel.avatarModel!.address) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + + Button(action: { + UIPasteboard.general.setValue( + accountModel.avatarModel!.address, + forPasteboardType: UTType.plainText.identifier + ) + + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" + ToastViewModel.shared.displayToast.toggle() + }, label: { + Image("copy") + .resizable() + .frame(width: 20, height: 20) + }) + } - TextField(accountModel.displayNameAvatar, text: Binding( + VStack(alignment: .leading) { + Text("sip_address_display_name") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + + TextField(accountModel.displayNameAvatar, text: Binding( get: { accountModel.displayNameAvatar }, set: { newValue in accountModel.displayNameAvatar = newValue @@ -315,66 +316,67 @@ struct AccountProfileFragment: View { .stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) ) .focused($isDisplayNameFocused) - } - - VStack(alignment: .leading) { - HStack { - Text("manage_account_international_prefix") - .default_text_style_700(styleSize: 15) - .padding(.bottom, -5) - .lineLimit(1) - - Button(action: { - isShowPopup = true - }, label: { - Image("question") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 20, height: 20) - }) - .padding(.bottom, -5) } - Menu { - Picker("", selection: $accountProfileViewModel.dialPlanValueSelected) { - ForEach(registerViewModel.dialPlansLabelList, id: \.self) { dialPlan in - Text(dialPlan).tag(dialPlan) - } - } - .onChange(of: accountProfileViewModel.dialPlanValueSelected) { newValue in - accountProfileViewModel.updateDialPlan(newDialPlan: newValue) - } - } label: { + + VStack(alignment: .leading) { HStack { - Text(accountProfileViewModel.dialPlanValueSelected) - .default_text_style(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) + Text("manage_account_international_prefix") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + .lineLimit(1) - Image("caret-down") - .resizable() - .frame(width: 20, height: 20) + Button(action: { + isShowPopup = true + }, label: { + Image("question") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 20, height: 20) + }) + .padding(.bottom, -5) + } + Menu { + Picker("", selection: $accountProfileViewModel.dialPlanValueSelected) { + ForEach(registerViewModel.dialPlansLabelList, id: \.self) { dialPlan in + Text(dialPlan).tag(dialPlan) + } + } + .onChange(of: accountProfileViewModel.dialPlanValueSelected) { newValue in + accountProfileViewModel.updateDialPlan(newDialPlan: newValue) + } + } label: { + HStack { + Text(accountProfileViewModel.dialPlanValueSelected) + .default_text_style(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + + Image("caret-down") + .resizable() + .frame(width: 20, height: 20) + } + .frame(height: 25) + .padding(.horizontal, 20) + .padding(.vertical, 15) + .background(.white) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(Color.gray200, lineWidth: 1) + ) } - .frame(height: 25) - .padding(.horizontal, 20) - .padding(.vertical, 15) - .background(.white) - .cornerRadius(60) - .overlay( - RoundedRectangle(cornerRadius: 60) - .inset(by: 0.5) - .stroke(Color.gray200, lineWidth: 1) - ) } } + .padding(.vertical, 30) + .padding(.horizontal, 20) } - .padding(.vertical, 30) - .padding(.horizontal, 20) + .background(.white) + .cornerRadius(15) + .padding(.horizontal) + .zIndex(-1) + .transition(.move(edge: .top)) } - .background(.white) - .cornerRadius(15) - .padding(.horizontal) - .zIndex(-1) - .transition(.move(edge: .top)) } VStack(spacing: 0) { @@ -383,7 +385,7 @@ struct AccountProfileFragment: View { Toggle("", isOn: $flag) .labelsHidden() - Text("drawer_menu_account_connection_status_connected") + Text(accountModel.humanReadableRegistrationState) .default_text_style_700(styleSize: 15) .frame(maxWidth: .infinity, alignment: .leading) .padding(.bottom, -5) @@ -403,6 +405,7 @@ struct AccountProfileFragment: View { .cornerRadius(15) .padding(.all) + /* HStack(alignment: .center) { Text("manage_account_details_title") .default_text_style_800(styleSize: 18) @@ -456,6 +459,7 @@ struct AccountProfileFragment: View { .background(.white) .cornerRadius(15) .padding(.all) + */ } .frame(maxWidth: sharedMainViewModel.maxWidth) } @@ -487,11 +491,13 @@ struct AccountProfileFragment: View { func saveImage() { let accountModel = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex ?? 0] + let usernameTmp = CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex ?? 0].usernaneAvatar + accountProfileViewModel.saveImage( image: selectedImage ?? ContactsManager.shared.textToImage( firstName: accountModel.avatarModel!.name, lastName: ""), - name: accountModel.avatarModel!.name, + name: usernameTmp, prefix: ((selectedImage == nil) ? "-default" : "")) } } diff --git a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift index f7b237895..501b39f42 100644 --- a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift +++ b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift @@ -42,11 +42,13 @@ class AccountProfileViewModel: ObservableObject { } if self.getImagePath().lastPathComponent.contains("-default") || self.getImagePath().lastPathComponent == "Documents" { + let usernameTmp = CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar + DispatchQueue.main.async { self.saveImage( image: ContactsManager.shared.textToImage( - firstName: displayNameAccountModel.isEmpty ? CoreContext.shared.accounts[self.accountModelIndex!].account.displayName() : displayNameAccountModel, lastName: ""), - name: displayNameAccountModel.isEmpty ? CoreContext.shared.accounts[self.accountModelIndex!].account.displayName() : displayNameAccountModel, + firstName: displayNameAccountModel.isEmpty ? usernameTmp : displayNameAccountModel, lastName: ""), + name: usernameTmp, prefix: "-default") } } @@ -67,10 +69,8 @@ class AccountProfileViewModel: ObservableObject { func setAvatarModel() { if accountModelIndex != nil { CoreContext.shared.doOnCoreQueue { _ in - let photoAvatarAccountModel = CoreContext.shared.accounts[self.accountModelIndex!].photoAvatarModel let displayNameTmp = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.identityAddress?.displayName ?? "" let contactAddressTmp = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.identityAddress?.asStringUriOnly() ?? "" - var photoAvatarModelTmp = "" let prefix = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.internationalPrefix ?? "" let isoCountryCode = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.internationalPrefixIsoCountryCode ?? "" @@ -88,17 +88,8 @@ class AccountProfileViewModel: ObservableObject { } } - let preferences = UserDefaults.standard - let accountDisplayName = CoreContext.shared.accounts[self.accountModelIndex!].account.displayName() - let photoAvatarModelKey = "photo_avatar_model" + CoreContext.shared.accounts[self.accountModelIndex!].address - if preferences.object(forKey: photoAvatarModelKey) == nil { - preferences.set(photoAvatarAccountModel ?? "", forKey: photoAvatarModelKey) - } else { - photoAvatarModelTmp = preferences.string(forKey: photoAvatarModelKey)! - } - DispatchQueue.main.async { CoreContext.shared.accounts[self.accountModelIndex!].avatarModel = ContactAvatarModel( friend: nil, @@ -107,8 +98,6 @@ class AccountProfileViewModel: ObservableObject { withPresence: false ) - CoreContext.shared.accounts[self.accountModelIndex!].photoAvatarModel = photoAvatarModelTmp - CoreContext.shared.accounts[self.accountModelIndex!].displayNameAvatar = displayNameTmp self.dialPlanValueSelected = dialPlanValueSelectedTmp } } @@ -127,11 +116,20 @@ class AccountProfileViewModel: ObservableObject { return } - let photoAvatarModelKey = "photo_avatar_model" + CoreContext.shared.accounts[self.accountModelIndex ?? 0].address + let photoAvatarModelKey = "photo_avatar_model" + CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in UserDefaults.standard.set(result, forKey: photoAvatarModelKey) - CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = result + + CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = "" + CoreContext.shared.accounts[self.accountModelIndex ?? 0].imagePathAvatar = nil + NotificationCenter.default.post(name: NSNotification.Name("ImageChanged"), object: nil) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = result + CoreContext.shared.accounts[self.accountModelIndex ?? 0].imagePathAvatar = CoreContext.shared.accounts[self.accountModelIndex ?? 0].getImagePath() + NotificationCenter.default.post(name: NSNotification.Name("ImageChanged"), object: nil) + } } } diff --git a/Linphone/UI/Main/Viewmodel/AccountModel.swift b/Linphone/UI/Main/Viewmodel/AccountModel.swift index ecc451fdf..9f4cb7cf3 100644 --- a/Linphone/UI/Main/Viewmodel/AccountModel.swift +++ b/Linphone/UI/Main/Viewmodel/AccountModel.swift @@ -33,6 +33,8 @@ class AccountModel: ObservableObject { @Published var avatarModel: ContactAvatarModel? @Published var photoAvatarModel: String? @Published var displayNameAvatar: String = "" + @Published var usernaneAvatar: String = "" + @Published var imagePathAvatar: URL? private var accountDelegate: AccountDelegate? private var coreDelegate: CoreDelegate? @@ -78,6 +80,21 @@ class AccountModel: ObservableObject { } let displayName = account.displayName() let address = account.params?.identityAddress?.asString() + + let displayNameTmp = account.params?.identityAddress?.displayName ?? "" + let usernaneAvatarTmp = account.contactAddress?.username ?? "" + var photoAvatarModelTmp = "" + + let preferences = UserDefaults.standard + + let photoAvatarModelKey = "photo_avatar_model" + usernaneAvatarTmp + + if preferences.object(forKey: photoAvatarModelKey) == nil { + preferences.set(photoAvatarModelKey, forKey: photoAvatarModelKey) + } else { + photoAvatarModelTmp = preferences.string(forKey: photoAvatarModelKey)! + } + DispatchQueue.main.async { [self] in switch state { case .Cleared, .None: @@ -99,6 +116,11 @@ class AccountModel: ObservableObject { isDefaultAccount = isDefault self.displayName = displayName address.map {self.address = $0} + + photoAvatarModel = photoAvatarModelTmp + displayNameAvatar = displayNameTmp + usernaneAvatar = usernaneAvatarTmp + imagePathAvatar = getImagePath() } } @@ -114,4 +136,12 @@ class AccountModel: ObservableObject { self.account.refreshRegister() } } + + func getImagePath() -> URL { + let imagePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent( + photoAvatarModel ?? "Error" + ) + + return imagePath + } }