Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto scroll to discover featured carousal #818

Merged
merged 8 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions base.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ android {
// Feature Flags
buildConfigField "boolean", "END_OF_YEAR_ENABLED", "false"
buildConfigField "boolean", "SINGLE_SIGN_ON_ENABLED", "false"
buildConfigField "boolean", "DISCOVER_FEATURED_AUTO_SCROLL", "false"

testInstrumentationRunner project.testInstrumentationRunner
testApplicationId "au.com.shiftyjelly.pocketcasts.test" + project.name.replace("-", "_")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package au.com.shiftyjelly.pocketcasts.discover.util

import java.util.Timer
import java.util.TimerTask

private const val AUTO_SCROLL_INTERVAL = 3000L

class AutoScrollHelper(private val onAutoScrollCompleted: () -> Unit) {
private var autoScrollTimer: Timer? = null
private var autoScrollTimerTask: TimerTask? = null
private var skipAutoScroll = false

fun startAutoScrollTimer() {
if (autoScrollTimerTask != null) return
autoScrollTimerTask = object : TimerTask() {
override fun run() {
if (!skipAutoScroll) onAutoScrollCompleted()
skipAutoScroll = false
}
}
autoScrollTimer = Timer().apply {
schedule(autoScrollTimerTask, 0, AUTO_SCROLL_INTERVAL)
}
}

fun stopAutoScrollTimer() {
autoScrollTimer?.cancel()
autoScrollTimerTask?.cancel()
autoScrollTimer = null
autoScrollTimerTask = null
}

fun skipAutoScroll() {
skipAutoScroll = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTrackerWrapper
import au.com.shiftyjelly.pocketcasts.analytics.FirebaseAnalyticsTracker
Expand All @@ -27,13 +28,15 @@ import au.com.shiftyjelly.pocketcasts.discover.databinding.RowPodcastSmallListBi
import au.com.shiftyjelly.pocketcasts.discover.databinding.RowSingleEpisodeBinding
import au.com.shiftyjelly.pocketcasts.discover.databinding.RowSinglePodcastBinding
import au.com.shiftyjelly.pocketcasts.discover.extensions.updateSubscribeButtonIcon
import au.com.shiftyjelly.pocketcasts.discover.util.AutoScrollHelper
import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment.Companion.EPISODE_UUID_KEY
import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment.Companion.LIST_ID_KEY
import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment.Companion.PODCAST_UUID_KEY
import au.com.shiftyjelly.pocketcasts.discover.viewmodel.PodcastList
import au.com.shiftyjelly.pocketcasts.localization.helper.TimeHelper
import au.com.shiftyjelly.pocketcasts.localization.helper.tryToLocalise
import au.com.shiftyjelly.pocketcasts.models.entity.Episode
import au.com.shiftyjelly.pocketcasts.preferences.BuildConfig
import au.com.shiftyjelly.pocketcasts.repositories.images.into
import au.com.shiftyjelly.pocketcasts.servers.cdn.ArtworkColors
import au.com.shiftyjelly.pocketcasts.servers.cdn.StaticServerManagerImpl
Expand Down Expand Up @@ -163,6 +166,28 @@ internal class DiscoverAdapter(
}

inner class CarouselListViewHolder(var binding: RowCarouselListBinding) : NetworkLoadableViewHolder(binding.root) {
private var autoScrollHelper: AutoScrollHelper? = null
private val scrollListener = object : OnScrollListener() {
private var draggingStarted: Boolean = false

override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_SETTLING -> Unit // Do nothing
RecyclerView.SCROLL_STATE_DRAGGING -> {
draggingStarted = true
autoScrollHelper?.stopAutoScrollTimer()
}
RecyclerView.SCROLL_STATE_IDLE -> {
if (draggingStarted) {
autoScrollHelper?.startAutoScrollTimer()
draggingStarted = false
}
}
}
}
}

val adapter = CarouselListRowAdapter(null, theme, listener::onPodcastClicked, listener::onPodcastSubscribe, analyticsTracker)

private val linearLayoutManager =
Expand All @@ -173,23 +198,61 @@ internal class DiscoverAdapter(
init {
recyclerView?.layoutManager = linearLayoutManager
recyclerView?.itemAnimator = null
recyclerView?.addOnScrollListener(scrollListener)

if (BuildConfig.DISCOVER_FEATURED_AUTO_SCROLL) {
autoScrollHelper = AutoScrollHelper {
if (adapter.itemCount == 0) return@AutoScrollHelper
val nextPosition = (binding.pageIndicatorView.position + 1)
.takeIf { it < adapter.itemCount } ?: 0
ashiagr marked this conversation as resolved.
Show resolved Hide resolved
recyclerView?.smoothScrollToPosition(nextPosition)
binding.pageIndicatorView.position = nextPosition
trackPageChanged(nextPosition)
}
}

val snapHelper = HorizontalPeekSnapHelper(0)
snapHelper.attachToRecyclerView(recyclerView)
snapHelper.onSnapPositionChanged = { position ->
/* Page just snapped, skip auto scroll */
autoScrollHelper?.skipAutoScroll()
binding.pageIndicatorView.position = position
analyticsTracker.track(AnalyticsEvent.DISCOVER_FEATURED_PAGE_CHANGED, mapOf(CURRENT_PAGE to position, TOTAL_PAGES to adapter.itemCount))
trackPageChanged(position)
}

recyclerView?.adapter = adapter
adapter.submitList(listOf(LoadingItem()))

itemView.viewTreeObserver?.apply {
/* Stop auto scroll when app is backgrounded */
addOnWindowFocusChangeListener { hasFocus ->
if (!hasFocus) autoScrollHelper?.stopAutoScrollTimer()
}
/* Manage auto scroll when itemView's visibility changes on going to next screen */
addOnGlobalLayoutListener {
if (itemView.isShown) {
autoScrollHelper?.startAutoScrollTimer()
} else {
autoScrollHelper?.stopAutoScrollTimer()
}
}
}
}

override fun onRestoreInstanceState(state: Parcelable?) {
super.onRestoreInstanceState(state)
recyclerView?.post {
binding.pageIndicatorView.position = linearLayoutManager.findFirstVisibleItemPosition()
recyclerView.scrollToPosition(binding.pageIndicatorView.position)
}
}

private fun trackPageChanged(position: Int) {
analyticsTracker.track(
AnalyticsEvent.DISCOVER_FEATURED_PAGE_CHANGED,
mapOf(CURRENT_PAGE to position, TOTAL_PAGES to adapter.itemCount)
)
}
}

inner class SmallListViewHolder(val binding: RowPodcastSmallListBinding) : NetworkLoadableViewHolder(binding.root), ShowAllRow {
Expand Down