mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Workout list: live-refresh on ACTION_NEW_DATA without blink
Long-running per-line fetches (e.g. ExploreSync's catalog walk) hold the device's busy task for the entire session, so the busy→idle edge WorkoutListActivity refreshes on only fires at session end. Listen for ACTION_NEW_DATA too so each newly imported activity appears as it lands. Add a silent loadSummaries path that skips the isLoading flips, and disable the RecyclerView change animator so the dashboard row's notifyItemChanged(0) doesn't cross-fade — both would otherwise read as a distracting blink during an active sync.
This commit is contained in:
committed by
José Rebelo
parent
c1946573fb
commit
3b9b4126ba
+48
-21
@@ -14,6 +14,8 @@ import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
@@ -52,26 +54,36 @@ class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != GBDevice.ACTION_DEVICE_CHANGED) {
|
||||
LOG.warn("Got unexpected action {}", intent.action)
|
||||
return
|
||||
}
|
||||
val device = intent.getParcelableExtra<GBDevice>(GBDevice.EXTRA_DEVICE)
|
||||
if (device == null) {
|
||||
LOG.error("Got device changed without device")
|
||||
return
|
||||
}
|
||||
if (device != gbDevice) {
|
||||
return
|
||||
}
|
||||
if (device.isBusy) {
|
||||
swipeLayout.isRefreshing = true
|
||||
} else {
|
||||
val wasBusy = swipeLayout.isRefreshing
|
||||
swipeLayout.isRefreshing = false
|
||||
if (wasBusy) {
|
||||
refresh()
|
||||
when (intent.action) {
|
||||
GBDevice.ACTION_DEVICE_CHANGED -> {
|
||||
val device = intent.getParcelableExtra<GBDevice>(GBDevice.EXTRA_DEVICE)
|
||||
if (device == null) {
|
||||
LOG.error("Got device changed without device")
|
||||
return
|
||||
}
|
||||
if (device != gbDevice) {
|
||||
return
|
||||
}
|
||||
if (device.isBusy) {
|
||||
swipeLayout.isRefreshing = true
|
||||
} else {
|
||||
val wasBusy = swipeLayout.isRefreshing
|
||||
swipeLayout.isRefreshing = false
|
||||
if (wasBusy) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
// ExploreSync (and other long-running fetches) hold the
|
||||
// device busy for the whole catalog walk, so the
|
||||
// busy→idle edge above only fires at session end.
|
||||
// Refresh on every per-line flush as well so newly
|
||||
// imported activities appear in the list right away.
|
||||
// Pass silent=true so the dashboard's loading shimmer
|
||||
// and swipe-refresh spinner don't blink on every
|
||||
// incremental update.
|
||||
GBApplication.ACTION_NEW_DATA -> refresh(silent = true)
|
||||
else -> LOG.warn("Got unexpected action {}", intent.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +144,10 @@ class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
// Load and apply saved quick filter
|
||||
applySavedQuickFilter()
|
||||
|
||||
val filterLocal = IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)
|
||||
val filterLocal = IntentFilter().apply {
|
||||
addAction(GBDevice.ACTION_DEVICE_CHANGED)
|
||||
addAction(GBApplication.ACTION_NEW_DATA)
|
||||
}
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filterLocal)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -195,6 +210,13 @@ class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
|
||||
setItemAdapter(workoutSummariesAdapter)
|
||||
|
||||
// The dashboard row gets a notifyItemChanged on every silent
|
||||
// refresh during sync; the default ChangeAnimator cross-fades
|
||||
// that, which reads as a distracting opacity blink. Disable
|
||||
// just the change animation — add/remove animations still run.
|
||||
val recycler = findViewById<RecyclerView>(R.id.itemListView)
|
||||
(recycler.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
|
||||
|
||||
swipeLayout = findViewById(R.id.list_activity_swipe_layout)
|
||||
swipeLayout.setOnRefreshListener {
|
||||
if (GBApplication.getPrefs().refreshOnSwipe()) {
|
||||
@@ -478,6 +500,10 @@ class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
refresh(silent = false)
|
||||
}
|
||||
|
||||
private fun refresh(silent: Boolean) {
|
||||
gbDevice?.let { device ->
|
||||
viewModel.loadSummaries(
|
||||
device,
|
||||
@@ -486,7 +512,8 @@ class WorkoutListActivity : AbstractListActivity<BaseActivitySummary>() {
|
||||
dateToFilter,
|
||||
nameContainsFilter,
|
||||
deviceFilter,
|
||||
itemsFilter
|
||||
itemsFilter,
|
||||
silent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -42,9 +42,17 @@ class WorkoutListViewModel : ViewModel() {
|
||||
dateToFilter: Long,
|
||||
nameContainsFilter: String?,
|
||||
deviceFilter: Long,
|
||||
itemsFilter: List<Long>?
|
||||
itemsFilter: List<Long>?,
|
||||
/** When true, skip the [isLoading] / [isDashboardLoading]
|
||||
* flips so the swipe-refresh spinner and dashboard shimmer
|
||||
* don't blink. Used by per-line refreshes during
|
||||
* long-running syncs, where the loading animation
|
||||
* distracts from the actual content updates. */
|
||||
silent: Boolean = false
|
||||
) {
|
||||
_isLoading.value = true
|
||||
if (!silent) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
_error.value = null
|
||||
|
||||
viewModelScope.launch {
|
||||
@@ -70,18 +78,22 @@ class WorkoutListViewModel : ViewModel() {
|
||||
|
||||
_summaries.value = allSummaries
|
||||
|
||||
loadDashboardStats(gbDevice, summaries)
|
||||
loadDashboardStats(gbDevice, summaries, silent)
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Error loading summaries", e)
|
||||
_error.value = "Error loading summaries: ${e.localizedMessage}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
if (!silent) {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDashboardStats(gbDevice: GBDevice, summaries: List<BaseActivitySummary>) {
|
||||
_isDashboardLoading.value = true
|
||||
private fun loadDashboardStats(gbDevice: GBDevice, summaries: List<BaseActivitySummary>, silent: Boolean) {
|
||||
if (!silent) {
|
||||
_isDashboardLoading.value = true
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
@@ -94,7 +106,9 @@ class WorkoutListViewModel : ViewModel() {
|
||||
LOG.error("Error loading dashboard stats", e)
|
||||
_error.value = "Error loading dashboard stats: ${e.localizedMessage}"
|
||||
} finally {
|
||||
_isDashboardLoading.value = false
|
||||
if (!silent) {
|
||||
_isDashboardLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user