Skip to content

Commit b31aaff

Browse files
committed
Merge branch 'refs/heads/main' into bugfix/fix-debug-keystores
2 parents 82feefd + 30137d2 commit b31aaff

39 files changed

+537
-422
lines changed

.github/workflows/Crane.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
test:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: macos-13
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

.github/workflows/JetLagged.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
test:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: ubuntu-latest
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

.github/workflows/JetNews.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
androidTest:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: ubuntu-latest
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

.github/workflows/Jetchat.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
test:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: ubuntu-latest
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

.github/workflows/Owl.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
test:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: ubuntu-latest
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

.github/workflows/Reply.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
test:
2727
needs: build
28-
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
28+
runs-on: ubuntu-latest
2929
timeout-minutes: 30
3030
strategy:
3131
matrix:

Jetcaster/README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ project from Android Studio following the steps
1414
## Screenshots
1515

1616
![readme_cover](https://github.com/android/compose-samples/assets/10263978/a58ab950-71aa-48e0-8bc7-85443a1b4f6b)
17+
## Phone app
1718

18-
## Features
19+
### Features
1920

2021
This sample has 3 components: the home screen, the podcast details screen, and the player screen
2122

@@ -38,7 +39,7 @@ Some other notable things which are implemented:
3839

3940
* Images are all provided from each podcast's RSS feed, and loaded using [Coil][coil] library.
4041

41-
## Architecture
42+
### Architecture
4243
The app is built in a Redux-style, where each UI 'screen' has its own [ViewModel][viewmodel], which exposes a single [StateFlow][stateflow] containing the entire view state. Each [ViewModel][viewmodel] is responsible for subscribing to any data streams required for the view, as well as exposing functions which allow the UI to send events.
4344

4445
Using the example of the home screen in the [`com.example.jetcaster.ui.home`](app/src/main/java/com/example/jetcaster/ui/home) package:
@@ -58,6 +59,36 @@ This pattern is used across the different screens:
5859
- __Discover:__ [`com.example.jetcaster.ui.home.discover`](app/src/main/java/com/example/jetcaster/ui/home/discover)
5960
- __Podcast Category:__ [`com.example.jetcaster.ui.category`](app/src/main/java/com/example/jetcaster/ui/home/category)
6061

62+
## Wear
63+
64+
This sample showcases a 2-screen pager which allows navigation between the Player and the Library.
65+
From the Library, users can access latest episodes from subscribed podcasts, and queue.
66+
From the podcast, users can access episode details and add episodes to the queue.
67+
From the Player screen, users can access a volume screen and a playback speed screen.
68+
69+
The sample implements [Wear UX best practices for media apps][mediappsbestpractices], such as:
70+
- Support rotating side button (RSB) and Bezel for scrollable screens
71+
- Display scrollbar on scrolling
72+
- Display the time on top of the screens
73+
74+
The sample is built using the [Media Toolkit][[mediatoolkit]] which is an open source
75+
project part of [Horologist][horologist] to ease the development of media apps on Wear OS built on top of Compose for Wear.
76+
It provides ready to use UI screens, such the [EntityScreen][entityscreen]
77+
that is used in this sample to implement many screens such as Podcast, LatestEpisodes and Queue. [Horologist][horologist] also provides
78+
a VolumeScreen that can be reused by media apps to conveniently control volume either by interacting with the rotating side button(RSB)/Bezel or by
79+
using the provided buttons.
80+
For simplicity, this sample uses a mock Player which is reused across form factors,
81+
if you want to see an advanced Media sample built on Compose that uses Exoplayer and plays media content,
82+
refer to the [Media Toolkit sample][mediatoolkitsample].
83+
84+
The [official media app guidance for Wear OS][ [wearmediaguidance]]
85+
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
86+
refer to the [Media Toolkit sample][mediatoolkitsample] to learn how to implement the media download feature.
87+
88+
### Architecture
89+
The architecture of the Wear app is similar to the phone app architecture: each UI 'screen' has its
90+
own [ViewModel][viewmodel] which exposes a `StateFlow<ScreenState>` for the UI to observe.
91+
6192
## Data
6293

6394
### Podcast data
@@ -114,3 +145,9 @@ limitations under the License.
114145
[jdk8desugar]: https://developer.android.com/studio/write/java8-support#library-desugaring
115146
[coil]: https://coil-kt.github.io/coil/
116147
[wsc]: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes
148+
[mediatoolkit]: https://google.github.io/horologist/media-toolkit/
149+
[mediatoolkitsample]: https://google.github.io/horologist/media-sample/
150+
[wearmediaguidance]: https://developer.android.com/media/implement/surfaces/wear-os#play-downloaded-content
151+
[horologist]: https://google.github.io/horologist/
152+
[entityscreen]: https://github.com/google/horologist/blob/main/media/ui/src/main/java/com/google/android/horologist/media/ui/screens/entity/EntityScreen.kt
153+
[mediappsbestpractices]: https://developer.android.com/design/ui/wear/guides/foundations/media-apps

Jetcaster/core/src/test/kotlin/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCaseTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ class PodcastCategoryFilterUseCaseTest {
113113
result.episodes
114114
)
115115
}
116+
117+
@Test
118+
fun whenCategoryInfoNotNull_verifyLimitFlow() = runTest {
119+
val resultFlow = useCase(testCategory.asExternalModel())
120+
121+
categoriesStore.setEpisodesFromPodcast(
122+
testCategory.id,
123+
List(8) { testEpisodeToPodcast }.flatten()
124+
)
125+
categoriesStore.setPodcastsInCategory(
126+
testCategory.id,
127+
List(4) { testPodcasts }.flatten()
128+
)
129+
130+
val result = resultFlow.first()
131+
assertEquals(20, result.episodes.size)
132+
assertEquals(10, result.topPodcasts.size)
133+
}
116134
}
117135

118136
val testPodcasts = listOf(

Jetcaster/core/src/test/kotlin/com/example/jetcaster/core/data/repository/TestCategoryStore.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ class TestCategoryStore : CategoryStore {
4343
categoryId: Long,
4444
limit: Int
4545
): Flow<List<PodcastWithExtraInfo>> = podcastsInCategoryFlow.map {
46-
it[categoryId] ?: emptyList()
46+
it[categoryId]?.take(limit) ?: emptyList()
4747
}
4848

4949
override fun episodesFromPodcastsInCategory(
5050
categoryId: Long,
5151
limit: Int
5252
): Flow<List<EpisodeToPodcast>> = episodesFromPodcasts.map {
53-
it[categoryId] ?: emptyList()
53+
it[categoryId]?.take(limit) ?: emptyList()
5454
}
5555

5656
override suspend fun addCategory(category: Category): Long = -1

Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import androidx.compose.foundation.layout.Box
2222
import androidx.compose.foundation.layout.fillMaxSize
2323
import androidx.compose.foundation.layout.size
2424
import androidx.compose.material3.CircularProgressIndicator
25-
import androidx.compose.material3.MaterialTheme
2625
import androidx.compose.runtime.Composable
2726
import androidx.compose.runtime.getValue
2827
import androidx.compose.runtime.mutableStateOf
2928
import androidx.compose.runtime.remember
3029
import androidx.compose.runtime.setValue
3130
import androidx.compose.ui.Alignment
3231
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.graphics.Brush
3333
import androidx.compose.ui.layout.ContentScale
3434
import androidx.compose.ui.platform.LocalContext
3535
import androidx.compose.ui.unit.dp
@@ -43,6 +43,7 @@ fun PodcastImage(
4343
contentDescription: String?,
4444
modifier: Modifier = Modifier,
4545
contentScale: ContentScale = ContentScale.Crop,
46+
placeholderBrush: Brush = thumbnailPlaceholderDefaultBrush(),
4647
) {
4748
var imagePainterState by remember {
4849
mutableStateOf<AsyncImagePainter.State>(AsyncImagePainter.State.Empty)
@@ -73,8 +74,9 @@ fun PodcastImage(
7374
else -> {
7475
Box(
7576
modifier = Modifier
77+
.background(placeholderBrush)
7678
.fillMaxSize()
77-
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
79+
7880
)
7981
}
8082
}

0 commit comments

Comments
 (0)