diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd440d59..377b4460 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,9 @@ [versions] agp = "8.7.0" androidx-benchmark = "1.3.2" +androidx-media3 = "1.4.1" androidx-test-ext-junit = "1.2.1" +assertk = "0.28.1" coil = "3.0.0-rc01" compose-multiplatform = "1.7.0-rc01" kotlinx-coroutines-swing = "1.9.0" @@ -36,6 +38,8 @@ androidx-core = "androidx.core:core-ktx:1.13.1" androidx-collection = "androidx.collection:collection:1.4.4" androidx-activity-compose = "androidx.activity:activity-compose:1.9.2" androidx-compose-ui-test-manifest = "androidx.compose.ui:ui-test-manifest:1.7.3" +androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" } +androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" } androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1" androidx-test-ext-junit = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-ext-junit" } androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0" @@ -43,6 +47,8 @@ androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0" androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "jetpack-compose" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "jetpack-compose" } +assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" } + coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } diff --git a/haze/build.gradle.kts b/haze/build.gradle.kts index 4b17cd58..605fe3a7 100644 --- a/haze/build.gradle.kts +++ b/haze/build.gradle.kts @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 +import org.jetbrains.compose.ExperimentalComposeLibrary + plugins { id("dev.chrisbanes.android.library") id("dev.chrisbanes.kotlin.multiplatform") @@ -72,11 +74,20 @@ kotlin { dependsOn(skikoMain) } + commonTest { + dependencies { + implementation(kotlin("test")) + implementation(libs.assertk) + + @OptIn(ExperimentalComposeLibrary::class) + implementation(compose.uiTest) + } + } + val screenshotTest by creating { dependsOn(commonTest.get()) dependencies { - implementation(kotlin("test")) implementation(projects.internal.screenshotTest) } } diff --git a/haze/src/commonTest/kotlin/dev/chrisbanes/haze/HazeTest.kt b/haze/src/commonTest/kotlin/dev/chrisbanes/haze/HazeTest.kt new file mode 100644 index 00000000..2aa78785 --- /dev/null +++ b/haze/src/commonTest/kotlin/dev/chrisbanes/haze/HazeTest.kt @@ -0,0 +1,23 @@ +// Copyright 2024, Christopher Banes and the Haze project contributors +// SPDX-License-Identifier: Apache-2.0 + +package dev.chrisbanes.haze + +import assertk.assertThat +import assertk.assertions.isFalse +import kotlin.test.Test + +class HazeTest { + + @Test + fun assertNoLogging() { + var blockCalled = false + + log("Foo") { + blockCalled = true + "foo" + } + + assertThat(blockCalled).isFalse() + } +} diff --git a/sample/android/build.gradle.kts b/sample/android/build.gradle.kts index 774bb497..83428e0d 100644 --- a/sample/android/build.gradle.kts +++ b/sample/android/build.gradle.kts @@ -55,5 +55,8 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.profileinstaller) + implementation(libs.androidx.media3.exoplayer) + implementation(libs.androidx.media3.ui) + baselineProfile(projects.internal.benchmark) } diff --git a/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/ExoPlayerSample.kt b/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/ExoPlayerSample.kt new file mode 100644 index 00000000..5f7027e3 --- /dev/null +++ b/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/ExoPlayerSample.kt @@ -0,0 +1,80 @@ +// Copyright 2024, Christopher Banes and the Haze project contributors +// SPDX-License-Identifier: Apache-2.0 + +package dev.chrisbanes.haze.sample.android + +import android.view.LayoutInflater +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.media3.common.MediaItem +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.ui.PlayerView +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.haze +import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.materials.HazeMaterials +import dev.chrisbanes.haze.sample.Navigator + +@OptIn(ExperimentalHazeMaterialsApi::class) +@Composable +fun ExoPlayerSample(navigator: Navigator) { + val hazeState = remember { HazeState() } + + val context = LocalContext.current + + val exoPlayer = remember(context) { + ExoPlayer.Builder(context).build() + } + + DisposableEffect(Unit) { + exoPlayer.setMediaItem(MediaItem.fromUri(BIG_BUCK_BUNNY)) + exoPlayer.prepare() + exoPlayer.play() + + onDispose { exoPlayer.release() } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(16 / 9f), + ) { + AndroidView( + factory = { ctx -> + // For Haze to work with video players, they need to be configured to use a TextureView. + // For ExoPlayer that needs to be done via a layout file. + val view = LayoutInflater.from(ctx) + .inflate(R.layout.exoplayer, null) as PlayerView + view.apply { + player = exoPlayer + } + }, + modifier = Modifier + .fillMaxSize() + .haze(hazeState), + ) + + Spacer( + Modifier + .fillMaxSize(0.5f) + .align(Alignment.Center) + .clip(MaterialTheme.shapes.large) + .hazeChild(hazeState, HazeMaterials.ultraThin()), + ) + } +} + +private const val BIG_BUCK_BUNNY = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" diff --git a/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/MainActivity.kt b/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/MainActivity.kt index fae815b9..b1b3126c 100644 --- a/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/MainActivity.kt +++ b/sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/MainActivity.kt @@ -7,6 +7,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import dev.chrisbanes.haze.sample.Sample import dev.chrisbanes.haze.sample.Samples class MainActivity : ComponentActivity() { @@ -15,7 +16,14 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - Samples(appTitle = title.toString()) + Samples( + appTitle = title.toString(), + samples = Samples + AndroidSamples, + ) } } } + +private val AndroidSamples = listOf( + Sample("ExoPlayer") { ExoPlayerSample(it) }, +) diff --git a/sample/android/src/main/res/layout/exoplayer.xml b/sample/android/src/main/res/layout/exoplayer.xml new file mode 100644 index 00000000..c05eb695 --- /dev/null +++ b/sample/android/src/main/res/layout/exoplayer.xml @@ -0,0 +1,6 @@ + + diff --git a/sample/shared/build.gradle.kts b/sample/shared/build.gradle.kts index a1ebb6db..e34c0dd7 100644 --- a/sample/shared/build.gradle.kts +++ b/sample/shared/build.gradle.kts @@ -16,8 +16,8 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(projects.haze) - implementation(projects.hazeMaterials) + api(projects.haze) + api(projects.hazeMaterials) implementation(libs.coil.compose) implementation(libs.coil.ktor) diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt index fa4d058e..93e5257c 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt @@ -44,9 +44,7 @@ import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeTint import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.hazeChild -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -@OptIn(ExperimentalHazeMaterialsApi::class) @Composable fun CreditCardSample(navigator: Navigator) { val hazeState = remember { HazeState() } diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt index 8ea92b73..a25ec284 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt @@ -62,7 +62,10 @@ fun SamplesTheme( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Samples(appTitle: String) { +fun Samples( + appTitle: String, + samples: List = Samples, +) { SamplesTheme { var currentSample by remember { mutableStateOf(null) } @@ -100,7 +103,7 @@ fun Samples(appTitle: String) { modifier = Modifier.fillMaxSize(), contentPadding = contentPadding, ) { - items(Samples) { sample -> + items(samples) { sample -> ListItem( headlineContent = { Text(text = sample.title) }, modifier = Modifier