Repository: Automattic/pocket-casts-android
Pull request #4021: Recommendation run analytics
Author: login: geekygecko
Labels: name: [Type] Feature
description: Adding a new feature.
name: [Project] Recommendations
Created: 2025-05-20T11:45:38Z | Merged: 2025-05-21T02:39:11Z

## Description

To help improve the recommendations, this change includes the list date in the analytics. We are using the date of the recommendation lists as a version so we can tell if changes are helping. 

## Testing Instructions
- Filter logcat by `list_datetime`
- Login with a Pocket Casts account that you have history on so you get recommendations
- Go to the Discover tab
- Scroll down to "You Might Like"
- Tap on the artwork follow button 
- ✅ Verify the event `discover_list_podcast_subscribed` with the properties `list_id`, `list_datetime`, and `podcast_uuid`
- Tap on the artwork to open the podcast page
- ✅ Verify the event `discover_list_podcast_tapped` with the properties `list_id`, `list_datetime`, and `podcast_uuid`
- Tap the follow button
- ✅ Verify the event `discover_list_podcast_subscribed` with the properties `list_id`, `list_datetime`, and `podcast_uuid`
- Go back to the Discover tab
- Tap the "Show all" button
- ✅ Verify the event `discover_list_show_all_tapped` with the properties `list_id` and `list_datetime`
- Tap on the row follow button 
- ✅ Verify the event `discover_list_podcast_subscribed` with the properties `list_id`, `list_datetime`, and `podcast_uuid`
- Tap on the row
- ✅ Verify the event `discover_list_podcast_tapped` with the properties `list_id`, `list_datetime`, and `podcast_uuid`

Repeat the same as above with the recommendation list "Because you like".

## Checklist
- [x] If this is a user-facing change, I have added an entry in CHANGELOG.md
- [x] Ensure the linter passes (`./gradlew spotlessApply` to automatically apply formatting/linting)
- [x] I have considered whether it makes sense to add tests for my changes
- [x] All strings that need to be localized are in `modules/services/localization/src/main/res/values/strings.xml`
- [x] Any jetpack compose components I added or changed are covered by compose previews
- [x] I have updated (or requested that someone edit) [the spreadsheet](https://docs.google.com/spreadsheets/d/107jqrutZhU0fVZJ19SBqxxVKbV2NWSdQC9MFYdLiAxc/edit?usp=sharing) to reflect any new or changed analytics.
 
#### I have tested any UI changes...
<!-- If this PR does not contain UI changes, ignore these items -->
- [x] with different themes
- [x] with a landscape orientation
- [x] with the device set to have a large display and font size
- [x] for accessibility with TalkBack



Diff:
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt
index e802fd4347c..2d856a3afdc 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt
@@ -32,7 +32,13 @@ private val differ: DiffUtil.ItemCallback<Any> = object : DiffUtil.ItemCallback<
     }
 }
 
-internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, val onPodcastClicked: ((DiscoverPodcast, String?, Boolean) -> Unit), val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit), private val analyticsTracker: AnalyticsTracker) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
+internal class CarouselListRowAdapter(
+    var pillText: String?,
+    val theme: Theme,
+    val onPodcastClicked: ((DiscoverPodcast, String?, String?, Boolean) -> Unit),
+    val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit),
+    private val analyticsTracker: AnalyticsTracker,
+) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CarouselItemViewHolder {
         val view = LayoutInflater.from(parent.context).inflate(R.layout.item_carousel, parent, false)
         return CarouselItemViewHolder(theme, view)
@@ -52,7 +58,7 @@ internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, v
             holder.setTaglineText(tagLineText)
             holder.itemView.setOnClickListener {
                 val isFeatured = podcast.isSponsored || podcast.listId == null
-                onPodcastClicked(podcast, podcast.listId, isFeatured)
+                onPodcastClicked(podcast, podcast.listId, null, isFeatured)
 
                 if (podcast.listId != null) {
                     val listId = podcast.listId as String
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverAdapter.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverAdapter.kt
index d5afbda15bc..8e7c15226d1 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverAdapter.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverAdapter.kt
@@ -128,9 +128,9 @@ internal class DiscoverAdapter(
     private val analyticsTracker: AnalyticsTracker,
 ) : ListAdapter<Any, RecyclerView.ViewHolder>(DiscoverRowDiffCallback()) {
     interface Listener {
-        fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, isFeatured: Boolean = false)
-        fun onPodcastSubscribe(podcast: DiscoverPodcast, listUuid: String?)
-        fun onPodcastListClicked(list: NetworkLoadableList)
+        fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, listDate: String? = null, isFeatured: Boolean = false)
+        fun onPodcastSubscribe(podcast: DiscoverPodcast, listUuid: String?, listDate: String? = null)
+        fun onPodcastListClicked(contentList: NetworkLoadableList, podcastList: PodcastList? = null)
         fun onCollectionHeaderClicked(list: NetworkLoadableList)
         fun onEpisodeClicked(episode: DiscoverEpisode, listUuid: String?)
         fun onEpisodePlayClicked(episode: DiscoverEpisode)
@@ -196,27 +196,40 @@ internal class DiscoverAdapter(
         val showAllButton: TextView
     }
 
-    inner class LargeListViewHolder(val binding: RowPodcastLargeListBinding) : NetworkLoadableViewHolder(binding.root), ShowAllRow {
-        val adapter = LargeListRowAdapter(context, listener::onPodcastClicked, listener::onPodcastSubscribe, analyticsTracker)
-        override val showAllButton: TextView
-            get() = binding.btnShowAll
+    inner class LargeListViewHolder(val binding: RowPodcastLargeListBinding) : NetworkLoadableViewHolder(binding.root) {
+        private val adapter = LargeListRowAdapter(context, listener::onPodcastClicked, listener::onPodcastSubscribe, analyticsTracker)
 
         init {
             val linearLayoutManager = LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
             linearLayoutManager.initialPrefetchItemCount = 3
             recyclerView?.layoutManager = linearLayoutManager
             recyclerView?.itemAnimator = null
-
             recyclerView?.adapter = adapter
+            adapter.showLoadingList()
+        }
 
+        fun loading(row: DiscoverRow) {
+            binding.lblTitle.text = row.title.tryToLocalise(context.resources)
+            binding.btnShowAll.setOnClickListener(null)
             adapter.showLoadingList()
         }
+
+        fun bind(list: PodcastList, row: DiscoverRow) {
+            adapter.list = list
+            adapter.submitList(list.podcasts) { onRestoreInstanceState(this) }
+            binding.btnShowAll.setOnClickListener {
+                listener.onPodcastListClicked(contentList = row, podcastList = list)
+            }
+            if (list.podcasts.isEmpty()) {
+                hideRow()
+            } else {
+                showRow()
+            }
+        }
     }
 
-    inner class LargeListWithPodcastViewHolder(val binding: RowPodcastLargeListWithPodcastBinding) : NetworkLoadableViewHolder(binding.root), ShowAllRow {
-        val adapter = LargeListRowAdapter(context, listener::onPodcastClicked, listener::onPodcastSubscribe, analyticsTracker)
-        override val showAllButton: TextView
-            get() = binding.btnShowAll
+    inner class LargeListWithPodcastViewHolder(val binding: RowPodcastLargeListWithPodcastBinding) : NetworkLoadableViewHolder(binding.root) {
+        private val adapter = LargeListRowAdapter(context, listener::onPodcastClicked, listener::onPodcastSubscribe, analyticsTracker)
 
         init {
             val linearLayoutManager = LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
@@ -226,6 +239,31 @@ internal class DiscoverAdapter(
             recyclerView?.adapter = adapter
             adapter.showLoadingList()
         }
+
+        fun loading() {
+            binding.title.text = ""
+            binding.subtitle.text = ""
+            binding.podcastImage.setImageResource(placeholderDrawable)
+            binding.btnShowAll.setOnClickListener(null)
+            adapter.showLoadingList()
+        }
+
+        fun bind(list: PodcastList, row: DiscoverRow) {
+            val resources = context.resources
+            adapter.list = list
+            adapter.submitList(list.podcasts) { onRestoreInstanceState(this) }
+            binding.title.text = list.title?.tryToLocalise(resources)
+            binding.subtitle.text = list.subtitle?.tryToLocalise(resources).orEmpty()
+            binding.btnShowAll.setOnClickListener {
+                listener.onPodcastListClicked(contentList = row, podcastList = list)
+            }
+            imageRequestFactory.createForPodcast(list.featureImage).loadInto(binding.podcastImage)
+            if (list.podcasts.isEmpty()) {
+                hideRow()
+            } else {
+                showRow()
+            }
+        }
     }
 
     inner class CarouselListViewHolder(var binding: RowCarouselListBinding) : NetworkLoadableViewHolder(binding.root) {
@@ -436,14 +474,7 @@ internal class DiscoverAdapter(
     }
 
     inner class RemainingPodcastsByCategoryViewHolder(val binding: RowRemainingPodcastsByCategoryBinding) : NetworkLoadableViewHolder(binding.root) {
-        val adapter = RemainingPodcastsAdapter(
-            onPodcastClick = { podcast, listUuid ->
-                listener.onPodcastClicked(podcast, listUuid)
-            },
-            onPodcastSubscribe = { podcast, listUuid ->
-                listener.onPodcastSubscribe(podcast, listUuid)
-            },
-        )
+        val adapter = RemainingPodcastsAdapter(listener::onPodcastClicked, listener::onPodcastSubscribe)
 
         init {
             recyclerView?.layoutManager = LinearLayoutManager(itemView.context, RecyclerView.VERTICAL, false)
@@ -682,12 +713,11 @@ internal class DiscoverAdapter(
         if (row is DiscoverRow) {
             when (holder) {
                 is LargeListViewHolder -> {
-                    holder.binding.lblTitle.text = row.title.tryToLocalise(resources)
+                    holder.loading(row)
                     holder.loadFlowable(
                         loadPodcastList(row.source, row.authenticated),
-                        onNext = {
-                            row.listUuid?.let { listUuid -> holder.adapter.setFromListId(listUuid) }
-                            holder.adapter.submitList(it.podcasts) { onRestoreInstanceState(holder) }
+                        onNext = { list ->
+                            holder.bind(list, row)
                         },
                         onError = { error ->
                             Timber.e(error, "Could not load feed ${row.source}")
@@ -701,23 +731,11 @@ internal class DiscoverAdapter(
                 }
 
                 is LargeListWithPodcastViewHolder -> {
-                    holder.binding.title.text = ""
-                    holder.binding.subtitle.text = ""
-                    holder.binding.podcastImage.setImageResource(placeholderDrawable)
-
+                    holder.loading()
                     holder.loadFlowable(
                         loadPodcastList(row.source, row.authenticated),
                         onNext = { list ->
-                            if (list.podcasts.isEmpty()) {
-                                holder.hideRow()
-                            } else {
-                                holder.showRow()
-                                row.listUuid?.let { listUuid -> holder.adapter.setFromListId(listUuid) }
-                                holder.adapter.submitList(list.podcasts) { onRestoreInstanceState(holder) }
-                                holder.binding.title.text = list.title?.tryToLocalise(resources)
-                                holder.binding.subtitle.text = list.subtitle?.tryToLocalise(resources).orEmpty()
-                                imageRequestFactory.createForPodcast(list.featureImage).loadInto(holder.binding.podcastImage)
-                            }
+                            holder.bind(list, row)
                         },
                         onError = { error ->
                             Timber.e(error, "Could not load feed ${row.source}")
@@ -769,10 +787,10 @@ internal class DiscoverAdapter(
                     holder.binding.lblTitle.text = row.title.tryToLocalise(resources)
                     holder.loadFlowable(
                         loadPodcastList(row.source, row.authenticated),
-                        onNext = {
-                            val podcasts = it.podcasts.subList(0, Math.min(MAX_ROWS_SMALL_LIST, it.podcasts.count()))
+                        onNext = { list ->
+                            val podcasts = list.podcasts.subList(0, Math.min(MAX_ROWS_SMALL_LIST, list.podcasts.count()))
                             holder.binding.pageIndicatorView.count = Math.ceil(podcasts.count().toDouble() / SmallListRowAdapter.SmallListViewHolder.NUMBER_OF_ROWS_PER_PAGE.toDouble()).toInt()
-                            row.listUuid?.let { listUuid -> holder.adapter.setFromListId(listUuid) }
+                            holder.adapter.list = list
                             holder.adapter.submitPodcastList(podcasts) { onRestoreInstanceState(holder) }
                         },
                     )
@@ -825,7 +843,7 @@ internal class DiscoverAdapter(
                             holder.itemView.setOnClickListener {
                                 row.listUuid?.let { listUuid ->
                                     trackDiscoverListPodcastTapped(listUuid, podcast.uuid)
-                                    listener.onPodcastClicked(podcast, row.listUuid)
+                                    listener.onPodcastClicked(podcast = podcast, listUuid = row.listUuid)
                                 }
                             }
 
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt
index f4eb4c5246f..fab6bf16189 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt
@@ -15,8 +15,11 @@ import androidx.recyclerview.widget.RecyclerView
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker
 import au.com.shiftyjelly.pocketcasts.analytics.SourceView
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListShowAllTapped
+import au.com.shiftyjelly.pocketcasts.analytics.discoverShowAllTapped
 import au.com.shiftyjelly.pocketcasts.discover.databinding.FragmentDiscoverBinding
 import au.com.shiftyjelly.pocketcasts.discover.viewmodel.DiscoverViewModel
+import au.com.shiftyjelly.pocketcasts.discover.viewmodel.PodcastList
 import au.com.shiftyjelly.pocketcasts.models.type.EpisodeViewSource
 import au.com.shiftyjelly.pocketcasts.podcasts.view.episode.EpisodeContainerFragment
 import au.com.shiftyjelly.pocketcasts.podcasts.view.podcast.PodcastFragment
@@ -67,12 +70,18 @@ class DiscoverFragment :
         adapter?.enablePageTracking(enable = true)
     }
 
-    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, isFeatured: Boolean) {
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = SourceView.DISCOVER, featuredPodcast = isFeatured)
+    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, listDate: String?, isFeatured: Boolean) {
+        val fragment = PodcastFragment.newInstance(
+            podcastUuid = podcast.uuid,
+            fromListUuid = listUuid,
+            fromListDate = listDate,
+            sourceView = SourceView.DISCOVER,
+            featuredPodcast = isFeatured,
+        )
         (activity as FragmentHostListener).addFragment(fragment)
     }
 
-    override fun onPodcastSubscribe(podcast: DiscoverPodcast, listUuid: String?) {
+    override fun onPodcastSubscribe(podcast: DiscoverPodcast, listUuid: String?, listDate: String?) {
         viewModel.subscribeToPodcast(podcast)
         analyticsTracker.track(
             AnalyticsEvent.PODCAST_SUBSCRIBED,
@@ -80,19 +89,20 @@ class DiscoverFragment :
         )
     }
 
-    override fun onPodcastListClicked(list: NetworkLoadableList) {
-        val transformedList = viewModel.transformNetworkLoadableList(list, resources) // Replace any [regionCode] etc references
-        val listId = list.listUuid
+    override fun onPodcastListClicked(contentList: NetworkLoadableList, podcastList: PodcastList?) {
+        val transformedList = viewModel.transformNetworkLoadableList(contentList, resources) // Replace any [regionCode] etc references
+        val listId = contentList.listUuid
+        val listDate = podcastList?.date ?: ""
         if (listId != null) {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_SHOW_ALL_TAPPED, mapOf(LIST_ID_KEY to listId))
+            analyticsTracker.discoverListShowAllTapped(listId = listId, listDate = listDate)
         } else {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_SHOW_ALL_TAPPED, mapOf(LIST_ID_KEY to transformedList.inferredId()))
+            analyticsTracker.discoverShowAllTapped(listId = transformedList.inferredId(), listDate = listDate)
         }
-        if (list is DiscoverCategory) {
-            trackCategoryShownImpression(list)
+        if (contentList is DiscoverCategory) {
+            trackCategoryShownImpression(contentList)
         }
 
-        if (list.expandedStyle is ExpandedStyle.GridList) {
+        if (contentList.expandedStyle is ExpandedStyle.GridList) {
             val fragment = PodcastGridFragment.newInstance(transformedList)
             (activity as FragmentHostListener).addFragment(fragment)
         } else {
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/LargeListRowAdapter.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/LargeListRowAdapter.kt
index 4b7b744af54..663ef33ca44 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/LargeListRowAdapter.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/LargeListRowAdapter.kt
@@ -10,13 +10,16 @@ import android.widget.TextView
 import androidx.core.view.isVisible
 import androidx.recyclerview.widget.ListAdapter
 import androidx.recyclerview.widget.RecyclerView
-import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
+import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsParameter.listDate
+import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsParameter.listId
+import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsParameter.podcastUuid
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastSubscribed
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastTapped
 import au.com.shiftyjelly.pocketcasts.discover.R
 import au.com.shiftyjelly.pocketcasts.discover.extensions.updateSubscribeButtonIcon
 import au.com.shiftyjelly.pocketcasts.discover.util.DISCOVER_PODCAST_DIFF_CALLBACK
-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.repositories.images.PocketCastsImageRequestFactory
 import au.com.shiftyjelly.pocketcasts.repositories.images.PocketCastsImageRequestFactory.PlaceholderType
 import au.com.shiftyjelly.pocketcasts.repositories.images.loadInto
@@ -27,11 +30,11 @@ import au.com.shiftyjelly.pocketcasts.ui.R as UR
 
 internal class LargeListRowAdapter(
     context: Context,
-    val onPodcastClicked: ((DiscoverPodcast, String?) -> Unit),
-    val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit),
+    val onPodcastClicked: ((DiscoverPodcast, String?, String?) -> Unit),
+    val onPodcastSubscribe: ((DiscoverPodcast, String?, String?) -> Unit),
     private val analyticsTracker: AnalyticsTracker,
 ) : ListAdapter<Any, LargeListRowAdapter.LargeListItemViewHolder>(DISCOVER_PODCAST_DIFF_CALLBACK) {
-    private var fromListId: String? = null
+    var list: PodcastList? = null
 
     class LargeListItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
         val placeholderDrawable = itemView.context.getThemeDrawable(UR.attr.defaultArtworkSmall)
@@ -62,18 +65,14 @@ internal class LargeListRowAdapter(
             holder.lblSubtitle.text = podcast.author
             holder.itemView.isClickable = true
             holder.itemView.setOnClickListener {
-                fromListId?.let {
-                    analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-                }
-                onPodcastClicked(podcast, fromListId)
+                analyticsTracker.discoverListPodcastTapped(podcastUuid = podcast.uuid, listId = list?.listId, listDate = list?.date)
+                onPodcastClicked(podcast, list?.listId, list?.date)
             }
             holder.btnSubscribe.isClickable = true
             holder.btnSubscribe.setOnClickListener {
                 holder.btnSubscribe.updateSubscribeButtonIcon(subscribed = true, colorSubscribed = UR.attr.contrast_01, colorUnsubscribed = UR.attr.contrast_01)
-                fromListId?.let {
-                    analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-                }
-                onPodcastSubscribe(podcast, fromListId)
+                analyticsTracker.discoverListPodcastSubscribed(podcastUuid = podcast.uuid, listId = list?.listId, listDate = list?.date)
+                onPodcastSubscribe(podcast, list?.listId, list?.date)
             }
             holder.btnSubscribe.updateSubscribeButtonIcon(subscribed = podcast.isSubscribed, colorSubscribed = UR.attr.contrast_01, colorUnsubscribed = UR.attr.contrast_01)
             holder.btnSubscribe.isVisible = true
@@ -86,11 +85,9 @@ internal class LargeListRowAdapter(
             holder.btnSubscribe.isVisible = false
         }
     }
+
     fun showLoadingList() {
         val loadingList = listOf(MutableList(NUMBER_OF_LOADING_ITEMS) { LoadingItem() })
         submitList(loadingList)
     }
-    fun setFromListId(value: String) {
-        this.fromListId = value
-    }
 }
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt
index 6f46cd6c8b9..ff0150690d0 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt
@@ -14,6 +14,8 @@ import androidx.fragment.app.viewModels
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker
 import au.com.shiftyjelly.pocketcasts.analytics.SourceView
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastSubscribed
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastTapped
 import au.com.shiftyjelly.pocketcasts.discover.R
 import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment.Companion.EPISODE_UUID_KEY
 import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment.Companion.LIST_ID_KEY
@@ -118,22 +120,24 @@ open class PodcastGridListFragment : BaseFragment(), Toolbar.OnMenuItemClickList
     protected val viewModel: PodcastListViewModel by viewModels()
 
     val onPodcastClicked: (DiscoverPodcast) -> Unit = { podcast ->
-        listUuid?.let {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-        }
+        val listDate = viewModel.listFeed?.date
+        analyticsTracker.discoverListPodcastTapped(podcastUuid = podcast.uuid, listId = listUuid, listDate = listDate)
         val sourceView = when (expandedStyle) {
             is ExpandedStyle.RankedList -> SourceView.DISCOVER_RANKED_LIST
             is ExpandedStyle.PlainList -> SourceView.DISCOVER_PLAIN_LIST
             else -> SourceView.DISCOVER
         }
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = sourceView)
+        val fragment = PodcastFragment.newInstance(
+            podcastUuid = podcast.uuid,
+            fromListUuid = listUuid,
+            fromListDate = listDate,
+            sourceView = sourceView,
+        )
         (activity as FragmentHostListener).addFragment(fragment)
     }
 
     val onPodcastSubscribe: (String) -> Unit = { podcastUuid ->
-        listUuid?.let {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcastUuid))
-        }
+        analyticsTracker.discoverListPodcastSubscribed(podcastUuid = podcastUuid, listId = listUuid, listDate = viewModel.listFeed?.date)
         var podcastSubscribedSource = SourceView.DISCOVER
         if (expandedStyle is ExpandedStyle.RankedList) {
             podcastSubscribedSource = SourceView.DISCOVER_RANKED_LIST
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/SmallListRowAdapter.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/SmallListRowAdapter.kt
index fa4404ba29d..39447e9444e 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/SmallListRowAdapter.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/SmallListRowAdapter.kt
@@ -5,11 +5,11 @@ import android.view.ViewGroup
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListAdapter
 import androidx.recyclerview.widget.RecyclerView
-import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastSubscribed
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastTapped
 import au.com.shiftyjelly.pocketcasts.discover.databinding.ItemSmallListBinding
-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.servers.model.DiscoverPodcast
 
 val SmallListDiffer = object : DiffUtil.ItemCallback<List<Any>>() {
@@ -32,8 +32,8 @@ val SmallListDiffer = object : DiffUtil.ItemCallback<List<Any>>() {
 }
 
 internal class SmallListRowAdapter(
-    val onPodcastClicked: ((DiscoverPodcast, String?) -> Unit),
-    val onPodcastSubscribe: (DiscoverPodcast, String?) -> Unit,
+    val onPodcastClicked: ((DiscoverPodcast, String?, String?) -> Unit),
+    val onPodcastSubscribe: (DiscoverPodcast, String?, String?) -> Unit,
     val analyticsTracker: AnalyticsTracker,
 ) : ListAdapter<List<Any>, SmallListRowAdapter.SmallListViewHolder>(SmallListDiffer) {
     class SmallListViewHolder(val binding: ItemSmallListBinding) : RecyclerView.ViewHolder(binding.root) {
@@ -45,7 +45,7 @@ internal class SmallListRowAdapter(
         val rows = listOf(binding.row0, binding.row1, binding.row2, binding.row3)
     }
 
-    private var fromListId: String? = null
+    var list: PodcastList? = null
 
     fun submitPodcastList(list: List<DiscoverPodcast>, commitCallback: Runnable?) {
         submitList(list.chunked(SmallListViewHolder.NUMBER_OF_ROWS_PER_PAGE), commitCallback)
@@ -74,16 +74,12 @@ internal class SmallListRowAdapter(
                 podcastRow.podcast = podcast
                 podcastRow.isClickable = true
                 podcastRow.setOnClickListener {
-                    fromListId?.let {
-                        analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-                    }
-                    onPodcastClicked(podcast, fromListId)
+                    analyticsTracker.discoverListPodcastTapped(podcastUuid = podcast.uuid, listId = list?.listId, listDate = list?.date)
+                    onPodcastClicked(podcast, list?.listId, list?.date)
                 }
                 podcastRow.onSubscribeClicked = {
-                    fromListId?.let {
-                        analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-                    }
-                    onPodcastSubscribe(podcast, fromListId)
+                    analyticsTracker.discoverListPodcastSubscribed(podcastUuid = podcast.uuid, listId = list?.listId, listDate = list?.date)
+                    onPodcastSubscribe(podcast, list?.listId, list?.date)
                 }
             } else {
                 podcastRow.podcast = null
@@ -91,7 +87,4 @@ internal class SmallListRowAdapter(
             }
         }
     }
-    fun setFromListId(value: String) {
-        this.fromListId = value
-    }
 }
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/DiscoverViewModel.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/DiscoverViewModel.kt
index ae53e4b34e0..4c10c2ae419 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/DiscoverViewModel.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/DiscoverViewModel.kt
@@ -166,6 +166,7 @@ class DiscoverViewModel @Inject constructor(
                     tintColors = it.tintColors,
                     images = it.collageImages,
                     listId = it.listId,
+                    date = it.date,
                 )
             }
             .flatMapPublisher { addSubscriptionStateToPodcasts(it) }
@@ -402,6 +403,7 @@ data class PodcastList(
     val tintColors: DiscoverFeedTintColors?,
     val images: List<DiscoverFeedImage>?,
     val listId: String? = null,
+    val date: String? = null,
 )
 
 data class CarouselSponsoredPodcast(
diff --git a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/PodcastListViewModel.kt b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/PodcastListViewModel.kt
index ae2703e7689..8ef6148ad5e 100644
--- a/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/PodcastListViewModel.kt
+++ b/modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/viewmodel/PodcastListViewModel.kt
@@ -39,6 +39,9 @@ class PodcastListViewModel @Inject constructor(
     val state: MutableLiveData<PodcastListViewState> = MutableLiveData()
     val disposables: CompositeDisposable = CompositeDisposable()
 
+    val listFeed: ListFeed?
+        get() = (state.value as? PodcastListViewState.ListLoaded)?.feed
+
     init {
         state.value = PodcastListViewState.Loading()
     }
diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
index c8e1bd6f688..3fa4e09471a 100644
--- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
+++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
@@ -38,6 +38,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
 import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker
 import au.com.shiftyjelly.pocketcasts.analytics.SourceView
+import au.com.shiftyjelly.pocketcasts.analytics.discoverListPodcastSubscribed
 import au.com.shiftyjelly.pocketcasts.compose.AppTheme
 import au.com.shiftyjelly.pocketcasts.localization.extensions.getStringPlural
 import au.com.shiftyjelly.pocketcasts.models.entity.BaseEpisode
@@ -137,6 +138,7 @@ class PodcastFragment : BaseFragment() {
             podcastUuid: String,
             sourceView: SourceView,
             fromListUuid: String? = null,
+            fromListDate: String? = null,
             featuredPodcast: Boolean = false,
         ): PodcastFragment = PodcastFragment().apply {
             arguments = bundleOf(
@@ -144,6 +146,7 @@ class PodcastFragment : BaseFragment() {
                     podcastUuid = podcastUuid,
                     sourceView = sourceView,
                     fromListUuid = fromListUuid,
+                    fromListDate = fromListDate,
                     featuredPodcast = featuredPodcast,
                 ),
             )
@@ -287,9 +290,7 @@ class PodcastFragment : BaseFragment() {
     }
 
     private val onSubscribeClicked: () -> Unit = {
-        fromListUuid?.let {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcastUuid))
-        }
+        analyticsTracker.discoverListPodcastSubscribed(podcastUuid = podcastUuid, listId = fromListUuid, listDate = fromListDate)
         if (featuredPodcast) {
             viewModel.podcast.value?.uuid?.let { podcastUuid ->
                 analyticsTracker.track(AnalyticsEvent.DISCOVER_FEATURED_PODCAST_SUBSCRIBED, mapOf(PODCAST_UUID_KEY to podcastUuid))
@@ -680,6 +681,9 @@ class PodcastFragment : BaseFragment() {
     private val fromListUuid: String?
         get() = args.fromListUuid
 
+    private val fromListDate: String
+        get() = args.fromListDate.orEmpty()
+
     private var lastSearchTerm: String? = null
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -1306,6 +1310,7 @@ class PodcastFragment : BaseFragment() {
         val podcastUuid: String,
         val sourceView: SourceView,
         val fromListUuid: String?,
+        val fromListDate: String?,
         val featuredPodcast: Boolean,
     ) : Parcelable
 }
diff --git a/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsParameter.kt b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsParameter.kt
new file mode 100644
index 00000000000..7cc9f5ff8e0
--- /dev/null
+++ b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsParameter.kt
@@ -0,0 +1,8 @@
+package au.com.shiftyjelly.pocketcasts.analytics
+
+object AnalyticsParameter {
+
+    const val listId = "list_id"
+    const val listDate = "list_datetime"
+    const val podcastUuid = "podcast_uuid"
+}
diff --git a/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsTrackerExtensions.kt b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsTrackerExtensions.kt
new file mode 100644
index 00000000000..49779778a0b
--- /dev/null
+++ b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsTrackerExtensions.kt
@@ -0,0 +1,59 @@
+package au.com.shiftyjelly.pocketcasts.analytics
+
+fun AnalyticsTracker.discoverListShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListEvent(analyticsEvent: AnalyticsEvent, listId: String, listDate: String) {
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to listDate,
+        ),
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastTapped(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastSubscribed(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListPodcastEvent(analyticsEvent: AnalyticsEvent, podcastUuid: String, listId: String?, listDate: String?) {
+    if (listId == null) {
+        return
+    }
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.podcastUuid to podcastUuid,
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to listDate.orEmpty(),
+        ),
+    )
+}


Reviews:
user: login: sztomek
body: Were able to verify the desired analytic events being logged. ✅ 

Left some minor comments about formatting, but it's just a personal preference, so feel free to disregard them. Approving the PR nevertheless
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T12:59:28Z
user: login: geekygecko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T02:18:01Z
user: login: geekygecko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T02:18:36Z
user: login: geekygecko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T02:19:07Z
user: login: geekygecko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T02:19:39Z

Review comments:
diff_hunk: @@ -32,7 +32,7 @@ private val differ: DiffUtil.ItemCallback<Any> = object : DiffUtil.ItemCallback<
     }
 }
 
-internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, val onPodcastClicked: ((DiscoverPodcast, String?, Boolean) -> Unit), val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit), private val analyticsTracker: AnalyticsTracker) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
+internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, val onPodcastClicked: ((DiscoverPodcast, String?, String?, Boolean) -> Unit), val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit), private val analyticsTracker: AnalyticsTracker) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt
user: login: sztomek
body: 💡 adding line breaks so each argument would have its own dedicated line would improve readability a lot imo.
created_at: 2025-05-20T12:40:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,32 +70,33 @@ class DiscoverFragment :
         adapter?.enablePageTracking(enable = true)
     }
 
-    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, isFeatured: Boolean) {
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = SourceView.DISCOVER, featuredPodcast = isFeatured)
+    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, listDate: String?, isFeatured: Boolean) {
+        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, fromListDate = listDate, sourceView = SourceView.DISCOVER, featuredPodcast = isFeatured)
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt
user: login: sztomek
body: ⛏️ formatting seems a bit weird to me with that line end, i usually lean towards putting each argument to new lines for a more pleasing reading experience
created_at: 2025-05-20T12:43:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -118,22 +120,19 @@ open class PodcastGridListFragment : BaseFragment(), Toolbar.OnMenuItemClickList
     protected val viewModel: PodcastListViewModel by viewModels()
 
     val onPodcastClicked: (DiscoverPodcast) -> Unit = { podcast ->
-        listUuid?.let {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-        }
+        val listDate = viewModel.listFeed?.date
+        analyticsTracker.discoverListPodcastTapped(podcastUuid = podcast.uuid, listId = listUuid, listDate = listDate)
         val sourceView = when (expandedStyle) {
             is ExpandedStyle.RankedList -> SourceView.DISCOVER_RANKED_LIST
             is ExpandedStyle.PlainList -> SourceView.DISCOVER_PLAIN_LIST
             else -> SourceView.DISCOVER
         }
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = sourceView)
+        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, fromListDate = listDate, sourceView = sourceView)
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt
user: login: sztomek
body: ⛏️ again, formatting could be improved 
created_at: 2025-05-20T12:48:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+package au.com.shiftyjelly.pocketcasts.analytics
+
+fun AnalyticsTracker.discoverListShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListEvent(analyticsEvent: AnalyticsEvent, listId: String, listDate: String) {
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to listDate,
+        ),
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastTapped(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastSubscribed(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListPodcastEvent(analyticsEvent: AnalyticsEvent, podcastUuid: String, listId: String?, listDate: String?) {
+    if (listId == null) {
+        return
+    }
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.podcastUuid to podcastUuid,
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to (listDate ?: ""),
path: modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsTrackerExtensions.kt
user: login: sztomek
body: ⛏️ could be `listDate.orEmpty()`
created_at: 2025-05-20T12:51:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,7 +32,7 @@ private val differ: DiffUtil.ItemCallback<Any> = object : DiffUtil.ItemCallback<
     }
 }
 
-internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, val onPodcastClicked: ((DiscoverPodcast, String?, Boolean) -> Unit), val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit), private val analyticsTracker: AnalyticsTracker) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
+internal class CarouselListRowAdapter(var pillText: String?, val theme: Theme, val onPodcastClicked: ((DiscoverPodcast, String?, String?, Boolean) -> Unit), val onPodcastSubscribe: ((DiscoverPodcast, String?) -> Unit), private val analyticsTracker: AnalyticsTracker) : ListAdapter<Any, CarouselItemViewHolder>(differ) {
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/CarouselListRowAdapter.kt
user: login: geekygecko
body: Thanks, I will do this.
created_at: 2025-05-21T02:18:01Z
author_association: MEMBER
diff_hunk: @@ -67,32 +70,33 @@ class DiscoverFragment :
         adapter?.enablePageTracking(enable = true)
     }
 
-    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, isFeatured: Boolean) {
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = SourceView.DISCOVER, featuredPodcast = isFeatured)
+    override fun onPodcastClicked(podcast: DiscoverPodcast, listUuid: String?, listDate: String?, isFeatured: Boolean) {
+        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, fromListDate = listDate, sourceView = SourceView.DISCOVER, featuredPodcast = isFeatured)
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/DiscoverFragment.kt
user: login: geekygecko
body: Thanks again
created_at: 2025-05-21T02:18:36Z
author_association: MEMBER
diff_hunk: @@ -118,22 +120,19 @@ open class PodcastGridListFragment : BaseFragment(), Toolbar.OnMenuItemClickList
     protected val viewModel: PodcastListViewModel by viewModels()
 
     val onPodcastClicked: (DiscoverPodcast) -> Unit = { podcast ->
-        listUuid?.let {
-            analyticsTracker.track(AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED, mapOf(LIST_ID_KEY to it, PODCAST_UUID_KEY to podcast.uuid))
-        }
+        val listDate = viewModel.listFeed?.date
+        analyticsTracker.discoverListPodcastTapped(podcastUuid = podcast.uuid, listId = listUuid, listDate = listDate)
         val sourceView = when (expandedStyle) {
             is ExpandedStyle.RankedList -> SourceView.DISCOVER_RANKED_LIST
             is ExpandedStyle.PlainList -> SourceView.DISCOVER_PLAIN_LIST
             else -> SourceView.DISCOVER
         }
-        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, sourceView = sourceView)
+        val fragment = PodcastFragment.newInstance(podcastUuid = podcast.uuid, fromListUuid = listUuid, fromListDate = listDate, sourceView = sourceView)
path: modules/features/discover/src/main/java/au/com/shiftyjelly/pocketcasts/discover/view/PodcastGridListFragment.kt
user: login: geekygecko
body: Thanks again
created_at: 2025-05-21T02:19:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+package au.com.shiftyjelly.pocketcasts.analytics
+
+fun AnalyticsTracker.discoverListShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverShowAllTapped(listId: String, listDate: String) {
+    discoverListEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_SHOW_ALL_TAPPED,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListEvent(analyticsEvent: AnalyticsEvent, listId: String, listDate: String) {
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to listDate,
+        ),
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastTapped(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_TAPPED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+fun AnalyticsTracker.discoverListPodcastSubscribed(podcastUuid: String, listId: String?, listDate: String?) {
+    discoverListPodcastEvent(
+        analyticsEvent = AnalyticsEvent.DISCOVER_LIST_PODCAST_SUBSCRIBED,
+        podcastUuid = podcastUuid,
+        listId = listId,
+        listDate = listDate,
+    )
+}
+
+private fun AnalyticsTracker.discoverListPodcastEvent(analyticsEvent: AnalyticsEvent, podcastUuid: String, listId: String?, listDate: String?) {
+    if (listId == null) {
+        return
+    }
+    track(
+        event = analyticsEvent,
+        properties = mapOf(
+            AnalyticsParameter.podcastUuid to podcastUuid,
+            AnalyticsParameter.listId to listId,
+            AnalyticsParameter.listDate to (listDate ?: ""),
path: modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsTrackerExtensions.kt
user: login: geekygecko
body: I have been trying hard to remember this one! Thanks for the reminder. 
created_at: 2025-05-21T02:19:39Z
author_association: MEMBER

PR comments:
user: login: dangermattic
created_at: 2025-05-20T11:46:21Z
body: <!--
  0 Errors
  1 Warning: This PR is assigned to the mil...
  0 Messages
  0 Markdowns
-->
<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true" data-kind="Warning">
          1 Warning
      </th>
     </tr>
  </thead>
  <tbody>
    <tr>
      <td>:warning:</td>
      <td data-sticky="false">This PR is assigned to the milestone <a href="https://github.com/Automattic/pocket-casts-android/milestone/93">7.89 ❄️</a>. The due date for this milestone has already passed.<br />
Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.</td>
    </tr>
  </tbody>
</table>

<p align="right" data-meta="generated_by_pr-check">
  Generated by :no_entry_sign: <a href="https://danger.systems/">Danger</a>
</p>

author_association: COLLABORATOR
user: login: wpmobilebot
created_at: 2025-05-20T11:48:02Z
body: <!-- REUSE_ID: app-prototype-build-link -->

<details><summary>📲 You can test the changes from this Pull Request in <b>📱 Mobile</b> by scanning the QR code below to install the corresponding build.</summary>
<table>
<tr>
  <td rowspan='4' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2F8af446086b2a2e077ae6d4c1b8fadd0fdea615fb%2Fpocketcasts-app-prototype-build-pr4021-68e90f0.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> 📱 Mobile</td>
</tr>
<tr><td><b>Build Type</b></td><td>debugProd</td></tr>
<tr><td><b>Commit</b></td><td>68e90f086db6f5b6207019dc8a71e1e810e297d0</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/8af446086b2a2e077ae6d4c1b8fadd0fdea615fb/pocketcasts-app-prototype-build-pr4021-68e90f0.apk'><code>pocketcasts-app-prototype-build-pr4021-68e90f0.apk</code></a></td></tr>
</table>

</details>
<details><summary>📲 You can test the changes from this Pull Request in <b>🚗 Automotive</b> by scanning the QR code below to install the corresponding build.</summary>
<table>
<tr>
  <td rowspan='4' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2F4ee1b6d78a0959a907f50a696d705f8c75f56c77%2Fpocketcasts-automotive-prototype-build-pr4021-68e90f0.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> 🚗 Automotive</td>
</tr>
<tr><td><b>Build Type</b></td><td>debugProd</td></tr>
<tr><td><b>Commit</b></td><td>68e90f086db6f5b6207019dc8a71e1e810e297d0</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/4ee1b6d78a0959a907f50a696d705f8c75f56c77/pocketcasts-automotive-prototype-build-pr4021-68e90f0.apk'><code>pocketcasts-automotive-prototype-build-pr4021-68e90f0.apk</code></a></td></tr>
</table>

</details>
<details><summary>📲 You can test the changes from this Pull Request in <b>⌚ Wear</b> by scanning the QR code below to install the corresponding build.</summary>
<table>
<tr>
  <td rowspan='4' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2Ffed995626e931f22c8a6527d801082253889a6fc%2Fpocketcasts-wear-prototype-build-pr4021-68e90f0.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> ⌚ Wear</td>
</tr>
<tr><td><b>Build Type</b></td><td>debugProd</td></tr>
<tr><td><b>Commit</b></td><td>68e90f086db6f5b6207019dc8a71e1e810e297d0</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/fed995626e931f22c8a6527d801082253889a6fc/pocketcasts-wear-prototype-build-pr4021-68e90f0.apk'><code>pocketcasts-wear-prototype-build-pr4021-68e90f0.apk</code></a></td></tr>
</table>

</details>

author_association: COLLABORATOR