Skip to content

Commit f670ffc

Browse files
committed
Adds PodcastDetailsScreen
1 parent b09d709 commit f670ffc

File tree

8 files changed

+440
-88
lines changed

8 files changed

+440
-88
lines changed

Jetcaster/wear/src/main/java/com/example/jetcaster/WearApp.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
4242
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
4343
import com.example.jetcaster.theme.WearAppTheme
4444
import com.example.jetcaster.ui.JetcasterNavController.navigateToLatestEpisode
45+
import com.example.jetcaster.ui.JetcasterNavController.navigateToPodcastDetails
4546
import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext
4647
import com.example.jetcaster.ui.JetcasterNavController.navigateToYourPodcast
4748
import com.example.jetcaster.ui.LatestEpisodes
49+
import com.example.jetcaster.ui.PodcastDetails
4850
import com.example.jetcaster.ui.YourPodcasts
4951
import com.example.jetcaster.ui.home.HomeScreen
5052
import com.example.jetcaster.ui.library.LatestEpisodesScreen
5153
import com.example.jetcaster.ui.library.PodcastsScreen
5254
import com.example.jetcaster.ui.player.PlayerScreen
55+
import com.example.jetcaster.ui.podcast.PodcastDetailsScreen
5356
import com.google.android.horologist.audio.ui.VolumeViewModel
5457
import com.google.android.horologist.media.ui.navigation.MediaNavController.navigateToPlayer
5558
import com.google.android.horologist.media.ui.navigation.MediaNavController.navigateToVolume
@@ -101,17 +104,27 @@ fun WearApp() {
101104
) {
102105
LatestEpisodesScreen(
103106
playlistName = stringResource(id = R.string.latest_episodes),
104-
onShuffleButtonClick = {
105-
// navController.navigateToPlayer(it[0].episode.uri)
106-
},
107+
// TODO implement change speed
108+
onChangeSpeedButtonClick = {},
107109
onPlayButtonClick = {
108110
navController.navigateToPlayer()
109111
}
110112
)
111113
}
112114
composable(route = YourPodcasts.navRoute) {
113115
PodcastsScreen(
114-
onPodcastsItemClick = { navController.navigateToPlayer() },
116+
onPodcastsItemClick = { navController.navigateToPodcastDetails(it.uri) },
117+
onErrorDialogCancelClick = { navController.popBackStack() }
118+
)
119+
}
120+
composable(route = PodcastDetails.navRoute) {
121+
PodcastDetailsScreen(
122+
// TODO implement change speed
123+
onChangeSpeedButtonClick = {},
124+
onPlayButtonClick = {
125+
navController.navigateToPlayer()
126+
},
127+
onEpisodeItemClick = { navController.navigateToPlayer() },
115128
onErrorDialogCancelClick = { navController.popBackStack() }
116129
)
117130
}

Jetcaster/wear/src/main/java/com/example/jetcaster/ui/JetcasterNavController.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package com.example.jetcaster.ui
1818

19+
import android.net.Uri
20+
import androidx.navigation.NamedNavArgument
1921
import androidx.navigation.NavController
22+
import androidx.navigation.NavType
23+
import androidx.navigation.navArgument
2024
import com.google.android.horologist.media.ui.navigation.NavigationScreens
2125

2226
/**
@@ -32,6 +36,10 @@ public object JetcasterNavController {
3236
navigate(LatestEpisodes.destination())
3337
}
3438

39+
public fun NavController.navigateToPodcastDetails(podcastUri: String) {
40+
navigate(PodcastDetails.destination(podcastUri))
41+
}
42+
3543
public fun NavController.navigateToUpNext() {
3644
navigate(UpNext.destination())
3745
}
@@ -45,6 +53,21 @@ public object LatestEpisodes : NavigationScreens("latestEpisodes") {
4553
public fun destination(): String = navRoute
4654
}
4755

56+
public object PodcastDetails : NavigationScreens("podcast?podcastUri={podcastUri}") {
57+
public const val podcastUri: String = "podcastUri"
58+
public fun destination(podcastUri: String): String {
59+
val encodedUri = Uri.encode(podcastUri)
60+
return "podcast?podcastUri=$encodedUri"
61+
}
62+
63+
override val arguments: List<NamedNavArgument>
64+
get() = listOf(
65+
navArgument(podcastUri) {
66+
type = NavType.StringType
67+
},
68+
)
69+
}
70+
4871
public object UpNext : NavigationScreens("upNext") {
4972
public fun destination(): String = navRoute
5073
}

Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodesScreen.kt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen
5252

5353
@Composable fun LatestEpisodesScreen(
5454
playlistName: String,
55-
onShuffleButtonClick: (List<EpisodeToPodcast>) -> Unit,
56-
onPlayButtonClick: (List<EpisodeToPodcast>) -> Unit,
55+
onChangeSpeedButtonClick: () -> Unit,
56+
onPlayButtonClick: () -> Unit,
5757
modifier: Modifier = Modifier,
5858
latestEpisodeViewModel: LatestEpisodeViewModel = hiltViewModel()
5959
) {
@@ -62,7 +62,7 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen
6262
modifier = modifier,
6363
playlistName = playlistName,
6464
viewState = viewState,
65-
onShuffleButtonClick = onShuffleButtonClick,
65+
onChangeSpeedButtonClick = onChangeSpeedButtonClick,
6666
onPlayButtonClick = onPlayButtonClick,
6767
onPlayEpisode = latestEpisodeViewModel::onPlayEpisode
6868
)
@@ -72,8 +72,8 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen
7272
fun LatestEpisodeScreen(
7373
playlistName: String,
7474
viewState: LatestEpisodeViewState,
75-
onShuffleButtonClick: (List<EpisodeToPodcast>) -> Unit,
76-
onPlayButtonClick: (List<EpisodeToPodcast>) -> Unit,
75+
onChangeSpeedButtonClick: () -> Unit,
76+
onPlayButtonClick: () -> Unit,
7777
modifier: Modifier = Modifier,
7878
onPlayEpisode: (PlayerEpisode) -> Unit,
7979
) {
@@ -93,14 +93,16 @@ fun LatestEpisodeScreen(
9393
downloadItemArtworkPlaceholder = rememberVectorPainter(
9494
image = Icons.Default.MusicNote,
9595
tintColor = Color.Blue,
96-
)
96+
),
97+
onPlayButtonClick = onPlayButtonClick,
98+
onPlayEpisode = onPlayEpisode
9799
)
98100
}
99101
},
100102
buttonsContent = {
101103
ButtonsContent(
102104
viewState = viewState,
103-
onShuffleButtonClick = onShuffleButtonClick,
105+
onChangeSpeedButtonClick = onChangeSpeedButtonClick,
104106
onPlayButtonClick = onPlayButtonClick,
105107
onPlayEpisode = onPlayEpisode
106108
)
@@ -112,15 +114,20 @@ fun LatestEpisodeScreen(
112114
@Composable
113115
fun MediaContent(
114116
episode: EpisodeToPodcast,
115-
downloadItemArtworkPlaceholder: Painter?
117+
downloadItemArtworkPlaceholder: Painter?,
118+
onPlayButtonClick: () -> Unit,
119+
onPlayEpisode: (PlayerEpisode) -> Unit
116120
) {
117121
val mediaTitle = episode.episode.title
118122

119123
val secondaryLabel = episode.episode.author
120124

121125
Chip(
122126
label = mediaTitle,
123-
onClick = { /*play*/ },
127+
onClick = {
128+
onPlayButtonClick()
129+
onPlayEpisode(episode.toPlayerEpisode())
130+
},
124131
secondaryLabel = secondaryLabel,
125132
icon = CoilPaintable(episode.podcast.imageUrl, downloadItemArtworkPlaceholder),
126133
largeIcon = true,
@@ -132,8 +139,8 @@ fun MediaContent(
132139
@Composable
133140
fun ButtonsContent(
134141
viewState: LatestEpisodeViewState,
135-
onShuffleButtonClick: (List<EpisodeToPodcast>) -> Unit,
136-
onPlayButtonClick: (List<EpisodeToPodcast>) -> Unit,
142+
onChangeSpeedButtonClick: () -> Unit,
143+
onPlayButtonClick: () -> Unit,
137144
onPlayEpisode: (PlayerEpisode) -> Unit
138145
) {
139146

@@ -147,7 +154,7 @@ fun ButtonsContent(
147154
Button(
148155
imageVector = ImageVector.vectorResource(R.drawable.speed),
149156
contentDescription = stringResource(id = R.string.speed_button_content_description),
150-
onClick = { onShuffleButtonClick(viewState.libraryEpisodes) },
157+
onClick = { onChangeSpeedButtonClick() },
151158
modifier = Modifier
152159
.weight(weight = 0.3F, fill = false),
153160
)
@@ -156,7 +163,7 @@ fun ButtonsContent(
156163
imageVector = Icons.Filled.PlayArrow,
157164
contentDescription = stringResource(id = R.string.button_play_content_description),
158165
onClick = {
159-
onPlayButtonClick(viewState.libraryEpisodes)
166+
onPlayButtonClick()
160167
onPlayEpisode(viewState.libraryEpisodes[0].toPlayerEpisode())
161168
},
162169
modifier = Modifier

Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/PodcastsScreen.kt

Lines changed: 65 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ package com.example.jetcaster.ui.library
1818

1919
import androidx.compose.foundation.layout.Column
2020
import androidx.compose.foundation.layout.Row
21-
import androidx.compose.foundation.layout.padding
2221
import androidx.compose.foundation.layout.size
2322
import androidx.compose.foundation.layout.wrapContentSize
2423
import androidx.compose.material.icons.Icons
2524
import androidx.compose.material.icons.filled.Close
25+
import androidx.compose.material.icons.filled.MusicNote
2626
import androidx.compose.runtime.Composable
2727
import androidx.compose.runtime.getValue
2828
import androidx.compose.ui.Alignment
2929
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.graphics.Color
3031
import androidx.compose.ui.graphics.painter.Painter
3132
import androidx.compose.ui.res.stringResource
3233
import androidx.compose.ui.text.style.TextAlign
@@ -44,14 +45,14 @@ import com.example.jetcaster.R
4445
import com.example.jetcaster.core.model.PodcastInfo
4546
import com.google.android.horologist.annotations.ExperimentalHorologistApi
4647
import com.google.android.horologist.composables.PlaceholderChip
47-
import com.google.android.horologist.composables.Section
48-
import com.google.android.horologist.composables.SectionedList
4948
import com.google.android.horologist.compose.layout.ScreenScaffold
5049
import com.google.android.horologist.compose.layout.rememberColumnState
5150
import com.google.android.horologist.compose.material.Button
5251
import com.google.android.horologist.compose.material.Chip
53-
import com.google.android.horologist.compose.material.Title
52+
import com.google.android.horologist.images.base.util.rememberVectorPainter
5453
import com.google.android.horologist.images.coil.CoilPaintable
54+
import com.google.android.horologist.media.ui.screens.entity.DefaultEntityScreenHeader
55+
import com.google.android.horologist.media.ui.screens.entity.EntityScreen
5556

5657
@Composable
5758
fun PodcastsScreen(
@@ -122,87 +123,81 @@ fun PodcastsScreen(
122123
@ExperimentalHorologistApi
123124
@Composable
124125
fun PodcastsScreen(
125-
podcastsScreenState: PodcastsScreenState<PodcastInfo>,
126+
podcastsScreenState: PodcastsScreenState,
126127
onPodcastsItemClick: (PodcastInfo) -> Unit,
127128
modifier: Modifier = Modifier,
128-
podcastItemArtworkPlaceholder: Painter? = null,
129129
) {
130130

131-
val podcastContent: @Composable (podcast: PodcastInfo) -> Unit = { podcast ->
132-
Chip(
133-
label = podcast.title,
134-
onClick = { onPodcastsItemClick(podcast) },
135-
icon = CoilPaintable(podcast.imageUrl, podcastItemArtworkPlaceholder),
136-
largeIcon = true,
137-
colors = ChipDefaults.secondaryChipColors(),
138-
)
139-
}
140-
141-
PodcastsScreen(
142-
podcastsScreenState = podcastsScreenState,
143-
modifier = modifier,
144-
content = { podcast ->
145-
Chip(
146-
label = podcast.title,
147-
onClick = { onPodcastsItemClick(podcast) },
148-
icon = CoilPaintable(podcast.imageUrl, podcastItemArtworkPlaceholder),
149-
largeIcon = true,
150-
colors = ChipDefaults.secondaryChipColors(),
151-
)
131+
val columnState = rememberColumnState()
132+
ScreenScaffold(
133+
scrollState = columnState,
134+
modifier = modifier
135+
) {
136+
when (podcastsScreenState) {
137+
is PodcastsScreenState.Loaded -> {
138+
EntityScreen(
139+
columnState = columnState,
140+
headerContent = {
141+
DefaultEntityScreenHeader(
142+
title = stringResource(
143+
R.string.podcasts
144+
)
145+
)
146+
},
147+
content = {
148+
items(count = podcastsScreenState.podcastList.size) {
149+
index ->
150+
MediaContent(
151+
podcast = podcastsScreenState.podcastList[index],
152+
downloadItemArtworkPlaceholder = rememberVectorPainter(
153+
image = Icons.Default.MusicNote,
154+
tintColor = Color.Blue,
155+
),
156+
onPodcastsItemClick = onPodcastsItemClick
157+
158+
)
159+
}
160+
}
161+
)
162+
}
163+
PodcastsScreenState.Empty,
164+
PodcastsScreenState.Loading -> {
165+
Column {
166+
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
167+
}
168+
}
152169
}
153-
)
170+
}
154171
}
155172

156-
@ExperimentalHorologistApi
157173
@Composable
158-
fun <T> PodcastsScreen(
159-
podcastsScreenState: PodcastsScreenState<T>,
160-
modifier: Modifier = Modifier,
161-
content: @Composable (podcast: T) -> Unit,
174+
fun MediaContent(
175+
podcast: PodcastInfo,
176+
downloadItemArtworkPlaceholder: Painter?,
177+
onPodcastsItemClick: (PodcastInfo) -> Unit
162178
) {
163-
val columnState = rememberColumnState()
164-
ScreenScaffold(scrollState = columnState) {
165-
SectionedList(
166-
modifier = modifier,
167-
columnState = columnState,
168-
) {
169-
val sectionState = when (podcastsScreenState) {
170-
is PodcastsScreenState.Loaded<T> -> {
171-
Section.State.Loaded(podcastsScreenState.podcastList)
172-
}
173-
174-
PodcastsScreenState.Empty -> Section.State.Failed
175-
PodcastsScreenState.Loading -> Section.State.Loading
176-
}
177-
178-
section(state = sectionState) {
179-
header {
180-
Title(
181-
R.string.podcasts,
182-
Modifier.padding(bottom = 12.dp),
183-
)
184-
}
179+
val mediaTitle = podcast.title
185180

186-
loaded { content(it) }
181+
val secondaryLabel = podcast.author
187182

188-
loading(count = 4) {
189-
Column {
190-
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
191-
}
192-
}
193-
}
194-
}
195-
}
183+
Chip(
184+
label = mediaTitle,
185+
onClick = { onPodcastsItemClick(podcast) },
186+
secondaryLabel = secondaryLabel,
187+
icon = CoilPaintable(podcast.imageUrl, downloadItemArtworkPlaceholder),
188+
largeIcon = true,
189+
colors = ChipDefaults.secondaryChipColors(),
190+
)
196191
}
197192

198193
@ExperimentalHorologistApi
199-
public sealed class PodcastsScreenState<out T> {
194+
sealed class PodcastsScreenState {
200195

201-
public object Loading : PodcastsScreenState<Nothing>()
196+
data object Loading : PodcastsScreenState()
202197

203-
public data class Loaded<T>(
204-
val podcastList: List<T>,
205-
) : PodcastsScreenState<T>()
198+
data class Loaded(
199+
val podcastList: List<PodcastInfo>,
200+
) : PodcastsScreenState()
206201

207-
public object Empty : PodcastsScreenState<Nothing>()
202+
data object Empty : PodcastsScreenState()
208203
}

Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/PodcastsViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class PodcastsViewModel @Inject constructor(
3535
podcastStore: PodcastStore,
3636
) : ViewModel() {
3737

38-
val uiState: StateFlow<PodcastsScreenState<PodcastInfo>> =
38+
val uiState: StateFlow<PodcastsScreenState> =
3939
podcastStore.followedPodcastsSortedByLastEpisode(limit = 10).map {
4040
if (it.isNotEmpty()) {
4141
PodcastsScreenState.Loaded(it.map(PodcastMapper::map))

0 commit comments

Comments
 (0)