feat: add WindowSizeClass system for responsive layout (Closes #1) #40
@@ -9,6 +9,7 @@ import androidx.compose.material.icons.filled.Person
|
|||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -23,6 +24,8 @@ import org.shahondin1624.lib.components.charactermodel.attributespage.Attributes
|
|||||||
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
||||||
import org.shahondin1624.theme.AppTheme
|
import org.shahondin1624.theme.AppTheme
|
||||||
import org.shahondin1624.theme.LocalThemeIsDark
|
import org.shahondin1624.theme.LocalThemeIsDark
|
||||||
|
import org.shahondin1624.theme.LocalWindowSizeClass
|
||||||
|
import org.shahondin1624.theme.WindowSizeClass
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Preview
|
@Preview
|
||||||
@@ -30,6 +33,20 @@ import org.shahondin1624.theme.LocalThemeIsDark
|
|||||||
fun App(
|
fun App(
|
||||||
onThemeChanged: @Composable (isDark: Boolean) -> Unit = {}
|
onThemeChanged: @Composable (isDark: Boolean) -> Unit = {}
|
||||||
) = AppTheme(onThemeChanged) {
|
) = AppTheme(onThemeChanged) {
|
||||||
|
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||||
|
val windowSizeClass = WindowSizeClass.fromWidth(maxWidth)
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalWindowSizeClass provides windowSizeClass) {
|
||||||
|
AppContent(onThemeChanged = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun AppContent(
|
||||||
|
onThemeChanged: @Composable (isDark: Boolean) -> Unit = {}
|
||||||
|
) {
|
||||||
val character = EXAMPLE_CHARACTER
|
val character = EXAMPLE_CHARACTER
|
||||||
var isDark by LocalThemeIsDark.current
|
var isDark by LocalThemeIsDark.current
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.shahondin1624.theme
|
||||||
|
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classification of window widths following Material Design 3 breakpoints.
|
||||||
|
*
|
||||||
|
* - [Compact] : width < 600 dp (phones in portrait)
|
||||||
|
* - [Medium] : 600 dp <= width <= 840 dp (tablets, foldables)
|
||||||
|
* - [Expanded] : width > 840 dp (desktops, landscape tablets)
|
||||||
|
*/
|
||||||
|
enum class WindowSizeClass {
|
||||||
|
Compact,
|
||||||
|
Medium,
|
||||||
|
Expanded;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Returns the [WindowSizeClass] for the given [width].
|
||||||
|
*/
|
||||||
|
fun fromWidth(width: Dp): WindowSizeClass = when {
|
||||||
|
width < 600.dp -> Compact
|
||||||
|
width <= 840.dp -> Medium
|
||||||
|
else -> Expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CompositionLocal providing the current [WindowSizeClass].
|
||||||
|
* Set at the app root via [BoxWithConstraints] so every composable
|
||||||
|
* in the tree can read it without parameter drilling.
|
||||||
|
*/
|
||||||
|
val LocalWindowSizeClass = compositionLocalOf { WindowSizeClass.Expanded }
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.shahondin1624
|
||||||
|
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.shahondin1624.theme.WindowSizeClass
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class WindowSizeClassTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactForWidthBelow600() {
|
||||||
|
assertEquals(WindowSizeClass.Compact, WindowSizeClass.fromWidth(0.dp))
|
||||||
|
assertEquals(WindowSizeClass.Compact, WindowSizeClass.fromWidth(320.dp))
|
||||||
|
assertEquals(WindowSizeClass.Compact, WindowSizeClass.fromWidth(599.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mediumForWidth600To840() {
|
||||||
|
assertEquals(WindowSizeClass.Medium, WindowSizeClass.fromWidth(600.dp))
|
||||||
|
assertEquals(WindowSizeClass.Medium, WindowSizeClass.fromWidth(720.dp))
|
||||||
|
assertEquals(WindowSizeClass.Medium, WindowSizeClass.fromWidth(840.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedForWidthAbove840() {
|
||||||
|
assertEquals(WindowSizeClass.Expanded, WindowSizeClass.fromWidth(841.dp))
|
||||||
|
assertEquals(WindowSizeClass.Expanded, WindowSizeClass.fromWidth(1200.dp))
|
||||||
|
assertEquals(WindowSizeClass.Expanded, WindowSizeClass.fromWidth(1920.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun boundaryAt600() {
|
||||||
|
assertEquals(WindowSizeClass.Compact, WindowSizeClass.fromWidth(599.9.dp))
|
||||||
|
assertEquals(WindowSizeClass.Medium, WindowSizeClass.fromWidth(600.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun boundaryAt840() {
|
||||||
|
assertEquals(WindowSizeClass.Medium, WindowSizeClass.fromWidth(840.dp))
|
||||||
|
assertEquals(WindowSizeClass.Expanded, WindowSizeClass.fromWidth(840.1.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user