Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
8.18
-----
* New Features
* Improve feedback when adding episodes to playlists
([#5632](https://github.com/Automattic/pocket-casts-android/pull/5632))
* Bug Fixes
* Allow copying description and show notes links with a long press
([#5628](https://github.com/Automattic/pocket-casts-android/pull/5628))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import au.com.shiftyjelly.pocketcasts.compose.CallOnce
import au.com.shiftyjelly.pocketcasts.compose.components.AnimatedNonNullVisibility
import au.com.shiftyjelly.pocketcasts.compose.components.ThemedSnackbarHost
import au.com.shiftyjelly.pocketcasts.models.to.EpisodeUuidPair
import au.com.shiftyjelly.pocketcasts.models.to.PlaylistPreviewForEpisode
import au.com.shiftyjelly.pocketcasts.playlists.PlaylistFragment
import au.com.shiftyjelly.pocketcasts.playlists.manual.AddToPlaylistViewModel.PlaylistChangeFeedback
import au.com.shiftyjelly.pocketcasts.repositories.playlist.Playlist
import au.com.shiftyjelly.pocketcasts.ui.helper.FragmentHostListener
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
Expand Down Expand Up @@ -105,7 +105,10 @@ internal class AddToPlaylistFragment : BaseDialogFragment() {
viewModel.removeFromPlaylist(playlist.uuid)
} else {
viewModel.trackEpisodeAddTapped(playlist, isPlaylistFull = false)
viewModel.addToPlaylist(playlist.uuid)
viewModel.addToPlaylist(
playlistUuid = playlist.uuid,
playlistTitle = playlist.title,
)
}
} else {
viewModel.trackEpisodeAddTapped(playlist, isPlaylistFull = true)
Expand Down Expand Up @@ -160,27 +163,31 @@ internal class AddToPlaylistFragment : BaseDialogFragment() {
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
if (!requireActivity().isChangingConfigurations && isDoneTapped) {
showDoneSnackbar(viewModel.getPlaylistsAddedTo())
showDoneSnackbar(viewModel.getPlaylistChangeFeedback())
}
}

private fun showDoneSnackbar(playlistsAddedTo: Set<PlaylistPreviewForEpisode>) {
private fun showDoneSnackbar(feedback: PlaylistChangeFeedback) {
val hostListener = requireActivity() as FragmentHostListener
val snackbarView = hostListener.snackBarView()
val snackbar = when (val size = playlistsAddedTo.size) {
0 -> return

1 -> {
val playlist = playlistsAddedTo.first()
val message = getString(LR.string.added_to_playlist_single, playlist.title)
val snackbar = when (feedback) {
is PlaylistChangeFeedback.SinglePlaylistAddition -> {
val playlist = feedback.playlist
val message = getString(feedback.resourceId, playlist.title)
Snackbar.make(snackbarView, message, Snackbar.LENGTH_LONG)
.setAction(LR.string.view) { hostListener.openManualPlaylist(playlist.uuid) }
}

else -> {
val message = resources.getQuantityString(LR.plurals.added_to_playlist_single_multiple, size, size)
is PlaylistChangeFeedback.StringResource -> {
Snackbar.make(snackbarView, getString(feedback.resourceId), Snackbar.LENGTH_LONG)
}

is PlaylistChangeFeedback.PluralResource -> {
val message = resources.getQuantityString(feedback.resourceId, feedback.quantity, feedback.quantity)
Snackbar.make(snackbarView, message, Snackbar.LENGTH_LONG)
}

PlaylistChangeFeedback.None -> return
}
snackbar.show()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package au.com.shiftyjelly.pocketcasts.playlists.manual

import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.ui.text.TextRange
import androidx.lifecycle.ViewModel
Expand Down Expand Up @@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import au.com.shiftyjelly.pocketcasts.localization.R as LR

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel(assistedFactory = AddToPlaylistViewModel.Factory::class)
Expand Down Expand Up @@ -93,25 +96,50 @@ class AddToPlaylistViewModel @AssistedInject constructor(
emitAll(uiStates)
}.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue = null)

private val playlistsChanges = mutableMapOf<String, Boolean>()
private val playlistsChanges = mutableMapOf<String, PlaylistChange>()

private fun cachePlaylistChange(uuid: String, shouldAdd: Boolean) {
private fun cachePlaylistChange(
uuid: String,
shouldAdd: Boolean,
playlistTitle: String? = null,
) {
val change = PlaylistChange(
shouldAdd = shouldAdd,
playlistTitle = playlistTitle,
)
// If the change is not present add it.
playlistsChanges.merge(uuid, shouldAdd) { isCurrentlyAdded, _ ->
if (isCurrentlyAdded == shouldAdd) {
playlistsChanges.merge(uuid, change) { currentChange, _ ->
if (currentChange.shouldAdd == shouldAdd) {
// If the change is already stored keep it.
isCurrentlyAdded
currentChange
} else {
// If playlist was added but will be removed or vice versa remove the change value.
null
}
}
}

fun getPlaylistsAddedTo(): Set<PlaylistPreviewForEpisode> {
val playlists = uiState.value?.playlistPreviews.orEmpty()
val uuidsAddedTo = playlistsChanges.filterValues { it }.keys
return playlists.filterTo(mutableSetOf()) { playlist -> playlist.uuid in uuidsAddedTo }
fun getPlaylistChangeSummary(): PlaylistChangeSummary {
return PlaylistChangeSummary(
addedCount = playlistsChanges.count { it.value.shouldAdd },
removedCount = playlistsChanges.count { !it.value.shouldAdd },
)
}

fun getPlaylistChangeFeedback(): PlaylistChangeFeedback {
val singleAddedPlaylist = playlistsChanges
.entries
.singleOrNull { it.value.shouldAdd }
?.let { (uuid, change) ->
AddedPlaylist(
uuid = uuid,
title = requireNotNull(change.playlistTitle),
)
}
return PlaylistChangeFeedback.from(
summary = getPlaylistChangeSummary(),
singleAddedPlaylist = singleAddedPlaylist,
)
}

fun getArtworkUuidsFlow(playlistUuid: String): StateFlow<List<String>?> {
Expand Down Expand Up @@ -141,8 +169,15 @@ class AddToPlaylistViewModel @AssistedInject constructor(
}
}

fun addToPlaylist(playlistUuid: String) {
cachePlaylistChange(playlistUuid, shouldAdd = true)
fun addToPlaylist(
playlistUuid: String,
playlistTitle: String,
) {
cachePlaylistChange(
uuid = playlistUuid,
shouldAdd = true,
playlistTitle = playlistTitle,
)

viewModelScope.launch(Dispatchers.Default) {
previewsFlow.update { previews ->
Expand Down Expand Up @@ -179,8 +214,8 @@ class AddToPlaylistViewModel @AssistedInject constructor(
viewModelScope.launch {
withContext(NonCancellable) {
val uuids = episodeUuids.map(EpisodeUuidPair::episodeUuid)
for ((playlistUuid, isAdded) in playlistsChanges) {
if (isAdded) {
for ((playlistUuid, change) in playlistsChanges) {
if (change.shouldAdd) {
playlistManager.addManualEpisodes(playlistUuid, uuids)
} else {
playlistManager.deleteManualEpisodes(playlistUuid, uuids)
Expand Down Expand Up @@ -291,6 +326,80 @@ class AddToPlaylistViewModel @AssistedInject constructor(
val title: String,
)

data class PlaylistChangeSummary(
val addedCount: Int,
val removedCount: Int,
)

data class AddedPlaylist(
val uuid: String,
val title: String,
)

private data class PlaylistChange(
val shouldAdd: Boolean,
val playlistTitle: String?,
)

sealed interface PlaylistChangeFeedback {
data class SinglePlaylistAddition(
@StringRes val resourceId: Int,
val playlist: AddedPlaylist,
) : PlaylistChangeFeedback

data class StringResource(
@StringRes val resourceId: Int,
) : PlaylistChangeFeedback

data class PluralResource(
@PluralsRes val resourceId: Int,
val quantity: Int,
) : PlaylistChangeFeedback

data object None : PlaylistChangeFeedback

companion object {
fun from(
summary: PlaylistChangeSummary,
singleAddedPlaylist: AddedPlaylist?,
): PlaylistChangeFeedback {
return when {
summary.addedCount > 0 && summary.removedCount > 0 -> {
PluralResource(
resourceId = LR.plurals.changed_playlists,
quantity = summary.addedCount + summary.removedCount,
)
}

summary.addedCount == 1 -> {
SinglePlaylistAddition(
resourceId = LR.string.added_to_playlist_single,
playlist = requireNotNull(singleAddedPlaylist),
)
}

summary.addedCount > 1 -> {
PluralResource(
resourceId = LR.plurals.added_to_playlist_single_multiple,
quantity = summary.addedCount,
)
}

summary.removedCount == 1 -> StringResource(LR.string.removed_from_playlist_feedback)

summary.removedCount > 1 -> {
PluralResource(
resourceId = LR.plurals.removed_from_playlists,
quantity = summary.removedCount,
)
}

else -> None
}
}
}
}

@AssistedFactory
interface Factory {
fun create(
Expand Down
Loading
Loading