Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into bugfix/fix-debug-keystores
Browse files Browse the repository at this point in the history
  • Loading branch information
riggaroo committed Apr 26, 2024
2 parents 82feefd + 30137d2 commit b31aaff
Show file tree
Hide file tree
Showing 39 changed files with 537 additions and 422 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/Crane.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

test:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: macos-13
timeout-minutes: 30
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/JetLagged.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

test:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/JetNews.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

androidTest:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/Jetchat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

test:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/Owl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

test:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/Reply.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

test:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
Expand Down
41 changes: 39 additions & 2 deletions Jetcaster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ project from Android Studio following the steps
## Screenshots

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

## Features
### Features

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

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

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

## Architecture
### Architecture
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.

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

## Wear

This sample showcases a 2-screen pager which allows navigation between the Player and the Library.
From the Library, users can access latest episodes from subscribed podcasts, and queue.
From the podcast, users can access episode details and add episodes to the queue.
From the Player screen, users can access a volume screen and a playback speed screen.

The sample implements [Wear UX best practices for media apps][mediappsbestpractices], such as:
- Support rotating side button (RSB) and Bezel for scrollable screens
- 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
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
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
using the provided buttons.
For simplicity, this sample uses a mock Player which is reused across form factors,
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]]
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.

### Architecture
The architecture of the Wear app is similar to the phone app architecture: each UI 'screen' has its
own [ViewModel][viewmodel] which exposes a `StateFlow<ScreenState>` for the UI to observe.

## Data

### Podcast data
Expand Down Expand Up @@ -114,3 +145,9 @@ limitations under the License.
[jdk8desugar]: https://developer.android.com/studio/write/java8-support#library-desugaring
[coil]: https://coil-kt.github.io/coil/
[wsc]: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes
[mediatoolkit]: https://google.github.io/horologist/media-toolkit/
[mediatoolkitsample]: https://google.github.io/horologist/media-sample/
[wearmediaguidance]: https://developer.android.com/media/implement/surfaces/wear-os#play-downloaded-content
[horologist]: https://google.github.io/horologist/
[entityscreen]: https://github.com/google/horologist/blob/main/media/ui/src/main/java/com/google/android/horologist/media/ui/screens/entity/EntityScreen.kt
[mediappsbestpractices]: https://developer.android.com/design/ui/wear/guides/foundations/media-apps
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ class PodcastCategoryFilterUseCaseTest {
result.episodes
)
}

@Test
fun whenCategoryInfoNotNull_verifyLimitFlow() = runTest {
val resultFlow = useCase(testCategory.asExternalModel())

categoriesStore.setEpisodesFromPodcast(
testCategory.id,
List(8) { testEpisodeToPodcast }.flatten()
)
categoriesStore.setPodcastsInCategory(
testCategory.id,
List(4) { testPodcasts }.flatten()
)

val result = resultFlow.first()
assertEquals(20, result.episodes.size)
assertEquals(10, result.topPodcasts.size)
}
}

val testPodcasts = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ class TestCategoryStore : CategoryStore {
categoryId: Long,
limit: Int
): Flow<List<PodcastWithExtraInfo>> = podcastsInCategoryFlow.map {
it[categoryId] ?: emptyList()
it[categoryId]?.take(limit) ?: emptyList()
}

override fun episodesFromPodcastsInCategory(
categoryId: Long,
limit: Int
): Flow<List<EpisodeToPodcast>> = episodesFromPodcasts.map {
it[categoryId] ?: emptyList()
it[categoryId]?.take(limit) ?: emptyList()
}

override suspend fun addCategory(category: Category): Long = -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
Expand All @@ -43,6 +43,7 @@ fun PodcastImage(
contentDescription: String?,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop,
placeholderBrush: Brush = thumbnailPlaceholderDefaultBrush(),
) {
var imagePainterState by remember {
mutableStateOf<AsyncImagePainter.State>(AsyncImagePainter.State.Empty)
Expand Down Expand Up @@ -73,8 +74,9 @@ fun PodcastImage(
else -> {
Box(
modifier = Modifier
.background(placeholderBrush)
.fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceContainerHigh)

)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,30 @@
* limitations under the License.
*/

package com.example.jetcaster.tv.ui.component
package com.example.jetcaster.designsystem.component

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.painter.BrushPainter
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.MaterialTheme
import com.example.jetcaster.designsystem.theme.surfaceVariantDark
import com.example.jetcaster.designsystem.theme.surfaceVariantLight

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
internal fun thumbnailPlaceholder(
brush: Brush = SolidColor(MaterialTheme.colorScheme.surfaceVariant)
): BrushPainter {
return BrushPainter(brush)
internal fun thumbnailPlaceholderDefaultBrush(
color: Color = thumbnailPlaceHolderDefaultColor()
): Brush {
return SolidColor(color)
}

@Composable
private fun thumbnailPlaceHolderDefaultColor(
isInDarkMode: Boolean = isSystemInDarkTheme()
): Color {
return if (isInDarkMode) {
surfaceVariantDark
} else {
surfaceVariantLight
}
}
8 changes: 4 additions & 4 deletions Jetcaster/tv-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

Expand Down Expand Up @@ -79,15 +81,13 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.coil.kt.compose)

// Dependency injection
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.hilt.android)
implementation(project(":core:model"))
ksp(libs.hilt.compiler)


implementation(project(":core"))
implementation(project(":designsystem"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package com.example.jetcaster.tv.model

import androidx.compose.runtime.Immutable
import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo
import com.example.jetcaster.core.model.PodcastInfo

@Immutable
data class PodcastList(
val member: List<PodcastWithExtraInfo>
) : List<PodcastWithExtraInfo> by member
val member: List<PodcastInfo>
) : List<PodcastInfo> by member
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
LibraryScreen(
navigateToDiscover = jetcasterAppState::navigateToDiscover,
showPodcastDetails = {
jetcasterAppState.showPodcastDetails(it.podcast.uri)
jetcasterAppState.showPodcastDetails(it.uri)
},
playEpisode = {
jetcasterAppState.playEpisode()
Expand All @@ -156,7 +156,7 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
composable(Screen.Search.route) {
SearchScreen(
onPodcastSelected = {
jetcasterAppState.showPodcastDetails(it.podcast.uri)
jetcasterAppState.showPodcastDetails(it.uri)
},
modifier = Modifier
.padding(JetcasterAppDefaults.overScanMargin.default.intoPaddingValues())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,54 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.example.jetcaster.core.data.database.model.Podcast
import com.example.jetcaster.core.model.PlayerEpisode
import com.example.jetcaster.core.model.PodcastInfo
import com.example.jetcaster.designsystem.component.ImageBackgroundRadialGradientScrim

@Composable
internal fun Background(
podcast: Podcast,
modifier: Modifier = Modifier,
) = Background(imageUrl = podcast.imageUrl, modifier)

@Composable
internal fun Background(
episode: PlayerEpisode,
internal fun BackgroundContainer(
playerEpisode: PlayerEpisode,
modifier: Modifier = Modifier,
) = Background(imageUrl = episode.podcastImageUrl, modifier)
contentAlignment: Alignment = Alignment.Center,
content: @Composable BoxScope.() -> Unit
) =
BackgroundContainer(
imageUrl = playerEpisode.podcastImageUrl,
modifier,
contentAlignment,
content
)

@Composable
internal fun Background(
imageUrl: String?,
internal fun BackgroundContainer(
podcastInfo: PodcastInfo,
modifier: Modifier = Modifier,
) {
ImageBackgroundRadialGradientScrim(
url = imageUrl,
colors = listOf(Color.Black, Color.Transparent),
modifier = modifier,
)
}
contentAlignment: Alignment = Alignment.Center,
content: @Composable BoxScope.() -> Unit
) =
BackgroundContainer(imageUrl = podcastInfo.imageUrl, modifier, contentAlignment, content)

@Composable
internal fun BackgroundContainer(
playerEpisode: PlayerEpisode,
imageUrl: String,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.Center,
content: @Composable BoxScope.() -> Unit
) {
Box(modifier = modifier, contentAlignment = contentAlignment) {
Background(episode = playerEpisode, modifier = Modifier.fillMaxSize())
Background(imageUrl = imageUrl, modifier = Modifier.fillMaxSize())
content()
}
}

@Composable
private fun Background(
imageUrl: String,
modifier: Modifier = Modifier,
) {
ImageBackgroundRadialGradientScrim(
url = imageUrl,
colors = listOf(Color.Black, Color.Transparent),
modifier = modifier,
)
}
Loading

0 comments on commit b31aaff

Please sign in to comment.