From 74e1caafb77c5ae5e9f7e4fafac4798b52e4929a Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 26 Jul 2024 15:53:56 +0200 Subject: [PATCH] Configure the "material3-window-size-class" module as CMP and include it to the publication (#1466) Configure the `material3-window-size-class` module as CMP and include it to the publication. Added new method to non-android source set to calculate `WindowSizeClass`. Fixes https://youtrack.jetbrains.com/issue/CMP-2404 ## Testing Checked it in the MPP demo app: image ## Release Notes ### Features - Multiple Platforms - Commonized `material3-window-size-class` module --- .../desktop/material3-window-size-class.api | 71 +++++ .../material3-window-size-class/build.gradle | 47 ++- .../windowsizeclass/WindowSizeClassTest.kt | 275 ------------------ .../windowsizeclass/WindowSizeClass.kt | 2 +- .../windowsizeclass/WindowSizeClassTest.kt | 267 +++++++++++++++++ .../windowsizeclass/TestOnly.nonJvm.kt | 19 ++ .../windowsizeclass/WindowSizeClass.skiko.kt | 40 +++ compose/mpp/demo/build.gradle.kts | 1 + .../compose/mpp/demo/components/Components.kt | 2 + .../material3/WindowSizeClassExample.kt | 35 +++ mpp/build.gradle.kts | 1 + 11 files changed, 472 insertions(+), 288 deletions(-) create mode 100644 compose/material3/material3-window-size-class/api/desktop/material3-window-size-class.api delete mode 100644 compose/material3/material3-window-size-class/src/androidUnitTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt create mode 100644 compose/material3/material3-window-size-class/src/commonTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt create mode 100644 compose/material3/material3-window-size-class/src/nonJvmMain/kotlin/androidx/compose/material3/windowsizeclass/TestOnly.nonJvm.kt create mode 100644 compose/material3/material3-window-size-class/src/skikoMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.skiko.kt create mode 100644 compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/WindowSizeClassExample.kt diff --git a/compose/material3/material3-window-size-class/api/desktop/material3-window-size-class.api b/compose/material3/material3-window-size-class/api/desktop/material3-window-size-class.api new file mode 100644 index 0000000000000..3f2488b089c1c --- /dev/null +++ b/compose/material3/material3-window-size-class/api/desktop/material3-window-size-class.api @@ -0,0 +1,71 @@ +public abstract interface annotation class androidx/compose/material3/windowsizeclass/ExperimentalMaterial3WindowSizeClassApi : java/lang/annotation/Annotation { +} + +public final class androidx/compose/material3/windowsizeclass/WindowHeightSizeClass : java/lang/Comparable { + public static final field Companion Landroidx/compose/material3/windowsizeclass/WindowHeightSizeClass$Companion; + public static final synthetic fun box-impl (I)Landroidx/compose/material3/windowsizeclass/WindowHeightSizeClass; + public synthetic fun compareTo (Ljava/lang/Object;)I + public fun compareTo-pav6bQQ (I)I + public static fun compareTo-pav6bQQ (II)I + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (ILjava/lang/Object;)Z + public static final fun equals-impl0 (II)Z + public fun hashCode ()I + public static fun hashCode-impl (I)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (I)Ljava/lang/String; + public final synthetic fun unbox-impl ()I +} + +public final class androidx/compose/material3/windowsizeclass/WindowHeightSizeClass$Companion { + public final fun getAllSizeClasses ()Ljava/util/Set; + public final fun getCompact-Pt018CI ()I + public final fun getDefaultSizeClasses ()Ljava/util/Set; + public final fun getExpanded-Pt018CI ()I + public final fun getMedium-Pt018CI ()I +} + +public final class androidx/compose/material3/windowsizeclass/WindowSizeClass { + public static final field $stable I + public static final field Companion Landroidx/compose/material3/windowsizeclass/WindowSizeClass$Companion; + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getHeightSizeClass-Pt018CI ()I + public final fun getWidthSizeClass-Y0FxcvE ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class androidx/compose/material3/windowsizeclass/WindowSizeClass$Companion { + public final fun calculateFromSize-qzXmJYc (JLjava/util/Set;Ljava/util/Set;)Landroidx/compose/material3/windowsizeclass/WindowSizeClass; + public static synthetic fun calculateFromSize-qzXmJYc$default (Landroidx/compose/material3/windowsizeclass/WindowSizeClass$Companion;JLjava/util/Set;Ljava/util/Set;ILjava/lang/Object;)Landroidx/compose/material3/windowsizeclass/WindowSizeClass; +} + +public final class androidx/compose/material3/windowsizeclass/WindowSizeClass_skikoKt { + public static final fun calculateWindowSizeClass (Landroidx/compose/runtime/Composer;I)Landroidx/compose/material3/windowsizeclass/WindowSizeClass; +} + +public final class androidx/compose/material3/windowsizeclass/WindowWidthSizeClass : java/lang/Comparable { + public static final field Companion Landroidx/compose/material3/windowsizeclass/WindowWidthSizeClass$Companion; + public static final synthetic fun box-impl (I)Landroidx/compose/material3/windowsizeclass/WindowWidthSizeClass; + public synthetic fun compareTo (Ljava/lang/Object;)I + public fun compareTo-GxU_lZo (I)I + public static fun compareTo-GxU_lZo (II)I + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (ILjava/lang/Object;)Z + public static final fun equals-impl0 (II)Z + public fun hashCode ()I + public static fun hashCode-impl (I)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (I)Ljava/lang/String; + public final synthetic fun unbox-impl ()I +} + +public final class androidx/compose/material3/windowsizeclass/WindowWidthSizeClass$Companion { + public final fun getAllSizeClasses ()Ljava/util/Set; + public final fun getCompact-Y0FxcvE ()I + public final fun getDefaultSizeClasses ()Ljava/util/Set; + public final fun getExpanded-Y0FxcvE ()I + public final fun getMedium-Y0FxcvE ()I +} + diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle index dbaa2b6d08a87..a2add1604abf2 100644 --- a/compose/material3/material3-window-size-class/build.gradle +++ b/compose/material3/material3-window-size-class/build.gradle @@ -14,21 +14,29 @@ * limitations under the License. */ +import androidx.build.AndroidXComposePlugin +import androidx.build.JetbrainsAndroidXPlugin import androidx.build.LibraryType -import androidx.build.PlatformIdentifier plugins { id("AndroidXPlugin") id("com.android.library") id("AndroidXComposePlugin") + id("JetbrainsAndroidXPlugin") } -androidXMultiplatform { +AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project) +JetbrainsAndroidXPlugin.applyAndConfigure(project) + +androidXComposeMultiplatform { android() desktop() + darwin() + wasm() + js() +} - defaultPlatform(PlatformIdentifier.ANDROID) - +kotlin { sourceSets { commonMain { dependencies { @@ -42,7 +50,8 @@ androidXMultiplatform { commonTest { dependencies { - + implementation(libs.kotlinTest) + implementation(libs.kotlinTestAnnotationsCommon) } } @@ -55,13 +64,6 @@ androidXMultiplatform { skikoMain { dependsOn(commonMain) - dependencies { - // Because dependencies are pinned in the android/common code. - implementation(project(":compose:ui:ui-util")) - api(project(":compose:runtime:runtime")) - api(project(":compose:ui:ui")) - api(project(":compose:ui:ui-unit")) - } } androidMain { @@ -111,6 +113,27 @@ androidXMultiplatform { } } + wasmJsMain { + dependencies { + implementation(libs.kotlinStdlib) + } + } + + skikoMain.dependsOn(commonMain) + desktopMain.dependsOn(skikoMain) + nonJvmMain.dependsOn(skikoMain) + webMain.dependsOn(nonJvmMain) + jsMain.dependsOn(webMain) + wasmJsMain.dependsOn(webMain) + nativeMain.dependsOn(nonJvmMain) + + skikoTest.dependsOn(commonTest) + desktopTest.dependsOn(skikoTest) + nonJvmTest.dependsOn(skikoTest) + webTest.dependsOn(nonJvmTest) + jsTest.dependsOn(webTest) + wasmJsTest.dependsOn(webTest) + nativeTest.dependsOn(nonJvmTest) } } diff --git a/compose/material3/material3-window-size-class/src/androidUnitTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt b/compose/material3/material3-window-size-class/src/androidUnitTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt deleted file mode 100644 index cb4b72f7b4b9b..0000000000000 --- a/compose/material3/material3-window-size-class/src/androidUnitTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 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 - * - * http://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 androidx.compose.material3.windowsizeclass - -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.google.common.truth.Truth.assertThat -import kotlin.test.assertFailsWith -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@RunWith(JUnit4::class) -class WindowSizeClassTest { - @Test - fun calculateWidthSizeClass_forNegativeWidth_throws() { - assertFailsWith(IllegalArgumentException::class) { - WindowWidthSizeClass.fromWidth((-10).dp, WindowWidthSizeClass.DefaultSizeClasses) - } - } - - @Test - fun calculateHeightSizeClass_forNegativeHeight_throws() { - assertFailsWith(IllegalArgumentException::class) { - WindowHeightSizeClass.fromHeight((-10).dp, WindowHeightSizeClass.DefaultSizeClasses) - } - } - - @Test - fun calculateWidthSizeClass_noSupportedSizeClass_throws() { - assertFailsWith(IllegalArgumentException::class) { - WindowWidthSizeClass.fromWidth(10.dp, emptySet()) - } - } - - @Test - fun calculateHeightSizeClass_noSupportedSizeClass_throws() { - assertFailsWith(IllegalArgumentException::class) { - WindowHeightSizeClass.fromHeight(10.dp, emptySet()) - } - } - - @Test - fun calculateWidthSizeClass() { - assertWidthClass(WindowWidthSizeClass.Compact, 0.dp) - assertWidthClass(WindowWidthSizeClass.Compact, 200.dp) - - assertWidthClass(WindowWidthSizeClass.Medium, 600.dp) - assertWidthClass(WindowWidthSizeClass.Medium, 700.dp) - - assertWidthClass(WindowWidthSizeClass.Expanded, 840.dp) - assertWidthClass(WindowWidthSizeClass.Expanded, 1000.dp) - } - - @Test - fun calculateHeightSizeClass() { - assertHeightClass(WindowHeightSizeClass.Compact, 0.dp) - assertHeightClass(WindowHeightSizeClass.Compact, 200.dp) - - assertHeightClass(WindowHeightSizeClass.Medium, 480.dp) - assertHeightClass(WindowHeightSizeClass.Medium, 700.dp) - - assertHeightClass(WindowHeightSizeClass.Expanded, 900.dp) - assertHeightClass(WindowHeightSizeClass.Expanded, 1000.dp) - } - - @Test - fun calculateWidthSizeClass_useBestMatchedSupportedSizeClasses() { - assertWidthClass( - WindowWidthSizeClass.Compact, - 700.dp, - supportedSizeClasses = - setOf(WindowWidthSizeClass.Compact, WindowWidthSizeClass.Expanded) - ) - - assertWidthClass( - WindowWidthSizeClass.Medium, - 1000.dp, - supportedSizeClasses = setOf(WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium) - ) - } - - @Test - fun calculateHeightSizeClass_useBestMatchedSupportedSizeClasses() { - assertHeightClass( - WindowHeightSizeClass.Compact, - 700.dp, - supportedSizeClasses = - setOf(WindowHeightSizeClass.Compact, WindowHeightSizeClass.Expanded) - ) - - assertHeightClass( - WindowHeightSizeClass.Medium, - 1000.dp, - supportedSizeClasses = - setOf(WindowHeightSizeClass.Compact, WindowHeightSizeClass.Medium) - ) - } - - @Test - fun calculateWidthSizeClass_fallbackToTheSmallestSizeClasses() { - assertWidthClass( - WindowWidthSizeClass.Medium, - 200.dp, - supportedSizeClasses = setOf(WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded) - ) - } - - @Test - fun calculateHeightSizeClass_fallbackToTheSmallestSizeClasses() { - assertHeightClass( - WindowHeightSizeClass.Medium, - 200.dp, - supportedSizeClasses = - setOf(WindowHeightSizeClass.Medium, WindowHeightSizeClass.Expanded) - ) - } - - @Test - fun widthSizeClassToString() { - assertThat(WindowWidthSizeClass.Compact.toString()) - .isEqualTo("WindowWidthSizeClass.Compact") - assertThat(WindowWidthSizeClass.Medium.toString()).isEqualTo("WindowWidthSizeClass.Medium") - assertThat(WindowWidthSizeClass.Expanded.toString()) - .isEqualTo("WindowWidthSizeClass.Expanded") - } - - @Test - fun heightSizeClassToString() { - assertThat(WindowHeightSizeClass.Compact.toString()) - .isEqualTo("WindowHeightSizeClass.Compact") - assertThat(WindowHeightSizeClass.Medium.toString()) - .isEqualTo("WindowHeightSizeClass.Medium") - assertThat(WindowHeightSizeClass.Expanded.toString()) - .isEqualTo("WindowHeightSizeClass.Expanded") - } - - @Test - fun widthSizeClassCompareTo() { - // Less than - assertThat(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Expanded).isTrue() - assertThat(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Expanded).isTrue() - - assertThat(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Compact).isFalse() - assertThat(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Expanded).isFalse() - - assertThat(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Compact).isFalse() - assertThat(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Compact).isFalse() - - // Less than or equal to - assertThat(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Compact).isTrue() - assertThat(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Expanded).isTrue() - assertThat(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Expanded).isTrue() - assertThat(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Expanded).isTrue() - - assertThat(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Compact).isFalse() - assertThat(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Compact).isFalse() - - // Greater than - assertThat(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Compact).isTrue() - assertThat(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Compact).isTrue() - - assertThat(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Expanded).isFalse() - assertThat(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Compact).isFalse() - - assertThat(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Expanded).isFalse() - assertThat(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Expanded).isFalse() - - // Greater than or equal to - assertThat(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Expanded).isTrue() - assertThat(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Compact).isTrue() - assertThat(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Medium).isTrue() - assertThat(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Compact).isTrue() - assertThat(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Compact).isTrue() - - assertThat(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Medium).isFalse() - assertThat(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Expanded).isFalse() - assertThat(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Expanded).isFalse() - } - - @Test - fun heightSizeClassCompareTo() { - // Less than - assertThat(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Expanded).isTrue() - assertThat(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Expanded).isTrue() - - assertThat(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Compact).isFalse() - assertThat(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Expanded).isFalse() - - assertThat(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Compact).isFalse() - assertThat(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Compact).isFalse() - - // Less than or equal to - assertThat(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Compact).isTrue() - assertThat(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Expanded).isTrue() - assertThat(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Expanded).isTrue() - assertThat(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Expanded).isTrue() - - assertThat(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Compact).isFalse() - assertThat(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Compact).isFalse() - - // Greater than - assertThat(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Compact).isTrue() - assertThat(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Compact).isTrue() - - assertThat(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Expanded).isFalse() - assertThat(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Compact).isFalse() - - assertThat(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Expanded).isFalse() - assertThat(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Expanded).isFalse() - - // Greater than or equal to - assertThat(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Expanded).isTrue() - assertThat(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Compact).isTrue() - assertThat(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Medium).isTrue() - assertThat(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Compact).isTrue() - assertThat(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Compact).isTrue() - - assertThat(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Medium).isFalse() - assertThat(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Expanded).isFalse() - assertThat(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Expanded).isFalse() - } - - private fun assertWidthClass( - expectedSizeClass: WindowWidthSizeClass, - width: Dp, - supportedSizeClasses: Set = WindowWidthSizeClass.DefaultSizeClasses - ) { - assertThat(WindowWidthSizeClass.fromWidth(width, supportedSizeClasses)) - .isEqualTo(expectedSizeClass) - } - - private fun assertHeightClass( - expectedSizeClass: WindowHeightSizeClass, - height: Dp, - supportedSizeClasses: Set = WindowHeightSizeClass.DefaultSizeClasses - ) { - assertThat(WindowHeightSizeClass.fromHeight(height, supportedSizeClasses)) - .isEqualTo(expectedSizeClass) - } -} diff --git a/compose/material3/material3-window-size-class/src/commonMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.kt b/compose/material3/material3-window-size-class/src/commonMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.kt index 8ad557c465a6b..3f6b70e1910fe 100644 --- a/compose/material3/material3-window-size-class/src/commonMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.kt +++ b/compose/material3/material3-window-size-class/src/commonMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.util.fastForEach * WindowSizeClass contains a [WindowWidthSizeClass] and [WindowHeightSizeClass], representing the * window size classes for this window's width and height respectively. * - * See [calculateWindowSizeClass] to calculate the WindowSizeClass for an Activity's current window + * See [calculateWindowSizeClass] to calculate the WindowSizeClass * * @property widthSizeClass width-based window size class ([WindowWidthSizeClass]) * @property heightSizeClass height-based window size class ([WindowHeightSizeClass]) diff --git a/compose/material3/material3-window-size-class/src/commonTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt b/compose/material3/material3-window-size-class/src/commonTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt new file mode 100644 index 0000000000000..7d66bee4bdb36 --- /dev/null +++ b/compose/material3/material3-window-size-class/src/commonTest/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClassTest.kt @@ -0,0 +1,267 @@ +/* + * 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 + * + * http://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 androidx.compose.material3.windowsizeclass + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class WindowSizeClassTest { + @Test + fun calculateWidthSizeClass_forNegativeWidth_throws() { + assertFailsWith(IllegalArgumentException::class) { + WindowWidthSizeClass.fromWidth((-10).dp, WindowWidthSizeClass.DefaultSizeClasses) + } + } + + @Test + fun calculateHeightSizeClass_forNegativeHeight_throws() { + assertFailsWith(IllegalArgumentException::class) { + WindowHeightSizeClass.fromHeight((-10).dp, WindowHeightSizeClass.DefaultSizeClasses) + } + } + + @Test + fun calculateWidthSizeClass_noSupportedSizeClass_throws() { + assertFailsWith(IllegalArgumentException::class) { + WindowWidthSizeClass.fromWidth(10.dp, emptySet()) + } + } + + @Test + fun calculateHeightSizeClass_noSupportedSizeClass_throws() { + assertFailsWith(IllegalArgumentException::class) { + WindowHeightSizeClass.fromHeight(10.dp, emptySet()) + } + } + + @Test + fun calculateWidthSizeClass() { + assertWidthClass(WindowWidthSizeClass.Compact, 0.dp) + assertWidthClass(WindowWidthSizeClass.Compact, 200.dp) + + assertWidthClass(WindowWidthSizeClass.Medium, 600.dp) + assertWidthClass(WindowWidthSizeClass.Medium, 700.dp) + + assertWidthClass(WindowWidthSizeClass.Expanded, 840.dp) + assertWidthClass(WindowWidthSizeClass.Expanded, 1000.dp) + } + + @Test + fun calculateHeightSizeClass() { + assertHeightClass(WindowHeightSizeClass.Compact, 0.dp) + assertHeightClass(WindowHeightSizeClass.Compact, 200.dp) + + assertHeightClass(WindowHeightSizeClass.Medium, 480.dp) + assertHeightClass(WindowHeightSizeClass.Medium, 700.dp) + + assertHeightClass(WindowHeightSizeClass.Expanded, 900.dp) + assertHeightClass(WindowHeightSizeClass.Expanded, 1000.dp) + } + + @Test + fun calculateWidthSizeClass_useBestMatchedSupportedSizeClasses() { + assertWidthClass( + WindowWidthSizeClass.Compact, + 700.dp, + supportedSizeClasses = + setOf(WindowWidthSizeClass.Compact, WindowWidthSizeClass.Expanded) + ) + + assertWidthClass( + WindowWidthSizeClass.Medium, + 1000.dp, + supportedSizeClasses = setOf(WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium) + ) + } + + @Test + fun calculateHeightSizeClass_useBestMatchedSupportedSizeClasses() { + assertHeightClass( + WindowHeightSizeClass.Compact, + 700.dp, + supportedSizeClasses = + setOf(WindowHeightSizeClass.Compact, WindowHeightSizeClass.Expanded) + ) + + assertHeightClass( + WindowHeightSizeClass.Medium, + 1000.dp, + supportedSizeClasses = + setOf(WindowHeightSizeClass.Compact, WindowHeightSizeClass.Medium) + ) + } + + @Test + fun calculateWidthSizeClass_fallbackToTheSmallestSizeClasses() { + assertWidthClass( + WindowWidthSizeClass.Medium, + 200.dp, + supportedSizeClasses = setOf(WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded) + ) + } + + @Test + fun calculateHeightSizeClass_fallbackToTheSmallestSizeClasses() { + assertHeightClass( + WindowHeightSizeClass.Medium, + 200.dp, + supportedSizeClasses = + setOf(WindowHeightSizeClass.Medium, WindowHeightSizeClass.Expanded) + ) + } + + @Test + fun widthSizeClassToString() { + assertEquals(WindowWidthSizeClass.Compact.toString(), "WindowWidthSizeClass.Compact") + assertEquals(WindowWidthSizeClass.Medium.toString(), "WindowWidthSizeClass.Medium") + assertEquals(WindowWidthSizeClass.Expanded.toString(), "WindowWidthSizeClass.Expanded") + } + + @Test + fun heightSizeClassToString() { + assertEquals(WindowHeightSizeClass.Compact.toString(), "WindowHeightSizeClass.Compact") + assertEquals(WindowHeightSizeClass.Medium.toString(), "WindowHeightSizeClass.Medium") + assertEquals(WindowHeightSizeClass.Expanded.toString(), "WindowHeightSizeClass.Expanded") + } + + @Test + fun widthSizeClassCompareTo() { + // Less than + assertTrue(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Expanded) + assertTrue(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Expanded) + + assertFalse(WindowWidthSizeClass.Compact < WindowWidthSizeClass.Compact) + assertFalse(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Expanded) + + assertFalse(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Expanded < WindowWidthSizeClass.Compact) + assertFalse(WindowWidthSizeClass.Medium < WindowWidthSizeClass.Compact) + + // Less than or equal to + assertTrue(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Compact) + assertTrue(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Compact <= WindowWidthSizeClass.Expanded) + assertTrue(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Expanded) + assertTrue(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Expanded) + + assertFalse(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Expanded <= WindowWidthSizeClass.Compact) + assertFalse(WindowWidthSizeClass.Medium <= WindowWidthSizeClass.Compact) + + // Greater than + assertTrue(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Compact) + assertTrue(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Compact) + + assertFalse(WindowWidthSizeClass.Expanded > WindowWidthSizeClass.Expanded) + assertFalse(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Compact) + + assertFalse(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Compact > WindowWidthSizeClass.Expanded) + assertFalse(WindowWidthSizeClass.Medium > WindowWidthSizeClass.Expanded) + + // Greater than or equal to + assertTrue(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Expanded) + assertTrue(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Expanded >= WindowWidthSizeClass.Compact) + assertTrue(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Medium) + assertTrue(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Compact) + assertTrue(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Compact) + + assertFalse(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Medium) + assertFalse(WindowWidthSizeClass.Compact >= WindowWidthSizeClass.Expanded) + assertFalse(WindowWidthSizeClass.Medium >= WindowWidthSizeClass.Expanded) + } + + @Test + fun heightSizeClassCompareTo() { + // Less than + assertTrue(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Expanded) + assertTrue(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Expanded) + + assertFalse(WindowHeightSizeClass.Compact < WindowHeightSizeClass.Compact) + assertFalse(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Expanded) + + assertFalse(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Expanded < WindowHeightSizeClass.Compact) + assertFalse(WindowHeightSizeClass.Medium < WindowHeightSizeClass.Compact) + + // Less than or equal to + assertTrue(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Compact) + assertTrue(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Compact <= WindowHeightSizeClass.Expanded) + assertTrue(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Expanded) + assertTrue(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Expanded) + + assertFalse(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Expanded <= WindowHeightSizeClass.Compact) + assertFalse(WindowHeightSizeClass.Medium <= WindowHeightSizeClass.Compact) + + // Greater than + assertTrue(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Compact) + assertTrue(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Compact) + + assertFalse(WindowHeightSizeClass.Expanded > WindowHeightSizeClass.Expanded) + assertFalse(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Compact) + + assertFalse(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Compact > WindowHeightSizeClass.Expanded) + assertFalse(WindowHeightSizeClass.Medium > WindowHeightSizeClass.Expanded) + + // Greater than or equal to + assertTrue(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Expanded) + assertTrue(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Expanded >= WindowHeightSizeClass.Compact) + assertTrue(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Medium) + assertTrue(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Compact) + assertTrue(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Compact) + + assertFalse(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Medium) + assertFalse(WindowHeightSizeClass.Compact >= WindowHeightSizeClass.Expanded) + assertFalse(WindowHeightSizeClass.Medium >= WindowHeightSizeClass.Expanded) + } + + private fun assertWidthClass( + expectedSizeClass: WindowWidthSizeClass, + width: Dp, + supportedSizeClasses: Set = WindowWidthSizeClass.DefaultSizeClasses + ) { + assertEquals(WindowWidthSizeClass.fromWidth(width, supportedSizeClasses), expectedSizeClass) + } + + private fun assertHeightClass( + expectedSizeClass: WindowHeightSizeClass, + height: Dp, + supportedSizeClasses: Set = WindowHeightSizeClass.DefaultSizeClasses + ) { + assertEquals(WindowHeightSizeClass.fromHeight(height, supportedSizeClasses), expectedSizeClass) + } +} diff --git a/compose/material3/material3-window-size-class/src/nonJvmMain/kotlin/androidx/compose/material3/windowsizeclass/TestOnly.nonJvm.kt b/compose/material3/material3-window-size-class/src/nonJvmMain/kotlin/androidx/compose/material3/windowsizeclass/TestOnly.nonJvm.kt new file mode 100644 index 0000000000000..488753e5b42f7 --- /dev/null +++ b/compose/material3/material3-window-size-class/src/nonJvmMain/kotlin/androidx/compose/material3/windowsizeclass/TestOnly.nonJvm.kt @@ -0,0 +1,19 @@ +/* + * 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 + * + * http://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 androidx.compose.material3.windowsizeclass + +actual annotation class TestOnly diff --git a/compose/material3/material3-window-size-class/src/skikoMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.skiko.kt b/compose/material3/material3-window-size-class/src/skikoMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.skiko.kt new file mode 100644 index 0000000000000..530cd7933fb65 --- /dev/null +++ b/compose/material3/material3-window-size-class/src/skikoMain/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass.skiko.kt @@ -0,0 +1,40 @@ +/* + * 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 + * + * http://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 androidx.compose.material3.windowsizeclass + +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.toSize + +/** + * Calculates the window's [WindowSizeClass]. + * + * A new [WindowSizeClass] will be returned whenever a configuration change causes the width or + * height of the window to cross a breakpoint, such as when the device is rotated or the window is + * resized. + */ +@OptIn(ExperimentalComposeUiApi::class) +@Composable +@ExperimentalMaterial3WindowSizeClassApi +fun calculateWindowSizeClass(): WindowSizeClass { + val density = LocalDensity.current + val windowInfo = LocalWindowInfo.current + val size = with(density) { windowInfo.containerSize.toSize().toDpSize() } + return WindowSizeClass.calculateFromSize(size) +} \ No newline at end of file diff --git a/compose/mpp/demo/build.gradle.kts b/compose/mpp/demo/build.gradle.kts index 6d7b96c0742de..df7f677629056 100644 --- a/compose/mpp/demo/build.gradle.kts +++ b/compose/mpp/demo/build.gradle.kts @@ -153,6 +153,7 @@ kotlin { implementation(project(":compose:foundation:foundation")) implementation(project(":compose:foundation:foundation-layout")) implementation(project(":compose:material3:material3")) + implementation(project(":compose:material3:material3-window-size-class")) implementation(project(":compose:material:material")) implementation(project(":compose:mpp")) implementation(project(":compose:runtime:runtime")) diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt index 49e285d7e6a1f..e9c1784bd08ad 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/Components.kt @@ -27,6 +27,7 @@ import androidx.compose.mpp.demo.components.material3.DropdownMenu3Example import androidx.compose.mpp.demo.components.material3.ModalBottomSheet3Example import androidx.compose.mpp.demo.components.material3.ModalNavigationDrawerExample import androidx.compose.mpp.demo.components.material3.SearchBarExample +import androidx.compose.mpp.demo.components.material3.WindowSizeClassExample import androidx.compose.mpp.demo.components.popup.Popups import androidx.compose.mpp.demo.textfield.TextFields @@ -45,6 +46,7 @@ private val Material3Components = Screen.Selection( Screen.Example("ModalBottomSheet") { ModalBottomSheet3Example() }, Screen.Example("ModalNavigationDrawer") { ModalNavigationDrawerExample() }, Screen.Example("SearchBar") { SearchBarExample() }, + Screen.Example("WindowSizeClass") { WindowSizeClassExample() }, ) val Components = Screen.Selection( diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/WindowSizeClassExample.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/WindowSizeClassExample.kt new file mode 100644 index 0000000000000..05e50d58debfa --- /dev/null +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/WindowSizeClassExample.kt @@ -0,0 +1,35 @@ +/* + * 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 + * + * http://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 androidx.compose.mpp.demo.components.material3 + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +fun WindowSizeClassExample() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + val size = calculateWindowSizeClass() + Text("width = ${size.widthSizeClass}\nheight = ${size.heightSizeClass}") + } +} \ No newline at end of file diff --git a/mpp/build.gradle.kts b/mpp/build.gradle.kts index 0e9eac8bf958a..f551bee466fc1 100644 --- a/mpp/build.gradle.kts +++ b/mpp/build.gradle.kts @@ -52,6 +52,7 @@ val libraryToComponents = mapOf( ComposeComponent(":compose:material3:material3"), ComposeComponent(":compose:material:material-icons-core"), ComposeComponent(":compose:material:material-ripple"), + ComposeComponent(":compose:material3:material3-window-size-class"), ComposeComponent(":compose:runtime:runtime", supportedPlatforms = ComposePlatforms.ALL), ComposeComponent(":compose:runtime:runtime-saveable", supportedPlatforms = ComposePlatforms.ALL), ComposeComponent(":compose:ui:ui"),