Added setting to choose whether to sort contacts by first or last name

This commit is contained in:
Sylvain Berfini 2025-03-13 11:44:58 +01:00
parent 0b6805a73c
commit dee684b364
11 changed files with 144 additions and 13 deletions

View file

@ -167,6 +167,13 @@ class CorePreferences
// Contacts related
@get:WorkerThread @set:WorkerThread
var sortContactsByFirstName: Boolean
get() = config.getBool("ui", "sort_contacts_by_first_name", true) // If disabled, last name will be used
set(value) {
config.setBool("ui", "sort_contacts_by_first_name", value)
}
@get:WorkerThread @set:WorkerThread
var contactsFilter: String
get() = config.getString("ui", "contacts_filter", "")!! // Default value must be empty!

View file

@ -125,12 +125,12 @@ class ContactsListAdapter(
val previousItem = bindingAdapterPosition - 1
val previousLetter = if (previousItem >= 0) {
getItem(previousItem).contactName?.get(0).toString()
getItem(previousItem).sortingName?.get(0).toString()
} else {
""
}
val currentLetter = contactModel.contactName?.get(0).toString()
val currentLetter = contactModel.sortingName?.get(0).toString()
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
firstContactStartingByThatLetter = displayLetter

View file

@ -37,6 +37,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.observe
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
@ -181,9 +182,7 @@ class ContactsListFragment : AbstractMainFragment() {
showFilterPopupMenu(binding.topBar.extraAction)
}
sharedViewModel.showContactEvent.observe(
viewLifecycleOwner
) {
sharedViewModel.showContactEvent.observe(viewLifecycleOwner) {
it.consume { refKey ->
Log.i("$TAG Displaying contact with ref key [$refKey]")
val navController = binding.contactsNavContainer.findNavController()
@ -194,9 +193,7 @@ class ContactsListFragment : AbstractMainFragment() {
}
}
sharedViewModel.showNewContactEvent.observe(
viewLifecycleOwner
) {
sharedViewModel.showNewContactEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.contactsListFragment) {
Log.i("$TAG Opening contact editor for creating new contact")
@ -207,6 +204,12 @@ class ContactsListFragment : AbstractMainFragment() {
}
}
sharedViewModel.forceRefreshContactsList.observe(viewLifecycleOwner) {
it.consume {
listViewModel.filter()
}
}
// AbstractMainFragment related
listViewModel.title.value = getString(R.string.bottom_navigation_contacts_label)

View file

@ -36,6 +36,7 @@ import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.TimestampUtils
import androidx.core.net.toUri
import org.linphone.LinphoneApplication.Companion.corePreferences
class ContactAvatarModel
@WorkerThread
@ -56,7 +57,9 @@ class ContactAvatarModel
val name = MutableLiveData<String>()
val firstLetter: String = AppUtils.getFirstLetter(friend.name.orEmpty())
var sortingName: String? = null
var firstLetter: String? = null
private val friendListener = object : FriendListenerStub() {
@WorkerThread
@ -76,6 +79,7 @@ class ContactAvatarModel
}
update(address)
refreshSortingName()
}
@WorkerThread
@ -85,6 +89,12 @@ class ContactAvatarModel
}
}
@WorkerThread
fun refreshSortingName() {
sortingName = getNameToUseForSorting()
firstLetter = AppUtils.getFirstLetter(getNameToUseForSorting().orEmpty())
}
@WorkerThread
fun update(address: Address?) {
updateSecurityLevel(address)
@ -148,6 +158,13 @@ class ContactAvatarModel
}
}
@WorkerThread
fun getNameToUseForSorting(): String? {
val sortByFirstName = corePreferences.sortContactsByFirstName
val firstOrLastName = if (sortByFirstName) friend.vcard?.givenName else friend.vcard?.familyName
return firstOrLastName ?: friend.name ?: friend.organization ?: friend.vcard?.fullName
}
@WorkerThread
private fun getAvatarUri(friend: Friend): Uri? {
val picturePath = friend.photo

View file

@ -284,6 +284,7 @@ class ContactsListViewModel
val list = arrayListOf<ContactAvatarModel>()
val favouritesList = arrayListOf<ContactAvatarModel>()
var count = 0
val collator = Collator.getInstance(Locale.getDefault())
for (result in results) {
val friend = result.friend
@ -308,6 +309,7 @@ class ContactsListViewModel
} else {
coreContext.contactsManager.getContactAvatarModelForAddress(result.address)
}
model.refreshSortingName()
list.add(model)
count += 1
@ -319,16 +321,18 @@ class ContactsListViewModel
}
if (firstLoad && count == 20) {
list.sortWith { model1, model2 ->
collator.compare(model1.getNameToUseForSorting(), model2.getNameToUseForSorting())
}
contactsList.postValue(list)
}
}
val collator = Collator.getInstance(Locale.getDefault())
favouritesList.sortWith { model1, model2 ->
collator.compare(model1.friend.name, model2.friend.name)
collator.compare(model1.getNameToUseForSorting(), model2.getNameToUseForSorting())
}
list.sortWith { model1, model2 ->
collator.compare(model1.friend.name, model2.friend.name)
collator.compare(model1.getNameToUseForSorting(), model2.getNameToUseForSorting())
}
favourites.postValue(favouritesList)

View file

@ -38,6 +38,7 @@ import org.linphone.ui.main.fragment.GenericMainFragment
import org.linphone.utils.ConfirmationDialogModel
import org.linphone.ui.main.settings.viewmodel.SettingsViewModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
@UiThread
class SettingsFragment : GenericMainFragment() {
@ -49,6 +50,20 @@ class SettingsFragment : GenericMainFragment() {
private lateinit var viewModel: SettingsViewModel
private val sortContactsByListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val label = viewModel.sortContactsByNames[position]
val value = viewModel.sortContactsByValues[position]
Log.i("$TAG Selected contact sorting is now [$label] ($value)")
viewModel.setContactSorting(value)
sharedViewModel.forceRefreshContactsList.postValue(Event(true))
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
private val layoutListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val label = viewModel.availableLayoutsNames[position]
@ -155,6 +170,22 @@ class SettingsFragment : GenericMainFragment() {
}
}
// Setup sort contacts by spinner
val sortContactsByAdapter = ArrayAdapter(
requireContext(),
R.layout.drop_down_item,
viewModel.sortContactsByNames
)
sortContactsByAdapter.setDropDownViewResource(R.layout.generic_dropdown_cell)
binding.contactsSettings.sortContactsByFirstNameSpinner.adapter = sortContactsByAdapter
viewModel.sortContactsBy.observe(viewLifecycleOwner) { sort ->
binding.contactsSettings.sortContactsByFirstNameSpinner.setSelection(
viewModel.sortContactsByValues.indexOf(sort)
)
}
binding.contactsSettings.sortContactsByFirstNameSpinner.onItemSelectedListener = sortContactsByListener
viewModel.addLdapServerEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.settingsFragment) {

View file

@ -97,6 +97,13 @@ class SettingsViewModel
// Contacts settings
val showContactsSettings = MutableLiveData<Boolean>()
val sortContactsBy = MutableLiveData<Int>()
val sortContactsByNames = arrayListOf(
AppUtils.getString(R.string.contact_editor_first_name),
AppUtils.getString(R.string.contact_editor_last_name),
)
val sortContactsByValues = arrayListOf(0, 1)
val ldapAvailable = MutableLiveData<Boolean>()
val ldapServers = MutableLiveData<List<CardDavLdapModel>>()
@ -293,6 +300,8 @@ class SettingsViewModel
corePreferences.markConversationAsReadWhenDismissingMessageNotification
)
sortContactsBy.postValue(if (corePreferences.sortContactsByFirstName) 0 else 1)
defaultLayout.postValue(core.defaultConferenceLayout.toInt())
theme.postValue(corePreferences.darkMode)
@ -467,6 +476,13 @@ class SettingsViewModel
expandContacts.value = expandContacts.value == false
}
@UiThread
fun setContactSorting(sortingValue: Int) {
coreContext.postOnCoreThread { core ->
corePreferences.sortContactsByFirstName = sortingValue == 0
}
}
@UiThread
fun addLdapServer() {
addLdapServerEvent.value = Event(true)

View file

@ -95,6 +95,10 @@ class SharedMainViewModel
MutableLiveData<Event<Boolean>>()
}
val forceRefreshContactsList: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
var sipAddressToAddToNewContact: String = ""
// Call logs related

View file

@ -15,6 +15,53 @@
android:paddingBottom="20dp"
android:background="@drawable/shape_squircle_white_background">
<androidx.appcompat.widget.AppCompatTextView
style="@style/settings_title_style"
android:id="@+id/sort_contacts_by_first_name_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="10dp"
android:text="@string/settings_contacts_sort_by_first_name_title"
android:maxLines="2"
android:ellipsize="end"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatSpinner
style="@style/material_switch_style"
android:id="@+id/sort_contacts_by_first_name_spinner"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:paddingStart="@dimen/spinner_start_padding"
android:paddingEnd="@dimen/spinner_end_padding"
android:overlapAnchor="false"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/sort_contacts_by_first_name_title"
app:layout_constraintStart_toStartOf="@id/sort_contacts_by_first_name_title"
app:layout_constraintEnd_toEndOf="@id/sort_contacts_by_first_name_title" />
<ImageView
android:id="@+id/sort_contacts_by_first_name_spinner_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spinner_caret_end_margin"
android:src="@drawable/caret_down"
android:contentDescription="@string/content_description_spinner_caret"
app:tint="?attr/color_main2_600"
app:layout_constraintTop_toTopOf="@id/sort_contacts_by_first_name_spinner"
app:layout_constraintBottom_toBottomOf="@id/sort_contacts_by_first_name_spinner"
app:layout_constraintEnd_toEndOf="@id/sort_contacts_by_first_name_spinner"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/settings_title_style"
android:onClick="@{() -> viewModel.addLdapServer()}"
@ -31,7 +78,7 @@
android:drawableEnd="@drawable/caret_right"
android:drawableTint="?attr/color_main2_600"
android:visibility="@{viewModel.ldapAvailable ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/sort_contacts_by_first_name_spinner"
app:layout_constraintBottom_toTopOf="@id/existing_ldap_servers"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View file

@ -205,6 +205,7 @@
<string name="settings_conversations_auto_export_media_to_native_gallery_title">Rendre visible dans la galerie les médias téléchargés</string>
<string name="settings_conversations_mark_as_read_when_dismissing_notif_title">Marquer la conversation comme lue lorsqu\'une notification de message est supprimée</string>
<string name="settings_contacts_title">Contacts</string>
<string name="settings_contacts_sort_by_first_name_title">Trier les contacts par</string>
<string name="settings_contacts_add_ldap_server_title">Ajouter un serveur LDAP</string>
<string name="settings_contacts_edit_ldap_server_title">Editer le serveur LDAP</string>
<string name="settings_contacts_add_carddav_server_title">Ajouter un carnet d\'adresse CardDAV</string>

View file

@ -244,6 +244,7 @@
<string name="settings_conversations_auto_export_media_to_native_gallery_title">Make downloaded media public</string>
<string name="settings_conversations_mark_as_read_when_dismissing_notif_title">Mark conversation as read when dismissing message notification</string>
<string name="settings_contacts_title">Contacts</string>
<string name="settings_contacts_sort_by_first_name_title">Sort contacts by</string>
<string name="settings_contacts_add_ldap_server_title">Add LDAP server</string>
<string name="settings_contacts_edit_ldap_server_title">Edit LDAP server</string>
<string name="settings_contacts_add_carddav_server_title">Add CardDAV address book</string>