Skip to content

Commit

Permalink
[Wear] Adds episode screen (#1350)
Browse files Browse the repository at this point in the history
- Adds episode screen
- Adds capability to delete the queue
- Removes playback speed change from entity screen (will be added to
Player screen in another PR)
- Refactor state classes to state interfaces for consistencies
  • Loading branch information
kul3r4 committed Apr 19, 2024
2 parents 78f5df9 + e690662 commit 2530904
Show file tree
Hide file tree
Showing 17 changed files with 822 additions and 383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ interface EpisodePlayer {

fun addToQueue(episode: PlayerEpisode)

/*
* Flushes the queue
*/
fun removeAllFromQueue()

/**
* Plays the current episode
*/
Expand All @@ -60,6 +65,11 @@ interface EpisodePlayer {
*/
fun play(playerEpisode: PlayerEpisode)

/**
* Plays the specified list of episodes
*/
fun play(playerEpisodes: List<PlayerEpisode>)

/**
* Pauses the currently played episode
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ class MockEpisodePlayer(
}
}

override fun removeAllFromQueue() {
queue.value = emptyList()
}

override fun play() {
// Do nothing if already playing
if (isPlaying.value) {
Expand Down Expand Up @@ -107,24 +111,31 @@ class MockEpisodePlayer(
}

override fun play(playerEpisode: PlayerEpisode) {
play(listOf(playerEpisode))
}

override fun play(playerEpisodes: List<PlayerEpisode>) {
if (isPlaying.value) {
pause()
}

// Keep the currently playing episode in the queue
val playingEpisode = _currentEpisode.value
queue.update {
val previousList = if (it.contains(playerEpisode)) {
val mutableList = it.toMutableList()
mutableList.remove(playerEpisode)
mutableList
} else {
it
var previousList: List<PlayerEpisode> = emptyList()
queue.update { queue ->
playerEpisodes.map { episode ->
if (queue.contains(episode)) {
val mutableList = queue.toMutableList()
mutableList.remove(episode)
previousList = mutableList
} else {
previousList = queue
}
}
if (playingEpisode != null) {
listOf(playerEpisode, playingEpisode) + previousList
playerEpisodes + listOf(playingEpisode) + previousList
} else {
listOf(playerEpisode) + previousList
playerEpisodes + previousList
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class MockEpisodePlayerTest {
uri = "currentEpisode",
duration = duration
)

mockEpisodePlayer.currentEpisode = currEpisode
testEpisodes.forEach { mockEpisodePlayer.addToQueue(it) }

Expand All @@ -94,6 +95,35 @@ class MockEpisodePlayerTest {

assertTrue(mockEpisodePlayer.playerState.value.isPlaying)
}
@Test
fun whenPlayListOfEpisodes_playerAutoPlaysNextEpisode() = runTest(testDispatcher) {
val duration = Duration.ofSeconds(60)
val currEpisode = PlayerEpisode(
uri = "currentEpisode",
duration = duration
)
val firstEpisodeFromList = PlayerEpisode(
uri = "firstEpisodeFromList",
duration = duration
)
val secondEpisodeFromList = PlayerEpisode(
uri = "secondEpisodeFromList",
duration = duration
)
val episodeListToBeAddedToTheQueue: List<PlayerEpisode> = listOf(
firstEpisodeFromList, secondEpisodeFromList
)
mockEpisodePlayer.currentEpisode = currEpisode

mockEpisodePlayer.play(episodeListToBeAddedToTheQueue)
assertEquals(firstEpisodeFromList, mockEpisodePlayer.currentEpisode)

advanceTimeBy(duration.toMillis() + 1)
assertEquals(secondEpisodeFromList, mockEpisodePlayer.currentEpisode)

advanceTimeBy(duration.toMillis() + 1)
assertEquals(currEpisode, mockEpisodePlayer.currentEpisode)
}

@Test
fun whenNext_queueIsEmpty_doesNothing() {
Expand Down
31 changes: 20 additions & 11 deletions Jetcaster/wear/src/main/java/com/example/jetcaster/WearApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.example.jetcaster.theme.WearAppTheme
import com.example.jetcaster.ui.Episode
import com.example.jetcaster.ui.JetcasterNavController.navigateToEpisode
import com.example.jetcaster.ui.JetcasterNavController.navigateToLatestEpisode
import com.example.jetcaster.ui.JetcasterNavController.navigateToPodcastDetails
import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext
Expand All @@ -33,6 +35,7 @@ import com.example.jetcaster.ui.LatestEpisodes
import com.example.jetcaster.ui.PodcastDetails
import com.example.jetcaster.ui.UpNext
import com.example.jetcaster.ui.YourPodcasts
import com.example.jetcaster.ui.episode.EpisodeScreen
import com.example.jetcaster.ui.home.HomeScreen
import com.example.jetcaster.ui.library.LatestEpisodesScreen
import com.example.jetcaster.ui.library.PodcastsScreen
Expand Down Expand Up @@ -90,39 +93,45 @@ fun WearApp() {
) {
LatestEpisodesScreen(
playlistName = stringResource(id = R.string.latest_episodes),
// TODO implement change speed
onChangeSpeedButtonClick = {},
onPlayButtonClick = {
navController.navigateToPlayer()
}
},
onDismiss = { navController.popBackStack() }
)
}
composable(route = YourPodcasts.navRoute) {
PodcastsScreen(
onPodcastsItemClick = { navController.navigateToPodcastDetails(it.uri) },
onErrorDialogCancelClick = { navController.popBackStack() }
onDismiss = { navController.popBackStack() }
)
}
composable(route = PodcastDetails.navRoute) {
PodcastDetailsScreen(
// TODO implement change speed
onChangeSpeedButtonClick = {},
onPlayButtonClick = {
navController.navigateToPlayer()
},
onEpisodeItemClick = { navController.navigateToPlayer() },
onErrorDialogCancelClick = { navController.popBackStack() }
onEpisodeItemClick = { navController.navigateToEpisode(it.uri) },
onDismiss = { navController.popBackStack() }
)
}
composable(route = UpNext.navRoute) {
QueueScreen(
// TODO implement change speed
onChangeSpeedButtonClick = {},
onPlayButtonClick = {
navController.navigateToPlayer()
},
onEpisodeItemClick = { navController.navigateToPlayer() },
onErrorDialogCancelClick = {
onDismiss = {
navController.popBackStack()
navController.navigateToYourPodcast()
}
)
}
composable(route = Episode.navRoute) {
EpisodeScreen(
onPlayButtonClick = {
navController.navigateToPlayer()
},
onDismiss = {
navController.popBackStack()
navController.navigateToYourPodcast()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public object JetcasterNavController {
public fun NavController.navigateToUpNext() {
navigate(UpNext.destination())
}

public fun NavController.navigateToEpisode(episodeUri: String) {
navigate(Episode.destination(episodeUri))
}
}

public object YourPodcasts : NavigationScreens("yourPodcasts") {
Expand All @@ -54,15 +58,30 @@ public object LatestEpisodes : NavigationScreens("latestEpisodes") {
}

public object PodcastDetails : NavigationScreens("podcast?podcastUri={podcastUri}") {
public const val podcastUri: String = "podcastUri"
public fun destination(podcastUriValue: String): String {
val encodedUri = Uri.encode(podcastUriValue)
return "podcast?$podcastUri=$encodedUri"
public const val PODCAST_URI: String = "podcastUri"
public fun destination(podcastUri: String): String {
val encodedUri = Uri.encode(podcastUri)
return "podcast?$PODCAST_URI=$encodedUri"
}

override val arguments: List<NamedNavArgument>
get() = listOf(
navArgument(PODCAST_URI) {
type = NavType.StringType
},
)
}

public object Episode : NavigationScreens("episode?episodeUri={episodeUri}") {
public const val EPISODE_URI: String = "episodeUri"
public fun destination(episodeUri: String): String {
val encodedUri = Uri.encode(episodeUri)
return "episode?$EPISODE_URI=$encodedUri"
}

override val arguments: List<NamedNavArgument>
get() = listOf(
navArgument(podcastUri) {
navArgument(EPISODE_URI) {
type = NavType.StringType
},
)
Expand Down

This file was deleted.

Loading

0 comments on commit 2530904

Please sign in to comment.