Skip to content

Commit b018671

Browse files
committed
Adds queue screen
1 parent 34ff374 commit b018671

File tree

9 files changed

+443
-61
lines changed

9 files changed

+443
-61
lines changed

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

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

1717
package com.example.jetcaster
1818

19-
/*
20-
* Copyright 2022 The Android Open Source Project
21-
*
22-
* Licensed under the Apache License, Version 2.0 (the "License");
23-
* you may not use this file except in compliance with the License.
24-
* You may obtain a copy of the License at
25-
*
26-
* https://www.apache.org/licenses/LICENSE-2.0
27-
*
28-
* Unless required by applicable law or agreed to in writing, software
29-
* distributed under the License is distributed on an "AS IS" BASIS,
30-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31-
* See the License for the specific language governing permissions and
32-
* limitations under the License.
33-
*/
34-
3519
import androidx.compose.foundation.layout.fillMaxSize
3620
import androidx.compose.runtime.Composable
3721
import androidx.compose.ui.Modifier
@@ -47,10 +31,12 @@ import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext
4731
import com.example.jetcaster.ui.JetcasterNavController.navigateToYourPodcast
4832
import com.example.jetcaster.ui.LatestEpisodes
4933
import com.example.jetcaster.ui.PodcastDetails
34+
import com.example.jetcaster.ui.UpNext
5035
import com.example.jetcaster.ui.YourPodcasts
5136
import com.example.jetcaster.ui.home.HomeScreen
5237
import com.example.jetcaster.ui.library.LatestEpisodesScreen
5338
import com.example.jetcaster.ui.library.PodcastsScreen
39+
import com.example.jetcaster.ui.library.QueueScreen
5440
import com.example.jetcaster.ui.player.PlayerScreen
5541
import com.example.jetcaster.ui.podcast.PodcastDetailsScreen
5642
import com.google.android.horologist.audio.ui.VolumeViewModel
@@ -128,6 +114,20 @@ fun WearApp() {
128114
onErrorDialogCancelClick = { navController.popBackStack() }
129115
)
130116
}
117+
composable(route = UpNext.navRoute) {
118+
QueueScreen(
119+
// TODO implement change speed
120+
onChangeSpeedButtonClick = {},
121+
onPlayButtonClick = {
122+
navController.navigateToPlayer()
123+
},
124+
onEpisodeItemClick = { navController.navigateToPlayer() },
125+
onErrorDialogCancelClick = {
126+
navController.popBackStack()
127+
navController.navigateToYourPodcast()
128+
}
129+
)
130+
}
131131
},
132132

133133
)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.foundation.layout.Arrangement
20+
import androidx.compose.foundation.layout.Row
21+
import androidx.compose.foundation.layout.height
22+
import androidx.compose.foundation.layout.padding
23+
import androidx.compose.material.icons.Icons
24+
import androidx.compose.material.icons.filled.PlayArrow
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.graphics.vector.ImageVector
29+
import androidx.compose.ui.res.stringResource
30+
import androidx.compose.ui.res.vectorResource
31+
import androidx.compose.ui.unit.dp
32+
import androidx.wear.compose.material.ChipDefaults
33+
import com.example.jetcaster.R
34+
import com.example.jetcaster.ui.library.ButtonsContent
35+
import com.google.android.horologist.annotations.ExperimentalHorologistApi
36+
import com.google.android.horologist.composables.PlaceholderChip
37+
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
38+
import com.google.android.horologist.compose.material.Button
39+
import com.google.android.horologist.media.ui.screens.entity.DefaultEntityScreenHeader
40+
import com.google.android.horologist.media.ui.screens.entity.EntityScreen
41+
42+
@Composable
43+
fun LoadingEntityScreen(columnState: ScalingLazyColumnState) {
44+
EntityScreen(
45+
columnState = columnState,
46+
headerContent = {
47+
DefaultEntityScreenHeader(
48+
title = stringResource(id = R.string.loading)
49+
)
50+
},
51+
buttonsContent = {
52+
ButtonsContent(
53+
onChangeSpeedButtonClick = {},
54+
onPlayButtonClick = {},
55+
)
56+
},
57+
content = {
58+
items(count = 2) {
59+
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
60+
}
61+
}
62+
)
63+
}
64+
65+
@OptIn(ExperimentalHorologistApi::class)
66+
@Composable
67+
fun ButtonsContent(
68+
onChangeSpeedButtonClick: () -> Unit,
69+
onPlayButtonClick: () -> Unit,
70+
enabled: Boolean = false
71+
) {
72+
73+
Row(
74+
modifier = Modifier
75+
.padding(bottom = 16.dp)
76+
.height(52.dp),
77+
verticalAlignment = Alignment.CenterVertically,
78+
horizontalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterHorizontally),
79+
) {
80+
Button(
81+
imageVector = ImageVector.vectorResource(R.drawable.speed),
82+
contentDescription = stringResource(id = R.string.speed_button_content_description),
83+
onClick = { onChangeSpeedButtonClick() },
84+
enabled = enabled,
85+
modifier = Modifier
86+
.weight(weight = 0.3F, fill = false),
87+
)
88+
89+
Button(
90+
imageVector = Icons.Filled.PlayArrow,
91+
contentDescription = stringResource(id = R.string.button_play_content_description),
92+
onClick = { onPlayButtonClick },
93+
enabled = enabled,
94+
modifier = Modifier
95+
.weight(weight = 0.3F, fill = false),
96+
)
97+
}
98+
}

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,13 @@ 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
35+
import androidx.wear.compose.foundation.lazy.items
3236
import androidx.wear.compose.material.ChipDefaults
37+
import androidx.wear.compose.material.MaterialTheme
3338
import androidx.wear.compose.material.Text
3439
import com.example.jetcaster.R
3540
import com.example.jetcaster.core.model.PodcastInfo
@@ -114,12 +119,16 @@ fun HomeScreen(
114119
}
115120
}
116121
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-
)
122+
if (viewState.queue.isEmpty()) {
123+
QueueEmpty()
124+
} else {
125+
Chip(
126+
label = stringResource(R.string.up_next),
127+
onClick = onUpNextClick,
128+
icon = DrawableResPaintable(R.drawable.up_next),
129+
colors = ChipDefaults.secondaryChipColors()
130+
)
131+
}
123132
}
124133
}
125134
}
@@ -130,8 +139,7 @@ fun HomeScreen(
130139

131140
content = {
132141
if (viewState.podcastCategoryFilterResult.topPodcasts.isNotEmpty()) {
133-
val podcast = viewState.podcastCategoryFilterResult.topPodcasts.first()
134-
items(viewState.podcastCategoryFilterResult.topPodcasts.take(1).size) {
142+
items(viewState.podcastCategoryFilterResult.topPodcasts.take(3)) { podcast ->
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)