Reworked contacts list to use search results from Magic Search instead of directly fetched contacts from native addressbook

This commit is contained in:
Sylvain Berfini 2022-02-15 12:24:12 +01:00
parent 69fdff6858
commit 7894311cdd
10 changed files with 111 additions and 29 deletions

View file

@ -10,7 +10,13 @@ Group changes to describe their impact on the project, as follows:
Fixed for any bug fixes.
Security to invite users to upgrade in case of vulnerabilities.
## [4.7.0] - Unreleased
## [4.6.2] - Unreleased
### Added
- LDAP settings if SDK is built with OpenLDAP (requires 5.1.1 or higher linphone-sdk), will add contacts if any
### Changed
- Contacts lists now show LDAP contacts if any, as well as "generated" contacts from SIP addresses you have interacted with
## [4.6.1] - 2022-02-14

View file

@ -173,6 +173,14 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
val viewModel = DialogViewModel(getString(R.string.contact_delete_one_dialog))
val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel)
val contactViewModel = adapter.currentList[viewHolder.bindingAdapterPosition]
if (contactViewModel.isNativeContact.value == false) {
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
val activity = requireActivity() as MainActivity
activity.showSnackBar(R.string.contact_cant_be_deleted)
return
}
viewModel.showCancelButton {
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
dialog.dismiss()
@ -212,6 +220,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
it.consume { contact ->
Log.i("[Contacts] Selected item in list changed: $contact")
sharedViewModel.selectedContact.value = contact
(requireActivity() as MainActivity).hideKeyboard()
if (editOnClick) {
navigateToContactEditor(sipUriToAdd, binding.slidingPane)

View file

@ -74,6 +74,8 @@ class ContactViewModel(val contactInternal: Contact) : ErrorReportingViewModel()
val waitForChatRoomCreation = MutableLiveData<Boolean>()
val isNativeContact = MutableLiveData<Boolean>()
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
override fun onContactUpdated(contact: Contact) {
if (contact is NativeContact && contactInternal is NativeContact && contact.nativeId == contactInternal.nativeId) {
@ -127,6 +129,7 @@ class ContactViewModel(val contactInternal: Contact) : ErrorReportingViewModel()
init {
contact.value = contactInternal
displayName.value = contactInternal.fullName ?: contactInternal.firstName + " " + contactInternal.lastName
isNativeContact.value = contactInternal is NativeContact
updateNumbersAndAddresses(contactInternal)
coreContext.contactsManager.addListener(contactsUpdatedListener)
@ -172,7 +175,7 @@ class ContactViewModel(val contactInternal: Contact) : ErrorReportingViewModel()
}
}
private fun updateNumbersAndAddresses(contact: Contact) {
fun updateNumbersAndAddresses(contact: Contact) {
val list = arrayListOf<ContactNumberOrAddressData>()
for (address in contact.sipAddresses) {
val value = address.asStringUriOnly()

View file

@ -28,6 +28,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.contact.Contact
import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.contact.NativeContact
import org.linphone.core.SearchResult
import org.linphone.core.tools.Log
class ContactsListViewModel : ViewModel() {
@ -36,6 +37,7 @@ class ContactsListViewModel : ViewModel() {
val contactsList = MutableLiveData<ArrayList<ContactViewModel>>()
val filter = MutableLiveData<String>()
private var previousFilter = "NotSet"
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
override fun onContactsUpdated() {
@ -57,27 +59,41 @@ class ContactsListViewModel : ViewModel() {
super.onCleared()
}
private fun getSelectedContactsList(): ArrayList<ContactViewModel> {
val list = arrayListOf<ContactViewModel>()
val source =
if (sipContactsSelected.value == true) coreContext.contactsManager.sipContacts
else coreContext.contactsManager.contacts
for (contact in source) {
list.add(ContactViewModel(contact))
}
return list
}
fun updateContactsList() {
val list: ArrayList<ContactViewModel>
val filterValue = filter.value.orEmpty()
list = if (filterValue.isNotEmpty()) {
getSelectedContactsList().filter { contact ->
contact.name.contains(filterValue, true)
} as ArrayList<ContactViewModel>
} else {
getSelectedContactsList()
contactsList.value.orEmpty().forEach(ContactViewModel::destroy)
if (previousFilter.isNotEmpty() && previousFilter.length > filterValue.length) {
coreContext.contactsManager.magicSearch.resetSearchCache()
}
previousFilter = filterValue
val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else ""
val results = coreContext.contactsManager.magicSearch.getContactListFromFilter(filterValue, domain)
val list = arrayListOf<ContactViewModel>()
for (result in results) {
val contact = searchMatchingContact(result) ?: Contact(searchResult = result)
if (contact is NativeContact) {
val found = list.find { contactViewModel ->
contactViewModel.contactInternal is NativeContact && contactViewModel.contactInternal.nativeId == contact.nativeId
}
if (found != null) {
Log.d("[Contacts] Found a search result that matches a native contact [$contact] we already have, skipping")
continue
}
} else {
val found = list.find { contactViewModel ->
contactViewModel.displayName.value == contact.fullName
}
if (found != null) {
Log.i("[Contacts] Found a search result that matches a contact [$contact] we already have, updating it with the new information")
found.contactInternal.addAddressAndPhoneNumberFromSearchResult(result)
found.updateNumbersAndAddresses(found.contactInternal)
continue
}
}
list.add(ContactViewModel(contact))
}
contactsList.postValue(list)
@ -146,4 +162,19 @@ class ContactsListViewModel : ViewModel() {
}
}
}
private fun searchMatchingContact(searchResult: SearchResult): Contact? {
val address = searchResult.address
if (address != null) {
val contact = coreContext.contactsManager.findContactByAddress(address, ignoreLocalContact = true)
if (contact != null) return contact
}
if (searchResult.phoneNumber != null) {
return coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber.orEmpty())
}
return null
}
}

View file

@ -29,8 +29,10 @@ import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.core.PresenceBasicStatus
import org.linphone.core.SearchResult
import org.linphone.core.tools.Log
import org.linphone.utils.ImageUtils
import org.linphone.utils.LinphoneUtils
data class PhoneNumber(val value: String, val typeLabel: String) : Comparable<PhoneNumber> {
override fun compareTo(other: PhoneNumber): Int {
@ -38,7 +40,7 @@ data class PhoneNumber(val value: String, val typeLabel: String) : Comparable<Ph
}
}
open class Contact : Comparable<Contact> {
open class Contact() : Comparable<Contact> {
var fullName: String? = null
var firstName: String? = null
var lastName: String? = null
@ -55,6 +57,31 @@ open class Contact : Comparable<Contact> {
private var thumbnailUri: Uri? = null
constructor(searchResult: SearchResult) : this() {
friend = searchResult.friend
addAddressAndPhoneNumberFromSearchResult(searchResult)
}
fun addAddressAndPhoneNumberFromSearchResult(searchResult: SearchResult) {
val address = searchResult.address
if (address != null) {
if (fullName == null) {
fullName = friend?.name ?: LinphoneUtils.getDisplayName(address)
}
sipAddresses.add(address)
}
val phoneNumber = searchResult.phoneNumber
if (phoneNumber != null) {
if (address == null && fullName == null) {
fullName = friend?.name ?: phoneNumber.orEmpty()
}
phoneNumbers.add(PhoneNumber(phoneNumber, ""))
}
}
override fun compareTo(other: Contact): Int {
val fn = fullName ?: ""
val otherFn = other.fullName ?: ""

View file

@ -236,13 +236,15 @@ class ContactsManager(private val context: Context) {
}
@Synchronized
fun findContactByAddress(address: Address): Contact? {
val localContact = localAccountsContacts.find { localContact ->
localContact.sipAddresses.find { localAddress ->
address.weakEqual(localAddress)
} != null
fun findContactByAddress(address: Address, ignoreLocalContact: Boolean = false): Contact? {
if (!ignoreLocalContact) {
val localContact = localAccountsContacts.find { localContact ->
localContact.sipAddresses.find { localAddress ->
address.weakEqual(localAddress)
} != null
}
if (localContact != null) return localContact
}
if (localContact != null) return localContact
val cleanAddress = address.clone()
cleanAddress.clean() // To remove gruu if any

View file

@ -52,6 +52,7 @@
<ImageView
android:onClick="@{editClickListener}"
android:visibility="@{viewModel.isNativeContact ? View.VISIBLE : View.INVISIBLE}"
android:contentDescription="@string/content_description_edit_contact"
android:layout_width="0dp"
android:layout_height="match_parent"
@ -62,6 +63,7 @@
<ImageView
android:onClick="@{deleteClickListener}"
android:visibility="@{viewModel.isNativeContact ? View.VISIBLE : View.INVISIBLE}"
android:contentDescription="@string/content_description_delete_contact"
android:layout_width="0dp"
android:layout_height="match_parent"

View file

@ -62,7 +62,7 @@
<CheckBox
android:onClick="@{() -> selectionListViewModel.onToggleSelect(position)}"
android:visibility="@{selectionListViewModel.isEditionEnabled ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{selectionListViewModel.isEditionEnabled &amp;&amp; viewModel.isNativeContact ? View.VISIBLE : View.GONE, default=gone}"
android:checked="@{selectionListViewModel.selectedItems.contains(position)}"
android:layout_width="wrap_content"
android:layout_height="match_parent"

View file

@ -652,4 +652,5 @@
<string name="contacts_settings_ldap_sip_domain_title">Domaine</string>
<string name="contacts_settings_ldap_misc_title">Divers</string>
<string name="contacts_settings_ldap_debug_title">Débogage</string>
<string name="contact_cant_be_deleted">Ce contact ne peut être supprimé</string>
</resources>

View file

@ -114,6 +114,7 @@
</plurals>
<string name="contact_new_choose_sync_account">Choose where to save the contact</string>
<string name="contact_local_sync_account">Store locally</string>
<string name="contact_cant_be_deleted">This contact can\'t be deleted</string>
<!-- Dialer -->
<string name="dialer_address_bar_hint">Enter a number or an address</string>