From 1c433fb5604abd55f963c424ace0ff6be1b9534a Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Fri, 4 Nov 2022 15:38:11 +0530 Subject: [PATCH] Add Media Notification controls for Android 13 under General-> Defaults setting --- CHANGELOG.md | 4 + .../settings/PlaybackSettingsFragment.kt | 26 ++++ .../pocketcasts/compose/components/Dialog.kt | 119 ++++++++++++++++++ .../compose/components/Settings.kt | 31 +++++ .../src/main/res/values/strings.xml | 8 ++ .../pocketcasts/preferences/Settings.kt | 30 +++++ .../pocketcasts/preferences/SettingsImpl.kt | 21 ++++ .../playback/MediaSessionManager.kt | 108 ++++++++++------ 8 files changed, 311 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f93900634..b7165c7b55f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ 7.27 ----- +* New Features: * Added ability to set playback effects in Tasker "Control Playback" action. ([#415](https://github.com/Automattic/pocket-casts-android/pull/509)). + * Allowed customization of actions through Settings in Media Notification Control for Android 13 users. + ([#499](https://github.com/Automattic/pocket-casts-android/pull/540)). + 7.26 ----- diff --git a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/PlaybackSettingsFragment.kt b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/PlaybackSettingsFragment.kt index ac5befc1185..2de8339e659 100644 --- a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/PlaybackSettingsFragment.kt +++ b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/PlaybackSettingsFragment.kt @@ -1,5 +1,6 @@ package au.com.shiftyjelly.pocketcasts.settings +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -39,6 +40,7 @@ import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground import au.com.shiftyjelly.pocketcasts.compose.bars.ThemedTopAppBar import au.com.shiftyjelly.pocketcasts.compose.components.DialogButtonState import au.com.shiftyjelly.pocketcasts.compose.components.DialogFrame +import au.com.shiftyjelly.pocketcasts.compose.components.SettingCheckBoxDialogRow import au.com.shiftyjelly.pocketcasts.compose.components.SettingRadioDialogRow import au.com.shiftyjelly.pocketcasts.compose.components.SettingRow import au.com.shiftyjelly.pocketcasts.compose.components.SettingRowToggle @@ -47,6 +49,7 @@ import au.com.shiftyjelly.pocketcasts.compose.theme import au.com.shiftyjelly.pocketcasts.images.R import au.com.shiftyjelly.pocketcasts.models.to.PodcastGrouping import au.com.shiftyjelly.pocketcasts.preferences.Settings +import au.com.shiftyjelly.pocketcasts.preferences.Settings.MediaNotificationControls import au.com.shiftyjelly.pocketcasts.repositories.podcast.PodcastManager import au.com.shiftyjelly.pocketcasts.utils.extensions.isPositive import au.com.shiftyjelly.pocketcasts.views.dialog.ConfirmationDialog @@ -133,6 +136,15 @@ class PlaybackSettingsFragment : BaseFragment() { showSetAllArchiveDialog(it) } ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + MediaNotificationControls( + saved = settings.defaultMediaNotificationControlsFlow.collectAsState().value, + onSave = { + settings.setDefaultMediaNotificationControls(it) + } + ) + } } SettingSection(heading = stringResource(LR.string.settings_general_player)) { @@ -272,6 +284,20 @@ class PlaybackSettingsFragment : BaseFragment() { }, ) + @Composable + fun MediaNotificationControls( + saved: List, + onSave: (List) -> Unit + ) = SettingCheckBoxDialogRow( + primaryText = stringResource(LR.string.settings_media_notification_controls), + secondaryText = stringResource(LR.string.settings_media_notification_controls_summary), + options = MediaNotificationControls.All, + maxOptions = MediaNotificationControls.MaxSelectedOptions, + savedOption = saved, + optionToLocalisedString = { getString(it.controlName) }, + onSave = onSave + ) + @Composable private fun SkipTime( primaryText: String, diff --git a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Dialog.kt b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Dialog.kt index 15aa429af02..ed04d36fdf6 100644 --- a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Dialog.kt +++ b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Dialog.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Card +import androidx.compose.material.Checkbox +import androidx.compose.material.CheckboxDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.RadioButton @@ -27,6 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview @@ -38,6 +41,7 @@ import au.com.shiftyjelly.pocketcasts.compose.AppTheme import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvider import au.com.shiftyjelly.pocketcasts.compose.theme import au.com.shiftyjelly.pocketcasts.ui.theme.Theme +import okhttp3.internal.toImmutableList import java.util.* import au.com.shiftyjelly.pocketcasts.localization.R as LR @@ -268,6 +272,95 @@ fun ProgressDialog( } } +@Composable +fun CheckboxDialog( + title: String, + options: List>, + savedOption: List, + maxOptions: Int, + onSave: (List) -> Unit, + dismissDialog: () -> Unit, +) { + var selected by remember { mutableStateOf(savedOption) } + + DialogFrame( + title = title, + buttons = listOf( + DialogButtonState( + text = stringResource(LR.string.cancel), + onClick = dismissDialog + ), + DialogButtonState( + text = stringResource(LR.string.ok), + onClick = { + onSave(selected) + dismissDialog() + } + ) + ), + onDismissRequest = dismissDialog, + ) { + Column { + options.forEach { (item, itemLabel) -> + DialogCheckBox( + text = itemLabel, + selected = selected.contains(item), + enabled = maxOptions > selected.size || selected.contains(item), + onClick = { + selected = if (selected.contains(item)) { + selected.toMutableList().apply { + remove(item) + }.toImmutableList() + } else { + selected.toMutableList().apply { + add(item) + }.toImmutableList() + } + } + ) + } + } + } +} + +@Composable +fun DialogCheckBox( + text: String, + selected: Boolean, + enabled: Boolean, + onClick: () -> Unit +) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 48.dp) + .selectable( + selected = selected, + enabled = enabled, + role = Role.Checkbox, + onClick = onClick, + ) + ) { + Spacer(Modifier.width(24.dp)) + Checkbox( + checked = selected, + enabled = enabled, + onCheckedChange = null, + colors = CheckboxDefaults.colors( + disabledColor = Color.Gray + ) + ) + Spacer(Modifier.width(12.dp)) + TextP40( + text = text, + modifier = Modifier.padding(vertical = 12.dp) + ) + Spacer(Modifier.width(24.dp)) + } +} + @Composable private fun DialogFramePreview( theme: Theme.ThemeType = Theme.ThemeType.LIGHT, @@ -323,6 +416,32 @@ private fun RadioDialogPreview_light() = RadioDialogPreview(Theme.ThemeType.LIGH @Composable private fun RadioDialogPreview_dark() = RadioDialogPreview(Theme.ThemeType.DARK) +@Composable +private fun CheckboxDialogPreview(theme: Theme.ThemeType) { + AppTheme(theme) { + CheckboxDialog( + title = "Title", + options = listOf( + Pair("Star", stringResource(id = LR.string.settings_media_notification_controls_title_star)), + Pair("Archive", stringResource(id = LR.string.settings_media_notification_controls_title_archive)), + Pair("PlayNext", stringResource(id = LR.string.settings_media_notification_controls_title_play_next)) + ), + savedOption = listOf("Archive", "PlayNext"), + maxOptions = 2, + onSave = {}, + dismissDialog = {} + ) + } +} + +@Preview +@Composable +private fun CheckboxDialogPreview_light() = CheckboxDialogPreview(Theme.ThemeType.LIGHT) + +@Preview +@Composable +private fun CheckboxDialogPreview_dark() = CheckboxDialogPreview(Theme.ThemeType.DARK) + @Preview(showBackground = true) @Composable private fun ProgressDialogPreview( diff --git a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Settings.kt b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Settings.kt index 94980694207..b048ed26fe3 100644 --- a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Settings.kt +++ b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/Settings.kt @@ -104,6 +104,37 @@ fun SettingRadioDialogRow( } } +@Composable +fun SettingCheckBoxDialogRow( + primaryText: String, + modifier: Modifier = Modifier, + secondaryText: String? = null, + options: List, + savedOption: List, + maxOptions: Int = savedOption.size, + optionToLocalisedString: (T) -> String, + onSave: (List) -> Unit, +) { + + var showDialog by remember { mutableStateOf(false) } + SettingRow( + primaryText = primaryText, + secondaryText = secondaryText, + modifier = modifier.clickable { showDialog = true } + ) { + if (showDialog) { + CheckboxDialog( + title = primaryText, + options = options.map { Pair(it, optionToLocalisedString(it)) }, + savedOption = savedOption, + maxOptions = maxOptions, + onSave = onSave, + dismissDialog = { showDialog = false } + ) + } + } +} + /* * Click handling should be done in the modifier passed to this composable to ensure the * entire row is clickable. diff --git a/modules/services/localization/src/main/res/values/strings.xml b/modules/services/localization/src/main/res/values/strings.xml index 6f9d56977cb..5e5207b05ea 100644 --- a/modules/services/localization/src/main/res/values/strings.xml +++ b/modules/services/localization/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ Play episode Play last Play next + Playback speed Play now Play on… Played @@ -1010,6 +1011,13 @@ lock image Include starred Total + Media notification controls + Choose two actions to be displayed in the Android 13 playback notification, Android Auto, and other places the custom media controls are available + @string/archive + @string/mark_as_played + @string/play_next + @string/playback_speed + @string/star No thanks New Episodes Actions diff --git a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt index 9c674a75727..9cc9f5b4463 100644 --- a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt +++ b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt @@ -2,6 +2,7 @@ package au.com.shiftyjelly.pocketcasts.preferences import android.content.Context import android.net.Uri +import androidx.annotation.StringRes import au.com.shiftyjelly.pocketcasts.models.to.PlaybackEffects import au.com.shiftyjelly.pocketcasts.models.to.PodcastGrouping import au.com.shiftyjelly.pocketcasts.models.to.RefreshState @@ -207,6 +208,32 @@ interface Settings { fun toIndex(): Int = options.indexOf(this) } + sealed class MediaNotificationControls(@StringRes val controlName: Int, val key: String) { + + companion object { + val All + get() = listOf(Archive, MarkAsPlayed, PlayNext, PlaybackSpeed, Star) + + const val MaxSelectedOptions = 2 + + private const val ARCHIVE_KEY = "default_media_control_archive" + private const val MARK_AS_PLAYED_KEY = "default_media_control_mark_as_played" + private const val PLAY_NEXT_KEY = "default_media_control_play_next_key" + private const val PLAYBACK_SPEED_KEY = "default_media_control_playback_speed_key" + private const val STAR_KEY = "default_media_control_star_key" + } + + object Archive : MediaNotificationControls(LR.string.archive, ARCHIVE_KEY) + + object MarkAsPlayed : MediaNotificationControls(LR.string.mark_as_played, MARK_AS_PLAYED_KEY) + + object PlayNext : MediaNotificationControls(LR.string.play_next, PLAY_NEXT_KEY) + + object PlaybackSpeed : MediaNotificationControls(LR.string.playback_speed, PLAYBACK_SPEED_KEY) + + object Star : MediaNotificationControls(LR.string.star, STAR_KEY) + } + sealed class AutoArchiveInactive(val timeSeconds: Int) { object Never : AutoArchiveInactive(-1) object Hours24 : AutoArchiveInactive(24 * 60 * 60) @@ -258,6 +285,7 @@ interface Settings { val autoAddUpNextLimit: Observable val defaultPodcastGroupingFlow: StateFlow + val defaultMediaNotificationControlsFlow: StateFlow> val defaultShowArchivedFlow: StateFlow val intelligentPlaybackResumptionFlow: StateFlow val keepScreenAwakeFlow: StateFlow @@ -520,6 +548,8 @@ interface Settings { fun getAutoPlayNextEpisodeOnEmpty(): Boolean fun defaultShowArchived(): Boolean fun setDefaultShowArchived(value: Boolean) + fun defaultMediaNotificationControls(): List + fun setDefaultMediaNotificationControls(mediaNotificationControls: List) fun setMultiSelectItems(items: List) fun getMultiSelectItems(): List fun setLastPauseTime(date: Date) diff --git a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt index bbcd21c2c62..b218b84f797 100644 --- a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt +++ b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt @@ -25,6 +25,7 @@ import au.com.shiftyjelly.pocketcasts.models.type.PodcastsSortType import au.com.shiftyjelly.pocketcasts.models.type.TrimMode import au.com.shiftyjelly.pocketcasts.preferences.Settings.Companion.DEFAULT_MAX_AUTO_ADD_LIMIT import au.com.shiftyjelly.pocketcasts.preferences.Settings.Companion.SETTINGS_ENCRYPT_SECRET +import au.com.shiftyjelly.pocketcasts.preferences.Settings.MediaNotificationControls import au.com.shiftyjelly.pocketcasts.preferences.Settings.NotificationChannel import au.com.shiftyjelly.pocketcasts.preferences.Settings.NotificationId import au.com.shiftyjelly.pocketcasts.preferences.di.PrivateSharedPreferences @@ -93,6 +94,7 @@ class SettingsImpl @Inject constructor( override val autoAddUpNextLimit = BehaviorRelay.create().apply { accept(getAutoAddUpNextLimit()) } override val defaultPodcastGroupingFlow = MutableStateFlow(defaultPodcastGrouping()) + override val defaultMediaNotificationControlsFlow = MutableStateFlow(defaultMediaNotificationControls()) override val defaultShowArchivedFlow = MutableStateFlow(defaultShowArchived()) override val keepScreenAwakeFlow = MutableStateFlow(keepScreenAwake()) override val intelligentPlaybackResumptionFlow = MutableStateFlow(getIntelligentPlaybackResumption()) @@ -1188,6 +1190,25 @@ class SettingsImpl @Inject constructor( defaultShowArchivedFlow.update { value } } + override fun defaultMediaNotificationControls(): List { + val selectedValue = MediaNotificationControls.All.map { mediaControl -> + val defaultValue = + (mediaControl == MediaNotificationControls.PlaybackSpeed || mediaControl == MediaNotificationControls.Star) + Pair(mediaControl, getBoolean(mediaControl.key, defaultValue)) + } + + return selectedValue.filter { (_, value) -> value }.map { (mediaControl, _) -> + mediaControl + } + } + + override fun setDefaultMediaNotificationControls(mediaNotificationControls: List) { + MediaNotificationControls.All.forEach { mediaControl -> + setBoolean(mediaControl.key, mediaNotificationControls.contains(mediaControl)) + } + defaultMediaNotificationControlsFlow.update { mediaNotificationControls } + } + override fun defaultPodcastGrouping(): PodcastGrouping { val index = getInt("default_podcast_grouping", 0) return PodcastGrouping.All.getOrNull(index) ?: PodcastGrouping.None diff --git a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt index e0edb9a852a..e86d4547dfe 100644 --- a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt +++ b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/MediaSessionManager.kt @@ -20,6 +20,7 @@ import au.com.shiftyjelly.pocketcasts.models.entity.Episode import au.com.shiftyjelly.pocketcasts.models.entity.Playable import au.com.shiftyjelly.pocketcasts.models.entity.Podcast import au.com.shiftyjelly.pocketcasts.preferences.Settings +import au.com.shiftyjelly.pocketcasts.preferences.Settings.MediaNotificationControls import au.com.shiftyjelly.pocketcasts.repositories.extensions.saveToGlobalSettings import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager.PlaybackSource import au.com.shiftyjelly.pocketcasts.repositories.playback.auto.AutoConverter @@ -103,6 +104,7 @@ class MediaSessionManager( fun startObserving() { observePlaybackState() + observeMediaNotificationControls() playbackManager.upNextQueue.changesObservable .observeOn(Schedulers.io()) .doOnNext { updateUpNext(it) } @@ -110,6 +112,17 @@ class MediaSessionManager( .addTo(disposables) } + private fun observeMediaNotificationControls() { + launch { + settings.defaultMediaNotificationControlsFlow.collect { + withContext(Dispatchers.Main) { + val playbackStateCompat = getPlaybackStateCompat(playbackManager.playbackStateRelay.blockingFirst(), currentEpisode = playbackManager.getCurrentEpisode()) + updatePlaybackState(playbackStateCompat) + } + } + } + } + private fun connect() { // start the foreground service val connectionCallback = object : MediaBrowserCompat.ConnectionCallback() {} @@ -323,45 +336,53 @@ class MediaSessionManager( addCustomAction(stateBuilder, APP_ACTION_SKIP_FWD, "Skip forward", IR.drawable.auto_skipforward) } - if (playbackManager.isAudioEffectsAvailable()) { - val currentSpeed = playbackState.playbackSpeed - val drawableId = when { - currentSpeed <= 1 -> IR.drawable.auto_1x - currentSpeed == 1.1 -> IR.drawable.auto_1_1x - currentSpeed == 1.2 -> IR.drawable.auto_1_2x - currentSpeed == 1.3 -> IR.drawable.auto_1_3x - currentSpeed == 1.4 -> IR.drawable.auto_1_4x - currentSpeed == 1.5 -> IR.drawable.auto_1_5x - currentSpeed == 1.6 -> IR.drawable.auto_1_6x - currentSpeed == 1.7 -> IR.drawable.auto_1_7x - currentSpeed == 1.8 -> IR.drawable.auto_1_8x - currentSpeed == 1.9 -> IR.drawable.auto_1_9x - currentSpeed == 2.0 -> IR.drawable.auto_2x - currentSpeed == 2.1 -> IR.drawable.auto_2_1x - currentSpeed == 2.2 -> IR.drawable.auto_2_2x - currentSpeed == 2.3 -> IR.drawable.auto_2_3x - currentSpeed == 2.4 -> IR.drawable.auto_2_4x - currentSpeed == 2.5 -> IR.drawable.auto_2_5x - currentSpeed == 2.6 -> IR.drawable.auto_2_6x - currentSpeed == 2.7 -> IR.drawable.auto_2_7x - currentSpeed == 2.8 -> IR.drawable.auto_2_8x - currentSpeed == 2.9 -> IR.drawable.auto_2_9x - currentSpeed == 3.0 -> IR.drawable.auto_3x - else -> IR.drawable.auto_1x - } - - stateBuilder.addCustomAction(APP_ACTION_CHANGE_SPEED, "Change speed", drawableId) - } + settings.defaultMediaNotificationControls().forEach { mediaControl -> + when (mediaControl) { + MediaNotificationControls.Archive -> addCustomAction(stateBuilder, APP_ACTION_ARCHIVE, "Archive", IR.drawable.ic_archive) + MediaNotificationControls.MarkAsPlayed -> addCustomAction(stateBuilder, APP_ACTION_MARK_AS_PLAYED, "Mark as played", IR.drawable.auto_markasplayed) + MediaNotificationControls.PlayNext -> addCustomAction(stateBuilder, APP_ACTION_PLAY_NEXT, "Play next", com.google.android.gms.cast.framework.R.drawable.cast_ic_mini_controller_skip_next) + MediaNotificationControls.PlaybackSpeed -> { + if (playbackManager.isAudioEffectsAvailable()) { + val currentSpeed = playbackState.playbackSpeed + val drawableId = when { + currentSpeed <= 1 -> IR.drawable.auto_1x + currentSpeed == 1.1 -> IR.drawable.auto_1_1x + currentSpeed == 1.2 -> IR.drawable.auto_1_2x + currentSpeed == 1.3 -> IR.drawable.auto_1_3x + currentSpeed == 1.4 -> IR.drawable.auto_1_4x + currentSpeed == 1.5 -> IR.drawable.auto_1_5x + currentSpeed == 1.6 -> IR.drawable.auto_1_6x + currentSpeed == 1.7 -> IR.drawable.auto_1_7x + currentSpeed == 1.8 -> IR.drawable.auto_1_8x + currentSpeed == 1.9 -> IR.drawable.auto_1_9x + currentSpeed == 2.0 -> IR.drawable.auto_2x + currentSpeed == 2.1 -> IR.drawable.auto_2_1x + currentSpeed == 2.2 -> IR.drawable.auto_2_2x + currentSpeed == 2.3 -> IR.drawable.auto_2_3x + currentSpeed == 2.4 -> IR.drawable.auto_2_4x + currentSpeed == 2.5 -> IR.drawable.auto_2_5x + currentSpeed == 2.6 -> IR.drawable.auto_2_6x + currentSpeed == 2.7 -> IR.drawable.auto_2_7x + currentSpeed == 2.8 -> IR.drawable.auto_2_8x + currentSpeed == 2.9 -> IR.drawable.auto_2_9x + currentSpeed == 3.0 -> IR.drawable.auto_3x + else -> IR.drawable.auto_1x + } - if (currentEpisode is Episode) { - if (currentEpisode.isStarred) { - addCustomAction(stateBuilder, APP_ACTION_UNSTAR, "Unstar", IR.drawable.auto_starred) - } else { - addCustomAction(stateBuilder, APP_ACTION_STAR, "Star", IR.drawable.auto_star) + stateBuilder.addCustomAction(APP_ACTION_CHANGE_SPEED, "Change speed", drawableId) + } + } + MediaNotificationControls.Star -> { + if (currentEpisode is Episode) { + if (currentEpisode.isStarred) { + addCustomAction(stateBuilder, APP_ACTION_UNSTAR, "Unstar", IR.drawable.auto_starred) + } else { + addCustomAction(stateBuilder, APP_ACTION_STAR, "Star", IR.drawable.auto_star) + } + } + } } } - - addCustomAction(stateBuilder, APP_ACTION_MARK_AS_PLAYED, "Mark as played", IR.drawable.auto_markasplayed) } private fun addCustomAction(stateBuilder: PlaybackStateCompat.Builder, action: String, name: CharSequence, @DrawableRes icon: Int) { @@ -530,6 +551,8 @@ class MediaSessionManager( APP_ACTION_STAR -> starEpisode() APP_ACTION_UNSTAR -> unstarEpisode() APP_ACTION_CHANGE_SPEED -> changePlaybackSpeed() + APP_ACTION_ARCHIVE -> archive() + APP_ACTION_PLAY_NEXT -> playbackManager.playNextInQueue() } } @@ -612,6 +635,17 @@ class MediaSessionManager( } } + private fun archive() { + launch { + playbackManager.getCurrentEpisode()?.let { + if (it is Episode) { + it.isArchived = true + episodeManager.archive(it, playbackManager) + } + } + } + } + fun playFromSearchExternal(extras: Bundle) { val searchTerm = extras.getString(SearchManager.QUERY) ?: return performPlayFromSearch(searchTerm) @@ -731,6 +765,8 @@ private const val APP_ACTION_SKIP_BACK = "jumpBack" private const val APP_ACTION_SKIP_FWD = "jumpFwd" private const val APP_ACTION_MARK_AS_PLAYED = "markAsPlayed" private const val APP_ACTION_CHANGE_SPEED = "changeSpeed" +private const val APP_ACTION_ARCHIVE = "archive" +private const val APP_ACTION_PLAY_NEXT = "playNext" private val NOTHING_PLAYING: MediaMetadataCompat = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, "")