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

Added new episode lists to Automotive OS. Starred, Listening History, and Files. #403

Merged
merged 2 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
([#372](https://github.com/Automattic/pocket-casts-android/pull/372)).
* Added Automotive OS setting to show played episodes.
([#389](https://github.com/Automattic/pocket-casts-android/pull/389)).
* Added new episode lists to Automotive OS. Starred, Listening History, and Files.
([#403](https://github.com/Automattic/pocket-casts-android/pull/403)).

7.24
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package au.com.shiftyjelly.pocketcasts

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import au.com.shiftyjelly.pocketcasts.account.AccountActivity
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.os.bundleOf
import androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
import androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
import au.com.shiftyjelly.pocketcasts.localization.helper.tryToLocalise
import au.com.shiftyjelly.pocketcasts.models.to.SubscriptionStatus
import au.com.shiftyjelly.pocketcasts.preferences.Settings
import au.com.shiftyjelly.pocketcasts.repositories.images.PodcastImage
import au.com.shiftyjelly.pocketcasts.repositories.playback.CONTENT_STYLE_BROWSABLE_HINT
import au.com.shiftyjelly.pocketcasts.repositories.playback.CONTENT_STYLE_LIST_ITEM_HINT_VALUE
import au.com.shiftyjelly.pocketcasts.repositories.playback.EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT
import au.com.shiftyjelly.pocketcasts.repositories.playback.FOLDER_ROOT_PREFIX
import au.com.shiftyjelly.pocketcasts.repositories.playback.MEDIA_ID_ROOT
Expand All @@ -37,29 +36,16 @@ import au.com.shiftyjelly.pocketcasts.localization.R as LR

const val FILTERS_ROOT = "__FILTERS__"
const val DISCOVER_ROOT = "__DISCOVER__"
const val PROFILE_ROOT = "__PROFILE__"
const val PROFILE_FILES = "__PROFILE_FILES__"
const val PROFILE_STARRED = "__PROFILE_STARRED__"
const val PROFILE_LISTENING_HISTORY = "__LISTENING_HISTORY__"

@SuppressLint("LogNotTimber")
@AndroidEntryPoint
class AutoPlaybackService : PlaybackService() {
@Inject
lateinit var listSource: ListRepository

private fun requireLogin() {
val loginIntent = Intent(this, AccountActivity::class.java)
val loginActivityPendingIntent = PendingIntent.getActivity(this, 0, loginIntent, PendingIntent.FLAG_IMMUTABLE)
val extras = Bundle().apply {
putString(ERROR_RESOLUTION_ACTION_LABEL, "Login now")
putParcelable(ERROR_RESOLUTION_ACTION_INTENT, loginActivityPendingIntent)
}

val playbackState = PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
.setErrorMessage(PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED, "Please log in to your Pocket Casts account")
.setExtras(extras)
.build()
playbackManager.mediaSession.setPlaybackState(playbackState)
playbackManager.mediaSession.setMetadata(MediaMetadataCompat.Builder().build()) // Set no metadata
}
@Inject lateinit var listSource: ListRepository

override fun onCreate() {
super.onCreate()
Expand All @@ -86,6 +72,10 @@ class AutoPlaybackService : PlaybackService() {
PODCASTS_ROOT -> loadPodcastsChildren()
FILTERS_ROOT -> loadFiltersRoot()
DISCOVER_ROOT -> loadDiscoverRoot()
PROFILE_ROOT -> loadProfileRoot()
PROFILE_FILES -> loadFilesChildren()
PROFILE_LISTENING_HISTORY -> loadListeningHistoryChildren()
PROFILE_STARRED -> loadStarredChildren()
else -> {
if (parentId.startsWith(FOLDER_ROOT_PREFIX)) {
loadFolderPodcastsChildren(folderUuid = parentId.substring(FOLDER_ROOT_PREFIX.length))
Expand All @@ -106,44 +96,23 @@ class AutoPlaybackService : PlaybackService() {
}

override suspend fun loadRootChildren(): List<MediaBrowserCompat.MediaItem> {
// podcasts
val podcastsDescription = MediaDescriptionCompat.Builder()
.setTitle(getString(LR.string.podcasts))
.setMediaId(PODCASTS_ROOT)
.setIconUri(AutoConverter.getBitmapUri(IR.drawable.auto_tab_podcasts, this))
.build()
val podcastItem = MediaBrowserCompat.MediaItem(podcastsDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)

// filters
val extras = Bundle().apply {
putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
}
val filtersDescription = MediaDescriptionCompat.Builder()
.setTitle(getString(LR.string.episode_filters))
.setMediaId(FILTERS_ROOT)
.setIconUri(AutoConverter.getBitmapUri(IR.drawable.auto_tab_filter, this))
.setExtras(extras)
.build()
val filtersItem = MediaBrowserCompat.MediaItem(filtersDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
val extrasContentAsList = bundleOf(DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE to DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)

// discover
val discoverDescription = MediaDescriptionCompat.Builder()
.setTitle(getString(LR.string.discover))
.setMediaId(DISCOVER_ROOT)
.setIconUri(AutoConverter.getBitmapUri(IR.drawable.auto_tab_discover, this))
.build()
val discoverItem = MediaBrowserCompat.MediaItem(discoverDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
val podcastsItem = buildListMediaItem(id = PODCASTS_ROOT, title = LR.string.podcasts, drawable = IR.drawable.auto_tab_podcasts)
val filtersItem = buildListMediaItem(id = FILTERS_ROOT, title = LR.string.filters, drawable = IR.drawable.auto_tab_filter, extras = extrasContentAsList)
val discoverItem = buildListMediaItem(id = DISCOVER_ROOT, title = LR.string.discover, drawable = IR.drawable.auto_tab_discover)
val profileItem = buildListMediaItem(id = PROFILE_ROOT, title = LR.string.profile, drawable = IR.drawable.auto_tab_profile, extras = extrasContentAsList)

// show the user's podcast collection first if they are subscribed any
return if (podcastManager.countSubscribed() > 0) {
listOf(podcastItem, filtersItem, discoverItem)
listOf(podcastsItem, filtersItem, discoverItem, profileItem)
} else {
listOf(discoverItem, podcastItem, filtersItem)
listOf(discoverItem, podcastsItem, filtersItem, profileItem)
}
}

fun loadFiltersRoot(): List<MediaBrowserCompat.MediaItem> {
return playlistManager.findAll().mapNotNull {
suspend fun loadFiltersRoot(): List<MediaBrowserCompat.MediaItem> {
return playlistManager.findAllSuspend().mapNotNull {
Log.d(Settings.LOG_TAG_AUTO, "Filters ${it.title}")

try {
Expand All @@ -155,6 +124,28 @@ class AutoPlaybackService : PlaybackService() {
}
}

private fun loadProfileRoot(): List<MediaBrowserCompat.MediaItem> {
return buildList {
// Add the user uploaded Files if they are a Plus subscriber
val isPlusUser = subscriptionManager.getCachedStatus() is SubscriptionStatus.Plus
if (isPlusUser) {
add(buildListMediaItem(id = PROFILE_FILES, title = LR.string.profile_navigation_files, drawable = IR.drawable.automotive_files))
}
add(buildListMediaItem(id = PROFILE_STARRED, title = LR.string.profile_navigation_starred, drawable = IR.drawable.automotive_filter_star))
add(buildListMediaItem(id = PROFILE_LISTENING_HISTORY, title = LR.string.profile_navigation_listening_history, drawable = IR.drawable.automotive_listening_history))
}
}

private fun buildListMediaItem(id: String, @StringRes title: Int, @DrawableRes drawable: Int, extras: Bundle? = null): MediaBrowserCompat.MediaItem {
val description = MediaDescriptionCompat.Builder()
.setTitle(getString(title))
.setMediaId(id)
.setExtras(extras)
.setIconUri(AutoConverter.getBitmapUri(drawable = drawable, this))
.build()
return MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
}

suspend fun loadDiscoverRoot(): List<MediaBrowserCompat.MediaItem> {
Log.d(Settings.LOG_TAG_AUTO, "Loading discover root")
val discoverFeed: Discover
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12C13.841,12 15.333,10.508 15.333,8.667C15.333,6.826 13.841,5.333 12,5.333C10.159,5.333 8.667,6.826 8.667,8.667C8.667,10.508 10.159,12 12,12ZM8.866,12.563C7.728,11.646 7,10.242 7,8.667C7,5.905 9.239,3.667 12,3.667C14.761,3.667 17,5.905 17,8.667C17,10.179 16.328,11.535 15.266,12.452C18.904,13.606 21.167,16.817 21.167,20.333C21.167,20.794 20.813,21.167 20.377,21.167C19.941,21.167 19.587,20.794 19.587,20.333C19.587,16.831 16.725,13.667 12.254,13.667C7.698,13.667 4.413,16.982 4.413,20.333C4.413,20.794 4.059,21.167 3.623,21.167C3.187,21.167 2.833,20.794 2.833,20.333C2.833,17.003 5.251,13.819 8.866,12.563Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="240dp"
android:height="240dp"
android:viewportWidth="240"
android:viewportHeight="240">
<path
android:pathData="M81.17,82.17C81.17,76.87 85.46,72.58 90.75,72.58H119.18L157.83,113.81V154.04C157.83,159.33 153.54,163.63 148.25,163.63H90.75C85.46,163.63 81.17,159.33 81.17,154.04V82.17ZM112.31,82.17H90.75V154.04H148.25V120.5H121.9C116.6,120.5 112.31,116.21 112.31,110.92V82.17ZM121.9,89.49L141.98,110.92H121.9V89.49Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="240dp"
android:height="240dp"
android:viewportWidth="240"
android:viewportHeight="240">
<path
android:pathData="M84.2,147.61C82.33,149.48 82.33,152.51 84.2,154.38C102.91,173.1 133.25,173.1 151.97,154.38C170.68,135.67 170.68,105.33 151.97,86.62C133.25,67.91 102.91,67.91 84.2,86.62C75.08,95.74 70.15,107.92 70.17,120.56V123.31L61.58,114.72C59.7,112.85 56.67,112.85 54.8,114.72C52.93,116.59 52.93,119.62 54.8,121.49L74.96,141.65L78.34,138.27C78.34,138.27 78.35,138.26 78.36,138.25L95.12,121.49C96.99,119.62 96.99,116.59 95.12,114.72C93.25,112.85 90.21,112.85 88.34,114.72L79.75,123.31V120.55C79.74,110.43 83.68,100.7 90.98,93.39C105.95,78.42 130.22,78.42 145.19,93.39C160.16,108.36 160.16,132.64 145.19,147.61C130.22,162.58 105.95,162.58 90.98,147.61C89.11,145.73 86.07,145.73 84.2,147.61Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M133.45,129.09C135.32,130.96 135.32,134 133.45,135.87C131.58,137.74 128.55,137.74 126.67,135.87L113.29,122.49V101.33C113.29,98.69 115.44,96.54 118.08,96.54C120.73,96.54 122.88,98.69 122.88,101.33V118.51L133.45,129.09Z"
android:strokeAlpha="0.5"
android:fillColor="#ffffff"
android:fillAlpha="0.5"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,15 @@ abstract class EpisodeDao {
@Query("SELECT * FROM episodes WHERE starred = 1")
abstract fun observeStarredEpisodes(): Flowable<List<Episode>>

@Query("SELECT * FROM episodes WHERE starred = 1")
abstract suspend fun findStarredEpisodes(): List<Episode>

@Query("SELECT * FROM episodes WHERE last_playback_interaction_date IS NOT NULL AND last_playback_interaction_date > 0 ORDER BY last_playback_interaction_date DESC LIMIT 1000")
abstract fun observePlaybackHistory(): Flowable<List<Episode>>

@Query("SELECT * FROM episodes WHERE last_playback_interaction_date IS NOT NULL AND last_playback_interaction_date > 0 ORDER BY last_playback_interaction_date DESC LIMIT 1000")
abstract suspend fun findPlaybackHistoryEpisodes(): List<Episode>

@Update
abstract fun update(episode: Episode)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ abstract class UserEpisodeDao {
@Query("SELECT * FROM user_episodes ORDER BY added_date DESC")
abstract fun observeUserEpisodesDesc(): Flowable<List<UserEpisode>>

@Query("SELECT * FROM user_episodes ORDER BY added_date DESC")
abstract suspend fun findUserEpisodesDesc(): List<UserEpisode>

@Query("SELECT * FROM user_episodes ORDER BY added_date ASC")
abstract fun observeUserEpisodesAsc(): Flowable<List<UserEpisode>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class MediaSessionManager(

if (settings.showArtworkOnLockScreen()) {
if (Util.isAutomotive(context)) {
val bitmapUri = AutoConverter.getBitmapUriForPodcast(podcast, context)?.toString()
val bitmapUri = AutoConverter.getBitmapUriForPodcast(podcast, episode, context)?.toString()
nowPlayingBuilder = nowPlayingBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, bitmapUri)
nowPlayingBuilder = nowPlayingBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, bitmapUri)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,27 @@ open class PlaybackService : MediaBrowserServiceCompat(), CoroutineScope {
return episodeItems
}

private fun loadFilesChildren(): List<MediaBrowserCompat.MediaItem> {
val podcast = Podcast(uuid = UserEpisodePodcastSubstitute.uuid, title = UserEpisodePodcastSubstitute.title)
return userEpisodeManager.observeUserEpisodes().firstOrError().blockingGet().map { AutoConverter.convertEpisodeToMediaItem(this, it, podcast) }
protected suspend fun loadFilesChildren(): List<MediaBrowserCompat.MediaItem> {
return userEpisodeManager.findUserEpisodes().map {
val podcast = Podcast(uuid = UserEpisodePodcastSubstitute.uuid, title = UserEpisodePodcastSubstitute.title, thumbnailUrl = it.artworkUrl)
AutoConverter.convertEpisodeToMediaItem(this, it, podcast)
}
}

protected suspend fun loadStarredChildren(): List<MediaBrowserCompat.MediaItem> {
return episodeManager.findStarredEpisodes().take(EPISODE_LIMIT).mapNotNull { episode ->
podcastManager.findPodcastByUuid(episode.podcastUuid)?.let { podcast ->
AutoConverter.convertEpisodeToMediaItem(context = this, episode = episode, parentPodcast = podcast)
}
}
}

protected suspend fun loadListeningHistoryChildren(): List<MediaBrowserCompat.MediaItem> {
return episodeManager.findPlaybackHistoryEpisodes().take(EPISODE_LIMIT).mapNotNull { episode ->
podcastManager.findPodcastByUuid(episode.podcastUuid)?.let { podcast ->
AutoConverter.convertEpisodeToMediaItem(context = this, episode = episode, parentPodcast = podcast)
}
}
}

override fun onSearch(query: String, extras: Bundle?, result: Result<List<MediaBrowserCompat.MediaItem>>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import au.com.shiftyjelly.pocketcasts.models.entity.Folder
import au.com.shiftyjelly.pocketcasts.models.entity.Playable
import au.com.shiftyjelly.pocketcasts.models.entity.Playlist
import au.com.shiftyjelly.pocketcasts.models.entity.Podcast
import au.com.shiftyjelly.pocketcasts.models.entity.UserEpisode
import au.com.shiftyjelly.pocketcasts.models.type.EpisodePlayingStatus
import au.com.shiftyjelly.pocketcasts.repositories.extensions.autoDrawableId
import au.com.shiftyjelly.pocketcasts.repositories.extensions.automotiveDrawableId
Expand Down Expand Up @@ -68,7 +69,7 @@ object AutoConverter {
private const val FULL_IMAGE_SIZE = 800

fun convertEpisodeToMediaItem(context: Context, episode: Playable, parentPodcast: Podcast, groupTrailers: Boolean = false, sourceId: String = parentPodcast.uuid): MediaBrowserCompat.MediaItem {
val localUri = getBitmapUriForPodcast(parentPodcast, context)
val localUri = getBitmapUriForPodcast(parentPodcast, episode, context)

val extrasForEpisode = extrasForEpisode(episode)
if (groupTrailers) {
Expand All @@ -90,7 +91,7 @@ object AutoConverter {

fun convertPodcastToMediaItem(podcast: Podcast, context: Context): MediaBrowserCompat.MediaItem? {
return try {
val localUri = getBitmapUriForPodcast(podcast, context)
val localUri = getBitmapUriForPodcast(podcast = podcast, episode = null, context = context)

val podcastDesc = MediaDescriptionCompat.Builder()
.setTitle(podcast.title)
Expand Down Expand Up @@ -130,10 +131,19 @@ object AutoConverter {
return MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
}

fun getBitmapUriForPodcast(podcast: Podcast?, context: Context): Uri? {
if (podcast == null) return null
fun getBitmapUriForPodcast(podcast: Podcast?, episode: Playable?, context: Context): Uri? {
val url = if (episode is UserEpisode) {
// the artwork for user uploaded episodes are stored on each episode
episode.artworkUrl
} else {
podcast?.getArtworkUrl(480)
}

if (url.isNullOrBlank()) {
return null
}

val podcastArtUri = Uri.parse(podcast.getArtworkUrl(480))
val podcastArtUri = Uri.parse(url)
return getArtworkUriForContentProvider(podcastArtUri, context)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface EpisodeManager {
fun observeDownloadEpisodes(): Flowable<List<Episode>>
fun observeDownloadedEpisodes(): Flowable<List<Episode>>
fun observeStarredEpisodes(): Flowable<List<Episode>>
suspend fun findStarredEpisodes(): List<Episode>

fun exists(episodeUuid: String): Boolean

Expand Down Expand Up @@ -120,6 +121,7 @@ interface EpisodeManager {
fun deleteEpisodes(episodes: List<Episode>, playbackManager: PlaybackManager)
fun unarchiveAllInList(episodes: List<Episode>)
fun observePlaybackHistoryEpisodes(): Flowable<List<Episode>>
suspend fun findPlaybackHistoryEpisodes(): List<Episode>
fun checkPodcastForEpisodeLimit(podcast: Podcast, playbackManager: PlaybackManager?)
fun checkPodcastForAutoArchive(podcast: Podcast, playbackManager: PlaybackManager?)
fun episodeCanBeCleanedUp(episode: Episode, playbackManager: PlaybackManager): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ class EpisodeManagerImpl @Inject constructor(
return episodeDao.observePlaybackHistory()
}

override suspend fun findPlaybackHistoryEpisodes(): List<Episode> {
return episodeDao.findPlaybackHistoryEpisodes()
}

override fun observeDownloadingEpisodes(): LiveData<List<Episode>> {
return episodeDao.observeDownloadingEpisodes()
}
Expand Down Expand Up @@ -836,6 +840,10 @@ class EpisodeManagerImpl @Inject constructor(
return episodeDao.observeStarredEpisodes()
}

override suspend fun findStarredEpisodes(): List<Episode> {
return episodeDao.findStarredEpisodes()
}

override fun exists(episodeUuid: String): Boolean {
return episodeDao.exists(episodeUuid)
}
Expand Down