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