Skip to content

Commit

Permalink
Introduce AutoSizeText component & use it for the top bar title of al…
Browse files Browse the repository at this point in the history
…l main screens
  • Loading branch information
mannodermaus committed Aug 30, 2024
1 parent 53f3418 commit 386206b
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fun AnimatedTextTopAppBar(
TopAppBar(
title = {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
AutoSizeText(
text = title,
color = textColor,
modifier = Modifier
Expand All @@ -47,10 +47,11 @@ fun AnimatedTextTopAppBar(
alpha = 1f - transitionFraction
},
textAlign = TextAlign.Start,
maxLines = 1,
style = MaterialTheme.typography.headlineSmall,
)

Text(
AutoSizeText(
text = title,
color = textColor,
modifier = Modifier
Expand All @@ -60,6 +61,7 @@ fun AnimatedTextTopAppBar(
alpha = transitionFraction
},
textAlign = TextAlign.Center,
maxLines = 1,
style = MaterialTheme.typography.titleMedium,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package io.github.droidkaigi.confsched.droidkaigiui.component

import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.text.Paragraph
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isFinite
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.sp
import kotlin.math.absoluteValue
import kotlin.math.ceil

/**
* A text component that automatically resizes its font size to fit its content,
* up to the given [maxFontSize]. (If you don't want to limit the upper bounds
* of the text size, use [TextUnit.Unspecified] for that parameter.)
*
* Adapted from Alchitry on Stack Overflow, licensed under CC BY-SA 4.0:
* https://stackoverflow.com/a/71416530
*/
@Composable
fun AutoSizeText(
text: String,
modifier: Modifier = Modifier,
acceptableError: Dp = 5.dp,
color: Color = Color.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
contentAlignment: Alignment? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
maxFontSize: TextUnit = style.fontSize,
) {
val alignment: Alignment = contentAlignment ?: when (textAlign) {
TextAlign.Left -> Alignment.TopStart
TextAlign.Right -> Alignment.TopEnd
TextAlign.Center -> Alignment.Center
TextAlign.Justify -> Alignment.TopCenter
TextAlign.Start -> Alignment.TopStart
TextAlign.End -> Alignment.TopEnd
else -> Alignment.TopStart
}
BoxWithConstraints(modifier = modifier, contentAlignment = alignment) {
var shrunkFontSize = if (maxFontSize.isSpecified) maxFontSize else 100.sp

val calculateIntrinsics = @Composable {
val mergedStyle = style.merge(
TextStyle(
color = color,
fontSize = shrunkFontSize,
fontWeight = fontWeight,
textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing,
),
)
Paragraph(
text = text,
style = mergedStyle,
constraints = Constraints(maxWidth = ceil(LocalDensity.current.run { maxWidth.toPx() }).toInt()),
density = LocalDensity.current,
fontFamilyResolver = LocalFontFamilyResolver.current,
spanStyles = listOf(),
placeholders = listOf(),
maxLines = maxLines,
ellipsis = false,
)
}

var intrinsics = calculateIntrinsics()

val targetWidth = maxWidth - acceptableError / 2f

check(targetWidth.isFinite || maxFontSize.isSpecified) {
"maxFontSize must be specified if the target with isn't finite!"
}

with(LocalDensity.current) {
// this loop will attempt to quickly find the correct size font by scaling it by the error
// it only runs if the max font size isn't specified or the font must be smaller
// minIntrinsicWidth is "The width for text if all soft wrap opportunities were taken."
if (maxFontSize.isUnspecified || targetWidth < intrinsics.minIntrinsicWidth.toDp()) {
while ((targetWidth - intrinsics.minIntrinsicWidth.toDp()).toPx().absoluteValue.toDp() > acceptableError / 2f) {
shrunkFontSize *= targetWidth.toPx() / intrinsics.minIntrinsicWidth
intrinsics = calculateIntrinsics()
}
}
// checks if the text fits in the bounds and scales it by 90% until it does
while (intrinsics.didExceedMaxLines || maxHeight < intrinsics.height.toDp() || maxWidth < intrinsics.minIntrinsicWidth.toDp()) {
shrunkFontSize *= 0.9f
intrinsics = calculateIntrinsics()
}
}

if (maxFontSize.isSpecified && shrunkFontSize > maxFontSize) {
shrunkFontSize = maxFontSize
}

Text(
text = text,
color = color,
fontSize = shrunkFontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
onTextLayout = onTextLayout,
maxLines = maxLines,
style = style,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -840,10 +840,9 @@ internal fun CardScreen(
text = stringResource(ProfileCardRes.string.edit),
modifier = Modifier.padding(8.dp),
style = MaterialTheme.typography.labelLarge,
color = Color.Black
color = Color.Black,
)
}

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
Expand All @@ -31,9 +31,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
Expand All @@ -51,6 +49,7 @@ import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect
import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder
import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolderImpl
import io.github.droidkaigi.confsched.droidkaigiui.component.AutoSizeText
import io.github.droidkaigi.confsched.droidkaigiui.compositionlocal.FakeClock
import io.github.droidkaigi.confsched.droidkaigiui.compositionlocal.LocalClock
import io.github.droidkaigi.confsched.model.DroidKaigi2024Day
Expand Down Expand Up @@ -158,12 +157,11 @@ private fun TimetableScreen(
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
AutoSizeText(
modifier = Modifier.weight(1f),
text = stringResource(SessionsRes.string.timetable),
fontSize = 24.sp,
lineHeight = 32.sp,
fontWeight = FontWeight.W400,
modifier = Modifier.weight(1F),
style = MaterialTheme.typography.headlineSmall,
maxLines = 1,
)
IconButton(
onClick = dropUnlessResumed(block = onSearchClick),
Expand Down

0 comments on commit 386206b

Please sign in to comment.