Fixed issues related to contacts being added/edited/removed

This commit is contained in:
Sylvain Berfini 2024-02-02 10:50:50 +01:00
parent 3864d54936
commit 523b762cac
9 changed files with 115 additions and 43 deletions

View file

@ -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

View file

@ -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<ContactAvatarModel>() {
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
}
}
}

View file

@ -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()
}

View file

@ -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}]"

View file

@ -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}]"

View file

@ -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()
}

View file

@ -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<String>()
val isEdit = MutableLiveData<Boolean>()
val picturePath = MutableLiveData<String>()
@ -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 "")

View file

@ -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))

View file

@ -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
)
}