Skip to content

Commit a068193

Browse files
committed
fix: scroll state not reset to latest video after refreshing feed/subscriptions
1 parent 6bf9327 commit a068193

2 files changed

Lines changed: 52 additions & 45 deletions

File tree

app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@ import com.github.libretube.ui.sheets.ChannelGroupsSheet
4343
import com.github.libretube.ui.sheets.FilterSortBottomSheet
4444
import com.github.libretube.ui.sheets.FilterSortBottomSheet.Companion.FILTER_SORT_REQUEST_KEY
4545
import com.github.libretube.util.PlayingQueue
46-
import com.github.libretube.util.deArrow
4746
import com.google.android.material.chip.Chip
4847
import kotlinx.coroutines.Dispatchers
4948
import kotlinx.coroutines.launch
50-
import kotlinx.coroutines.withContext
5149

5250
class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_subscriptions) {
5351
private var _binding: FragmentSubscriptionsBinding? = null
@@ -138,12 +136,24 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
138136
viewModel.fetchSubscriptions(requireContext())
139137
}
140138

139+
// only restore the previous state (i.e. scroll position) the first time the feed is shown
140+
// any other feed updates are caused by manual refreshing and thus should reset the scroll
141+
// position to zero
142+
var alreadyShowedFeedOnce = false
141143
viewModel.videoFeed.observe(viewLifecycleOwner) {
142-
if (!viewModel.isCurrentTabSubChannels && it != null) showFeed()
144+
if (!viewModel.isCurrentTabSubChannels && it != null) {
145+
showFeed(!alreadyShowedFeedOnce)
146+
alreadyShowedFeedOnce = true
147+
}
143148
}
144149

150+
// restore the scroll position, same conditions as above
151+
var alreadyShowedSubscriptionsOnce = false
145152
viewModel.subscriptions.observe(viewLifecycleOwner) {
146-
if (viewModel.isCurrentTabSubChannels && it != null) showSubscriptions()
153+
if (viewModel.isCurrentTabSubChannels && it != null) {
154+
showSubscriptions(!alreadyShowedSubscriptionsOnce)
155+
alreadyShowedSubscriptionsOnce = true
156+
}
147157
}
148158

149159
viewModel.feedProgress.observe(viewLifecycleOwner) { progress ->
@@ -209,18 +219,20 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
209219
binding.subChannels.addOnScrollListener(object : RecyclerView.OnScrollListener() {
210220
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
211221
super.onScrollStateChanged(recyclerView, newState)
212-
viewModel.subChannelsRecyclerViewState = binding.subChannels.layoutManager?.onSaveInstanceState()?.takeIf {
213-
binding.subChannels.computeVerticalScrollOffset() != 0
214-
}
222+
viewModel.subChannelsRecyclerViewState =
223+
binding.subChannels.layoutManager?.onSaveInstanceState()?.takeIf {
224+
binding.subChannels.computeVerticalScrollOffset() != 0
225+
}
215226
}
216227
})
217228

218229
binding.subFeed.addOnScrollListener(object : RecyclerView.OnScrollListener() {
219230
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
220231
super.onScrollStateChanged(recyclerView, newState)
221-
viewModel.subFeedRecyclerViewState = binding.subFeed.layoutManager?.onSaveInstanceState()?.takeIf {
222-
binding.subFeed.computeVerticalScrollOffset() != 0
223-
}
232+
viewModel.subFeedRecyclerViewState =
233+
binding.subFeed.layoutManager?.onSaveInstanceState()?.takeIf {
234+
binding.subFeed.computeVerticalScrollOffset() != 0
235+
}
224236
}
225237
})
226238

@@ -235,29 +247,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
235247
}
236248
}
237249

238-
private fun loadFeedItems(sortedFeed: List<StreamItem>) {
239-
val binding = _binding ?: return
240-
241-
if (viewModel.videoFeed.value != null && !viewModel.isCurrentTabSubChannels && !binding.subRefresh.isRefreshing) {
242-
binding.subRefresh.isRefreshing = true
243-
244-
lifecycleScope.launch {
245-
val streamItemsToInsert = sortedFeed.let {
246-
withContext(Dispatchers.IO) {
247-
runCatching { it.deArrow() }.getOrDefault(it)
248-
}
249-
}
250-
251-
feedAdapter.submitList(streamItemsToInsert) {
252-
// manually restore the previous feed state
253-
binding.subFeed.layoutManager?.onRestoreInstanceState(viewModel.subFeedRecyclerViewState)
254-
binding.subscriptionsAppBar.setExpanded(viewModel.subFeedRecyclerViewState == null)
255-
}
256-
binding.subRefresh.isRefreshing = false
257-
}
258-
}
259-
}
260-
261250
private fun setupSortAndFilter() {
262251
binding.filterSort.setOnClickListener {
263252
childFragmentManager.setFragmentResultListener(
@@ -371,27 +360,28 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
371360
else -> this
372361
}
373362

374-
private fun showFeed() {
363+
private fun showFeed(restoreScrollState: Boolean = true) {
364+
val binding = _binding ?: return
375365
val videoFeed = viewModel.videoFeed.value ?: return
376366

377-
binding.subRefresh.isRefreshing = false
378367
val feed = videoFeed
379368
.filterByGroup(selectedFilterGroup)
380369
.filter { showUpcoming || !it.isUpcoming }
381370
.let {
382371
DatabaseHelper.filterByStatusAndWatchPosition(it, hideWatched)
383372
}
384373

385-
val sorted = feed
374+
val sortedFeed = feed
386375
.sortedBySelectedOrder()
387376
.toMutableList()
388377

389378
// add an "all caught up item"
390379
if (selectedSortOrder == 0) {
391380
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime()
392-
val caughtUpIndex = feed.indexOfFirst { it.uploaded <= lastCheckedFeedTime && !it.isUpcoming }
393-
if (caughtUpIndex > 0 && !feed[caughtUpIndex-1].isUpcoming) {
394-
sorted.add(
381+
val caughtUpIndex =
382+
feed.indexOfFirst { it.uploaded <= lastCheckedFeedTime && !it.isUpcoming }
383+
if (caughtUpIndex > 0 && !feed[caughtUpIndex - 1].isUpcoming) {
384+
sortedFeed.add(
395385
caughtUpIndex,
396386
StreamItem(type = VideoCardsAdapter.CAUGHT_UP_STREAM_TYPE)
397387
)
@@ -404,18 +394,30 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
404394
val notLoaded = viewModel.videoFeed.value.isNullOrEmpty()
405395
binding.subFeed.isGone = notLoaded
406396
binding.emptyFeed.isVisible = notLoaded
407-
loadFeedItems(sorted)
408397

409398
binding.toggleSubs.text = getString(R.string.subscriptions)
410399

411400
feed.firstOrNull { !it.isUpcoming }?.uploaded?.let {
412401
PreferenceHelper.setLastFeedWatchedTime(it)
413402
}
403+
404+
binding.subRefresh.isRefreshing = false
405+
406+
feedAdapter.submitList(sortedFeed) {
407+
if (restoreScrollState) {
408+
// manually restore the previous feed state
409+
binding.subFeed.layoutManager?.onRestoreInstanceState(viewModel.subFeedRecyclerViewState)
410+
binding.subscriptionsAppBar.setExpanded(viewModel.subFeedRecyclerViewState == null)
411+
} else {
412+
binding.subFeed.scrollToPosition(0)
413+
}
414+
}
414415
}
415416

416417
@SuppressLint("SetTextI18n")
417-
private fun showSubscriptions() {
418-
val subscriptions = viewModel.subscriptions.value?.filterByGroup(selectedFilterGroup) ?: return
418+
private fun showSubscriptions(restoreScrollState: Boolean = true) {
419+
val subscriptions =
420+
viewModel.subscriptions.value?.filterByGroup(selectedFilterGroup) ?: return
419421

420422
val legacySubscriptions = PreferenceHelper.getBoolean(
421423
PreferenceKeys.LEGACY_SUBSCRIPTIONS,
@@ -424,8 +426,12 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
424426

425427
val adapter = if (legacySubscriptions) legacySubscriptionsAdapter else channelsAdapter
426428
adapter.submitList(subscriptions) {
427-
binding.subFeed.layoutManager?.onRestoreInstanceState(viewModel.subChannelsRecyclerViewState)
428-
binding.subscriptionsAppBar.setExpanded(viewModel.subChannelsRecyclerViewState == null)
429+
if (restoreScrollState) {
430+
binding.subFeed.layoutManager?.onRestoreInstanceState(viewModel.subChannelsRecyclerViewState)
431+
binding.subscriptionsAppBar.setExpanded(viewModel.subChannelsRecyclerViewState == null)
432+
} else {
433+
binding.subFeed.scrollToPosition(0)
434+
}
429435
}
430436

431437
binding.subRefresh.isRefreshing = false

app/src/main/java/com/github/libretube/ui/models/SubscriptionsViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.github.libretube.extensions.toID
1515
import com.github.libretube.extensions.toastFromMainDispatcher
1616
import com.github.libretube.helpers.PreferenceHelper
1717
import com.github.libretube.repo.FeedProgress
18+
import com.github.libretube.util.deArrow
1819
import kotlinx.coroutines.Dispatchers
1920
import kotlinx.coroutines.launch
2021

@@ -33,7 +34,7 @@ class SubscriptionsViewModel : ViewModel() {
3334
val videoFeed = try {
3435
SubscriptionHelper.getFeed(forceRefresh = forceRefresh) { feedProgress ->
3536
this@SubscriptionsViewModel.feedProgress.postValue(feedProgress)
36-
}
37+
}.let { runCatching { it.deArrow() }.getOrDefault(it) }
3738
} catch (e: Exception) {
3839
context.toastFromMainDispatcher(R.string.server_error)
3940
Log.e(TAG(), e.toString())

0 commit comments

Comments
 (0)