diff --git a/app/build.gradle b/app/build.gradle
index 9447d4166..60d878550 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -157,7 +157,7 @@ dependencies {
implementation 'com.google.android.material:material:1.11.0'
// https://github.com/coil-kt/coil/blob/main/LICENSE.txt Apache v2.0
- def coil_version = "2.5.0"
+ def coil_version = "2.6.0"
implementation("io.coil-kt:coil:$coil_version")
implementation("io.coil-kt:coil-gif:$coil_version")
implementation("io.coil-kt:coil-svg:$coil_version")
diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/CardDavAddressBookConfigurationFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/CardDavAddressBookConfigurationFragment.kt
new file mode 100644
index 000000000..b32a885f8
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/CardDavAddressBookConfigurationFragment.kt
@@ -0,0 +1,72 @@
+package org.linphone.ui.main.settings.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.UiThread
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.navArgs
+import org.linphone.core.tools.Log
+import org.linphone.databinding.SettingsContactsCarddavBinding
+import org.linphone.ui.main.MainActivity
+import org.linphone.ui.main.fragment.GenericFragment
+import org.linphone.ui.main.settings.viewmodel.CardDavViewModel
+
+@UiThread
+class CardDavAddressBookConfigurationFragment : GenericFragment() {
+ companion object {
+ private const val TAG = "[CardDAV Address Book Configuration Fragment]"
+ }
+
+ private lateinit var binding: SettingsContactsCarddavBinding
+
+ private lateinit var viewModel: CardDavViewModel
+
+ private val args: CardDavAddressBookConfigurationFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = SettingsContactsCarddavBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ viewModel = ViewModelProvider(this)[CardDavViewModel::class.java]
+
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+
+ val friendListDisplayName = args.displayName
+ if (friendListDisplayName != null) {
+ Log.i("$TAG Found display name in arguments, loading friends list values")
+ viewModel.loadFriendList(friendListDisplayName)
+ } else {
+ Log.i("$TAG No display name found in arguments, starting from scratch")
+ }
+
+ binding.setBackClickListener {
+ goBack()
+ }
+
+ viewModel.cardDavOperationSuccessfulEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ Log.i("$TAG CardDAV friend list operation was successful, going back")
+ goBack()
+ }
+ }
+
+ viewModel.showErrorToastEvent.observe(viewLifecycleOwner) {
+ it.consume { pair ->
+ val icon = pair.first
+ val message = pair.second
+ (requireActivity() as MainActivity).showRedToast(message, icon)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/LdapServerConfigurationFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/LdapServerConfigurationFragment.kt
new file mode 100644
index 000000000..ff13fded5
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/LdapServerConfigurationFragment.kt
@@ -0,0 +1,72 @@
+package org.linphone.ui.main.settings.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.UiThread
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.navArgs
+import org.linphone.core.tools.Log
+import org.linphone.databinding.SettingsContactsLdapBinding
+import org.linphone.ui.main.MainActivity
+import org.linphone.ui.main.fragment.GenericFragment
+import org.linphone.ui.main.settings.viewmodel.LdapViewModel
+
+@UiThread
+class LdapServerConfigurationFragment : GenericFragment() {
+ companion object {
+ private const val TAG = "[LDAP Server Configuration Fragment]"
+ }
+
+ private lateinit var binding: SettingsContactsLdapBinding
+
+ private lateinit var viewModel: LdapViewModel
+
+ private val args: LdapServerConfigurationFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = SettingsContactsLdapBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ viewModel = ViewModelProvider(this)[LdapViewModel::class.java]
+
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+
+ val ldapServerUrl = args.serverUrl
+ if (ldapServerUrl != null) {
+ Log.i("$TAG Found server URL in arguments, loading values")
+ viewModel.loadLdap(ldapServerUrl)
+ } else {
+ Log.i("$TAG No server URL found in arguments, starting from scratch")
+ }
+
+ binding.setBackClickListener {
+ goBack()
+ }
+
+ viewModel.ldapServerOperationSuccessfulEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ Log.i("$TAG LDAP server operation was successful, going back")
+ goBack()
+ }
+ }
+
+ viewModel.showErrorToastEvent.observe(viewLifecycleOwner) {
+ it.consume { pair ->
+ val icon = pair.first
+ val message = pair.second
+ (requireActivity() as MainActivity).showRedToast(message, icon)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt
index 2292113ea..1b7a40fdd 100644
--- a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt
+++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsFragment.kt
@@ -8,6 +8,7 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.annotation.UiThread
import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.findNavController
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.compatibility.Compatibility
@@ -109,6 +110,42 @@ class SettingsFragment : GenericFragment() {
binding.callsSettings.deviceRingtoneSpinner.onItemSelectedListener = ringtoneListener
+ viewModel.addLdapServerEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ val action = SettingsFragmentDirections.actionSettingsFragmentToLdapServerConfigurationFragment(
+ null
+ )
+ findNavController().navigate(action)
+ }
+ }
+
+ viewModel.editLdapServerEvent.observe(viewLifecycleOwner) {
+ it.consume { name ->
+ val action = SettingsFragmentDirections.actionSettingsFragmentToLdapServerConfigurationFragment(
+ name
+ )
+ findNavController().navigate(action)
+ }
+ }
+
+ viewModel.addCardDavServerEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ val action = SettingsFragmentDirections.actionSettingsFragmentToCardDavAddressBookConfigurationFragment(
+ null
+ )
+ findNavController().navigate(action)
+ }
+ }
+
+ viewModel.editCardDavServerEvent.observe(viewLifecycleOwner) {
+ it.consume { name ->
+ val action = SettingsFragmentDirections.actionSettingsFragmentToCardDavAddressBookConfigurationFragment(
+ name
+ )
+ findNavController().navigate(action)
+ }
+ }
+
// Meeting default layout related
val layoutAdapter = ArrayAdapter(
requireContext(),
@@ -152,4 +189,11 @@ class SettingsFragment : GenericFragment() {
viewModel.stopRingtonePlayer()
}
}
+
+ override fun onResume() {
+ super.onResume()
+
+ viewModel.reloadLdapServers()
+ viewModel.reloadConfiguredCardDavServers()
+ }
}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/model/CardDavLdapModel.kt b/app/src/main/java/org/linphone/ui/main/settings/model/CardDavLdapModel.kt
new file mode 100644
index 000000000..2b3e960bd
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/main/settings/model/CardDavLdapModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 .
+ */
+package org.linphone.ui.main.settings.model
+
+import androidx.annotation.AnyThread
+import androidx.annotation.UiThread
+
+@AnyThread
+class CardDavLdapModel(val name: String, private val onClicked: (name: String) -> (Unit)) {
+ @UiThread
+ fun clicked() {
+ onClicked.invoke(name)
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt
new file mode 100644
index 000000000..0831cb718
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/CardDavViewModel.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 .
+ */
+package org.linphone.ui.main.settings.viewmodel
+
+import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.R
+import org.linphone.core.Factory
+import org.linphone.core.FriendList
+import org.linphone.core.FriendListListenerStub
+import org.linphone.core.tools.Log
+import org.linphone.utils.Event
+
+class CardDavViewModel : ViewModel() {
+ companion object {
+ private const val TAG = "[CardDAV ViewModel]"
+ }
+
+ val isEdit = MutableLiveData()
+
+ val displayName = MutableLiveData()
+
+ val serverUrl = MutableLiveData()
+
+ val username = MutableLiveData()
+
+ val password = MutableLiveData()
+
+ val realm = MutableLiveData()
+
+ val showPassword = MutableLiveData()
+
+ val syncInProgress = MutableLiveData()
+
+ val cardDavOperationSuccessfulEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ val showErrorToastEvent: MutableLiveData>> by lazy {
+ MutableLiveData>>()
+ }
+
+ private lateinit var friendList: FriendList
+
+ private val friendListListener = object : FriendListListenerStub() {
+ @WorkerThread
+ override fun onSyncStatusChanged(
+ friendList: FriendList,
+ status: FriendList.SyncStatus,
+ message: String?
+ ) {
+ Log.i(
+ "$TAG Friend list [${friendList.displayName}] sync status changed to [$status] with message [$message]"
+ )
+ when (status) {
+ FriendList.SyncStatus.Successful -> {
+ syncInProgress.postValue(false)
+ cardDavOperationSuccessfulEvent.postValue(Event(true))
+ }
+ FriendList.SyncStatus.Failure -> {
+ syncInProgress.postValue(false)
+ val icon = R.drawable.x
+ showErrorToastEvent.postValue(Event(Pair(icon, message.orEmpty())))
+ }
+ else -> {}
+ }
+ }
+ }
+
+ init {
+ isEdit.value = false
+ showPassword.value = false
+ syncInProgress.value = false
+ }
+
+ override fun onCleared() {
+ if (::friendList.isInitialized) {
+ friendList.removeListener(friendListListener)
+ }
+
+ super.onCleared()
+ }
+
+ @UiThread
+ fun loadFriendList(name: String) {
+ coreContext.postOnCoreThread { core ->
+ val found = core.getFriendListByName(name)
+ if (found == null) {
+ Log.e("$TAG Failed to find friend list with display name [$name]!")
+ return@postOnCoreThread
+ }
+
+ isEdit.postValue(true)
+ friendList = found
+ friendList.addListener(friendListListener)
+
+ displayName.postValue(name)
+ serverUrl.postValue(friendList.uri)
+ Log.i("$TAG Existing friend list CardDAV values loaded")
+ }
+ }
+
+ @UiThread
+ fun delete() {
+ coreContext.postOnCoreThread { core ->
+ if (isEdit.value == true && ::friendList.isInitialized) {
+ val name = friendList.displayName
+ core.removeFriendList(friendList)
+ Log.i("$TAG Removed friends list with display name [$name]")
+ cardDavOperationSuccessfulEvent.postValue(Event(true))
+ }
+ }
+ }
+
+ @UiThread
+ fun toggleShowPassword() {
+ showPassword.value = showPassword.value == false
+ }
+
+ @UiThread
+ fun addAddressBook() {
+ val name = displayName.value.orEmpty().trim()
+ val server = serverUrl.value.orEmpty().trim()
+ if (name.isEmpty() || server.isEmpty()) {
+ // TODO FIXME: improve toast
+ showErrorToastEvent.postValue(Event(Pair(R.drawable.x, "Name or Server is empty!")))
+ return
+ }
+
+ val user = username.value.orEmpty().trim()
+ val pwd = password.value.orEmpty().trim()
+ val authRealm = realm.value.orEmpty().trim()
+
+ coreContext.postOnCoreThread { core ->
+ // TODO FIXME: add dialog to ask user before removing existing friend list & auth info ?
+ if (isEdit.value == false) {
+ val foundFriendList = core.getFriendListByName(name)
+ if (foundFriendList != null) {
+ Log.w("$TAG Friend list [$name] already exists, removing it first")
+ core.removeFriendList(foundFriendList)
+ }
+ }
+
+ if (user.isNotEmpty() && authRealm.isNotEmpty()) {
+ val foundAuthInfo = core.findAuthInfo(authRealm, user, null)
+ if (foundAuthInfo != null) {
+ Log.w("$TAG Auth info with username [$user] already exists, removing it first")
+ core.removeAuthInfo(foundAuthInfo)
+ }
+
+ Log.i("$TAG Adding auth info with username [$user]")
+ val authInfo = Factory.instance().createAuthInfo(
+ user,
+ null,
+ pwd,
+ null,
+ authRealm,
+ null
+ )
+ core.addAuthInfo(authInfo)
+ }
+
+ if (isEdit.value == true && ::friendList.isInitialized) {
+ Log.i(
+ "$TAG Changes were made to CardDAV friend list [$name], synchronizing it"
+ )
+ } else {
+ friendList = core.createFriendList()
+ friendList.displayName = name
+ friendList.type = FriendList.Type.CardDAV
+ friendList.uri = server
+ friendList.isDatabaseStorageEnabled = true
+ friendList.addListener(friendListListener)
+ core.addFriendList(friendList)
+
+ Log.i(
+ "$TAG CardDAV friend list [$name] created with server URL [$server], synchronizing it"
+ )
+ }
+
+ syncInProgress.postValue(true)
+ friendList.synchronizeFriendsFromServer()
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt
new file mode 100644
index 000000000..b3fe476c0
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/LdapViewModel.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 .
+ */
+package org.linphone.ui.main.settings.viewmodel
+
+import androidx.annotation.UiThread
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.R
+import org.linphone.core.Ldap
+import org.linphone.core.tools.Log
+import org.linphone.utils.Event
+
+class LdapViewModel : ViewModel() {
+ companion object {
+ private const val TAG = "[LDAP ViewModel]"
+ }
+
+ val isEdit = MutableLiveData()
+
+ val serverUrl = MutableLiveData()
+
+ val bindDn = MutableLiveData()
+
+ val password = MutableLiveData()
+
+ val showPassword = MutableLiveData()
+
+ val useTls = MutableLiveData()
+
+ val searchBase = MutableLiveData()
+
+ val searchFilter = MutableLiveData()
+
+ val maxResults = MutableLiveData()
+
+ val requestTimeout = MutableLiveData()
+
+ val requestDelay = MutableLiveData()
+
+ val minCharacters = MutableLiveData()
+
+ val nameAttributes = MutableLiveData()
+
+ val sipAttributes = MutableLiveData()
+
+ val sipDomain = MutableLiveData()
+
+ val ldapServerOperationSuccessfulEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ val showErrorToastEvent: MutableLiveData>> by lazy {
+ MutableLiveData>>()
+ }
+
+ private lateinit var ldapToEdit: Ldap
+
+ init {
+ isEdit.value = false
+ showPassword.value = false
+
+ useTls.value = true
+ minCharacters.value = "3"
+ }
+
+ @UiThread
+ fun loadLdap(url: String) {
+ coreContext.postOnCoreThread { core ->
+ val found = core.ldapList.find {
+ it.params.server == url
+ }
+ if (found == null) {
+ Log.e("$TAG Failed to find LDAP server with URL [$url]!")
+ return@postOnCoreThread
+ }
+
+ isEdit.postValue(true)
+ ldapToEdit = found
+ val ldapParams = ldapToEdit.params
+
+ serverUrl.postValue(ldapParams.server)
+ bindDn.postValue(ldapParams.bindDn.orEmpty())
+ useTls.postValue(ldapParams.isTlsEnabled)
+ searchBase.postValue(ldapParams.baseObject)
+ searchFilter.postValue(ldapParams.filter.orEmpty())
+ maxResults.postValue(ldapParams.maxResults.toString())
+ requestTimeout.postValue(ldapParams.timeout.toString())
+ requestDelay.postValue(ldapParams.delay.toString())
+ minCharacters.postValue(ldapParams.minChars.toString())
+ nameAttributes.postValue(ldapParams.nameAttribute.orEmpty())
+ sipAttributes.postValue(ldapParams.sipAttribute.orEmpty())
+ sipDomain.postValue(ldapParams.sipDomain.orEmpty())
+ Log.i("$TAG Existing LDAP server values loaded")
+ }
+ }
+
+ @UiThread
+ fun delete() {
+ coreContext.postOnCoreThread { core ->
+ if (isEdit.value == true && ::ldapToEdit.isInitialized) {
+ val serverUrl = ldapToEdit.params.server
+ core.removeLdap(ldapToEdit)
+ Log.i("$TAG Removed LDAP config for server URL [$serverUrl]")
+ ldapServerOperationSuccessfulEvent.postValue(Event(true))
+ }
+ }
+ }
+
+ @UiThread
+ fun toggleShowPassword() {
+ showPassword.value = showPassword.value == false
+ }
+
+ @UiThread
+ fun toggleTls() {
+ useTls.value = useTls.value == false
+ }
+
+ @UiThread
+ fun addServer() {
+ coreContext.postOnCoreThread { core ->
+ try {
+ val ldapParams = core.createLdapParams()
+
+ ldapParams.enabled = true
+ ldapParams.server = serverUrl.value.orEmpty().trim()
+ ldapParams.bindDn = bindDn.value.orEmpty().trim()
+ ldapParams.password = password.value.orEmpty().trim()
+ ldapParams.authMethod = Ldap.AuthMethod.Simple
+ ldapParams.isTlsEnabled = useTls.value == true
+ ldapParams.serverCertificatesVerificationMode = Ldap.CertVerificationMode.Default
+ ldapParams.baseObject = searchBase.value.orEmpty().trim()
+ ldapParams.filter = searchFilter.value.orEmpty().trim()
+ ldapParams.maxResults = maxResults.value.orEmpty().trim().toInt()
+ ldapParams.timeout = requestTimeout.value.orEmpty().trim().toInt()
+ ldapParams.delay = requestDelay.value.orEmpty().trim().toInt()
+ ldapParams.minChars = minCharacters.value.orEmpty().trim().toInt()
+ ldapParams.nameAttribute = nameAttributes.value.orEmpty().trim()
+ ldapParams.sipAttribute = sipAttributes.value.orEmpty().trim()
+ ldapParams.sipDomain = sipDomain.value.orEmpty().trim()
+ ldapParams.debugLevel = Ldap.DebugLevel.Verbose
+
+ if (isEdit.value == true && ::ldapToEdit.isInitialized) {
+ ldapToEdit.params = ldapParams
+ Log.i("$TAG LDAP changes have been applied")
+ } else {
+ val ldap = core.createLdapWithParams(ldapParams)
+ core.addLdap(ldap)
+ Log.i("$TAG New LDAP config created")
+ }
+ ldapServerOperationSuccessfulEvent.postValue(Event(true))
+ } catch (e: Exception) {
+ Log.e("$TAG Exception while creating LDAP: $e")
+ // TODO FIXME: improve toast
+ showErrorToastEvent.postValue(
+ Event(Pair(R.drawable.x, e.toString()))
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
index cc4f65e40..c59dffd61 100644
--- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
@@ -35,11 +35,14 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.core.Conference
+import org.linphone.core.FriendList
import org.linphone.core.Player
import org.linphone.core.PlayerListener
import org.linphone.core.tools.Log
+import org.linphone.ui.main.settings.model.CardDavLdapModel
import org.linphone.utils.AppUtils
import org.linphone.utils.AudioUtils
+import org.linphone.utils.Event
class SettingsViewModel @UiThread constructor() : ViewModel() {
companion object {
@@ -79,6 +82,25 @@ class SettingsViewModel @UiThread constructor() : ViewModel() {
// Contacts settings
val showContactsSettings = MutableLiveData()
+ val ldapServers = MutableLiveData>()
+
+ val cardDavFriendsLists = MutableLiveData>()
+
+ val addLdapServerEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+ val editLdapServerEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ val addCardDavServerEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ val editCardDavServerEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
// Meetings settings
val showMeetingsSettings = MutableLiveData()
@@ -336,6 +358,58 @@ class SettingsViewModel @UiThread constructor() : ViewModel() {
expandContacts.value = expandContacts.value == false
}
+ @UiThread
+ fun addLdapServer() {
+ addLdapServerEvent.value = Event(true)
+ }
+
+ @UiThread
+ fun reloadLdapServers() {
+ coreContext.postOnCoreThread { core ->
+ val list = arrayListOf()
+
+ for (ldap in core.ldapList) {
+ val label = ldap.params.server
+ if (label.isNotEmpty()) {
+ list.add(
+ CardDavLdapModel(label) {
+ editLdapServerEvent.postValue(Event(label))
+ }
+ )
+ }
+ }
+
+ ldapServers.postValue(list)
+ }
+ }
+
+ @UiThread
+ fun addCardDavServer() {
+ addCardDavServerEvent.value = Event(true)
+ }
+
+ @UiThread
+ fun reloadConfiguredCardDavServers() {
+ coreContext.postOnCoreThread { core ->
+ val list = arrayListOf()
+
+ for (friendList in core.friendsLists) {
+ if (friendList.type == FriendList.Type.CardDAV) {
+ val label = friendList.displayName ?: friendList.uri ?: ""
+ if (label.isNotEmpty()) {
+ list.add(
+ CardDavLdapModel(label) {
+ editCardDavServerEvent.postValue(Event(label))
+ }
+ )
+ }
+ }
+ }
+
+ cardDavFriendsLists.postValue(list)
+ }
+ }
+
@UiThread
fun toggleMeetingsExpand() {
expandMeetings.value = expandMeetings.value == false
diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
index 7e9af23b4..aea50d218 100644
--- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
+++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
@@ -107,7 +107,9 @@ fun setEntries(
)
binding.setVariable(BR.model, entry)
- binding.setVariable(BR.onLongClickListener, onLongClick)
+ if (onLongClick != null) {
+ binding.setVariable(BR.onLongClickListener, onLongClick)
+ }
// This is a bit hacky...
if (viewGroup.context as? LifecycleOwner != null) {
diff --git a/app/src/main/res/layout/settings_contacts.xml b/app/src/main/res/layout/settings_contacts.xml
index db9813bc3..33dc9a35b 100644
--- a/app/src/main/res/layout/settings_contacts.xml
+++ b/app/src/main/res/layout/settings_contacts.xml
@@ -1,6 +1,6 @@
-
+
@@ -15,6 +15,76 @@
android:paddingBottom="20dp"
android:background="@drawable/shape_squircle_white_background">
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_contacts_carddav.xml b/app/src/main/res/layout/settings_contacts_carddav.xml
new file mode 100644
index 000000000..d1c599d45
--- /dev/null
+++ b/app/src/main/res/layout/settings_contacts_carddav.xml
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_contacts_carddav_ldap_list_cell.xml b/app/src/main/res/layout/settings_contacts_carddav_ldap_list_cell.xml
new file mode 100644
index 000000000..1b6360491
--- /dev/null
+++ b/app/src/main/res/layout/settings_contacts_carddav_ldap_list_cell.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_contacts_ldap.xml b/app/src/main/res/layout/settings_contacts_ldap.xml
new file mode 100644
index 000000000..58a3d7e64
--- /dev/null
+++ b/app/src/main/res/layout/settings_contacts_ldap.xml
@@ -0,0 +1,523 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml
index d0ec3516c..a5a51cf1a 100644
--- a/app/src/main/res/navigation/main_nav_graph.xml
+++ b/app/src/main/res/navigation/main_nav_graph.xml
@@ -112,7 +112,24 @@
android:id="@+id/settingsFragment"
android:name="org.linphone.ui.main.settings.fragment.SettingsFragment"
android:label="SettingsFragment"
- tools:layout="@layout/settings_fragment" />
+ tools:layout="@layout/settings_fragment" >
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 948b20bc2..a29597442 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -203,6 +203,29 @@
Exporter les médias dans la gallerie
Les médias des messages éphémères ne seront jamais exportés
Contacts
+ Ajouter un serveur LDAP
+ Editer le serveur LDAP
+ Ajouter un carnet d\'adresse CardDAV
+ Editer le carnet d\'adresse CardDAV
+ Nom d\'affichage
+ URL du serveur
+ Nom d\'utilisateur
+ Mot de passe
+ Domaine d\'authentification
+ Ajouter le carnet d\'adresse
+ URL du serveur
+ Bind DN
+ Mot de passe
+ Utiliser TLS
+ Base de recherche (ne peut être vide)
+ Filtre
+ Nombre de résultats maximum
+ Durée maximum
+ Délai entre 2 requêtes (en secondes)
+ Nombre minimum de caractères pour lancer la requête (en millisecondes)
+ Attributs de nom
+ Attributs SIP
+ Domaine SIP
Réunions
Disposition par défaut
Intervenant actif
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9d2ca2fc3..36856e576 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -247,6 +247,29 @@
Export media in native gallery
Media from ephemeral messages will never be exported
Contacts
+ Add LDAP server
+ Edit LDAP server
+ Add CardDAV address book
+ Edit CardDAV address book
+ Display name
+ Server URL
+ Username
+ Password
+ Auth realm
+ Add address book
+ Server URL
+ Bind DN
+ Password
+ Use TLS
+ Search
+ Search base (can\'t be empty)
+ Filter
+ Max results
+ Timeout (in seconds)
+ Delay between two requests (in milliseconds)
+ Name attributes
+ SIP attributes
+ SIP domain
Meetings
Default layout
Active speaker