From aefa334038cdd0eeb5e48372591eef550bfd1763 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 28 Oct 2025 16:13:57 +0100 Subject: [PATCH] Prevent editing when contact is read-only (LDAP contacts) --- .../ContactInnerActionsFragment.swift | 172 +++++++++--------- .../Fragments/ContactInnerFragment.swift | 48 ++--- .../Fragments/ContactListBottomSheet.swift | 1 - .../Contacts/Fragments/ContactsFragment.swift | 2 +- .../Fragments/ContactsListBottomSheet.swift | 137 +++++++------- .../Contacts/Model/ContactAvatarModel.swift | 3 + .../xcshareddata/swiftpm/Package.resolved | 4 +- 7 files changed, 188 insertions(+), 179 deletions(-) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index 1279fd6dc..f25d815c3 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -211,33 +211,11 @@ struct ContactInnerActionsFragment: View { .background(Color.gray100) VStack(spacing: 0) { - if !contactAvatarModel.editable { - Button { - actionEditButton() - } label: { - HStack { - Image("pencil-simple") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25) - .padding(.all, 10) - - Text("contact_details_edit") - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - .fixedSize(horizontal: false, vertical: true) - Spacer() - } - .padding(.vertical, 15) - .padding(.horizontal, 20) - } - } else { - NavigationLink(destination: EditContactFragment( - contactAvatarModel: contactAvatarModel, - isShowEditContactFragment: $isShowEditContactFragmentInContactDetails, - isShowDismissPopup: $isShowDismissPopup)) { + if !contactAvatarModel.isReadOnly { + if !contactAvatarModel.editable { + Button { + actionEditButton() + } label: { HStack { Image("pencil-simple") .renderingMode(.template) @@ -255,47 +233,71 @@ struct ContactInnerActionsFragment: View { } .padding(.vertical, 15) .padding(.horizontal, 20) + } + } else { + NavigationLink(destination: EditContactFragment( + contactAvatarModel: contactAvatarModel, + isShowEditContactFragment: $isShowEditContactFragmentInContactDetails, + isShowDismissPopup: $isShowDismissPopup)) { + HStack { + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .padding(.all, 10) + + Text("contact_details_edit") + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) } .simultaneousGesture( TapGesture().onEnded { isShowEditContactFragmentInContactDetails = true } ) - } - - VStack { - Divider() - } - .padding(.horizontal) - - Button { - contactsListViewModel.toggleStarredSelectedFriend() - } label: { - HStack { - Image(contactAvatarModel.starred == true ? "heart-fill" : "heart") - .renderingMode(.template) - .resizable() - .foregroundStyle(contactAvatarModel.starred == true ? Color.redDanger500 : Color.grayMain2c500) - .frame(width: 25, height: 25) - .padding(.all, 10) - Text(contactAvatarModel.starred == true - ? "contact_details_remove_from_favourites" - : "contact_details_add_to_favourites") - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - .fixedSize(horizontal: false, vertical: true) - Spacer() } - .padding(.vertical, 15) - .padding(.horizontal, 20) + + VStack { + Divider() + } + .padding(.horizontal) + + Button { + contactsListViewModel.toggleStarredSelectedFriend() + } label: { + HStack { + Image(contactAvatarModel.starred == true ? "heart-fill" : "heart") + .renderingMode(.template) + .resizable() + .foregroundStyle(contactAvatarModel.starred == true ? Color.redDanger500 : Color.grayMain2c500) + .frame(width: 25, height: 25) + .padding(.all, 10) + Text(contactAvatarModel.starred == true + ? "contact_details_remove_from_favourites" + : "contact_details_add_to_favourites") + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) + } + + VStack { + Divider() + } + .padding(.horizontal) } - VStack { - Divider() - } - .padding(.horizontal) - Button { showShareSheet.toggle() } label: { @@ -318,32 +320,34 @@ struct ContactInnerActionsFragment: View { .padding(.horizontal, 20) } - VStack { - Divider() - } - .padding(.horizontal) - - Button { - isShowDeletePopup.toggle() - } label: { - HStack { - Image("trash-simple") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.redDanger500) - .frame(width: 25, height: 25) - .padding(.all, 10) - - Text("contact_details_delete") - .foregroundStyle(Color.redDanger500) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - .fixedSize(horizontal: false, vertical: true) - Spacer() + if !contactAvatarModel.isReadOnly { + VStack { + Divider() + } + .padding(.horizontal) + + Button { + isShowDeletePopup.toggle() + } label: { + HStack { + Image("trash-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.redDanger500) + .frame(width: 25, height: 25) + .padding(.all, 10) + + Text("contact_details_delete") + .foregroundStyle(Color.redDanger500) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) } - .padding(.vertical, 15) - .padding(.horizontal, 20) } } .background(.white) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index eb9088e58..e57fc1105 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -72,24 +72,11 @@ struct ContactInnerFragment: View { Spacer() - if !contactAvatarModel.editable { - Button(action: { - print("nativeUrinativeUri 00 \(contactAvatarModel.nativeUri)") - editNativeContact() - }, label: { - Image("pencil-simple") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 2) - }) - } else { - NavigationLink(destination: EditContactFragment( - contactAvatarModel: contactAvatarModel, - isShowEditContactFragment: $isShowEditContactFragmentInContactDetails, - isShowDismissPopup: $isShowDismissPopup)) { + if !contactAvatarModel.isReadOnly { + if !contactAvatarModel.editable { + Button(action: { + editNativeContact() + }, label: { Image("pencil-simple") .renderingMode(.template) .resizable() @@ -97,13 +84,26 @@ struct ContactInnerFragment: View { .frame(width: 25, height: 25, alignment: .leading) .padding(.all, 10) .padding(.top, 2) - } - .simultaneousGesture( - TapGesture().onEnded { - print("nativeUrinativeUri 11 \(contactAvatarModel.nativeUri)") - isShowEditContactFragmentInContactDetails = true + }) + } else { + NavigationLink(destination: EditContactFragment( + contactAvatarModel: contactAvatarModel, + isShowEditContactFragment: $isShowEditContactFragmentInContactDetails, + isShowDismissPopup: $isShowDismissPopup)) { + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 2) } - ) + .simultaneousGesture( + TapGesture().onEnded { + isShowEditContactFragmentInContactDetails = true + } + ) + } } } .frame(maxWidth: .infinity) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift index d7e770001..f1e3794e6 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift @@ -52,7 +52,6 @@ struct ContactListBottomSheet: View { .padding(.trailing) } - Spacer() Button { UIPasteboard.general.setValue( contactsListViewModel.stringToCopy.prefix(4) == "sip:" diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift index 5399fbb40..a12e50a13 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift @@ -41,7 +41,7 @@ struct ContactsFragment: View { showingSheet: $showingSheet, showShareSheet: $showShareSheet ) - .presentationDetents([.fraction(0.3)]) + .presentationDetents(contactsListViewModel.selectedFriend?.isReadOnly == true ? [.fraction(0.1)] : [.fraction(0.3)]) } .sheet(isPresented: $showShareSheet) { ShareSheet(friendToShare: contactsListViewModel.selectedFriendToShare!) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift index 7af728c3e..d7b583da7 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift @@ -55,49 +55,51 @@ struct ContactsListBottomSheet: View { .padding(.trailing) } - Spacer() - - Button { - self.contactsListViewModel.toggleStarredSelectedFriend() + if !contactsListViewModel.selectedFriend!.isReadOnly { + Spacer() - if #available(iOS 16.0, *) { - if idiom != .pad { - showingSheet.toggle() + Button { + self.contactsListViewModel.toggleStarredSelectedFriend() + + if #available(iOS 16.0, *) { + if idiom != .pad { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } } else { showingSheet.toggle() - dismiss() + dismiss() } - } else { - showingSheet.toggle() - dismiss() + } label: { + HStack { + Image(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart") + .renderingMode(.template) + .resizable() + .foregroundStyle( + contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true + ? Color.redDanger500 + : Color.grayMain2c500 + ) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + Text(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true + ? "contact_details_remove_from_favourites" + : "contact_details_add_to_favourites") + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) } - } label: { - HStack { - Image(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart") - .renderingMode(.template) - .resizable() - .foregroundStyle( - contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true - ? Color.redDanger500 - : Color.grayMain2c500 - ) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - Text(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true - ? "contact_details_remove_from_favourites" - : "contact_details_add_to_favourites") - .default_text_style(styleSize: 16) - Spacer() + .padding(.horizontal, 30) + .background(Color.gray100) + + VStack { + Divider() } - .frame(maxHeight: .infinity) + .frame(maxWidth: .infinity) } - .padding(.horizontal, 30) - .background(Color.gray100) - - VStack { - Divider() - } - .frame(maxWidth: .infinity) Button { if #available(iOS 16.0, *) { @@ -135,45 +137,46 @@ struct ContactsListBottomSheet: View { .padding(.horizontal, 30) .background(Color.gray100) - VStack { - Divider() - } - .frame(maxWidth: .infinity) - - Button { - if contactsListViewModel.selectedFriend != nil { - isShowDeletePopup.toggle() + if !contactsListViewModel.selectedFriend!.isReadOnly { + VStack { + Divider() } + .frame(maxWidth: .infinity) - if #available(iOS 16.0, *) { - if idiom != .pad { - showingSheet.toggle() + Button { + if contactsListViewModel.selectedFriend != nil { + isShowDeletePopup.toggle() + } + + if #available(iOS 16.0, *) { + if idiom != .pad { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } } else { showingSheet.toggle() dismiss() } - } else { - showingSheet.toggle() - dismiss() + } label: { + HStack { + Image("trash-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.redDanger500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + Text("contact_details_delete") + .foregroundStyle(Color.redDanger500) + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) } - } label: { - HStack { - Image("trash-simple") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.redDanger500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - Text("contact_details_delete") - .foregroundStyle(Color.redDanger500) - .default_text_style(styleSize: 16) - Spacer() - } - .frame(maxHeight: .infinity) + .padding(.horizontal, 30) + .background(Color.gray100) } - .padding(.horizontal, 30) - .background(Color.gray100) - } .background(Color.gray100) .frame(maxWidth: .infinity) diff --git a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift index bf1478136..d4e37cf7d 100644 --- a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift +++ b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift @@ -33,6 +33,7 @@ class ContactAvatarModel: ObservableObject, Identifiable { var nativeUri: String = "" var editable: Bool = true + var isReadOnly: Bool = false var withPresence: Bool? @Published var starred: Bool = false @@ -72,6 +73,7 @@ class ContactAvatarModel: ObservableObject, Identifiable { } let nativeUriTmp = friend?.nativeUri ?? "" let editableTmp = friend?.friendList?.type == .CardDAV || nativeUriTmp.isEmpty + let isReadOnlyTmp = (friend?.isReadOnly == true) || (friend?.inList() == false) let withPresenceTmp = withPresence let starredTmp = friend?.starred ?? false let vcardTmp = friend?.vcard ?? nil @@ -117,6 +119,7 @@ class ContactAvatarModel: ObservableObject, Identifiable { self.phoneNumbersWithLabel = phoneNumbersWithLabelTmp self.nativeUri = nativeUriTmp self.editable = editableTmp + self.isReadOnly = isReadOnlyTmp self.withPresence = withPresenceTmp self.starred = starredTmp self.vcard = vcardTmp diff --git a/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7cc611a1d..b9615e75e 100644 --- a/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -150,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "c6fe6442e6a64250495669325044052e113e990c", - "version" : "1.32.0" + "revision" : "97bb244f7a575a419ebc8f3c2d33f2feb9c8f7f2", + "version" : "1.33.1" } } ],