Change account photo

This commit is contained in:
Benoit Martins 2024-12-05 17:56:40 +01:00
parent 7d394f5313
commit 9421135fac
9 changed files with 182 additions and 37 deletions

View file

@ -40,6 +40,7 @@ struct CallView: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var meetingViewModel: MeetingViewModel
@ObservedObject var accountProfileViewModel: AccountProfileViewModel
@State private var addParticipantsViewModel: AddParticipantsViewModel?
@ -211,6 +212,7 @@ struct CallView: View {
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
meetingViewModel: meetingViewModel,
accountProfileViewModel: accountProfileViewModel,
isShowConversationFragment: $isShowConversationFragment,
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
isShowEditContactFragment: $isShowEditContactFragment,
@ -2842,6 +2844,7 @@ struct PressedButtonStyle: ButtonStyle {
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
meetingViewModel: MeetingViewModel(),
accountProfileViewModel: AccountProfileViewModel(),
fullscreenVideo: .constant(false),
isShowStartCallFragment: .constant(false),
isShowConversationFragment: .constant(false),

View file

@ -78,6 +78,8 @@ struct ContentView: View {
@State var isShowScheduleMeetingFragment = false
@State private var isShowLoginFragment: Bool = false
private let avatarSize = 45.0
var body: some View {
let pub = NotificationCenter.default
.publisher(for: NSNotification.Name("ContactLoaded"))
@ -307,13 +309,31 @@ struct ContentView: View {
VStack(spacing: 0) {
if searchIsActive == false {
HStack {
Image("profile-image-example")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
.onTapGesture {
openMenu()
AsyncImage(url: accountProfileViewModel.getImagePath()) { 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: accountProfileViewModel.avatarModel?.name ?? "",
lastName: ""))
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
.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"))))
.default_text_style_white_800(styleSize: 20)
@ -867,6 +887,7 @@ struct ContentView: View {
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
meetingViewModel: meetingViewModel,
accountProfileViewModel: accountProfileViewModel,
isShowConversationFragment: $isShowConversationFragment,
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
isShowEditContactFragment: $isShowEditContactFragment,
@ -909,6 +930,7 @@ struct ContentView: View {
}
SideMenu(
accountProfileViewModel: accountProfileViewModel,
width: geometry.size.width / 5 * 4,
isOpen: self.sideMenuIsOpen,
menuClose: self.openMenu,
@ -1253,6 +1275,7 @@ struct ContentView: View {
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
meetingViewModel: meetingViewModel,
accountProfileViewModel: accountProfileViewModel,
fullscreenVideo: $fullscreenVideo,
isShowStartCallFragment: $isShowStartCallFragment,
isShowConversationFragment: $isShowConversationFragment,

View file

@ -36,6 +36,7 @@ struct ConversationFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var meetingViewModel: MeetingViewModel
@ObservedObject var accountProfileViewModel: AccountProfileViewModel
@State var isMenuOpen = false
@State private var isMuted: Bool = false
@ -995,6 +996,7 @@ struct ConversationFragment: View {
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
meetingViewModel: meetingViewModel,
accountProfileViewModel: accountProfileViewModel,
isMuted: $isMuted,
isShowEphemeralFragment: $isShowEphemeralFragment,
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,

View file

@ -31,6 +31,7 @@ struct ConversationInfoFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var meetingViewModel: MeetingViewModel
@ObservedObject var accountProfileViewModel: AccountProfileViewModel
@State var addParticipantsViewModel = AddParticipantsViewModel()
@ -287,7 +288,33 @@ struct ConversationInfoFragment: View {
VStack(spacing: 0) {
ForEach(conversationViewModel.participantConversationModel) { participantConversationModel in
HStack {
Avatar(contactAvatarModel: participantConversationModel, avatarSize: 50)
if conversationViewModel.myParticipantConversationModel != nil && conversationViewModel.myParticipantConversationModel!.address != participantConversationModel.address {
Avatar(contactAvatarModel: participantConversationModel, avatarSize: 50)
} else {
let avatarSize = 50.0
AsyncImage(url: accountProfileViewModel.getImagePath()) { 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: accountProfileViewModel.avatarModel?.name ?? "",
lastName: ""))
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
}
VStack {
Text(participantConversationModel.name)
@ -667,6 +694,7 @@ struct ConversationInfoFragment: View {
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
meetingViewModel: MeetingViewModel(),
accountProfileViewModel: AccountProfileViewModel(),
addParticipantsViewModel: AddParticipantsViewModel(),
isMuted: .constant(false),
isShowEphemeralFragment: .constant(false),

View file

@ -469,17 +469,19 @@ class ConversationViewModel: ObservableObject {
}
}
if let currentUser = self.displayedConversation?.chatRoom.me,
let address = currentUser.address {
ContactAvatarModel.getAvatarModelFromAddress(address: address) { avatarResult in
let avatarModelTmp = avatarResult
DispatchQueue.main.async {
if currentUser.isAdmin {
self.isUserAdmin = true
self.participantConversationModelAdmin.append(avatarModelTmp)
if !self.displayedConversation!.isReadOnly {
if let currentUser = self.displayedConversation?.chatRoom.me,
let address = currentUser.address {
ContactAvatarModel.getAvatarModelFromAddress(address: address) { avatarResult in
let avatarModelTmp = avatarResult
DispatchQueue.main.async {
if currentUser.isAdmin {
self.isUserAdmin = true
self.participantConversationModelAdmin.append(avatarModelTmp)
}
self.participantConversationModel.append(avatarModelTmp)
self.myParticipantConversationModel = avatarModelTmp
}
self.participantConversationModel.append(avatarModelTmp)
self.myParticipantConversationModel = avatarModelTmp
}
}
}

View file

@ -23,6 +23,8 @@ import UniformTypeIdentifiers
struct SideMenu: View {
@ObservedObject var accountProfileViewModel: AccountProfileViewModel
let width: CGFloat
let isOpen: Bool
let menuClose: () -> Void
@ -67,7 +69,7 @@ struct SideMenu: View {
List {
ForEach(0..<CoreContext.shared.accounts.count, id: \.self) { index in
SideMenuAccountRow( model: CoreContext.shared.accounts[index], isShowAccountProfileFragment: $isShowAccountProfileFragment)
SideMenuAccountRow( model: CoreContext.shared.accounts[index], accountProfileViewModel: accountProfileViewModel, isShowAccountProfileFragment: $isShowAccountProfileFragment)
.background()
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.listRowSeparator(.hidden)
@ -171,6 +173,7 @@ struct SideMenu: View {
GeometryReader { geometry in
@State var triggerNavigateToLogin: Bool = false
SideMenu(
accountProfileViewModel: AccountProfileViewModel(),
width: geometry.size.width / 5 * 4,
isOpen: true,
menuClose: {},

View file

@ -22,19 +22,40 @@ import linphonesw
import UniformTypeIdentifiers
struct SideMenuAccountRow: View {
@ObservedObject var contactsManager = ContactsManager.shared
@ObservedObject var model: AccountModel
@ObservedObject var accountProfileViewModel: AccountProfileViewModel
@State private var navigateToOption = false
@Binding var isShowAccountProfileFragment: Bool
private let avatarSize = 45.0
var body: some View {
HStack {
Avatar(contactAvatarModel:
ContactAvatarModel(friend: nil,
name: model.displayName,
address: model.address,
withPresence: true),
avatarSize: 45)
AsyncImage(url: accountProfileViewModel.getImagePath()) { 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: accountProfileViewModel.avatarModel?.name ?? "",
lastName: ""))
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
.padding(.leading, 6)
VStack {

View file

@ -33,6 +33,8 @@ struct AccountProfileFragment: View {
@State private var selectedImage: UIImage?
@State private var removedImage = false
private let avatarSize = 100.0
var body: some View {
GeometryReader { geometry in
VStack(spacing: 1) {
@ -72,8 +74,6 @@ struct AccountProfileFragment: View {
.padding(.bottom, 4)
.background(.white)
Spacer()
/*
ScrollView {
VStack(spacing: 0) {
VStack(spacing: 0) {
@ -87,22 +87,43 @@ struct AccountProfileFragment: View {
VStack(spacing: 0) {
if accountProfileViewModel.avatarModel != nil
&& accountProfileViewModel.photoAvatarModel != nil
&& !accountProfileViewModel.photoAvatarModel!.isEmpty && selectedImage == nil && !removedImage {
Avatar(
contactAvatarModel: ContactAvatarModel(accountProfileViewModel.avatarModel!),
avatarSize: 100
)
&& !accountProfileViewModel.photoAvatarModel!.isEmpty
&& selectedImage == nil && !removedImage {
AsyncImage(url: accountProfileViewModel.getImagePath()) { 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: accountProfileViewModel.avatarModel?.name ?? "",
lastName: ""))
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
@unknown default:
EmptyView()
}
}
} else if selectedImage == nil {
Image("profil-picture-default")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
Image(uiImage: contactsManager.textToImage(
firstName: accountProfileViewModel.avatarModel?.name ?? "",
lastName: ""))
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
} else {
Image(uiImage: selectedImage!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
}
@ -140,6 +161,7 @@ struct AccountProfileFragment: View {
if let first = images.first {
selectedImage = first
removedImage = false
saveImage()
}
}
}
@ -150,6 +172,7 @@ struct AccountProfileFragment: View {
Button(action: {
removedImage = true
selectedImage = nil
saveImage()
}, label: {
HStack {
Image("trash-simple")
@ -192,6 +215,8 @@ struct AccountProfileFragment: View {
if let first = images.first {
selectedImage = first
removedImage = false
showPhotoPicker = false
saveImage()
}
}
}
@ -248,11 +273,19 @@ struct AccountProfileFragment: View {
.padding(.top, 2)
}
.background(Color.gray100)
*/
}
.background(Color.gray100)
}
.navigationTitle("")
.navigationBarHidden(true)
}
func saveImage() {
accountProfileViewModel.saveImage(
image: selectedImage
?? ContactsManager.shared.textToImage(
firstName: accountProfileViewModel.avatarModel!.name, lastName: ""),
name: accountProfileViewModel.avatarModel!.name,
prefix: ((selectedImage == nil) ? "-default" : ""))
}
}

View file

@ -21,6 +21,8 @@ import linphonesw
class AccountProfileViewModel: ObservableObject {
let photoAvatarModelKey = "photo_avatar_model"
@Published var avatarModel: ContactAvatarModel?
@Published var photoAvatarModel: String?
@ -31,10 +33,38 @@ class AccountProfileViewModel: ObservableObject {
if core.defaultAccount != nil {
let displayNameTmp = core.defaultAccount!.displayName()
let contactAddressTmp = core.defaultAccount!.contactAddress?.asStringUriOnly() ?? ""
var photoAvatarModelTmp = ""
let preferences = UserDefaults.standard
if preferences.object(forKey: self.photoAvatarModelKey) == nil {
preferences.set(self.photoAvatarModel ?? "", forKey: self.photoAvatarModelKey)
} else {
photoAvatarModelTmp = preferences.string(forKey: self.photoAvatarModelKey)!
}
DispatchQueue.main.async {
self.avatarModel = ContactAvatarModel(friend: nil, name: displayNameTmp, address: contactAddressTmp, withPresence: false)
self.photoAvatarModel = photoAvatarModelTmp
}
}
}
}
func saveImage(image: UIImage, name: String, prefix: String) {
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
return
}
ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
UserDefaults.standard.set(result, forKey: self.photoAvatarModelKey)
self.photoAvatarModel = result
}
}
func getImagePath() -> URL {
let imagePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(self.photoAvatarModel ?? "Error")
return imagePath
}
}