Added swipe/pull to refresh on contacts list when a CardDAV friend list is configured to force the synchronization

This commit is contained in:
Sylvain Berfini 2025-09-02 10:23:13 +02:00
parent ac521557ce
commit db7ca6793b
6 changed files with 96 additions and 6 deletions

View file

@ -220,6 +220,7 @@ dependencies {
implementation(libs.androidx.telecom)
implementation(libs.androidx.media)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.slidingpanelayout)
implementation(libs.androidx.window)
implementation(libs.androidx.gridlayout)

View file

@ -40,9 +40,12 @@ import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.LinphoneApplication.Companion.coreContext
import java.io.File
import org.linphone.R
import org.linphone.core.FriendList
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactsListFilterPopupMenuBinding
import org.linphone.databinding.ContactsListFragmentBinding
@ -81,6 +84,11 @@ class ContactsListFragment : AbstractMainFragment() {
}
}
private val swipeToRefreshListener = SwipeRefreshLayout.OnRefreshListener {
Log.i("$TAG Swipe to refresh triggered, updating CardDAV friend lists")
listViewModel.refreshCardDavContacts()
}
override fun onDefaultAccountChanged() {
Log.i(
"$TAG Default account changed, updating avatar in top bar & refreshing contacts list"
@ -122,6 +130,10 @@ class ContactsListFragment : AbstractMainFragment() {
binding.viewModel = listViewModel
observeToastEvents(listViewModel)
// Disabled by default, may be enabled in onResume()
binding.contactsListSwipeRefresh.isEnabled = false
binding.contactsListSwipeRefresh.setOnRefreshListener(swipeToRefreshListener)
binding.contactsList.setHasFixedSize(true)
binding.contactsList.layoutManager = LinearLayoutManager(requireContext())
@ -173,6 +185,13 @@ class ContactsListFragment : AbstractMainFragment() {
}
}
listViewModel.cardDavSynchronizationCompletedEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i("$TAG CardDAV synchronization has completed")
binding.contactsListSwipeRefresh.isRefreshing = false
}
}
binding.setOnNewContactClicked {
sharedViewModel.showNewContactEvent.value = Event(true)
}
@ -237,6 +256,25 @@ class ContactsListFragment : AbstractMainFragment() {
bottomSheetDialog = null
}
override fun onResume() {
super.onResume()
coreContext.postOnCoreThread { core ->
val cardDavFriendList = core.friendsLists.find {
it.type == FriendList.Type.CardDAV
}
val cardDavFriendListFound = cardDavFriendList != null
if (cardDavFriendListFound) {
Log.i("$TAG CardDAV friend list [${cardDavFriendList.displayName}] found, enabling swipe to refresh")
} else {
Log.i("$TAG No CardDAV friend list was found, disabling swipe to refresh")
}
coreContext.postOnMainThread {
binding.contactsListSwipeRefresh.isEnabled = cardDavFriendListFound
}
}
}
private fun configureAdapter(adapter: ContactsListAdapter) {
adapter.contactLongClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->

View file

@ -33,6 +33,8 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.contacts.ContactsManager.ContactsListener
import org.linphone.core.Friend
import org.linphone.core.FriendList
import org.linphone.core.FriendListListenerStub
import org.linphone.core.MagicSearch
import org.linphone.core.MagicSearchListenerStub
import org.linphone.core.SearchResult
@ -73,6 +75,10 @@ class ContactsListViewModel
MutableLiveData<Event<Pair<String, File>>>()
}
val cardDavSynchronizationCompletedEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private var previousFilter = "NotSet"
private var domainFilter = ""
@ -106,6 +112,22 @@ class ContactsListViewModel
}
}
private val friendListListener = object : FriendListListenerStub() {
@WorkerThread
override fun onSyncStatusChanged(
friendList: FriendList,
status: FriendList.SyncStatus?,
message: String?
) {
Log.i("$TAG Synchronization status changed to [$status] for friend list [${friendList.displayName}] with message [$message]")
if (status == FriendList.SyncStatus.Successful || status == FriendList.SyncStatus.Failure) {
friendList.removeListener(this)
cardDavSynchronizationCompletedEvent.postValue(Event(true))
}
// TODO FIXME: alert user when failure ?
}
}
private val contactsListener = object : ContactsListener {
@WorkerThread
override fun onContactsLoaded() {
@ -265,6 +287,19 @@ class ContactsListViewModel
}
}
@UiThread
fun refreshCardDavContacts() {
coreContext.postOnCoreThread { core ->
for (friendList in core.friendsLists) {
if (friendList.type == FriendList.Type.CardDAV) {
Log.i("$TAG Found CardDAV friend list [${friendList.displayName}], starting update")
friendList.addListener(friendListListener)
friendList.synchronizeFriendsFromServer()
}
}
}
}
@WorkerThread
private fun applyFilter(
filter: String,

View file

@ -117,11 +117,18 @@
android:gravity="center"
android:visibility="@{viewModel.showResultsLimitReached ? View.VISIBLE : View.GONE, default=gone}"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contactsList"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/contactsListSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp" />
android:layout_marginTop="4dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contactsList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

View file

@ -118,11 +118,18 @@
android:gravity="center"
android:visibility="@{viewModel.showResultsLimitReached ? View.VISIBLE : View.GONE, default=gone}"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contactsList"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/contactsListSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp" />
android:layout_marginTop="4dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contactsList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

View file

@ -16,6 +16,7 @@ splashscreen = "1.2.0-rc01"
telecom = "1.0.1"
media = "1.7.1"
recyclerview = "1.4.0"
swipeRefreshLayout = "1.1.0"
slidingpanelayout = "1.2.0"
window = "1.4.0"
gridlayout = "1.1.0"
@ -43,6 +44,7 @@ androidx-splashscreen = { group = "androidx.core", name = "core-splashscreen", v
androidx-telecom = { group = "androidx.core", name = "core-telecom", version.ref = "telecom" }
androidx-media = { group = "androidx.media", name = "media", version.ref = "media" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefreshLayout" }
androidx-slidingpanelayout = { group = "androidx.slidingpanelayout", name = "slidingpanelayout", version.ref = "slidingpanelayout" }
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }