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/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt index 6a116aa60..76ed0b39e 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt @@ -11,10 +11,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -25,6 +25,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.testTag import io.github.droidkaigi.confsched2023.contributors.component.ContributorListItem import io.github.droidkaigi.confsched2023.model.Contributor +import io.github.droidkaigi.confsched2023.ui.AutoSizableText import io.github.droidkaigi.confsched2023.ui.SnackbarMessageEffect import kotlinx.collections.immutable.PersistentList @@ -78,7 +79,12 @@ private fun ContributorsScreen( if (scrollBehavior != null) { LargeTopAppBar( title = { - Text(text = "Contributor") + AutoSizableText( + text = "Contributor", + minFontSize = MaterialTheme.typography.bodySmall.fontSize, + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + ) }, navigationIcon = { IconButton( 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 0a85e3086..b53a0961c 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, ) } },