Skip to content

Commit 54d97b9

Browse files
committed
Adds queue screen
1 parent 34ff374 commit 54d97b9

File tree

9 files changed

+413
-45
lines changed

9 files changed

+413
-45
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext
4747
import com.example.jetcaster.ui.JetcasterNavController.navigateToYourPodcast
4848
import com.example.jetcaster.ui.LatestEpisodes
4949
import com.example.jetcaster.ui.PodcastDetails
50+
import com.example.jetcaster.ui.UpNext
5051
import com.example.jetcaster.ui.YourPodcasts
5152
import com.example.jetcaster.ui.home.HomeScreen
5253
import com.example.jetcaster.ui.library.LatestEpisodesScreen
5354
import com.example.jetcaster.ui.library.PodcastsScreen
55+
import com.example.jetcaster.ui.library.QueueScreen
5456
import com.example.jetcaster.ui.player.PlayerScreen
5557
import com.example.jetcaster.ui.podcast.PodcastDetailsScreen
5658
import com.google.android.horologist.audio.ui.VolumeViewModel
@@ -128,6 +130,20 @@ fun WearApp() {
128130
onErrorDialogCancelClick = { navController.popBackStack() }
129131
)
130132
}
133+
composable(route = UpNext.navRoute) {
134+
QueueScreen(
135+
// TODO implement change speed
136+
onChangeSpeedButtonClick = {},
137+
onPlayButtonClick = {
138+
navController.navigateToPlayer()
139+
},
140+
onEpisodeItemClick = { navController.navigateToPlayer() },
141+
onErrorDialogCancelClick = {
142+
navController.popBackStack()
143+
navController.navigateToYourPodcast()
144+
}
145+
)
146+
}
131147
},
132148

133149
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.jetcaster.ui.components
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.res.stringResource
21+
import androidx.wear.compose.material.ChipDefaults
22+
import com.example.jetcaster.R
23+
import com.example.jetcaster.ui.library.ButtonsContent
24+
import com.google.android.horologist.composables.PlaceholderChip
25+
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
26+
import com.google.android.horologist.media.ui.screens.entity.DefaultEntityScreenHeader
27+
import com.google.android.horologist.media.ui.screens.entity.EntityScreen
28+
29+
@Composable
30+
fun LoadingEntityScreen(columnState: ScalingLazyColumnState) {
31+
EntityScreen(
32+
columnState = columnState,
33+
headerContent = {
34+
DefaultEntityScreenHeader(
35+
title = stringResource(id = R.string.loading)
36+
)
37+
},
38+
buttonsContent = {
39+
ButtonsContent(
40+
episodes = emptyList(),
41+
onChangeSpeedButtonClick = {},
42+
onPlayButtonClick = {},
43+
onPlayEpisode = {}
44+
)
45+
},
46+
content = {
47+
items(count = 2) {
48+
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
49+
}
50+
}
51+
)
52+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fun SettingsButtons(
6666
}
6767

6868
@Composable
69-
public fun AddToQueueButton(
69+
fun AddToQueueButton(
7070
onAddToQueueClick: () -> Unit,
7171
modifier: Modifier = Modifier,
7272
enabled: Boolean = true,

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.example.jetcaster.ui.home
1818

19+
import androidx.compose.foundation.layout.padding
1920
import androidx.compose.material.icons.Icons
2021
import androidx.compose.material.icons.filled.MusicNote
2122
import androidx.compose.runtime.Composable
@@ -27,9 +28,12 @@ import androidx.compose.ui.Modifier
2728
import androidx.compose.ui.graphics.Color
2829
import androidx.compose.ui.graphics.painter.Painter
2930
import androidx.compose.ui.res.stringResource
31+
import androidx.compose.ui.text.style.TextAlign
32+
import androidx.compose.ui.unit.dp
3033
import androidx.hilt.navigation.compose.hiltViewModel
3134
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3235
import androidx.wear.compose.material.ChipDefaults
36+
import androidx.wear.compose.material.MaterialTheme
3337
import androidx.wear.compose.material.Text
3438
import com.example.jetcaster.R
3539
import com.example.jetcaster.core.model.PodcastInfo
@@ -114,12 +118,16 @@ fun HomeScreen(
114118
}
115119
}
116120
item {
117-
Chip(
118-
label = stringResource(R.string.up_next),
119-
onClick = onUpNextClick,
120-
icon = DrawableResPaintable(R.drawable.up_next),
121-
colors = ChipDefaults.secondaryChipColors()
122-
)
121+
if (viewState.queue.isEmpty()) {
122+
QueueEmpty()
123+
} else {
124+
Chip(
125+
label = stringResource(R.string.up_next),
126+
onClick = onUpNextClick,
127+
icon = DrawableResPaintable(R.drawable.up_next),
128+
colors = ChipDefaults.secondaryChipColors()
129+
)
130+
}
123131
}
124132
}
125133
}
@@ -130,8 +138,8 @@ fun HomeScreen(
130138

131139
content = {
132140
if (viewState.podcastCategoryFilterResult.topPodcasts.isNotEmpty()) {
133-
val podcast = viewState.podcastCategoryFilterResult.topPodcasts.first()
134-
items(viewState.podcastCategoryFilterResult.topPodcasts.take(1).size) {
141+
items(viewState.podcastCategoryFilterResult.topPodcasts.take(3).size) { index ->
142+
val podcast = viewState.podcastCategoryFilterResult.topPodcasts[index]
135143
PodcastContent(
136144
podcast = podcast,
137145
downloadItemArtworkPlaceholder = rememberVectorPainter(
@@ -170,3 +178,13 @@ private fun PodcastContent(
170178
colors = ChipDefaults.secondaryChipColors(),
171179
)
172180
}
181+
182+
@Composable
183+
private fun QueueEmpty() {
184+
Text(
185+
text = stringResource(id = R.string.add_episode_to_queue),
186+
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp),
187+
textAlign = TextAlign.Center,
188+
style = MaterialTheme.typography.body2,
189+
)
190+
}

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

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import androidx.lifecycle.viewModelScope
2121
import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
2222
import com.example.jetcaster.core.data.database.model.Podcast
2323
import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo
24-
import com.example.jetcaster.core.data.database.model.toPlayerEpisode
2524
import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase
2625
import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase
2726
import com.example.jetcaster.core.data.repository.EpisodeStore
2827
import com.example.jetcaster.core.data.repository.PodcastStore
2928
import com.example.jetcaster.core.data.repository.PodcastsRepository
3029
import com.example.jetcaster.core.model.CategoryInfo
3130
import com.example.jetcaster.core.model.FilterableCategoriesModel
31+
import com.example.jetcaster.core.model.PlayerEpisode
3232
import com.example.jetcaster.core.model.PodcastCategoryFilterResult
3333
import com.example.jetcaster.core.player.EpisodePlayer
3434
import com.example.jetcaster.core.util.combine
@@ -38,7 +38,9 @@ import kotlinx.collections.immutable.toPersistentList
3838
import kotlinx.coroutines.ExperimentalCoroutinesApi
3939
import kotlinx.coroutines.flow.MutableStateFlow
4040
import kotlinx.coroutines.flow.SharingStarted
41+
import kotlinx.coroutines.flow.combine
4142
import kotlinx.coroutines.flow.flatMapLatest
43+
import kotlinx.coroutines.flow.map
4244
import kotlinx.coroutines.flow.stateIn
4345
import kotlinx.coroutines.launch
4446

@@ -55,9 +57,7 @@ class HomeViewModel @Inject constructor(
5557
// Holds our currently selected podcast in the library
5658
private val selectedLibraryPodcast = MutableStateFlow<Podcast?>(null)
5759
// Holds our currently selected home category
58-
private val selectedHomeCategory = MutableStateFlow(HomeCategory.Discover)
59-
// Holds the currently available home categories
60-
private val homeCategories = MutableStateFlow(HomeCategory.entries)
60+
private val selectedHomeCategory = MutableStateFlow(HomeCategory.Library)
6161
// Holds our currently selected category
6262
private val _selectedCategory = MutableStateFlow<CategoryInfo?>(null)
6363

@@ -67,7 +67,6 @@ class HomeViewModel @Inject constructor(
6767
// Combines the latest value from each of the flows, allowing us to generate a
6868
// view state instance which only contains the latest values.
6969
val uiState = combine(
70-
homeCategories,
7170
selectedHomeCategory,
7271
podcastStore.followedPodcastsSortedByLastEpisode(limit = 10),
7372
refreshing,
@@ -82,27 +81,31 @@ class HomeViewModel @Inject constructor(
8281
podcastUri = it?.uri ?: "",
8382
limit = 20
8483
)
84+
},
85+
episodePlayer.playerState.map {
86+
it.queue
8587
}
86-
) { homeCategories,
87-
homeCategory,
88-
podcasts,
89-
refreshing,
90-
filterableCategories,
91-
podcastCategoryFilterResult,
92-
libraryEpisodes ->
88+
) {
89+
homeCategory,
90+
podcasts,
91+
refreshing,
92+
filterableCategories,
93+
podcastCategoryFilterResult,
94+
libraryEpisodes,
95+
queue ->
9396

9497
_selectedCategory.value = filterableCategories.selectedCategory
9598

9699
selectedHomeCategory.value = homeCategory
97100

98101
HomeViewState(
99-
homeCategories = homeCategories,
100102
selectedHomeCategory = homeCategory,
101103
featuredPodcasts = podcasts.toPersistentList(),
102104
refreshing = refreshing,
103105
filterableCategoriesModel = filterableCategories,
104106
podcastCategoryFilterResult = podcastCategoryFilterResult,
105107
libraryEpisodes = libraryEpisodes,
108+
queue = queue,
106109
errorMessage = null, /* TODO */
107110
)
108111
}.stateIn(viewModelScope, SharingStarted.Lazily, initialValue = HomeViewState())
@@ -130,27 +133,19 @@ class HomeViewModel @Inject constructor(
130133
podcastStore.togglePodcastFollowed(podcastUri)
131134
}
132135
}
133-
134-
fun onLibraryPodcastSelected(podcast: Podcast?) {
135-
selectedLibraryPodcast.value = podcast
136-
}
137-
138-
fun onQueuePodcast(episodeToPodcast: EpisodeToPodcast) {
139-
episodePlayer.addToQueue(episodeToPodcast.toPlayerEpisode())
140-
}
141136
}
142137

143138
enum class HomeCategory {
144-
Library, Discover
139+
Library,
145140
}
146141

147142
data class HomeViewState(
148143
val featuredPodcasts: List<PodcastWithExtraInfo> = listOf(),
149144
val refreshing: Boolean = false,
150-
val selectedHomeCategory: HomeCategory = HomeCategory.Discover,
151-
val homeCategories: List<HomeCategory> = emptyList(),
145+
val selectedHomeCategory: HomeCategory = HomeCategory.Library,
152146
val filterableCategoriesModel: FilterableCategoriesModel = FilterableCategoriesModel(),
153147
val podcastCategoryFilterResult: PodcastCategoryFilterResult = PodcastCategoryFilterResult(),
154148
val libraryEpisodes: List<EpisodeToPodcast> = emptyList(),
149+
val queue: List<PlayerEpisode> = emptyList(),
155150
val errorMessage: String? = null
156151
)

0 commit comments

Comments
 (0)