@@ -43,11 +43,9 @@ import com.github.libretube.ui.sheets.ChannelGroupsSheet
4343import com.github.libretube.ui.sheets.FilterSortBottomSheet
4444import com.github.libretube.ui.sheets.FilterSortBottomSheet.Companion.FILTER_SORT_REQUEST_KEY
4545import com.github.libretube.util.PlayingQueue
46- import com.github.libretube.util.deArrow
4746import com.google.android.material.chip.Chip
4847import kotlinx.coroutines.Dispatchers
4948import kotlinx.coroutines.launch
50- import kotlinx.coroutines.withContext
5149
5250class 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
0 commit comments