Fixed issue with first letter displayed when refreshing contacts list

This commit is contained in:
Sylvain Berfini 2023-11-23 11:18:30 +01:00
parent 20fbeda124
commit 92835a1e10
7 changed files with 142 additions and 139 deletions

View file

@ -90,7 +90,7 @@ class ContactsManager @UiThread constructor() {
@WorkerThread
override fun onFriendListRemoved(core: Core, friendList: FriendList) {
Log.i("$TAG Friend list [${friendList.displayName}] remoed")
Log.i("$TAG Friend list [${friendList.displayName}] removed")
friendList.removeListener(friendListListener)
}
}

View file

@ -105,6 +105,17 @@ class ContactsListAdapter(
binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition
val previousItem = bindingAdapterPosition - 1
val previousLetter = if (previousItem >= 0) {
getItem(previousItem).contactName?.get(0).toString()
} else {
""
}
val currentLetter = contactModel.contactName?.get(0).toString()
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
firstContactStartingByThatLetter = displayLetter
executePendingBindings()
}
}
@ -127,12 +138,11 @@ class ContactsListAdapter(
private class ContactDiffCallback : DiffUtil.ItemCallback<ContactAvatarModel>() {
override fun areItemsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
return oldItem.id == newItem.id
return oldItem.id == newItem.id && oldItem.contactName == newItem.contactName
}
override fun areContentsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
return oldItem.firstContactStartingByThatLetter.value == newItem.firstContactStartingByThatLetter.value &&
oldItem.presenceStatus.value == newItem.presenceStatus.value &&
return oldItem.presenceStatus.value == newItem.presenceStatus.value &&
(newItem.presenceStatus.value == ConsolidatedPresence.Busy || newItem.presenceStatus.value == ConsolidatedPresence.Online)
}
}

View file

@ -42,6 +42,8 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) : Abstrac
val id = friend.refKey ?: friend.name
val contactName = friend.name
val starred = friend.starred
val lastPresenceInfo = MutableLiveData<String>()
@ -52,8 +54,6 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) : Abstrac
val firstLetter: String = AppUtils.getFirstLetter(friend.name.orEmpty())
val firstContactStartingByThatLetter = MutableLiveData<Boolean>()
private val friendListener = object : FriendListenerStub() {
@WorkerThread
override fun onPresenceReceived(fr: Friend) {

View file

@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import java.io.File
import java.text.Collator
import java.util.ArrayList
import java.util.Locale
import kotlinx.coroutines.launch
@ -110,6 +111,18 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
super.onCleared()
}
@UiThread
override fun filter() {
isListFiltered.value = currentFilter.isNotEmpty()
coreContext.postOnCoreThread {
applyFilter(
currentFilter,
if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "",
MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt()
)
}
}
@UiThread
fun applyCurrentDefaultAccountFilter() {
coreContext.postOnCoreThread { core ->
@ -135,63 +148,6 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
showFavourites.value = showFavourites.value == false
}
@WorkerThread
fun processMagicSearchResults(results: Array<SearchResult>) {
Log.i("$TAG Processing [${results.size}] results")
val list = arrayListOf<ContactAvatarModel>()
val favouritesList = arrayListOf<ContactAvatarModel>()
var previousLetter = ""
var count = 0
for (result in results) {
val friend = result.friend
val model = if (friend != null) {
coreContext.contactsManager.getContactAvatarModelForFriend(friend)
} else {
coreContext.contactsManager.getContactAvatarModelForAddress(result.address)
}
val currentLetter = model.friend.name?.get(0).toString()
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
if (currentLetter != previousLetter) {
previousLetter = currentLetter
}
model.firstContactStartingByThatLetter.postValue(displayLetter)
list.add(model)
count += 1
if (friend?.starred == true) {
favouritesList.add(model)
}
if (count == 20) {
contactsList.postValue(list)
fetchInProgress.postValue(false)
}
}
favourites.postValue(favouritesList)
contactsList.postValue(list)
fetchInProgress.postValue(false)
Log.i("$TAG Processed [${results.size}] results")
}
@UiThread
override fun filter() {
isListFiltered.value = currentFilter.isNotEmpty()
coreContext.postOnCoreThread {
applyFilter(
currentFilter,
if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "",
MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt()
)
}
}
@UiThread
fun exportContactAsVCard(friend: Friend) {
coreContext.postOnCoreThread {
@ -251,26 +207,47 @@ class ContactsListViewModel @UiThread constructor() : AbstractTopBarViewModel()
}
@WorkerThread
private fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
val searchResultFriend = searchResult.friend
if (searchResultFriend != null) return searchResultFriend
private fun processMagicSearchResults(results: Array<SearchResult>) {
Log.i("$TAG Processing [${results.size}] results")
val friend = coreContext.core.createFriend()
val list = arrayListOf<ContactAvatarModel>()
val favouritesList = arrayListOf<ContactAvatarModel>()
var count = 0
val address = searchResult.address
if (address != null) {
friend.address = address
}
for (result in results) {
val friend = result.friend
val number = searchResult.phoneNumber
if (number != null) {
friend.addPhoneNumber(number)
val model = if (friend != null) {
coreContext.contactsManager.getContactAvatarModelForFriend(friend)
} else {
coreContext.contactsManager.getContactAvatarModelForAddress(result.address)
}
if (address != null && address.username == number) {
friend.removeAddress(address)
list.add(model)
count += 1
if (friend?.starred == true) {
favouritesList.add(model)
}
if (count == 20) {
contactsList.postValue(list)
fetchInProgress.postValue(false)
}
}
return friend
val collator = Collator.getInstance(Locale.getDefault())
favouritesList.sortWith { model1, model2 ->
collator.compare(model1.friend.name, model2.friend.name)
}
list.sortWith { model1, model2 ->
collator.compare(model1.friend.name, model2.friend.name)
}
favourites.postValue(favouritesList)
contactsList.postValue(list)
fetchInProgress.postValue(false)
Log.i("$TAG Processed [${results.size}] results")
}
}

View file

@ -110,9 +110,22 @@ class ContactsAndSuggestionsListAdapter :
fun bind(contactOrSuggestionModel: ContactOrSuggestionModel) {
with(binding) {
model = contactOrSuggestionModel.avatarModel.value
setOnClickListener {
contactClickedEvent.value = Event(contactOrSuggestionModel)
}
val previousItem = bindingAdapterPosition - 1
val previousLetter = if (previousItem >= 0) {
getItem(previousItem).name[0].toString()
} else {
""
}
val currentLetter = contactOrSuggestionModel.name[0].toString()
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
firstContactStartingByThatLetter = displayLetter
executePendingBindings()
}
}

View file

@ -23,6 +23,8 @@ import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.text.Collator
import java.util.Locale
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
@ -178,67 +180,6 @@ abstract class AddressSelectionViewModel @UiThread constructor() : ViewModel() {
}
}
@WorkerThread
fun processMagicSearchResults(results: Array<SearchResult>) {
Log.i("$TAG Processing [${results.size}] results")
val contactsList = arrayListOf<ContactOrSuggestionModel>()
val suggestionsList = arrayListOf<ContactOrSuggestionModel>()
var previousLetter = ""
for (result in results) {
val address = result.address
if (address != null) {
val friend = coreContext.contactsManager.findContactByAddress(address)
if (friend != null) {
val model = ContactOrSuggestionModel(address, friend)
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(
address
)
model.avatarModel.postValue(avatarModel)
val currentLetter = friend.name?.get(0).toString()
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
if (currentLetter != previousLetter) {
previousLetter = currentLetter
}
avatarModel.firstContactStartingByThatLetter.postValue(
displayLetter
)
contactsList.add(model)
} else {
// If user-input generated result (always last) already exists, don't show it again
if (result.sourceFlags == MagicSearch.Source.Request.toInt()) {
val found = suggestionsList.find {
it.address.weakEqual(address)
}
if (found != null) {
Log.i(
"$TAG Result generated from user input is a duplicate of an existing solution, preventing double"
)
continue
}
}
val model = ContactOrSuggestionModel(address) {
coreContext.startCall(address)
}
suggestionsList.add(model)
}
}
}
val list = arrayListOf<ContactOrSuggestionModel>()
list.addAll(contactsList)
list.addAll(suggestionsList)
contactsAndSuggestionsList.postValue(list)
Log.i(
"$TAG Processed [${results.size}] results, extracted [${suggestionsList.size}] suggestions"
)
}
@UiThread
fun applyFilter(filter: String) {
coreContext.postOnCoreThread {
@ -278,4 +219,63 @@ abstract class AddressSelectionViewModel @UiThread constructor() : ViewModel() {
aggregation
)
}
@WorkerThread
private fun processMagicSearchResults(results: Array<SearchResult>) {
Log.i("$TAG Processing [${results.size}] results")
val contactsList = arrayListOf<ContactOrSuggestionModel>()
val suggestionsList = arrayListOf<ContactOrSuggestionModel>()
for (result in results) {
val address = result.address
if (address != null) {
val friend = coreContext.contactsManager.findContactByAddress(address)
if (friend != null) {
val model = ContactOrSuggestionModel(address, friend)
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(
address
)
model.avatarModel.postValue(avatarModel)
contactsList.add(model)
} else {
// If user-input generated result (always last) already exists, don't show it again
if (result.sourceFlags == MagicSearch.Source.Request.toInt()) {
val found = suggestionsList.find {
it.address.weakEqual(address)
}
if (found != null) {
Log.i(
"$TAG Result generated from user input is a duplicate of an existing solution, preventing double"
)
continue
}
}
val model = ContactOrSuggestionModel(address) {
coreContext.startCall(address)
}
suggestionsList.add(model)
}
}
}
val collator = Collator.getInstance(Locale.getDefault())
contactsList.sortWith { model1, model2 ->
collator.compare(model1.name, model2.name)
}
suggestionsList.sortWith { model1, model2 ->
collator.compare(model1.name, model2.name)
}
val list = arrayListOf<ContactOrSuggestionModel>()
list.addAll(contactsList)
list.addAll(suggestionsList)
contactsAndSuggestionsList.postValue(list)
Log.i(
"$TAG Processed [${results.size}] results, extracted [${suggestionsList.size}] suggestions"
)
}
}

View file

@ -17,6 +17,9 @@
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="firstContactStartingByThatLetter"
type="Boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -45,7 +48,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="@{model.firstLetter, default=`A`}"
android:visibility="@{model.firstContactStartingByThatLetter ? View.VISIBLE : View.INVISIBLE}"
android:visibility="@{firstContactStartingByThatLetter ? View.VISIBLE : View.INVISIBLE}"
android:textColor="@color/gray_main2_400"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"