Skip to content

Commit 03a4772

Browse files
authored
[Jetcaster] Add mock support for playing an episode (#1293)
Introduces `EpisodePlayer` interface and `MockEpisodePlayer` conforming class to mock playing an episode. This enables play/pause and the slider to update as you would expect in a real podcast app. [Screen_recording_20240322_160937.webm](https://github.com/android/compose-samples/assets/463186/09a4e7bd-7d99-4381-8ec2-9b2af3bfefac) #1292 should be reviewed and merged first.
2 parents e36547c + 3d221a2 commit 03a4772

File tree

17 files changed

+710
-127
lines changed

17 files changed

+710
-127
lines changed

Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ fun JetcasterApp(
5757
)
5858
)
5959
PlayerScreen(
60-
playerViewModel,
6160
windowSizeClass,
6261
displayFeatures,
62+
playerViewModel,
6363
onBackPress = appState::navigateBack
6464
)
6565
}

Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@ import androidx.compose.foundation.layout.heightIn
3737
import androidx.compose.foundation.layout.only
3838
import androidx.compose.foundation.layout.padding
3939
import androidx.compose.foundation.layout.size
40-
import androidx.compose.foundation.layout.statusBars
4140
import androidx.compose.foundation.layout.systemBars
4241
import androidx.compose.foundation.layout.width
4342
import androidx.compose.foundation.layout.windowInsetsPadding
44-
import androidx.compose.foundation.layout.windowInsetsTopHeight
4543
import androidx.compose.foundation.lazy.LazyColumn
4644
import androidx.compose.foundation.pager.HorizontalPager
4745
import androidx.compose.foundation.pager.PageSize
@@ -55,6 +53,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
5553
import androidx.compose.material3.Icon
5654
import androidx.compose.material3.IconButton
5755
import androidx.compose.material3.MaterialTheme
56+
import androidx.compose.material3.Scaffold
57+
import androidx.compose.material3.SnackbarHost
58+
import androidx.compose.material3.SnackbarHostState
5859
import androidx.compose.material3.Surface
5960
import androidx.compose.material3.Tab
6061
import androidx.compose.material3.TabPosition
@@ -65,6 +66,7 @@ import androidx.compose.material3.TopAppBar
6566
import androidx.compose.runtime.Composable
6667
import androidx.compose.runtime.LaunchedEffect
6768
import androidx.compose.runtime.getValue
69+
import androidx.compose.runtime.remember
6870
import androidx.compose.runtime.rememberCoroutineScope
6971
import androidx.compose.runtime.snapshotFlow
7072
import androidx.compose.ui.Alignment
@@ -120,6 +122,7 @@ fun Home(
120122
navigateToPlayer = navigateToPlayer,
121123
onTogglePodcastFollowed = viewModel::onTogglePodcastFollowed,
122124
onLibraryPodcastSelected = viewModel::onLibraryPodcastSelected,
125+
onQueuePodcast = viewModel::onQueuePodcast,
123126
modifier = Modifier.fillMaxSize()
124127
)
125128
}
@@ -184,7 +187,8 @@ fun Home(
184187
onCategorySelected: (Category) -> Unit,
185188
navigateToPlayer: (String) -> Unit,
186189
onTogglePodcastFollowed: (String) -> Unit,
187-
onLibraryPodcastSelected: (Podcast?) -> Unit
190+
onLibraryPodcastSelected: (Podcast?) -> Unit,
191+
onQueuePodcast: (EpisodeToPodcast) -> Unit,
188192
) {
189193
// Effect that changes the home category selection when there are no subscribed podcasts
190194
LaunchedEffect(key1 = featuredPodcasts) {
@@ -193,39 +197,25 @@ fun Home(
193197
}
194198
}
195199

196-
Column(
200+
val coroutineScope = rememberCoroutineScope()
201+
val snackbarHostState = remember { SnackbarHostState() }
202+
Scaffold(
197203
modifier = modifier.windowInsetsPadding(
198204
WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
199-
)
200-
) {
201-
// We dynamically theme this sub-section of the layout to match the selected
202-
// 'top podcast'
203-
204-
val surfaceColor = MaterialTheme.colorScheme.surface
205-
val appBarColor = surfaceColor.copy(alpha = 0.87f)
206-
207-
val scrimColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.38f)
208-
209-
// Top Bar
210-
Column(
211-
modifier = Modifier
212-
.fillMaxWidth()
213-
.background(color = scrimColor)
214-
) {
215-
// Draw a scrim over the status bar which matches the app bar
216-
Spacer(
217-
Modifier
218-
.background(appBarColor)
219-
.fillMaxWidth()
220-
.windowInsetsTopHeight(WindowInsets.statusBars)
221-
)
205+
),
206+
topBar = {
222207
HomeAppBar(
223-
backgroundColor = appBarColor,
208+
backgroundColor = MaterialTheme.colorScheme.surface,
224209
modifier = Modifier.fillMaxWidth()
225210
)
211+
},
212+
snackbarHost = {
213+
SnackbarHost(hostState = snackbarHostState)
226214
}
227-
215+
) { contentPadding ->
228216
// Main Content
217+
val scrimColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.38f)
218+
val snackBarText = stringResource(id = R.string.episode_added_to_your_queue)
229219
HomeContent(
230220
featuredPodcasts = featuredPodcasts,
231221
isRefreshing = isRefreshing,
@@ -235,12 +225,19 @@ fun Home(
235225
podcastCategoryFilterResult = podcastCategoryFilterResult,
236226
libraryEpisodes = libraryEpisodes,
237227
scrimColor = scrimColor,
228+
modifier = Modifier.padding(contentPadding),
238229
onPodcastUnfollowed = onPodcastUnfollowed,
239230
onHomeCategorySelected = onHomeCategorySelected,
240231
onCategorySelected = onCategorySelected,
241232
navigateToPlayer = navigateToPlayer,
242233
onTogglePodcastFollowed = onTogglePodcastFollowed,
243-
onLibraryPodcastSelected = onLibraryPodcastSelected
234+
onLibraryPodcastSelected = onLibraryPodcastSelected,
235+
onQueuePodcast = {
236+
coroutineScope.launch {
237+
snackbarHostState.showSnackbar(snackBarText)
238+
}
239+
onQueuePodcast(it)
240+
}
244241
)
245242
}
246243
}
@@ -262,7 +259,8 @@ private fun HomeContent(
262259
onCategorySelected: (Category) -> Unit,
263260
navigateToPlayer: (String) -> Unit,
264261
onTogglePodcastFollowed: (String) -> Unit,
265-
onLibraryPodcastSelected: (Podcast?) -> Unit
262+
onLibraryPodcastSelected: (Podcast?) -> Unit,
263+
onQueuePodcast: (EpisodeToPodcast) -> Unit,
266264
) {
267265
val pagerState = rememberPagerState { featuredPodcasts.size }
268266
LaunchedEffect(pagerState, featuredPodcasts) {
@@ -308,7 +306,8 @@ private fun HomeContent(
308306
HomeCategory.Library -> {
309307
libraryItems(
310308
episodes = libraryEpisodes,
311-
navigateToPlayer = navigateToPlayer
309+
navigateToPlayer = navigateToPlayer,
310+
onQueuePodcast = onQueuePodcast
312311
)
313312
}
314313

@@ -318,7 +317,8 @@ private fun HomeContent(
318317
podcastCategoryFilterResult = podcastCategoryFilterResult,
319318
navigateToPlayer = navigateToPlayer,
320319
onCategorySelected = onCategorySelected,
321-
onTogglePodcastFollowed = onTogglePodcastFollowed
320+
onTogglePodcastFollowed = onTogglePodcastFollowed,
321+
onQueuePodcast = onQueuePodcast
322322
)
323323
}
324324
}
@@ -529,7 +529,8 @@ fun PreviewHomeContent() {
529529
navigateToPlayer = {},
530530
onHomeCategorySelected = {},
531531
onTogglePodcastFollowed = {},
532-
onLibraryPodcastSelected = {}
532+
onLibraryPodcastSelected = {},
533+
onQueuePodcast = {}
533534
)
534535
}
535536
}

Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase
2828
import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase
2929
import com.example.jetcaster.core.data.model.FilterableCategoriesModel
3030
import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult
31+
import com.example.jetcaster.core.data.model.toPlayerEpisode
3132
import com.example.jetcaster.core.data.repository.EpisodeStore
3233
import com.example.jetcaster.core.data.repository.PodcastStore
3334
import com.example.jetcaster.core.data.repository.PodcastsRepository
35+
import com.example.jetcaster.core.player.EpisodePlayer
3436
import com.example.jetcaster.util.combine
3537
import kotlinx.collections.immutable.PersistentList
3638
import kotlinx.collections.immutable.persistentListOf
@@ -52,7 +54,8 @@ class HomeViewModel(
5254
private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase =
5355
Graph.podcastCategoryFilterUseCase,
5456
private val filterableCategoriesUseCase: FilterableCategoriesUseCase =
55-
Graph.filterableCategoriesUseCase
57+
Graph.filterableCategoriesUseCase,
58+
private val episodePlayer: EpisodePlayer = Graph.episodePlayer
5659
) : ViewModel() {
5760
// Holds our currently selected podcast in the library
5861
private val selectedLibraryPodcast = MutableStateFlow<Podcast?>(null)
@@ -162,6 +165,10 @@ class HomeViewModel(
162165
fun onLibraryPodcastSelected(podcast: Podcast?) {
163166
selectedLibraryPodcast.value = podcast
164167
}
168+
169+
fun onQueuePodcast(episodeToPodcast: EpisodeToPodcast) {
170+
episodePlayer.addToQueue(episodeToPodcast.toPlayerEpisode())
171+
}
165172
}
166173

167174
enum class HomeCategory {

Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ fun LazyListScope.podcastCategory(
8080
topPodcasts: List<PodcastWithExtraInfo>,
8181
episodes: List<EpisodeToPodcast>,
8282
navigateToPlayer: (String) -> Unit,
83+
onQueuePodcast: (EpisodeToPodcast) -> Unit,
8384
onTogglePodcastFollowed: (String) -> Unit,
8485
) {
8586
item {
@@ -91,6 +92,7 @@ fun LazyListScope.podcastCategory(
9192
episode = item.episode,
9293
podcast = item.podcast,
9394
onClick = navigateToPlayer,
95+
onQueuePodcast = onQueuePodcast,
9496
modifier = Modifier.fillParentMaxWidth()
9597
)
9698
}
@@ -113,6 +115,7 @@ fun EpisodeListItem(
113115
episode: Episode,
114116
podcast: Podcast,
115117
onClick: (String) -> Unit,
118+
onQueuePodcast: (EpisodeToPodcast) -> Unit,
116119
modifier: Modifier = Modifier,
117120
showDivider: Boolean = true,
118121
) {
@@ -241,7 +244,14 @@ fun EpisodeListItem(
241244
)
242245

243246
IconButton(
244-
onClick = { /* TODO */ },
247+
onClick = {
248+
onQueuePodcast(
249+
EpisodeToPodcast().apply {
250+
this.episode = episode
251+
this._podcasts = listOf(podcast)
252+
}
253+
)
254+
},
245255
modifier = Modifier.constrainAs(addPlaylist) {
246256
end.linkTo(overflow.start)
247257
centerVerticallyTo(playIcon)
@@ -358,6 +368,7 @@ fun PreviewEpisodeListItem() {
358368
episode = PreviewEpisodes[0],
359369
podcast = PreviewPodcasts[0],
360370
onClick = { },
371+
onQueuePodcast = { },
361372
modifier = Modifier.fillMaxWidth()
362373
)
363374
}

Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import androidx.compose.ui.res.stringResource
3838
import androidx.compose.ui.unit.dp
3939
import com.example.jetcaster.R
4040
import com.example.jetcaster.core.data.database.model.Category
41+
import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
4142
import com.example.jetcaster.core.data.model.FilterableCategoriesModel
4243
import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult
4344
import com.example.jetcaster.designsystem.theme.Keyline1
@@ -49,6 +50,7 @@ fun LazyListScope.discoverItems(
4950
navigateToPlayer: (String) -> Unit,
5051
onCategorySelected: (Category) -> Unit,
5152
onTogglePodcastFollowed: (String) -> Unit,
53+
onQueuePodcast: (EpisodeToPodcast) -> Unit,
5254
) {
5355
if (filterableCategoriesModel.isEmpty) {
5456
// TODO: empty state
@@ -71,7 +73,8 @@ fun LazyListScope.discoverItems(
7173
topPodcasts = podcastCategoryFilterResult.topPodcasts,
7274
episodes = podcastCategoryFilterResult.episodes,
7375
navigateToPlayer = navigateToPlayer,
74-
onTogglePodcastFollowed = onTogglePodcastFollowed
76+
onTogglePodcastFollowed = onTogglePodcastFollowed,
77+
onQueuePodcast = onQueuePodcast,
7578
)
7679
}
7780

Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/library/Library.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import com.example.jetcaster.ui.home.category.EpisodeListItem
3131

3232
fun LazyListScope.libraryItems(
3333
episodes: List<EpisodeToPodcast>,
34-
navigateToPlayer: (String) -> Unit
34+
navigateToPlayer: (String) -> Unit,
35+
onQueuePodcast: (EpisodeToPodcast) -> Unit
3536
) {
3637
if (episodes.isEmpty()) {
3738
// TODO: Empty state
@@ -57,6 +58,7 @@ fun LazyListScope.libraryItems(
5758
episode = item.episode,
5859
podcast = item.podcast,
5960
onClick = navigateToPlayer,
61+
onQueuePodcast = onQueuePodcast,
6062
modifier = Modifier.fillParentMaxWidth(),
6163
showDivider = index != 0
6264
)

0 commit comments

Comments
 (0)