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