Skip to content

Commit

Permalink
Adds previews to queue and podcasts
Browse files Browse the repository at this point in the history
  • Loading branch information
kul3r4 committed Apr 30, 2024
1 parent bf5ce41 commit 926b99a
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 145 deletions.
4 changes: 2 additions & 2 deletions Jetcaster/README.md
Expand Up @@ -71,7 +71,7 @@ The sample implements [Wear UX best practices for media apps][mediappsbestpracti
- Display scrollbar on scrolling
- Display the time on top of the screens

The sample is built using the [Media Toolkit][[mediatoolkit]] which is an open source
The sample is built using the [Media Toolkit][mediatoolkit] which is an open source
project part of [Horologist][horologist] to ease the development of media apps on Wear OS built on top of Compose for Wear.
It provides ready to use UI screens, such the [EntityScreen][entityscreen]
that is used in this sample to implement many screens such as Podcast, LatestEpisodes and Queue. [Horologist][horologist] also provides
Expand All @@ -81,7 +81,7 @@ For simplicity, this sample uses a mock Player which is reused across form facto
if you want to see an advanced Media sample built on Compose that uses Exoplayer and plays media content,
refer to the [Media Toolkit sample][mediatoolkitsample].

The [official media app guidance for Wear OS][ [wearmediaguidance]]
The [official media app guidance for Wear OS][wearmediaguidance]
advices to download content on the watch before listening to preserve power, this feature will be added to this sample in future iterations. You can
refer to the [Media Toolkit sample][mediatoolkitsample] to learn how to implement the media download feature.

Expand Down
51 changes: 51 additions & 0 deletions Jetcaster/wear/src/main/java/com/example/jetcaster/PreviewData.kt
@@ -0,0 +1,51 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster

import com.example.jetcaster.core.model.PlayerEpisode
import com.example.jetcaster.core.model.PodcastInfo
import java.time.OffsetDateTime
import java.time.ZoneOffset

val PreviewPodcasts = listOf(
PodcastInfo(
uri = "fakeUri://podcast/1",
title = "Android Developers Backstage",
author = "Android Developers",
isSubscribed = true,
lastEpisodeDate = OffsetDateTime.now()
),
PodcastInfo(
uri = "fakeUri://podcast/2",
title = "Google Developers podcast",
author = "Google Developers",
lastEpisodeDate = OffsetDateTime.now()
)
)

val PreviewEpisodes = listOf(
PlayerEpisode(
uri = "fakeUri://episode/1",
title = "Episode 140: Lorem ipsum dolor",
summary = "In this episode, Romain, Chet and Tor talked with Mady Melor and Artur " +
"Tsurkan from the System UI team about... Bubbles!",
published = OffsetDateTime.of(
2020, 6, 2, 9,
27, 0, 0, ZoneOffset.of("-0800")
)
)
)
Expand Up @@ -16,43 +16,36 @@

package com.example.jetcaster.podcasts

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MusicNote
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.material.ButtonDefaults
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.dialog.Alert
import androidx.wear.compose.material.dialog.Dialog
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import com.example.jetcaster.R
import com.example.jetcaster.core.model.PodcastInfo
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.composables.PlaceholderChip
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
import com.google.android.horologist.compose.material.Button
import com.google.android.horologist.compose.material.AlertDialog
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.material.ListHeaderDefaults
import com.google.android.horologist.compose.material.ResponsiveListHeader
import com.google.android.horologist.images.base.util.rememberVectorPainter
import com.google.android.horologist.images.coil.CoilPaintable
import com.google.android.horologist.media.ui.screens.entity.DefaultEntityScreenHeader
import com.google.android.horologist.media.ui.screens.entity.EntityScreen

@Composable
Expand Down Expand Up @@ -80,52 +73,17 @@ fun PodcastsScreen(

PodcastsScreen(
podcastsScreenState = modifiedState,
onPodcastsItemClick = onPodcastsItemClick
onPodcastsItemClick = onPodcastsItemClick,
onDismiss = onDismiss
)

Dialog(
showDialog = modifiedState == PodcastsScreenState.Empty,
onDismissRequest = onDismiss,
scrollState = rememberScalingLazyListState(),
) {
Alert(
title = {
Text(
text = stringResource(R.string.podcasts_no_podcasts),
color = MaterialTheme.colors.onBackground,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.title3,
)
},
) {
item {
Row(
verticalAlignment = Alignment.CenterVertically,
) {

Button(
imageVector = Icons.Default.Close,
contentDescription = stringResource(
id = R.string
.podcasts_failed_dialog_cancel_button_content_description,
),
onClick = onDismiss,
modifier = Modifier
.size(24.dp)
.wrapContentSize(align = Alignment.Center),
colors = ButtonDefaults.secondaryButtonColors()
)
}
}
}
}
}

@ExperimentalHorologistApi
@Composable
fun PodcastsScreen(
podcastsScreenState: PodcastsScreenState,
onPodcastsItemClick: (PodcastInfo) -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {

Expand All @@ -135,40 +93,114 @@ fun PodcastsScreen(
modifier = modifier
) {
when (podcastsScreenState) {
is PodcastsScreenState.Loaded -> {
EntityScreen(
columnState = columnState,
headerContent = {
ResponsiveListHeader(
contentPadding = ListHeaderDefaults.firstItemPadding()
) {
Text(text = stringResource(id = R.string.podcasts))
}
},
content = {
items(count = podcastsScreenState.podcastList.size) {
index ->
MediaContent(
podcast = podcastsScreenState.podcastList[index],
downloadItemArtworkPlaceholder = rememberVectorPainter(
image = Icons.Default.MusicNote,
tintColor = Color.Blue,
),
onPodcastsItemClick = onPodcastsItemClick

)
}
}
is PodcastsScreenState.Loaded -> PodcastScreenLoaded(
columnState = columnState,
podcastList = podcastsScreenState.podcastList,
onPodcastsItemClick = onPodcastsItemClick
)
PodcastsScreenState.Empty ->
PodcastScreenEmpty(onDismiss)
PodcastsScreenState.Loading ->
PodcastScreenLoading(columnState)
}
}
}

@Composable
fun PodcastScreenLoaded(
columnState: ScalingLazyColumnState,
podcastList: List<PodcastInfo>,
onPodcastsItemClick: (PodcastInfo) -> Unit,
) {
EntityScreen(
columnState = columnState,
headerContent = {
ResponsiveListHeader(
contentPadding = ListHeaderDefaults.firstItemPadding()
) {
Text(text = stringResource(id = R.string.podcasts))
}
},
content = {
items(count = podcastList.size) {
index ->
MediaContent(
podcast = podcastList[index],
downloadItemArtworkPlaceholder = rememberVectorPainter(
image = Icons.Default.MusicNote,
tintColor = Color.Blue,
),
onPodcastsItemClick = onPodcastsItemClick

)
}
PodcastsScreenState.Empty,
PodcastsScreenState.Loading -> {
Column {
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
}
}
)
}

@Composable
fun PodcastScreenEmpty(onDismiss: () -> Unit) {
AlertDialog(
showDialog = true,
message = stringResource(R.string.podcasts_no_podcasts),
onDismiss = onDismiss,
)
}

@Composable
fun PodcastScreenLoading(columnState: ScalingLazyColumnState) {
EntityScreen(
columnState = columnState,
headerContent = {
DefaultEntityScreenHeader(
title = stringResource(R.string.podcasts)
)
},
content = {
items(count = 2) {
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
}
}
}
)
}

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun PodcastScreenLoadedPreview(
@PreviewParameter(WearPreviewPodcasts::class) podcasts: PodcastInfo
) {
val columnState = rememberResponsiveColumnState(
contentPadding = ScalingLazyColumnDefaults.padding(
first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Chip
)
)
PodcastScreenLoaded(
columnState = columnState,
podcastList = listOf(podcasts),
onPodcastsItemClick = {}
)
}

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun PodcastScreenLoadingPreview() {
val columnState = rememberResponsiveColumnState(
contentPadding = ScalingLazyColumnDefaults.padding(
first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Chip
)
)
PodcastScreenLoading(columnState)
}

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun PodcastScreenEmptyPreview() {
PodcastScreenEmpty(onDismiss = {})
}

@Composable
Expand Down
@@ -0,0 +1,25 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster.podcasts
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.example.jetcaster.PreviewPodcasts
import com.example.jetcaster.core.model.PodcastInfo

public class WearPreviewPodcasts : PreviewParameterProvider<PodcastInfo> {
public override val values: Sequence<PodcastInfo>
get() = PreviewPodcasts.asSequence()
}

0 comments on commit 926b99a

Please sign in to comment.