diff --git a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/ui/AutoSizableText.kt b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/ui/AutoSizableText.kt new file mode 100644 index 000000000..f78a4a406 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/ui/AutoSizableText.kt @@ -0,0 +1,64 @@ +package io.github.droidkaigi.confsched2023.ui + +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFontFamilyResolver +import androidx.compose.ui.text.Paragraph +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +private const val CONTRACTION_RATIO = 0.95f + +/** + * ref: https://blog.canopas.com/autosizing-textfield-in-jetpack-compose-7a80f0270853 + */ +@Composable +fun AutoSizableText( + text: String, + minFontSize: TextUnit, + style: TextStyle, + modifier: Modifier = Modifier, + maxLines: Int = Int.MAX_VALUE, + fontWeight: FontWeight? = null, +) { + val density = LocalDensity.current + var tempFontSize by remember(text, style) { mutableFloatStateOf(style.fontSize.value) } + + // Calculate size before displaying + BoxWithConstraints(modifier = modifier) { + val calculateParagraph = @Composable { + Paragraph( + text = text, + style = style.copy(fontSize = tempFontSize.sp), + constraints = this.constraints, + density = density, + fontFamilyResolver = LocalFontFamilyResolver.current, + ) + } + + var paragraph = calculateParagraph() + while ( + tempFontSize > minFontSize.value && + (paragraph.height / density.density > maxHeight.value || paragraph.lineCount > maxLines) + ) { + tempFontSize *= CONTRACTION_RATIO + paragraph = calculateParagraph() + } + + Text( + text = text, + maxLines = maxLines, + fontWeight = fontWeight, + style = style.copy(fontSize = tempFontSize.sp), + ) + } +} diff --git a/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/FloorMapScreen.kt b/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/FloorMapScreen.kt index 125463c2a..75164846c 100644 --- a/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/FloorMapScreen.kt +++ b/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/FloorMapScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.windowsizeclass.WindowSizeClass @@ -63,6 +62,7 @@ import io.github.droidkaigi.confsched2023.model.FloorLevel import io.github.droidkaigi.confsched2023.model.FloorLevel.Basement import io.github.droidkaigi.confsched2023.model.FloorLevel.Ground import io.github.droidkaigi.confsched2023.model.SideEvents +import io.github.droidkaigi.confsched2023.ui.AutoSizableText import io.github.droidkaigi.confsched2023.ui.SnackbarMessageEffect import kotlinx.collections.immutable.toImmutableList @@ -152,16 +152,20 @@ private fun FloorMapScreen( TopAppBar( title = { if (scrollBehavior.state.overlappedFraction == 0f) { - Text( + AutoSizableText( text = FloorMapStrings.Title.asString(), - style = MaterialTheme.typography.headlineLarge, + minFontSize = MaterialTheme.typography.bodySmall.fontSize, + maxLines = 1, fontWeight = FontWeight.Medium, + style = MaterialTheme.typography.headlineLarge, ) } else { - Text( + AutoSizableText( text = FloorMapStrings.Title.asString(), - style = MaterialTheme.typography.titleLarge, modifier = Modifier.alpha(scrollBehavior.state.overlappedFraction), + minFontSize = MaterialTheme.typography.bodySmall.fontSize, + maxLines = 1, + style = MaterialTheme.typography.titleLarge, ) } }, diff --git a/feature/floor-map/src/test/java/io/github/droidkaigi/confsched2023/FloorMapScreenTest.kt b/feature/floor-map/src/test/java/io/github/droidkaigi/confsched2023/FloorMapScreenTest.kt index a1e8aae83..7263eb2e6 100644 --- a/feature/floor-map/src/test/java/io/github/droidkaigi/confsched2023/FloorMapScreenTest.kt +++ b/feature/floor-map/src/test/java/io/github/droidkaigi/confsched2023/FloorMapScreenTest.kt @@ -48,4 +48,34 @@ class FloorMapScreenTest { checkScreenCapture() } } + + @Test + @Category(ScreenshotTests::class) + @Config(fontScale = 0.5f) + fun smallFontScaleShot() { + floorMapScreenRobot { + setupScreenContent() + checkScreenCapture() + } + } + + @Test + @Category(ScreenshotTests::class) + @Config(fontScale = 1.5f) + fun largeFontScaleShot() { + floorMapScreenRobot { + setupScreenContent() + checkScreenCapture() + } + } + + @Test + @Category(ScreenshotTests::class) + @Config(fontScale = 2.0f) + fun hugeFontScaleShot() { + floorMapScreenRobot { + setupScreenContent() + checkScreenCapture() + } + } }