From 1bbb48aa1b57c15225f2cc0761340f5e373a59d8 Mon Sep 17 00:00:00 2001 From: Nek-12 Date: Tue, 27 Aug 2024 12:33:43 +0200 Subject: [PATCH] feat: use official window size library --- .../kmmutils__dokkaHtml_.xml | 23 -- ...shAllPublicationsToSonatypeRepository_.xml | 24 -- buildSrc/src/main/kotlin/Config.kt | 2 +- compose/build.gradle.kts | 1 + .../respawn/kmmutils/compose/Annotations.kt | 17 +- .../pro/respawn/kmmutils/compose/Utils.kt | 1 - .../compose/windowsize/WindowSizeClass.kt | 297 ------------------ .../compose/windowsize/WindowSizeExt.kt | 16 + 8 files changed, 26 insertions(+), 355 deletions(-) delete mode 100644 .idea/runConfigurations/kmmutils__dokkaHtml_.xml delete mode 100644 .idea/runConfigurations/kmmutils__publishAllPublicationsToSonatypeRepository_.xml delete mode 100644 compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Utils.kt delete mode 100644 compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeClass.kt diff --git a/.idea/runConfigurations/kmmutils__dokkaHtml_.xml b/.idea/runConfigurations/kmmutils__dokkaHtml_.xml deleted file mode 100644 index cfb3025..0000000 --- a/.idea/runConfigurations/kmmutils__dokkaHtml_.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/.idea/runConfigurations/kmmutils__publishAllPublicationsToSonatypeRepository_.xml b/.idea/runConfigurations/kmmutils__publishAllPublicationsToSonatypeRepository_.xml deleted file mode 100644 index 8f18692..0000000 --- a/.idea/runConfigurations/kmmutils__publishAllPublicationsToSonatypeRepository_.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - true - true - false - false - - - \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 6455bc3..1e0ca07 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -42,12 +42,12 @@ object Config { "kotlin.contracts.ExperimentalContracts" ) val compilerArgs = listOf( - "-Xbackend-threads=0", // parallel IR compilation "-Xconsistent-data-class-copy-visibility", ) val jvmCompilerArgs = buildList { add("-Xjvm-default=all") // enable all jvm optimizations add("-Xstring-concat=inline") + add("-Xbackend-threads=0") // parallel IR compilation addAll(optIns.map { "-opt-in=$it" }) } diff --git a/compose/build.gradle.kts b/compose/build.gradle.kts index 1a02359..4d70443 100644 --- a/compose/build.gradle.kts +++ b/compose/build.gradle.kts @@ -45,6 +45,7 @@ kotlin { api(libs.lifecycle.runtime) api(libs.lifecycle.compose) + implementation(libs.compose.window.size) implementation(compose.runtime) implementation(compose.foundation) implementation(compose.animationGraphics) diff --git a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Annotations.kt b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Annotations.kt index ac39d53..a76c57c 100644 --- a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Annotations.kt +++ b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Annotations.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontFamily @@ -129,12 +130,10 @@ public fun String.shadow( */ public fun String.font(fontFamily: FontFamily): AnnotatedString = annotate(SpanStyle(fontFamily = fontFamily)) -// TODO: Waiting for compose update - -// public fun String.clickable(onClick: () -> Unit): AnnotatedString = annotate { -// pushLink(LinkAnnotation.Clickable("clickable") { onClick() }) -// pushStyle(SpanStyle(textDecoration = TextDecoration.Underline)) -// append(this@clickable) -// pop() -// pop() -// } +public fun String.clickable(onClick: () -> Unit): AnnotatedString = annotate { + pushLink(LinkAnnotation.Clickable("clickable") { onClick() }) + pushStyle(SpanStyle(textDecoration = TextDecoration.Underline)) + append(this@clickable) + pop() + pop() +} diff --git a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Utils.kt b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Utils.kt deleted file mode 100644 index 65a80e5..0000000 --- a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/Utils.kt +++ /dev/null @@ -1 +0,0 @@ -package pro.respawn.kmmutils.compose diff --git a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeClass.kt b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeClass.kt deleted file mode 100644 index ad79a6b..0000000 --- a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeClass.kt +++ /dev/null @@ -1,297 +0,0 @@ -package pro.respawn.kmmutils.compose.windowsize - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.toSize - -/** - * Calculates the window's [WindowSizeClass]. - * - * A new [WindowSizeClass] will be returned whenever a 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. - */ -@Composable -@ReadOnlyComposable -public fun calculateWindowSizeClass(): WindowSizeClass = WindowSizeClass.calculateFromSize( - size = windowSizePx.toSize(), - density = LocalDensity.current -) - -/** - * Window size classes are a set of opinionated viewport breakpoints to design, develop, and test - * responsive application layouts against. - * For more details check Support different screen sizes documentation. - * - * 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. - * - * @property widthSizeClass width-based window size class ([WindowWidthSizeClass]) - * @property heightSizeClass height-based window size class ([WindowHeightSizeClass]) - */ -@Immutable -public class WindowSizeClass private constructor( - public val widthSizeClass: WindowWidthSizeClass, - public val heightSizeClass: WindowHeightSizeClass, -) { - - public companion object { - - internal fun calculateFromSize(size: DpSize): WindowSizeClass { - val windowWidthSizeClass = WindowWidthSizeClass.fromWidth(size.width) - val windowHeightSizeClass = WindowHeightSizeClass.fromHeight(size.height) - return WindowSizeClass(windowWidthSizeClass, windowHeightSizeClass) - } - - /** - * Calculates the best matched [WindowSizeClass] for a given [size] and [Density] according - * to the provided [supportedWidthSizeClasses] and [supportedHeightSizeClasses]. - * - * @param size of the window - * @param density of the window - * @param supportedWidthSizeClasses the set of width size classes that are supported - * @param supportedHeightSizeClasses the set of height size classes that are supported - * @return [WindowSizeClass] corresponding to the given width and height - */ - public fun calculateFromSize( - size: Size, - density: Density, - supportedWidthSizeClasses: Set = - WindowWidthSizeClass.DefaultSizeClasses, - supportedHeightSizeClasses: Set = - WindowHeightSizeClass.DefaultSizeClasses, - ): WindowSizeClass { - val windowWidthSizeClass = - WindowWidthSizeClass.fromWidth(size.width, density, supportedWidthSizeClasses) - val windowHeightSizeClass = - WindowHeightSizeClass.fromHeight(size.height, density, supportedHeightSizeClasses) - return WindowSizeClass(windowWidthSizeClass, windowHeightSizeClass) - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as WindowSizeClass - - if (widthSizeClass != other.widthSizeClass) return false - if (heightSizeClass != other.heightSizeClass) return false - - return true - } - - override fun hashCode(): Int { - var result = widthSizeClass.hashCode() - result = 31 * result + heightSizeClass.hashCode() - return result - } - - override fun toString(): String = "WindowSizeClass($widthSizeClass, $heightSizeClass)" -} - -/** - * Width-based window size class. - * - * A window size class represents a breakpoint that can be used to build responsive layouts. Each - * window size class breakpoint represents a majority case for typical device scenarios so your - * layouts will work well on most devices and configurations. - * - * For more details see Window size classes documentation. - */ -@Immutable -@kotlin.jvm.JvmInline -public value class WindowWidthSizeClass private constructor(private val value: Int) : - Comparable { - - override operator fun compareTo(other: WindowWidthSizeClass): Int = - breakpoint().compareTo(other.breakpoint()) - - override fun toString(): String = "WindowWidthSizeClass." + when (this) { - Compact -> "Compact" - Medium -> "Medium" - Expanded -> "Expanded" - else -> "" - } - - public companion object { - - /** Represents the majority of phones in portrait. */ - public val Compact: WindowWidthSizeClass = WindowWidthSizeClass(0) - - /** - * Represents the majority of tablets in portrait and large unfolded inner displays in - * portrait. - */ - public val Medium: WindowWidthSizeClass = WindowWidthSizeClass(1) - - /** - * Represents the majority of tablets in landscape and large unfolded inner displays in - * landscape. - */ - public val Expanded: WindowWidthSizeClass = WindowWidthSizeClass(2) - - /** - * The default set of size classes that includes [Compact], [Medium], and [Expanded] size - * classes. Should never expand to ensure behavioral consistency. - */ - public val DefaultSizeClasses: Set = setOf(Compact, Medium, Expanded) - - /** - * The standard set of size classes. It's supposed to include all size classes and will be - * expanded whenever a new size class is defined. By default - * [WindowSizeClass.calculateFromSize] will only return size classes in [DefaultSizeClasses] - * in order to avoid behavioral changes when new size classes are added. You can opt in to - * support all available size classes by doing: - * ``` - * WindowSizeClass.calculateFromSize( - * size = size, - * density = density, - * supportedWidthSizeClasses = WindowWidthSizeClass.StandardSizeClasses, - * supportedHeightSizeClasses = WindowHeightSizeClass.StandardSizeClasses - * ) - * ``` - */ - public val StandardSizeClasses: Set get() = DefaultSizeClasses - - private fun WindowWidthSizeClass.breakpoint(): Dp = when { - this == Expanded -> 840.dp - this == Medium -> 600.dp - else -> 0.dp - } - - /** Calculates the [WindowWidthSizeClass] for a given [width] */ - internal fun fromWidth(width: Dp): WindowWidthSizeClass = fromWidth( - with(defaultDensity) { width.toPx() }, - defaultDensity, - DefaultSizeClasses, - ) - - /** - * Calculates the best matched [WindowWidthSizeClass] for a given [width] in Pixels and - * a given [Density] from [supportedSizeClasses]. - */ - internal fun fromWidth( - width: Float, - density: Density, - supportedSizeClasses: Set, - ): WindowWidthSizeClass { - require(width >= 0) { "Width must not be negative" } - require(supportedSizeClasses.isNotEmpty()) { "Must support at least one size class" } - val sortedSizeClasses = supportedSizeClasses.sortedDescending() - // Find the largest supported size class that matches the width - sortedSizeClasses.forEach { - if (width >= with(density) { it.breakpoint().toPx() }) { - return it - } - } - // If none of the size classes matches, return the smallest one. - return sortedSizeClasses.last() - } - } -} - -/** - * Height-based window size class. - * - * A window size class represents a breakpoint that can be used to build responsive layouts. Each - * window size class breakpoint represents a majority case for typical device scenarios so your - * layouts will work well on most devices and configurations. - * - * For more details see Window size classes documentation. - */ -@Immutable -@kotlin.jvm.JvmInline -public value class WindowHeightSizeClass private constructor(private val value: Int) : - Comparable { - - override operator fun compareTo(other: WindowHeightSizeClass): Int = breakpoint().compareTo(other.breakpoint()) - - override fun toString(): String = "WindowHeightSizeClass." + when (this) { - Compact -> "Compact" - Medium -> "Medium" - Expanded -> "Expanded" - else -> "" - } - - public companion object { - - /** Represents the majority of phones in landscape */ - public val Compact: WindowHeightSizeClass = WindowHeightSizeClass(0) - - /** Represents the majority of tablets in landscape and majority of phones in portrait */ - public val Medium: WindowHeightSizeClass = WindowHeightSizeClass(1) - - /** Represents the majority of tablets in portrait */ - public val Expanded: WindowHeightSizeClass = WindowHeightSizeClass(2) - - /** - * The default set of size classes that includes [Compact], [Medium], and [Expanded] size - * classes. Should never expand to ensure behavioral consistency. - */ - public val DefaultSizeClasses: Set = setOf(Compact, Medium, Expanded) - - /** - * The standard set of size classes. It's supposed to include all size classes and will be - * expanded whenever a new size class is defined. By default - * [WindowSizeClass.calculateFromSize] will only return size classes in [DefaultSizeClasses] - * in order to avoid behavioral changes when new size classes are added. You can opt in to - * support all available size classes by doing: - * ``` - * WindowSizeClass.calculateFromSize( - * size = size, - * density = density, - * supportedWidthSizeClasses = WindowWidthSizeClass.StandardSizeClasses, - * supportedHeightSizeClasses = WindowHeightSizeClass.StandardSizeClasses - * ) - * ``` - */ - public val StandardSizeClasses: Set get() = DefaultSizeClasses - - private fun WindowHeightSizeClass.breakpoint(): Dp = when { - this == Expanded -> 900.dp - this == Medium -> 480.dp - else -> 0.dp - } - - /** Calculates the [WindowHeightSizeClass] for a given [height] */ - internal fun fromHeight(height: Dp) = fromHeight( - with(defaultDensity) { height.toPx() }, - defaultDensity, - DefaultSizeClasses, - ) - - /** - * Calculates the best matched [WindowHeightSizeClass] for a given [height] in Pixels and - * a given [Density] from [supportedSizeClasses]. - */ - internal fun fromHeight( - height: Float, - density: Density, - supportedSizeClasses: Set, - ): WindowHeightSizeClass { - require(height >= 0) { "Width must not be negative" } - require(supportedSizeClasses.isNotEmpty()) { "Must support at least one size class" } - val sortedSizeClasses = supportedSizeClasses.sortedDescending() - // Find the largest supported size class that matches the width - sortedSizeClasses.forEach { - if (height >= with(density) { it.breakpoint().toPx() }) { - return it - } - } - // If none of the size classes matches, return the smallest one. - return sortedSizeClasses.last() - } - } -} - -private val defaultDensity = Density(1F, 1F) diff --git a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeExt.kt b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeExt.kt index 56d561d..54d193a 100644 --- a/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeExt.kt +++ b/compose/src/commonMain/kotlin/pro/respawn/kmmutils/compose/windowsize/WindowSizeExt.kt @@ -1,5 +1,9 @@ package pro.respawn.kmmutils.compose.windowsize +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.platform.LocalDensity @@ -8,6 +12,18 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toSize +/** + * Calculates the window's [WindowSizeClass]. + * + * A new [WindowSizeClass] will be returned whenever a 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(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +@ReadOnlyComposable +public fun calculateWindowSizeClass(): WindowSizeClass = WindowSizeClass.calculateFromSize(size = windowSize) + /** * Whether the window width is long (longer than most phones **in portrait**). *