From db7ca6793bb114e8113a65cd6fc1f83dc6399322 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 2 Sep 2025 10:23:13 +0200 Subject: [PATCH] Added swipe/pull to refresh on contacts list when a CardDAV friend list is configured to force the synchronization --- app/build.gradle.kts | 1 + .../contacts/fragment/ContactsListFragment.kt | 38 +++++++++++++++++++ .../viewmodel/ContactsListViewModel.kt | 35 +++++++++++++++++ .../layout-land/contacts_list_fragment.xml | 13 +++++-- .../res/layout/contacts_list_fragment.xml | 13 +++++-- gradle/libs.versions.toml | 2 + 6 files changed, 96 insertions(+), 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e64ad64a3..17291a5ba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) 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 4f85183b2..02df724a7 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 @@ -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 -> 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 cbaba8d70..f93148850 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 @@ -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>>() } + val cardDavSynchronizationCompletedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + 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, diff --git a/app/src/main/res/layout-land/contacts_list_fragment.xml b/app/src/main/res/layout-land/contacts_list_fragment.xml index 27b527952..b63594359 100644 --- a/app/src/main/res/layout-land/contacts_list_fragment.xml +++ b/app/src/main/res/layout-land/contacts_list_fragment.xml @@ -117,11 +117,18 @@ android:gravity="center" android:visibility="@{viewModel.showResultsLimitReached ? View.VISIBLE : View.GONE, default=gone}"/> - + android:layout_marginTop="4dp"> + + + + diff --git a/app/src/main/res/layout/contacts_list_fragment.xml b/app/src/main/res/layout/contacts_list_fragment.xml index 11c7a86bf..9d4717c01 100644 --- a/app/src/main/res/layout/contacts_list_fragment.xml +++ b/app/src/main/res/layout/contacts_list_fragment.xml @@ -118,11 +118,18 @@ android:gravity="center" android:visibility="@{viewModel.showResultsLimitReached ? View.VISIBLE : View.GONE, default=gone}"/> - + android:layout_marginTop="4dp"> + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f36c5982..272de2b91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }