Merge pull request #5607 from abGit9/fix_stuttering_list_2

fix: resolve stuttering when scrolling to the bottom of channel/playlist videos list
This commit is contained in:
Bnyro 2024-02-25 12:56:20 +01:00 committed by GitHub
commit 066e01be51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 298 additions and 267 deletions

View File

@ -59,6 +59,8 @@ class ChannelFragment : DynamicLayoutManagerFragment() {
private var nextPages = Array<String?>(5) { null } private var nextPages = Array<String?>(5) { null }
private var searchChannelAdapter: SearchChannelAdapter? = null private var searchChannelAdapter: SearchChannelAdapter? = null
private var isAppBarFullyExpanded: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
channelName = args.channelName channelName = args.channelName
@ -86,14 +88,24 @@ class ChannelFragment : DynamicLayoutManagerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Check if the AppBarLayout is fully expanded
binding.channelAppBar.addOnOffsetChangedListener { _, verticalOffset ->
isAppBarFullyExpanded = verticalOffset == 0
}
// Determine if the child can scroll up
binding.channelRefresh.setOnChildScrollUpCallback { _, _ ->
!isAppBarFullyExpanded
}
binding.channelRefresh.setOnRefreshListener { binding.channelRefresh.setOnRefreshListener {
fetchChannel() fetchChannel()
} }
binding.channelScrollView.viewTreeObserver.addOnScrollChangedListener { binding.channelRecView.viewTreeObserver.addOnScrollChangedListener {
val binding = _binding ?: return@addOnScrollChangedListener val binding = _binding ?: return@addOnScrollChangedListener
if (binding.channelScrollView.canScrollVertically(1) || isLoading) return@addOnScrollChangedListener if (binding.channelRecView.canScrollVertically(1) || isLoading) return@addOnScrollChangedListener
loadNextPage() loadNextPage()
} }
@ -199,7 +211,8 @@ class ChannelFragment : DynamicLayoutManagerFragment() {
isLoading = false isLoading = false
binding.channelRefresh.isRefreshing = false binding.channelRefresh.isRefreshing = false
binding.channelScrollView.isVisible = true binding.channelCoordinator.isVisible = true
binding.channelName.text = response.name binding.channelName.text = response.name
if (response.verified) { if (response.verified) {
binding.channelName binding.channelName

View File

@ -123,7 +123,6 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
} }
private fun fetchPlaylist() { private fun fetchPlaylist() {
binding.playlistScrollview.isGone = true
lifecycleScope.launch { lifecycleScope.launch {
val response = try { val response = try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -136,12 +135,13 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
val binding = _binding ?: return@launch val binding = _binding ?: return@launch
playlistFeed = response.relatedStreams.toMutableList() playlistFeed = response.relatedStreams.toMutableList()
binding.playlistScrollview.isVisible = true
nextPage = response.nextpage nextPage = response.nextpage
playlistName = response.name playlistName = response.name
isLoading = false isLoading = false
ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail) ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail)
binding.playlistProgress.isGone = true binding.playlistProgress.isGone = true
binding.playlistAppBar.isVisible = true
binding.playlistRecView.isVisible = true
binding.playlistName.text = response.name binding.playlistName.text = response.name
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos) binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
@ -312,8 +312,8 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
} }
}) })
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener { binding.playlistRecView.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.playlistScrollview?.canScrollVertically(1) == false && if (_binding?.playlistRecView?.canScrollVertically(1) == false &&
!isLoading !isLoading
) { ) {
// append more playlists to the recycler view // append more playlists to the recycler view

View File

@ -6,165 +6,175 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/channel_scrollView" android:id="@+id/channel_coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:visibility="gone">
android:visibility="invisible"
tools:context=".ui.fragments.ChannelFragment">
<LinearLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/channel_app_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="vertical">
<ImageView <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/channel_banner" android:id="@+id/channel_collapsing_tb"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="80dp" /> android:layout_height="wrap_content"
app:layout_scrollFlags="scroll"
<LinearLayout app:titleCollapseMode="scale">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="10dp"
android:orientation="horizontal">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/channel_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
app:shapeAppearance="@style/CircleImageView" />
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:orientation="vertical"
android:layout_marginEnd="5dp" app:layout_collapseMode="pin">
android:layout_weight="1"
android:orientation="vertical">
<TextView <ImageView
android:id="@+id/channel_name" android:id="@+id/channel_banner"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="80dp" />
android:layout_gravity="start"
android:layout_marginTop="3.5dp"
android:drawablePadding="3dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Channel Name" />
<TextView <LinearLayout
android:id="@+id/channel_subs" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="10dp"
android:orientation="horizontal">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/channel_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
app:shapeAppearance="@style/CircleImageView" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="3.5dp"
android:drawablePadding="3dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Channel Name" />
<TextView
android:id="@+id/channel_subs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/channel_share"
style="@style/ElevatedIconButton"
android:tooltipText="@string/share"
app:icon="@drawable/ic_share"
tools:targetApi="m" />
<com.google.android.material.button.MaterialButton
android:id="@+id/notification_bell"
style="@style/ElevatedIconButton"
android:tooltipText="@string/notifications"
app:icon="@drawable/ic_notification"
tools:targetApi="m" />
<com.google.android.material.button.MaterialButton
android:id="@+id/channel_subscribe"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTint="?android:attr/textColorPrimary"
android:stateListAnimator="@null"
android:text="@string/subscribe"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:cornerRadius="20dp"
app:elevation="20dp"
tools:targetApi="m" />
</LinearLayout>
<com.github.libretube.ui.views.ExpandableTextView
android:id="@+id/channel_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start" android:layout_marginHorizontal="5dp"
android:maxLines="1" android:autoLink="web"
android:text="@string/app_name" android:padding="10dp" />
android:textSize="12sp" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/tab_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:checkedChip="@+id/videos"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@id/videos"
style="@style/channelChip"
android:text="@string/videos"
android:visibility="visible" />
<com.google.android.material.chip.Chip
android:id="@+id/shorts"
style="@style/channelChip"
android:text="@string/yt_shorts" />
<com.google.android.material.chip.Chip
android:id="@+id/livestreams"
style="@style/channelChip"
android:text="@string/livestreams" />
<com.google.android.material.chip.Chip
android:id="@+id/playlists"
style="@style/channelChip"
android:text="@string/playlists" />
<com.google.android.material.chip.Chip
android:id="@+id/channels"
style="@style/channelChip"
android:text="@string/channels" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton </com.google.android.material.appbar.CollapsingToolbarLayout>
android:id="@+id/channel_share"
android:tooltipText="@string/share"
style="@style/ElevatedIconButton"
app:icon="@drawable/ic_share"
tools:targetApi="m" />
<com.google.android.material.button.MaterialButton </com.google.android.material.appbar.AppBarLayout>
android:id="@+id/notification_bell"
android:tooltipText="@string/notifications"
style="@style/ElevatedIconButton"
app:icon="@drawable/ic_notification"
tools:targetApi="m" />
<com.google.android.material.button.MaterialButton <androidx.recyclerview.widget.RecyclerView
android:id="@+id/channel_subscribe" android:id="@+id/channel_recView"
style="@style/Widget.Material3.Button.ElevatedButton" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior" />
android:drawableTint="?android:attr/textColorPrimary"
android:stateListAnimator="@null"
android:text="@string/subscribe"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:cornerRadius="20dp"
app:elevation="20dp"
tools:targetApi="m" />
</LinearLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.github.libretube.ui.views.ExpandableTextView
android:id="@+id/channel_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:autoLink="web"
android:padding="10dp" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/tab_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:checkedChip="@+id/videos"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@id/videos"
style="@style/channelChip"
android:text="@string/videos"
android:visibility="visible" />
<com.google.android.material.chip.Chip
android:id="@+id/shorts"
style="@style/channelChip"
android:text="@string/yt_shorts" />
<com.google.android.material.chip.Chip
android:id="@+id/livestreams"
style="@style/channelChip"
android:text="@string/livestreams" />
<com.google.android.material.chip.Chip
android:id="@+id/playlists"
style="@style/channelChip"
android:text="@string/playlists" />
<com.google.android.material.chip.Chip
android:id="@+id/channels"
style="@style/channelChip"
android:text="@string/channels" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/channel_recView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
</com.github.libretube.ui.views.CustomSwipeToRefresh> </com.github.libretube.ui.views.CustomSwipeToRefresh>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -10,148 +10,156 @@
android:id="@+id/playlist_progress" android:id="@+id/playlist_progress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:visibility="gone"
android:layout_centerVertical="true" android:layout_gravity="center" />
android:visibility="gone" />
<ScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_scrollview" android:id="@+id/playlist_recView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:clipToPadding="false"
android:visibility="gone"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/playlist_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/playlist_collapsing_tb"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> app:layout_scrollFlags="scroll"
app:titleCollapseMode="scale">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="10dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:orientation="vertical">
android:orientation="horizontal">
<com.github.libretube.ui.views.ExpandableTextView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/playlist_name" android:id="@+id/thumbnail"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginHorizontal="20dp"
android:layout_weight="1" android:layout_marginVertical="10dp"
android:paddingHorizontal="5dp" android:adjustViewBounds="true"
android:paddingVertical="2dp" android:scaleType="fitCenter"
android:textSize="20sp" app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Small"
android:textStyle="bold" /> tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout <LinearLayout
android:id="@+id/sortContainer" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="8dp"
android:layout_marginEnd="8dp" android:orientation="horizontal">
android:background="@drawable/rounded_ripple"
android:padding="5dp"
android:visibility="gone"
tools:visibility="visible">
<TextView <com.github.libretube.ui.views.ExpandableTextView
android:id="@+id/sortTV" android:id="@+id/playlist_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:paddingHorizontal="5dp"
android:paddingVertical="2dp"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/sortContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="5dp" android:layout_gravity="center"
tools:text="@string/most_recent" /> android:layout_marginEnd="8dp"
android:background="@drawable/rounded_ripple"
android:padding="5dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/sortTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
tools:text="@string/most_recent" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:src="@drawable/ic_sort" />
</LinearLayout>
<ImageView <ImageView
android:id="@+id/optionsMenu"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/ic_sort" /> android:layout_marginEnd="20dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_three_dots" />
</LinearLayout> </LinearLayout>
<ImageView <TextView
android:id="@+id/optionsMenu" android:id="@+id/playlistInfo"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginEnd="20dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_three_dots" />
</LinearLayout>
<TextView
android:id="@+id/playlistInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="15dp"
android:paddingBottom="5dp"
android:textStyle="bold" />
<com.github.libretube.ui.views.ExpandableTextView
android:id="@+id/playlistDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:autoLink="web"
android:paddingHorizontal="10dp"
android:paddingTop="5dp"
android:paddingBottom="10dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/play_all"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:maxLines="1"
android:text="@string/play_all"
app:icon="@drawable/ic_playlist" />
<com.google.android.material.button.MaterialButton
android:id="@+id/bookmark"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:maxLines="1"
android:text="@string/bookmark"
app:icon="@drawable/ic_bookmark_outlined" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_recView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:paddingHorizontal="15dp"
android:nestedScrollingEnabled="false" /> android:paddingBottom="5dp"
</RelativeLayout> android:textStyle="bold" />
</LinearLayout> <com.github.libretube.ui.views.ExpandableTextView
android:id="@+id/playlistDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:autoLink="web"
android:paddingHorizontal="10dp"
android:paddingTop="5dp"
android:paddingBottom="10dp" />
</ScrollView> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:orientation="horizontal">
</RelativeLayout> <com.google.android.material.button.MaterialButton
android:id="@+id/play_all"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:maxLines="1"
android:text="@string/play_all"
app:icon="@drawable/ic_playlist" />
<com.google.android.material.button.MaterialButton
android:id="@+id/bookmark"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:maxLines="1"
android:text="@string/bookmark"
app:icon="@drawable/ic_bookmark_outlined" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>