mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 11:08:06 +00:00
Add Ldap and Cardav settings
This commit is contained in:
parent
4b3d99245f
commit
41f9db8199
18 changed files with 1485 additions and 52 deletions
|
|
@ -113,6 +113,8 @@ final class ContactsManager: ObservableObject {
|
|||
core.addFriendList(list: tempRemoteFriendList)
|
||||
}
|
||||
}
|
||||
|
||||
self.refreshCardDavContacts()
|
||||
}
|
||||
|
||||
let store = CNContactStore()
|
||||
|
|
@ -229,6 +231,22 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func imageFromBase64(_ base64String: String) -> UIImage? {
|
||||
let cleanedString: String
|
||||
if let range = base64String.range(of: "base64,") {
|
||||
cleanedString = String(base64String[range.upperBound...])
|
||||
} else {
|
||||
cleanedString = base64String
|
||||
}
|
||||
|
||||
guard let imageData = Data(base64Encoded: cleanedString, options: .ignoreUnknownCharacters) else {
|
||||
print("Error: failed to decode Base64 string")
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIImage(data: imageData)
|
||||
}
|
||||
|
||||
func saveImage(image: UIImage, name: String, prefix: String, contact: Contact, linphoneFriend: String, existingFriend: Friend?, completion: @escaping () -> Void) {
|
||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||
return
|
||||
|
|
@ -242,7 +260,9 @@ final class ContactsManager: ObservableObject {
|
|||
if let linphoneFL = self.linphoneFriendList, linphoneFriend == linphoneFL.displayName {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
} else if let linphoneFL = self.tempRemoteFriendList {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
if friend.friendList?.type != .CardDAV {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
} else if existingFriend == nil {
|
||||
if let friendListTmp = self.friendList {
|
||||
|
|
@ -338,10 +358,11 @@ final class ContactsManager: ObservableObject {
|
|||
|
||||
do {
|
||||
let fileName = name + prefix + ".png"
|
||||
let fileURL = directory.appendingPathComponent(fileName)
|
||||
|
||||
let fileURL = directory.appendingPathComponent(fileName.replacingOccurrences(of: " ", with: ""))
|
||||
|
||||
try data.write(to: fileURL)
|
||||
completion(fileName)
|
||||
completion(fileName.replacingOccurrences(of: " ", with: ""))
|
||||
} catch {
|
||||
print("Error writing image: \(error)")
|
||||
completion("")
|
||||
|
|
@ -379,6 +400,12 @@ final class ContactsManager: ObservableObject {
|
|||
friend = tempRemoteFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
}
|
||||
|
||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||
if friendList.type == .CardDAV {
|
||||
friend = friendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
}
|
||||
}
|
||||
|
||||
return friend
|
||||
}
|
||||
|
||||
|
|
@ -396,6 +423,13 @@ final class ContactsManager: ObservableObject {
|
|||
friendList.updateSubscriptions()
|
||||
}
|
||||
|
||||
if let friendListDelegateToDelete = self.friendListDelegate {
|
||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||
friendList.removeDelegate(delegate: friendListDelegateToDelete)
|
||||
}
|
||||
}
|
||||
self.friendListDelegate = nil
|
||||
|
||||
let friendListDelegateTmp = FriendListDelegateStub(
|
||||
onContactCreated: { (friendList: FriendList, linphoneFriend: Friend) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactCreated")
|
||||
|
|
@ -407,7 +441,7 @@ final class ContactsManager: ObservableObject {
|
|||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactUpdated")
|
||||
},
|
||||
onSyncStatusChanged: { (friendList: FriendList, status: FriendList.SyncStatus?, message: String?) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged")
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged \(friendList.displayName ?? "No Display Name") -- Status: \(status != nil ? String(describing: status!) : "No Status")")
|
||||
if status == .Successful {
|
||||
if friendList.displayName != self.nativeAddressBookFriendList && friendList.displayName != self.linphoneAddressBookFriendList {
|
||||
if let tempRemoteFriendList = self.tempRemoteFriendList {
|
||||
|
|
@ -421,31 +455,42 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
friendList.friends.forEach { friend in
|
||||
dispatchGroup.enter()
|
||||
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
|
||||
let newContact = Contact(
|
||||
identifier: UUID().uuidString,
|
||||
firstName: friend.name ?? addressTmp,
|
||||
lastName: "",
|
||||
organizationName: "",
|
||||
jobTitle: "",
|
||||
firstName: friend.firstName ?? addressTmp,
|
||||
lastName: friend.lastName ?? "",
|
||||
organizationName: friend.organization ?? "",
|
||||
jobTitle: friend.jobTitle ?? "",
|
||||
displayName: friend.address?.displayName ?? "",
|
||||
sipAddresses: friend.addresses.map { $0.asStringUriOnly() },
|
||||
phoneNumbers: [],
|
||||
phoneNumbers: friend.phoneNumbersWithLabel.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.phoneNumber)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
let image: UIImage?
|
||||
if let photo = friend.photo, !photo.isEmpty {
|
||||
if let imageTmp = self.imageFromBase64(photo) {
|
||||
image = imageTmp
|
||||
} else {
|
||||
image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
}
|
||||
} else {
|
||||
image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: friend.friendList?.type == .CardDAV ? friend : nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
|
|
@ -561,6 +606,11 @@ final class ContactsManager: ObservableObject {
|
|||
|
||||
func addCoreDelegate(core: Core) {
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
if let coreDelegate = self.coreDelegate {
|
||||
core.removeDelegate(delegate: coreDelegate)
|
||||
self.coreDelegate = nil
|
||||
}
|
||||
|
||||
self.coreDelegate = CoreDelegateStub(
|
||||
onFriendListCreated: { (_: Core, friendList: FriendList) in
|
||||
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) created")
|
||||
|
|
@ -602,6 +652,17 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshCardDavContacts() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
core.friendsLists.forEach{ friendList in
|
||||
if (friendList.type == .CardDAV) {
|
||||
Log.info("\(ContactsManager.TAG) Found CardDAV friend list \(friendList.displayName), starting update")
|
||||
friendList.synchronizeFriendsFromServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PhoneNumber {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,15 @@ class CorePreferences {
|
|||
}
|
||||
}
|
||||
|
||||
static var friendListInWhichStoreNewlyCreatedFriends: String {
|
||||
get {
|
||||
return Config.get().getString(section: "app", key: "friend_list_to_store_newly_created_contacts", defaultString: "Linphone address-book")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "app", key: "friend_list_to_store_newly_created_contacts", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var voiceRecordingMaxDuration: Int {
|
||||
get {
|
||||
return Config.get().getInt(section: "app", key: "voice_recording_max_duration", defaultValue: 600000)
|
||||
|
|
|
|||
|
|
@ -467,15 +467,22 @@
|
|||
"settings_contacts_carddav_username_title" = "Username";
|
||||
"settings_contacts_edit_carddav_server_title" = "Edit CardDAV address book";
|
||||
"settings_contacts_edit_ldap_server_title" = "Edit LDAP server";
|
||||
"settings_contacts_ldap_enabled_title" = "Enabled";
|
||||
"settings_contacts_ldap_server_url_title" = "Server URL (can't be empty)";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Bind user password";
|
||||
"settings_contacts_ldap_max_results_title" = "Maximum results";
|
||||
"settings_contacts_ldap_password_title" = "Password";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Request timeout";
|
||||
"settings_contacts_ldap_use_tls_title" = "Use TLS";
|
||||
"settings_contacts_ldap_search_base_title" = "Search base (can't be empty)";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filter";
|
||||
"settings_contacts_ldap_server_url_title" = "Server URL (can't be empty)";
|
||||
"settings_contacts_ldap_use_tls_title" = "Use TLS";
|
||||
"settings_contacts_ldap_max_results_title" = "Max results";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Timeout (in seconds)";
|
||||
"settings_contacts_ldap_request_delay_title" = "Delay between two queries (in milliseconds)";
|
||||
"settings_contacts_ldap_min_characters_title" = "Min characters to start a query";
|
||||
"settings_contacts_ldap_name_attributes_title" = "Name attributes";
|
||||
"settings_contacts_ldap_sip_attributes_title" = "SIP attributes";
|
||||
"settings_contacts_ldap_sip_domain_title" = "SIP domain";
|
||||
"settings_contacts_ldap_error_toast" = "A error occurred, LDAP server not saved!";
|
||||
"settings_contacts_ldap_empty_server_error_toast" = "Server URL can't be empty";
|
||||
"settings_contacts_title" = "Contacts";
|
||||
"settings_conversations_auto_download_title" = "Auto-download files";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Mark conversation as read when dismissing message notification";
|
||||
|
|
|
|||
|
|
@ -467,15 +467,22 @@
|
|||
"settings_contacts_carddav_username_title" = "Nom d'utilisateur";
|
||||
"settings_contacts_edit_carddav_server_title" = "Editer le carnet d'adresse CardDAV";
|
||||
"settings_contacts_edit_ldap_server_title" = "Editer le serveur LDAP";
|
||||
"settings_contacts_ldap_enabled_title" = "Activé";
|
||||
"settings_contacts_ldap_server_url_title" = "URL du serveur (ne peut être vide)";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Mot de passe de l'utilisateur Bind";
|
||||
"settings_contacts_ldap_max_results_title" = "Nombre de résultats maximum";
|
||||
"settings_contacts_ldap_password_title" = "Mot de passe";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Délai d'attente de la requête";
|
||||
"settings_contacts_ldap_use_tls_title" = "Utiliser TLS";
|
||||
"settings_contacts_ldap_search_base_title" = "Base de recherche (ne peut être vide)";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filtre";
|
||||
"settings_contacts_ldap_server_url_title" = "URL du serveur (ne peut être vide)";
|
||||
"settings_contacts_ldap_use_tls_title" = "Utiliser TLS";
|
||||
"settings_contacts_ldap_max_results_title" = "Nombre de résultats maximum";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Durée maximum (en secondes)";
|
||||
"settings_contacts_ldap_request_delay_title" = "Délai entre 2 requêtes (en millisecondes)";
|
||||
"settings_contacts_ldap_min_characters_title" = "Nombre minimum de caractères pour lancer la requête";
|
||||
"settings_contacts_ldap_name_attributes_title" = "Attributs de nom";
|
||||
"settings_contacts_ldap_sip_attributes_title" = "Attributs SIP";
|
||||
"settings_contacts_ldap_sip_domain_title" = "Domaine SIP";
|
||||
"settings_contacts_ldap_error_toast" = "Une erreur s'est produite, la configuration LDAP n'a pas été sauvegardée !";
|
||||
"settings_contacts_ldap_empty_server_error_toast" = "L'URL du serveur ne peut être vide";
|
||||
"settings_contacts_title" = "Contacts";
|
||||
"settings_conversations_auto_download_title" = "Télécharger automatiquement les fichiers";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Marquer la conversation comme lue lorsqu'une notification de message est supprimée";
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ struct ContactsInnerFragment: View {
|
|||
.frame(height: 12)
|
||||
})
|
||||
.listStyle(.plain)
|
||||
.refreshable {
|
||||
contactsManager.refreshCardDavContacts()
|
||||
}
|
||||
.overlay(
|
||||
VStack {
|
||||
if contactsManager.avatarListModel.isEmpty {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class ContactsListViewModel: ObservableObject {
|
|||
|
||||
private var contactChatRoomDelegate: ChatRoomDelegate?
|
||||
|
||||
private let nativeAddressBookFriendList = "Native address-book"
|
||||
let linphoneAddressBookFriendList = "Linphone address-book"
|
||||
let tempRemoteAddressBookFriendList = "TempRemoteDirectoryContacts address-book"
|
||||
|
||||
init() {}
|
||||
|
||||
func createOneToOneChatRoomWith(remote: Address) {
|
||||
|
|
|
|||
|
|
@ -315,6 +315,27 @@ struct ToastView: View {
|
|||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_settings_contacts_carddav_sync_successful_toast":
|
||||
Text("settings_contacts_carddav_sync_successful_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "settings_contacts_carddav_sync_error_toast":
|
||||
Text("settings_contacts_carddav_sync_error_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_settings_contacts_carddav_deleted_toast":
|
||||
Text("settings_contacts_carddav_deleted_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
default:
|
||||
Text("Error")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CardDavAddressBookConfigurationFragment: View {
|
||||
@StateObject private var cardDavViewModel: CardDavViewModel
|
||||
|
||||
@EnvironmentObject var settingsViewModel: SettingsViewModel
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@State var isSecured: Bool = true
|
||||
|
||||
@FocusState var isDisplayNameFocused: Bool
|
||||
@FocusState var isServerUrlFocused: Bool
|
||||
@FocusState var isUsernameFocused: Bool
|
||||
@FocusState var isPasswordFocused: Bool
|
||||
@FocusState var isRealmFocused: Bool
|
||||
|
||||
init(name: String? = "") {
|
||||
_cardDavViewModel = StateObject(wrappedValue: CardDavViewModel(name: name))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
Text(cardDavViewModel.isEdit ? "settings_contacts_edit_carddav_server_title" : "settings_contacts_add_carddav_server_title")
|
||||
.default_text_style_orange_800(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 4)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
if cardDavViewModel.isEdit {
|
||||
Button {
|
||||
cardDavViewModel.delete()
|
||||
} label: {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundStyle(.red)
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 30) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_carddav_name_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_carddav_name_title", text: $cardDavViewModel.displayName)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isDisplayNameFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_carddav_server_url_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_carddav_server_url_title", text: $cardDavViewModel.serverUrl)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isServerUrlFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_carddav_username_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_carddav_username_title", text: $cardDavViewModel.username)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isUsernameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isUsernameFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_carddav_password_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("settings_contacts_carddav_password_title", text: $cardDavViewModel.password)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("settings_contacts_carddav_password_title", text: $cardDavViewModel.password)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_carddav_realm_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_carddav_realm_title", text: $cardDavViewModel.realm)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isRealmFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isRealmFocused)
|
||||
}
|
||||
|
||||
Toggle("settings_contacts_carddav_use_as_default_title", isOn: $cardDavViewModel.storeNewContactsInIt)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
|
||||
}
|
||||
.padding(.vertical, 30)
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.gray100)
|
||||
}
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
cardDavViewModel.addAddressBook()
|
||||
} label: {
|
||||
Image(cardDavViewModel.isEdit ? "check" : "plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
.background(cardDavViewModel.isFormComplete ? Color.orangeMain500 : Color.gray300)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
}
|
||||
.padding()
|
||||
.disabled(!cardDavViewModel.isFormComplete)
|
||||
}
|
||||
}
|
||||
|
||||
if cardDavViewModel.cardDavServerOperationInProgress {
|
||||
PopupLoadingView()
|
||||
.background(.black.opacity(0.65))
|
||||
}
|
||||
}
|
||||
.onChange(of: cardDavViewModel.cardDavServerOperationSuccessful) { event in
|
||||
if event {
|
||||
dismiss()
|
||||
settingsViewModel.reloadConfiguredCardDavServers()
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LdapServerConfigurationFragment: View {
|
||||
@StateObject private var ldapViewModel: LdapViewModel
|
||||
|
||||
@EnvironmentObject var settingsViewModel: SettingsViewModel
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@State var isSecured: Bool = true
|
||||
|
||||
@FocusState var isServerUrlFocused: Bool
|
||||
@FocusState var isBindDnFocused: Bool
|
||||
@FocusState var isPasswordFocused: Bool
|
||||
@FocusState var isSearchBaseFocused: Bool
|
||||
@FocusState var isSearchFilterFocused: Bool
|
||||
@FocusState var isMaxResultsFocused: Bool
|
||||
@FocusState var isRequestTimeoutFocused: Bool
|
||||
@FocusState var isRequestDelayFocused: Bool
|
||||
@FocusState var isMinCharactersFocused: Bool
|
||||
@FocusState var isNameAttributesFocused: Bool
|
||||
@FocusState var isSipAttributesFocused: Bool
|
||||
@FocusState var isSipDomainFocused: Bool
|
||||
|
||||
init(url: String? = "") {
|
||||
_ldapViewModel = StateObject(wrappedValue: LdapViewModel(url: url))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
Text(ldapViewModel.isEdit ? "settings_contacts_edit_ldap_server_title" : "settings_contacts_add_ldap_server_title")
|
||||
.default_text_style_orange_800(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 4)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
if ldapViewModel.isEdit {
|
||||
Button {
|
||||
ldapViewModel.delete()
|
||||
} label: {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundStyle(.red)
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 4)
|
||||
.background(.white)
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 30) {
|
||||
Toggle("settings_contacts_ldap_enabled_title", isOn: $ldapViewModel.isEnabled)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_server_url_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_server_url_title", text: $ldapViewModel.serverUrl)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isServerUrlFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_bind_dn_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_bind_dn_title", text: $ldapViewModel.bindDn)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isBindDnFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isBindDnFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_password_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecured {
|
||||
SecureField("settings_contacts_ldap_password_title", text: $ldapViewModel.password)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
} else {
|
||||
TextField("settings_contacts_ldap_password_title", text: $ldapViewModel.password)
|
||||
.default_text_style(styleSize: 15)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.frame(height: 25)
|
||||
.focused($isPasswordFocused)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isSecured.toggle()
|
||||
}, label: {
|
||||
Image(self.isSecured ? "eye-slash" : "eye")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
})
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
Toggle("settings_contacts_ldap_use_tls_title", isOn: $ldapViewModel.useTls)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_search_base_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_search_base_title", text: $ldapViewModel.searchBase)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSearchBaseFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isSearchBaseFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_search_filter_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_search_filter_title", text: $ldapViewModel.searchFilter)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSearchFilterFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isSearchFilterFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_max_results_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_max_results_title", text: $ldapViewModel.maxResults)
|
||||
.default_text_style(styleSize: 15)
|
||||
.keyboardType(.numberPad)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isMaxResultsFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isMaxResultsFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_request_timeout_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_request_timeout_title", text: $ldapViewModel.requestTimeout)
|
||||
.default_text_style(styleSize: 15)
|
||||
.keyboardType(.numberPad)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isRequestTimeoutFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isRequestTimeoutFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_request_delay_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_request_delay_title", text: $ldapViewModel.requestDelay)
|
||||
.default_text_style(styleSize: 15)
|
||||
.keyboardType(.numberPad)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isRequestDelayFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isRequestDelayFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_min_characters_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_min_characters_title", text: $ldapViewModel.minCharacters)
|
||||
.default_text_style(styleSize: 15)
|
||||
.keyboardType(.numberPad)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isMinCharactersFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isMinCharactersFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_name_attributes_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_name_attributes_title", text: $ldapViewModel.nameAttributes)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isNameAttributesFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isNameAttributesFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_sip_attributes_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_sip_attributes_title", text: $ldapViewModel.sipAttributes)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSipAttributesFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isSipAttributesFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings_contacts_ldap_sip_domain_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("settings_contacts_ldap_sip_domain_title", text: $ldapViewModel.sipDomain)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isSipDomainFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isSipDomainFocused)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 30)
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.gray100)
|
||||
}
|
||||
}
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
ldapViewModel.addServer()
|
||||
} label: {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
.background(ldapViewModel.isFormComplete ? Color.orangeMain500 : Color.gray300)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
}
|
||||
.padding()
|
||||
.disabled(!ldapViewModel.isFormComplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: ldapViewModel.ldapServerOperationSuccessful) { event in
|
||||
if event {
|
||||
dismiss()
|
||||
settingsViewModel.reloadLdapServers()
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -245,9 +245,6 @@ struct SettingsFragment: View {
|
|||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
/*
|
||||
// Hide Contacts
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("settings_contacts_title")
|
||||
.default_text_style_800(styleSize: 18)
|
||||
|
|
@ -273,12 +270,100 @@ struct SettingsFragment: View {
|
|||
|
||||
if contactsIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 30) {
|
||||
Toggle("account_settings_avpf_title", isOn: $isOn)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
VStack(spacing: 20) {
|
||||
NavigationLink(destination: {
|
||||
LdapServerConfigurationFragment()
|
||||
.environmentObject(settingsViewModel)
|
||||
}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text("settings_contacts_add_ldap_server_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("caret-right")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
|
||||
Toggle("account_settings_avpf_title", isOn: $isOn)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
if !settingsViewModel.ldapServers.isEmpty {
|
||||
ForEach(settingsViewModel.ldapServers, id: \.self) { ldap in
|
||||
NavigationLink(destination: {
|
||||
LdapServerConfigurationFragment(url: ldap)
|
||||
.environmentObject(settingsViewModel)
|
||||
}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text(ldap)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(destination: {
|
||||
CardDavAddressBookConfigurationFragment()
|
||||
.environmentObject(settingsViewModel)
|
||||
}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text("settings_contacts_add_carddav_server_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("caret-right")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
|
||||
if !settingsViewModel.cardDavFriendsLists.isEmpty {
|
||||
ForEach(settingsViewModel.cardDavFriendsLists, id: \.self) { cardDavName in
|
||||
NavigationLink(destination: {
|
||||
CardDavAddressBookConfigurationFragment(name: cardDavName)
|
||||
.environmentObject(settingsViewModel)
|
||||
}, label: {
|
||||
HStack(alignment: .center) {
|
||||
Text(cardDavName)
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 30)
|
||||
.padding(.horizontal, 20)
|
||||
|
|
@ -289,7 +374,6 @@ struct SettingsFragment: View {
|
|||
.zIndex(-4)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
*/
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("settings_meetings_title")
|
||||
|
|
|
|||
7
Linphone/UI/Main/Settings/Fragments/Untitled.swift
Normal file
7
Linphone/UI/Main/Settings/Fragments/Untitled.swift
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//
|
||||
// Untitled.swift
|
||||
// LinphoneApp
|
||||
//
|
||||
// Created by Benoît Martins on 06/10/2025.
|
||||
//
|
||||
|
||||
279
Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift
Normal file
279
Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
class CardDavViewModel: ObservableObject {
|
||||
static let TAG = "[CardDAV ViewModel]"
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
|
||||
let linphoneAddressBookFriendList = "Linphone address-book"
|
||||
let tempRemoteAddressBookFriendList = "TempRemoteDirectoryContacts address-book"
|
||||
|
||||
@Published var isEdit: Bool = false
|
||||
@Published var displayName: String = ""
|
||||
@Published var serverUrl: String = ""
|
||||
@Published var username: String = ""
|
||||
@Published var password: String = ""
|
||||
@Published var realm: String = ""
|
||||
@Published var storeNewContactsInIt: Bool = false
|
||||
@Published var isReadOnly: Bool = false
|
||||
|
||||
var isFormComplete: Bool {
|
||||
!displayName.isEmpty &&
|
||||
!serverUrl.isEmpty &&
|
||||
!username.isEmpty &&
|
||||
!realm.isEmpty
|
||||
}
|
||||
|
||||
@Published var cardDavServerOperationInProgress = false
|
||||
@Published var cardDavServerOperationSuccessful = false
|
||||
|
||||
private var friendList: FriendList?
|
||||
private var friendListDelegate: FriendListDelegate?
|
||||
|
||||
init(name: String? = "") {
|
||||
isEdit = false
|
||||
cardDavServerOperationInProgress = false
|
||||
storeNewContactsInIt = false
|
||||
|
||||
if let name = name, !name.isEmpty {
|
||||
loadcardDav(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let friendList = self.friendList, let friendListDelegate = self.friendListDelegate {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
friendList.removeDelegate(delegate: friendListDelegate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadcardDav(name: String) {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
let found = core.getFriendListByName(name: name)
|
||||
guard let found = found else {
|
||||
Log.error("\(CardDavViewModel.TAG) Failed to find friend list with display name \(name)!")
|
||||
return
|
||||
}
|
||||
|
||||
self.friendList = found
|
||||
|
||||
guard let friendList = self.friendList else {
|
||||
return
|
||||
}
|
||||
|
||||
let isReadOnlyTmp = friendList.isReadOnly
|
||||
let friendListInWhichStoreNewlyCreatedFriendsTmp = CorePreferences.friendListInWhichStoreNewlyCreatedFriends
|
||||
let uriTmp = friendList.uri ?? ""
|
||||
DispatchQueue.main.async {
|
||||
self.isEdit = true
|
||||
self.isReadOnly = isReadOnlyTmp
|
||||
|
||||
self.displayName = name
|
||||
self.storeNewContactsInIt = name == friendListInWhichStoreNewlyCreatedFriendsTmp
|
||||
|
||||
self.serverUrl = uriTmp
|
||||
}
|
||||
Log.info("\(CardDavViewModel.TAG) Existing friend list CardDAV values loaded")
|
||||
}
|
||||
}
|
||||
|
||||
func delete() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
if self.isEdit, let friendList = self.friendList {
|
||||
let name = friendList.displayName
|
||||
if name == CorePreferences.friendListInWhichStoreNewlyCreatedFriends {
|
||||
Log.info("\(CardDavViewModel.TAG) Deleting friend list configured to be used to store newly created friends, updating default friend list back to \(self.linphoneAddressBookFriendList)")
|
||||
CorePreferences.friendListInWhichStoreNewlyCreatedFriends = self.linphoneAddressBookFriendList
|
||||
}
|
||||
|
||||
if let tempRemoteFriendList = core.getFriendListByName(name: self.tempRemoteAddressBookFriendList) {
|
||||
tempRemoteFriendList.friends.forEach { friend in
|
||||
if let friendAddress = friend.address,
|
||||
friendList.friends.contains(where: { $0.address?.weakEqual(address2: friendAddress) == true }) {
|
||||
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core.removeFriendList(list: friendList)
|
||||
Log.info("\(CardDavViewModel.TAG) Removed friends list with display name \(name ?? "")")
|
||||
|
||||
Log.info("\(CardDavViewModel.TAG) Notifying contacts manager that contacts have changed")
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("ContactLoaded"), object: nil)
|
||||
self.cardDavServerOperationSuccessful = true
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_settings_contacts_carddav_deleted_toast"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addAddressBook() {
|
||||
let name = displayName
|
||||
let server = serverUrl
|
||||
if name.isEmpty || server.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let user = username
|
||||
let pwd = password
|
||||
let authRealm = realm
|
||||
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
// TODO: add dialog to ask user before removing existing friend list & auth info ?
|
||||
if !self.isEdit == false {
|
||||
let foundFriendList = core.getFriendListByName(name: name)
|
||||
if let foundFriendList = foundFriendList {
|
||||
Log.warn("\(CardDavViewModel.TAG) Friend list \(name) already exists, removing it first")
|
||||
core.removeFriendList(list: foundFriendList)
|
||||
}
|
||||
}
|
||||
|
||||
if !user.isEmpty && !authRealm.isEmpty {
|
||||
let foundAuthInfo = core.findAuthInfo(realm: authRealm, username: user, sipDomain: nil)
|
||||
if let foundAuthInfo = foundAuthInfo {
|
||||
Log.warn("\(CardDavViewModel.TAG) Auth info with username \(user) already exists, removing it first")
|
||||
core.removeAuthInfo(info: foundAuthInfo)
|
||||
}
|
||||
|
||||
Log.info("\(CardDavViewModel.TAG) Adding auth info with username \(user)")
|
||||
if let authInfo = try? Factory.Instance.createAuthInfo(
|
||||
username: user,
|
||||
userid: nil,
|
||||
passwd: pwd,
|
||||
ha1: nil,
|
||||
realm: authRealm,
|
||||
domain: nil
|
||||
) {
|
||||
core.addAuthInfo(info: authInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if self.isEdit && self.friendList != nil {
|
||||
Log.info("\(CardDavViewModel.TAG) Changes were made to CardDAV friend list \(name), synchronizing it")
|
||||
} else {
|
||||
self.friendList = try? core.createFriendList()
|
||||
|
||||
guard let friendList = self.friendList else {
|
||||
Log.error("\(CardDavViewModel.TAG) Failed to create CardDAV friend list")
|
||||
return
|
||||
}
|
||||
|
||||
friendList.displayName = name
|
||||
friendList.type = .CardDAV
|
||||
friendList.uri = if (server.hasPrefix("http://") || server.hasPrefix("https://")) {
|
||||
server
|
||||
} else {
|
||||
"https://$server"
|
||||
}
|
||||
friendList.databaseStorageEnabled = true
|
||||
|
||||
self.addFriendListDelegate(friendList: friendList)
|
||||
|
||||
core.addFriendList(list: friendList)
|
||||
|
||||
Log.info("\(CardDavViewModel.TAG) CardDAV friend list \(name) created with server URL \(server), synchronizing it")
|
||||
}
|
||||
|
||||
if !self.storeNewContactsInIt && CorePreferences.friendListInWhichStoreNewlyCreatedFriends == name {
|
||||
Log.info("\(CardDavViewModel.TAG) No longer using friend list \(name) as default friend list, switching back to \(self.linphoneAddressBookFriendList)")
|
||||
CorePreferences.friendListInWhichStoreNewlyCreatedFriends = self.linphoneAddressBookFriendList
|
||||
}
|
||||
|
||||
if let friendList = self.friendList {
|
||||
friendList.synchronizeFriendsFromServer()
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.cardDavServerOperationInProgress = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addFriendListDelegate(friendList: FriendList) {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
let delegate = FriendListDelegateStub(
|
||||
onSyncStatusChanged: { (friendList: FriendList, status: FriendList.SyncStatus, message: String?) in
|
||||
Log.info("\(CardDavViewModel.TAG) Friend list \(friendList.displayName ?? "") sync status changed to \(status) with message \(message ?? "")")
|
||||
switch status {
|
||||
case .Successful:
|
||||
DispatchQueue.main.async {
|
||||
self.cardDavServerOperationInProgress = false
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_settings_contacts_carddav_sync_successful_toast"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
|
||||
let name = self.displayName
|
||||
if self.storeNewContactsInIt {
|
||||
let previous = CorePreferences.friendListInWhichStoreNewlyCreatedFriends
|
||||
if friendList.isReadOnly {
|
||||
Log.warn("\(CardDavViewModel.TAG) User asked to add newly created contacts in this friend list but it is read only, keep currently default friend list \(previous)")
|
||||
self.storeNewContactsInIt = false
|
||||
} else {
|
||||
Log.info("\(CardDavViewModel.TAG) Updating default friend list to store newly created contacts from \(previous) to \(name)")
|
||||
CorePreferences.friendListInWhichStoreNewlyCreatedFriends = name
|
||||
}
|
||||
self.isReadOnly = friendList.isReadOnly
|
||||
}
|
||||
|
||||
Log.info("\(CardDavViewModel.TAG) Notifying contacts manager that contacts have changed")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.cardDavServerOperationSuccessful = true
|
||||
}
|
||||
case .Failure:
|
||||
DispatchQueue.main.async {
|
||||
self.cardDavServerOperationInProgress = false
|
||||
|
||||
ToastViewModel.shared.toastMessage = "settings_contacts_carddav_sync_error_toast"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
}
|
||||
if !self.isEdit {
|
||||
Log.error("\(CardDavViewModel.TAG) Synchronization failed, removing Friend list from Core")
|
||||
if let friendListDelegate = self.friendListDelegate {
|
||||
friendList.removeDelegate(delegate: friendListDelegate)
|
||||
}
|
||||
core.removeFriendList(list: friendList)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
self.friendListDelegate = delegate
|
||||
|
||||
if let friendList = self.friendList {
|
||||
friendList.addDelegate(delegate: delegate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
188
Linphone/UI/Main/Settings/ViewModel/LdapViewModel.swift
Normal file
188
Linphone/UI/Main/Settings/ViewModel/LdapViewModel.swift
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
class LdapViewModel: ObservableObject {
|
||||
static let TAG = "[LDAP ViewModel]"
|
||||
|
||||
private var coreContext = CoreContext.shared
|
||||
|
||||
@Published var isEdit: Bool = false
|
||||
@Published var isEnabled: Bool = true
|
||||
@Published var serverUrl: String = ""
|
||||
@Published var bindDn: String = ""
|
||||
@Published var password: String = ""
|
||||
@Published var useTls: Bool = true
|
||||
@Published var searchBase: String = ""
|
||||
@Published var searchFilter: String = ""
|
||||
@Published var maxResults: String = ""
|
||||
@Published var requestTimeout: String = "5"
|
||||
@Published var requestDelay: String = "2000"
|
||||
@Published var minCharacters: String = "3"
|
||||
@Published var nameAttributes: String = ""
|
||||
@Published var sipAttributes: String = ""
|
||||
@Published var sipDomain: String = ""
|
||||
|
||||
var isFormComplete: Bool {
|
||||
!serverUrl.isEmpty &&
|
||||
!bindDn.isEmpty &&
|
||||
!searchBase.isEmpty &&
|
||||
!searchFilter.isEmpty &&
|
||||
!maxResults.isEmpty &&
|
||||
!requestTimeout.isEmpty &&
|
||||
!requestDelay.isEmpty &&
|
||||
!minCharacters.isEmpty &&
|
||||
!nameAttributes.isEmpty &&
|
||||
!sipAttributes.isEmpty &&
|
||||
!sipDomain.isEmpty
|
||||
}
|
||||
|
||||
@Published var ldapServerOperationSuccessful = false
|
||||
|
||||
private var ldapToEdit: Ldap?
|
||||
|
||||
init(url: String? = "") {
|
||||
isEdit = false
|
||||
isEnabled = true
|
||||
|
||||
useTls = true
|
||||
minCharacters = "3"
|
||||
requestTimeout = "5"
|
||||
requestDelay = "2000"
|
||||
|
||||
if let url = url, !url.isEmpty {
|
||||
loadLdap(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
func loadLdap(url: String) {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
if let found = core.ldapList.first(where: { $0.params?.server == url }) {
|
||||
let isEditTmp = true
|
||||
self.ldapToEdit = found
|
||||
if let ldapParams = self.ldapToEdit?.params {
|
||||
let isEnabledTmp = ldapParams.enabled
|
||||
|
||||
let serverUrlTmp = ldapParams.server
|
||||
let bindDnTmp = ldapParams.bindDn ?? ""
|
||||
let useTlsTmp = ldapParams.tlsEnabled
|
||||
let searchBaseTmp = ldapParams.baseObject
|
||||
let searchFilterTmp = ldapParams.filter ?? ""
|
||||
let maxResultsTmp = String(ldapParams.maxResults)
|
||||
let requestTimeoutTmp = String(ldapParams.timeout)
|
||||
let requestDelayTmp = String(ldapParams.delay)
|
||||
let minCharactersTmp = String(ldapParams.minChars)
|
||||
let nameAttributesTmp = ldapParams.nameAttribute ?? ""
|
||||
let sipAttributesTmp = ldapParams.sipAttribute ?? ""
|
||||
let sipDomainTmp = ldapParams.sipDomain ?? ""
|
||||
Log.info("\(LdapViewModel.TAG) Existing LDAP server values loaded")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.isEdit = isEditTmp
|
||||
self.isEnabled = isEnabledTmp
|
||||
|
||||
self.serverUrl = serverUrlTmp
|
||||
self.bindDn = bindDnTmp
|
||||
self.useTls = useTlsTmp
|
||||
self.searchBase = searchBaseTmp
|
||||
self.searchFilter = searchFilterTmp
|
||||
self.maxResults = maxResultsTmp
|
||||
self.requestTimeout = requestTimeoutTmp
|
||||
self.requestDelay = requestDelayTmp
|
||||
self.minCharacters = minCharactersTmp
|
||||
self.nameAttributes = nameAttributesTmp
|
||||
self.sipAttributes = sipAttributesTmp
|
||||
self.sipDomain = sipDomainTmp
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("\(LdapViewModel.TAG) Failed to find LDAP server with URL \(url)!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func delete() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
if let ldapToEdit = self.ldapToEdit {
|
||||
if self.isEdit {
|
||||
let serverUrl = ldapToEdit.params?.server
|
||||
core.removeLdap(ldap: ldapToEdit)
|
||||
Log.info("\(LdapViewModel.TAG) Removed LDAP config for server URL \(serverUrl)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.ldapServerOperationSuccessful = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addServer() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
do {
|
||||
let server = self.serverUrl
|
||||
if server.isEmpty {
|
||||
Log.error("\(LdapViewModel.TAG) Server field can't be empty!")
|
||||
return
|
||||
}
|
||||
|
||||
let ldapParams = try core.createLdapParams()
|
||||
|
||||
ldapParams.enabled = self.isEnabled == true
|
||||
ldapParams.server = server
|
||||
ldapParams.bindDn = self.bindDn
|
||||
ldapParams.password = self.password
|
||||
ldapParams.authMethod = Ldap.AuthMethod.Simple
|
||||
ldapParams.tlsEnabled = self.useTls == true
|
||||
ldapParams.serverCertificatesVerificationMode = Ldap.CertVerificationMode.Default
|
||||
ldapParams.baseObject = self.searchBase
|
||||
ldapParams.filter = self.searchFilter
|
||||
ldapParams.maxResults = Int(self.maxResults) ?? 0
|
||||
ldapParams.timeout = Int(self.requestTimeout) ?? 0
|
||||
ldapParams.delay = Int(self.requestDelay) ?? 0
|
||||
ldapParams.minChars = Int(self.minCharacters) ?? 0
|
||||
ldapParams.nameAttribute = self.nameAttributes
|
||||
ldapParams.sipAttribute = self.sipAttributes
|
||||
ldapParams.sipDomain = self.sipDomain
|
||||
ldapParams.debugLevel = Ldap.DebugLevel.Verbose
|
||||
|
||||
if self.isEdit && self.ldapToEdit != nil {
|
||||
self.ldapToEdit?.params = ldapParams
|
||||
Log.info("\(LdapViewModel.TAG) LDAP changes have been applied")
|
||||
} else {
|
||||
let ldap = try core.createLdapWithParams(params: ldapParams)
|
||||
core.addLdap(ldap: ldap)
|
||||
Log.info("\(LdapViewModel.TAG) New LDAP config created")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.ldapServerOperationSuccessful = true
|
||||
}
|
||||
} catch let error {
|
||||
Log.error("\(LdapViewModel.TAG) Exception while creating LDAP: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +38,10 @@ class SettingsViewModel: ObservableObject {
|
|||
// Conversations settings
|
||||
@Published var autoDownload: Bool = false
|
||||
|
||||
// Contacts settings
|
||||
@Published var ldapServers: [String] = []
|
||||
@Published var cardDavFriendsLists: [String] = []
|
||||
|
||||
// Meetings settings
|
||||
@Published var defaultLayout: String = ""
|
||||
|
||||
|
|
@ -141,6 +145,8 @@ class SettingsViewModel: ObservableObject {
|
|||
core.addDelegate(delegate: self.coreDelegate!)
|
||||
*/
|
||||
|
||||
self.reloadLdapServers()
|
||||
self.reloadConfiguredCardDavServers()
|
||||
self.setupCodecs()
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +160,43 @@ class SettingsViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func reloadLdapServers() {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
var list: [String] = []
|
||||
|
||||
core.ldapList.forEach({ ldap in
|
||||
let label = ldap.params?.server ?? ""
|
||||
if !label.isEmpty {
|
||||
list.append(label)
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.ldapServers = list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reloadConfiguredCardDavServers() {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
var list: [String] = []
|
||||
|
||||
core.friendsLists.forEach({ friendList in
|
||||
if friendList.type == .CardDAV {
|
||||
let label = friendList.displayName ?? friendList.uri ?? ""
|
||||
if !label.isEmpty {
|
||||
list.append(label)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.cardDavFriendsLists = list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func downloadAndApplyRemoteProvisioning() {
|
||||
Log.info("\(SettingsViewModel.TAG) Updating remote provisioning URI now and then download/apply it")
|
||||
|
||||
|
|
|
|||
|
|
@ -77,14 +77,31 @@ struct Avatar: View {
|
|||
}
|
||||
}
|
||||
} else if !contactAvatarModel.name.isEmpty {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: contactAvatarModel.name,
|
||||
lastName: contactAvatarModel.name.components(separatedBy: " ").count > 1
|
||||
? contactAvatarModel.name.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
ZStack {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: contactAvatarModel.name,
|
||||
lastName: contactAvatarModel.name.components(separatedBy: " ").count > 1
|
||||
? contactAvatarModel.name.components(separatedBy: " ")[1]
|
||||
: ""))
|
||||
.resizable()
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
.clipShape(Circle())
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
if !hidePresence && (contactAvatarModel.presenceStatus == .Online || contactAvatarModel.presenceStatus == .Busy) {
|
||||
Image(contactAvatarModel.presenceStatus == .Online ? "presence-online" : "presence-busy")
|
||||
.resizable()
|
||||
.frame(width: avatarSize/4, height: avatarSize/4)
|
||||
.padding(.trailing, avatarSize == 50 || avatarSize == 35 ? 1 : 3)
|
||||
.padding(.bottom, avatarSize == 50 || avatarSize == 35 ? 1 : 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: avatarSize, height: avatarSize)
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
!lastSearchFriend.contains(where: { $0.phoneNumber == phoneNumber }) {
|
||||
lastSearchFriend.append(searchResult)
|
||||
}
|
||||
} else if searchResult.friend != nil && (searchResult.hasSourceFlag(source: .RemoteCardDAV) || searchResult.friend?.friendList?.type == .CardDAV || searchResult.hasSourceFlag(source: .LdapServers)) {
|
||||
lastSearchFriend.append(searchResult)
|
||||
} else {
|
||||
lastSearchSuggestions.append(searchResult)
|
||||
}
|
||||
|
|
@ -106,7 +108,6 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
sortedLastSearch.forEach { searchResult in
|
||||
if searchResult.friend != nil {
|
||||
if (searchResult.friend?.friendList?.displayName == self.nativeAddressBookFriendList || searchResult.friend?.friendList?.displayName == self.linphoneAddressBookFriendList || searchResult.friend?.friendList?.displayName == self.tempRemoteAddressBookFriendList) {
|
||||
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: searchResult.friend!,
|
||||
|
|
@ -115,7 +116,25 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
withPresence: true
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if searchResult.hasSourceFlag(source: .RemoteCardDAV) || searchResult.friend?.friendList?.type == .CardDAV {
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: searchResult.friend!,
|
||||
name: searchResult.friend?.name ?? "",
|
||||
address: searchResult.friend?.address?.clone()?.asStringUriOnly() ?? "",
|
||||
withPresence: true
|
||||
)
|
||||
)
|
||||
} else if searchResult.hasSourceFlag(source: .LdapServers) {
|
||||
addedAvatarListModel.append(
|
||||
ContactAvatarModel(
|
||||
friend: searchResult.friend!,
|
||||
name: searchResult.friend?.name ?? "",
|
||||
address: searchResult.friend?.address?.clone()?.asStringUriOnly() ?? "",
|
||||
withPresence: false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,8 +197,9 @@ final class MagicSearchSingleton: ObservableObject {
|
|||
magicSearch.getContactsListAsync(
|
||||
filter: self.currentFilter,
|
||||
domain: self.allContact ? "" : self.domainDefaultAccount,
|
||||
sourceFlags: MagicSearch.Source.All.rawValue,
|
||||
aggregation: MagicSearch.Aggregation.Friend)
|
||||
sourceFlags: MagicSearch.Source.All.rawValue, //MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue | MagicSearch.Source.RemoteCardDAV.rawValue,
|
||||
aggregation: MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@
|
|||
D70A26F22B7F5D95006CC8FC /* ConversationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26F12B7F5D95006CC8FC /* ConversationFragment.swift */; };
|
||||
D70C82A52C85EDCA0087F43F /* ConversationForwardMessageFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70C82A42C85EDC90087F43F /* ConversationForwardMessageFragment.swift */; };
|
||||
D70C82A72C85F5910087F43F /* ConversationForwardMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70C82A62C85F5910087F43F /* ConversationForwardMessageViewModel.swift */; };
|
||||
D711B1302E8FCEDE00DF8C71 /* CardDavViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D711B12F2E8FCED900DF8C71 /* CardDavViewModel.swift */; };
|
||||
D711B1322E8FCF8800DF8C71 /* LdapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D711B1312E8FCF8600DF8C71 /* LdapViewModel.swift */; };
|
||||
D711B1342E93F18700DF8C71 /* LdapServerConfigurationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D711B1332E93F18300DF8C71 /* LdapServerConfigurationFragment.swift */; };
|
||||
D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */; };
|
||||
D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */; };
|
||||
D714DE622C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */; };
|
||||
|
|
@ -129,6 +132,7 @@
|
|||
D759CB642C3FBD4200AC35E8 /* StartConversationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D759CB632C3FBD4200AC35E8 /* StartConversationFragment.swift */; };
|
||||
D759CB662C3FBE1D00AC35E8 /* StartConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D759CB652C3FBE1D00AC35E8 /* StartConversationViewModel.swift */; };
|
||||
D76005F62B0798B00054B79A /* IntExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76005F52B0798B00054B79A /* IntExtension.swift */; };
|
||||
D762102C2E97FDFD002E7999 /* CardDavAddressBookConfigurationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D762102B2E97FDF8002E7999 /* CardDavAddressBookConfigurationFragment.swift */; };
|
||||
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7702EF12AC7205000557C00 /* WelcomeView.swift */; };
|
||||
D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D777DBB22AE12C5900565A99 /* ContactsManager.swift */; };
|
||||
D77A080E2CB6BCAF0095D589 /* MessageConferenceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77A080D2CB6BCA10095D589 /* MessageConferenceInfo.swift */; };
|
||||
|
|
@ -291,6 +295,9 @@
|
|||
D70C3B5E2E0ABAB900F3F938 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = Localizable/uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D70C82A42C85EDC90087F43F /* ConversationForwardMessageFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationForwardMessageFragment.swift; sourceTree = "<group>"; };
|
||||
D70C82A62C85F5910087F43F /* ConversationForwardMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationForwardMessageViewModel.swift; sourceTree = "<group>"; };
|
||||
D711B12F2E8FCED900DF8C71 /* CardDavViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDavViewModel.swift; sourceTree = "<group>"; };
|
||||
D711B1312E8FCF8600DF8C71 /* LdapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LdapViewModel.swift; sourceTree = "<group>"; };
|
||||
D711B1332E93F18300DF8C71 /* LdapServerConfigurationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LdapServerConfigurationFragment.swift; sourceTree = "<group>"; };
|
||||
D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMediaEncryptionModel.swift; sourceTree = "<group>"; };
|
||||
D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterViewModel.swift; sourceTree = "<group>"; };
|
||||
D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterCodeConfirmationFragment.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -356,6 +363,7 @@
|
|||
D759CB632C3FBD4200AC35E8 /* StartConversationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartConversationFragment.swift; sourceTree = "<group>"; };
|
||||
D759CB652C3FBE1D00AC35E8 /* StartConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartConversationViewModel.swift; sourceTree = "<group>"; };
|
||||
D76005F52B0798B00054B79A /* IntExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntExtension.swift; sourceTree = "<group>"; };
|
||||
D762102B2E97FDF8002E7999 /* CardDavAddressBookConfigurationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDavAddressBookConfigurationFragment.swift; sourceTree = "<group>"; };
|
||||
D7702EF12AC7205000557C00 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
|
||||
D777DBB22AE12C5900565A99 /* ContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsManager.swift; sourceTree = "<group>"; };
|
||||
D77A080D2CB6BCA10095D589 /* MessageConferenceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageConferenceInfo.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1031,6 +1039,8 @@
|
|||
D7DC096B2CFA192F00A6D47C /* Fragments */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D762102B2E97FDF8002E7999 /* CardDavAddressBookConfigurationFragment.swift */,
|
||||
D711B1332E93F18300DF8C71 /* LdapServerConfigurationFragment.swift */,
|
||||
D78607702D36CB87009E6A7E /* SettingsAdvancedFragment.swift */,
|
||||
D732C38B2D311D2100F78100 /* SettingsFragment.swift */,
|
||||
D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */,
|
||||
|
|
@ -1050,6 +1060,8 @@
|
|||
D7DC096D2CFA194600A6D47C /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D711B1312E8FCF8600DF8C71 /* LdapViewModel.swift */,
|
||||
D711B12F2E8FCED900DF8C71 /* CardDavViewModel.swift */,
|
||||
D756C8142D34FF8900A58F2F /* SettingsViewModel.swift */,
|
||||
D7C500412D2BE96E00DD53EC /* AccountSettingsViewModel.swift */,
|
||||
D7DC09702CFDBF8300A6D47C /* AccountProfileViewModel.swift */,
|
||||
|
|
@ -1301,6 +1313,7 @@
|
|||
D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */,
|
||||
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */,
|
||||
D78607712D36CB8A009E6A7E /* SettingsAdvancedFragment.swift in Sources */,
|
||||
D762102C2E97FDFD002E7999 /* CardDavAddressBookConfigurationFragment.swift in Sources */,
|
||||
66E50A492BD12B2300AD61CA /* MeetingsView.swift in Sources */,
|
||||
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
||||
D717630D2BD7BD0E00464097 /* ParticipantsListFragment.swift in Sources */,
|
||||
|
|
@ -1319,6 +1332,7 @@
|
|||
D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */,
|
||||
C6A5A9482C10B6A30070FEA4 /* AuthState.swift in Sources */,
|
||||
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
|
||||
D711B1342E93F18700DF8C71 /* LdapServerConfigurationFragment.swift in Sources */,
|
||||
D70A26F02B7D02E6006CC8FC /* ConversationViewModel.swift in Sources */,
|
||||
6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */,
|
||||
D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */,
|
||||
|
|
@ -1374,6 +1388,7 @@
|
|||
D7DC09712CFDBF9A00A6D47C /* AccountProfileViewModel.swift in Sources */,
|
||||
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */,
|
||||
D72343362AD037AF009AA24E /* ToastView.swift in Sources */,
|
||||
D711B1302E8FCEDE00DF8C71 /* CardDavViewModel.swift in Sources */,
|
||||
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */,
|
||||
D7E6ADF32B9875C20009A2BC /* Message.swift in Sources */,
|
||||
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,
|
||||
|
|
@ -1383,6 +1398,7 @@
|
|||
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
|
||||
66246C6A2C622AE900973E97 /* TimeZoneExtension.swift in Sources */,
|
||||
66C491FB2B24D32600CEA16D /* CoreExtension.swift in Sources */,
|
||||
D711B1322E8FCF8800DF8C71 /* LdapViewModel.swift in Sources */,
|
||||
D7F5F6412C359F3B007FCF2F /* SipAddressesPopup.swift in Sources */,
|
||||
D72A9A052B9750A1000DC093 /* UIList.swift in Sources */,
|
||||
D726E43D2B19E4FE0083C415 /* StartCallFragment.swift in Sources */,
|
||||
|
|
@ -1908,7 +1924,7 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git";
|
||||
requirement = {
|
||||
branch = stable;
|
||||
branch = alpha;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
"location" : "https://github.com/Finalet/Elegant-Emoji-Picker",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "12c1a2be1adbe7a774ebdd2c48f02d95b8884df6"
|
||||
"revision" : "598ff0a72198375d7317b61982fa8648d0ba3a44"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -123,8 +123,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git",
|
||||
"state" : {
|
||||
"branch" : "stable",
|
||||
"revision" : "4d51a91278236d1a22d880b769397c94a2bb7b3e"
|
||||
"branch" : "alpha",
|
||||
"revision" : "3b79481215d235e3e7cd142bc7c7eda077f1d99a"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue