diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 313bea9b2..3620869ab 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -89,17 +89,7 @@ class ContactsManager @UiThread constructor() { Log.d( "$TAG Newly discovered SIP Address [$sipUri] for friend [${friend.name}] in list [${friendList.displayName}]" ) - - if (unknownContactsAvatarsMap.keys.contains(sipUri)) { - Log.d("$TAG Found SIP Address in unknownContactsAvatarsMap, removing it") - val oldModel = unknownContactsAvatarsMap[sipUri] - oldModel?.destroy() - unknownContactsAvatarsMap.remove(sipUri) - } else if (knownContactsAvatarsMap.keys.contains(sipUri)) { - Log.d("$TAG Found SIP Address in knownContactsAvatarsMap, forcing presence update") - val oldModel = knownContactsAvatarsMap[sipUri] - oldModel?.updatePresence() - } + newContactAddedWithSipUri(sipUri) reloadContactsJob = coroutineScope.launch { delay(DELAY_BEFORE_RELOADING_CONTACTS_AFTER_PRESENCE_RECEIVED) @@ -162,6 +152,49 @@ class ContactsManager @UiThread constructor() { } } + @WorkerThread + private fun newContactAddedWithSipUri(sipUri: String) { + if (unknownContactsAvatarsMap.keys.contains(sipUri)) { + Log.d("$TAG Found SIP URI [$sipUri] in unknownContactsAvatarsMap, removing it") + val oldModel = unknownContactsAvatarsMap[sipUri] + oldModel?.destroy() + unknownContactsAvatarsMap.remove(sipUri) + } else if (knownContactsAvatarsMap.keys.contains(sipUri)) { + Log.d( + "$TAG Found SIP URI [$sipUri] in knownContactsAvatarsMap, forcing presence update" + ) + val oldModel = knownContactsAvatarsMap[sipUri] + oldModel?.update() + } + } + + @WorkerThread + fun newContactAdded(friend: Friend) { + for (sipAddress in friend.addresses) { + newContactAddedWithSipUri(sipAddress.asStringUriOnly()) + } + + conferenceAvatarMap.values.forEach(ContactAvatarModel::destroy) + conferenceAvatarMap.clear() + coreContext.contactsManager.notifyContactsListChanged() + } + + fun contactRemoved(friend: Friend) { + for (sipAddress in friend.addresses) { + val sipUri = sipAddress.asStringUriOnly() + if (knownContactsAvatarsMap.keys.contains(sipUri)) { + Log.d("$TAG Found SIP URI [$sipUri] in knownContactsAvatarsMap, removing it") + val oldModel = knownContactsAvatarsMap[sipUri] + oldModel?.destroy() + knownContactsAvatarsMap.remove(sipUri) + } + } + + conferenceAvatarMap.values.forEach(ContactAvatarModel::destroy) + conferenceAvatarMap.clear() + coreContext.contactsManager.notifyContactsListChanged() + } + @WorkerThread fun onNativeContactsLoaded() { nativeContactsLoaded = true diff --git a/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt index 0594f5fd3..b356081d6 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/adapter/ContactsListAdapter.kt @@ -10,7 +10,6 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.linphone.R -import org.linphone.core.ConsolidatedPresence import org.linphone.databinding.ContactFavouriteListCellBinding import org.linphone.databinding.ContactListCellBinding import org.linphone.ui.main.contacts.model.ContactAvatarModel @@ -138,13 +137,11 @@ class ContactsListAdapter( private class ContactDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean { - return oldItem.id == newItem.id && oldItem.contactName == newItem.contactName + return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean { - return oldItem.presenceStatus.value == newItem.presenceStatus.value && - oldItem.isFavourite.value == newItem.isFavourite.value && - (newItem.presenceStatus.value == ConsolidatedPresence.Busy || newItem.presenceStatus.value == ConsolidatedPresence.Online) + return false // oldItem & newItem are always the same because fetched from cache, so return false to force refresh } } } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt index 117a6cf08..6553845fe 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/ContactsListFragment.kt @@ -230,6 +230,7 @@ class ContactsListFragment : AbstractTopBarFragment() { { // onDelete coreContext.postOnCoreThread { Log.w("$TAG Removing friend [${model.name.value}]") + coreContext.contactsManager.contactRemoved(model.friend) model.friend.remove() coreContext.contactsManager.notifyContactsListChanged() } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt index 9ee6d37e5..d15917631 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt @@ -71,11 +71,18 @@ class EditContactFragment : SlidingPaneChildFragment() { private val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> if (uri != null) { Log.i("$TAG Picture picked [$uri]") - // TODO FIXME: use a better file name - val localFileName = FileUtils.getFileStoragePath("temp.jpg", true) + val localFileName = FileUtils.getFileStoragePath( + viewModel.getPictureFileName(), + true, + overrideExisting = true + ) lifecycleScope.launch { if (FileUtils.copyFile(uri, localFileName)) { - viewModel.picturePath.postValue(localFileName.absolutePath) + val newPath = FileUtils.getProperFilePath( + localFileName.absolutePath + ) + Log.i("$TAG Copied file [$uri] to [$newPath]") + viewModel.picturePath.value = newPath } else { Log.e( "$TAG Failed to copy file from [$uri] to [${localFileName.absolutePath}]" diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt index 03857d793..c7c3f54f0 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt @@ -67,11 +67,16 @@ class NewContactFragment : GenericFragment() { private val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> if (uri != null) { Log.i("$TAG Picture picked [$uri]") - // TODO FIXME: use a better file name - val localFileName = FileUtils.getFileStoragePath("temp.jpg", true) + val localFileName = FileUtils.getFileStorageCacheDir( + ContactNewOrEditViewModel.TEMP_PICTURE_NAME + ) lifecycleScope.launch { if (FileUtils.copyFile(uri, localFileName)) { - viewModel.picturePath.postValue(localFileName.absolutePath) + val newPath = FileUtils.getProperFilePath( + localFileName.absolutePath + ) + Log.i("$TAG Copied file [$uri] to [$newPath]") + viewModel.picturePath.value = newPath } else { Log.e( "$TAG Failed to copy file from [$uri] to [${localFileName.absolutePath}]" diff --git a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt index 842a831bb..fdfd96ec7 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactAvatarModel.kt @@ -69,14 +69,7 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) : Abstrac friend.addListener(friendListener) } - isFavourite.postValue(friend.starred) - initials.postValue(AppUtils.getInitials(friend.name.orEmpty())) - trust.postValue(SecurityLevel.Encrypted) // TODO FIXME: use API - showTrust.postValue(coreContext.core.defaultAccount?.isInSecureMode()) - images.postValue(arrayListOf(getAvatarUri(friend).toString())) - - name.postValue(friend.name) - computePresence() + update() } @WorkerThread @@ -87,8 +80,14 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) : Abstrac } @WorkerThread - fun updatePresence() { - Log.i("$TAG Force update presence information for friend [${friend.name}]") + fun update() { + isFavourite.postValue(friend.starred) + initials.postValue(AppUtils.getInitials(friend.name.orEmpty())) + trust.postValue(SecurityLevel.Encrypted) // TODO FIXME: use API + showTrust.postValue(coreContext.core.defaultAccount?.isInSecureMode()) + images.postValue(arrayListOf(getAvatarUri(friend).toString())) + + name.postValue(friend.name) computePresence() } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt index dcb50ffa4..4f250b4a7 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt @@ -19,10 +19,14 @@ */ package org.linphone.ui.main.contacts.viewmodel +import android.net.Uri +import androidx.annotation.AnyThread import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.contacts.ContactLoader.Companion.LINPHONE_ADDRESS_BOOK_FRIEND_LIST import org.linphone.core.Friend @@ -36,10 +40,14 @@ import org.linphone.utils.FileUtils class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { companion object { private const val TAG = "[Contact New/Edit View Model]" + + const val TEMP_PICTURE_NAME = "new_contact_temp_picture.jpg" } private lateinit var friend: Friend + val id = MutableLiveData() + val isEdit = MutableLiveData() val picturePath = MutableLiveData() @@ -89,6 +97,8 @@ class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { // TODO ? What to do when vCard is null } + id.postValue(friend.refKey ?: friend.vcard?.uid) + val photo = friend.photo.orEmpty() if (photo.isNotEmpty()) { picturePath.postValue(photo) @@ -114,6 +124,12 @@ class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { } } + @AnyThread + fun getPictureFileName(): String { + val name = id.value?.replace(" ", "_") ?: "${firstName.value.orEmpty().trim()}_${lastName.value.orEmpty().trim()}" + return "$name.jpg" + } + @UiThread fun saveChanges() { var check = true @@ -140,9 +156,9 @@ class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { } val fn = firstName.value.orEmpty().trim() val ln = lastName.value.orEmpty().trim() - friend.name = "$fn $ln" friend.edit() + friend.name = "$fn $ln" val vCard = friend.vcard if (vCard != null) { @@ -151,7 +167,22 @@ class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { val picture = picturePath.value.orEmpty() if (picture.isNotEmpty()) { - friend.photo = FileUtils.getProperFilePath(picture) + if (picture.contains(TEMP_PICTURE_NAME)) { + val newFile = FileUtils.getFileStoragePath( + getPictureFileName(), + true, + overrideExisting = true + ) + val oldFile = Uri.parse(FileUtils.getProperFilePath(picture)) + viewModelScope.launch { + FileUtils.copyFile(oldFile, newFile) + } + val newPicture = FileUtils.getProperFilePath(newFile.absolutePath) + Log.i("$TAG Temporary picture [$picture] copied to [$newPicture]") + friend.photo = newPicture + } else { + friend.photo = FileUtils.getProperFilePath(picture) + } } else { friend.photo = null } @@ -215,7 +246,7 @@ class ContactNewOrEditViewModel @UiThread constructor() : ViewModel() { friend.done() } - coreContext.contactsManager.notifyContactsListChanged() + coreContext.contactsManager.newContactAdded(friend) saveChangesEvent.postValue( Event(if (status == Status.OK) friend.refKey.orEmpty() else "") diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index c12bee6c0..38f001557 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -354,6 +354,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() { coreContext.postOnCoreThread { if (::friend.isInitialized) { Log.w("$TAG Deleting friend [$friend]") + coreContext.contactsManager.contactRemoved(friend) friend.remove() coreContext.contactsManager.notifyContactsListChanged() contactRemovedEvent.postValue(Event(true)) diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt index a8159cacc..c050cb2a6 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactsListViewModel.kt @@ -79,10 +79,10 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel() override fun onContactsLoaded() { Log.i("$TAG Contacts have been (re)loaded, updating list") magicSearch.resetSearchCache() + applyFilter( currentFilter, - if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt() + if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "" ) } } @@ -118,8 +118,7 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel() coreContext.postOnCoreThread { applyFilter( currentFilter, - if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt() + if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "" ) } } @@ -179,8 +178,7 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel() @WorkerThread private fun applyFilter( filter: String, - domain: String, - sources: Int + domain: String ) { if (contactsList.value.orEmpty().isEmpty()) { fetchInProgress.postValue(true) @@ -197,12 +195,12 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel() previousFilter = filter Log.i( - "$TAG Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources [$sources]" + "$TAG Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources Friends/LDAP" ) magicSearch.getContactsListAsync( filter, domain, - sources, + MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt(), MagicSearch.Aggregation.Friend ) }