Skip to content

Commit

Permalink
Fixed MarqueeText and Type Composable
Browse files Browse the repository at this point in the history
  • Loading branch information
jeluchu committed Jul 11, 2023
1 parent 8387880 commit 4319d03
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.jeluchu.jchucomponents.ktx.colors.applyOpacity
import com.jeluchu.jchucomponents.ktx.strings.empty
import com.jeluchu.jchucomponents.ui.R
import com.jeluchu.jchucomponents.ui.composables.chips.Type
import com.jeluchu.jchucomponents.ui.composables.chips.TypeColors
import com.jeluchu.jchucomponents.ui.extensions.modifier.cornerRadius

@Composable
Expand Down Expand Up @@ -60,8 +61,10 @@ fun CategoryCard(
Spacer(modifier = Modifier.align(Alignment.Center))
Type(
modifier = Modifier.align(Alignment.BottomEnd),
type = title,
textColor = textColor.applyOpacity(enabled),
text = title,
colors = TypeColors(
container = textColor.applyOpacity(enabled)
),
fontSize = fontSize,
style = textStyle
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ package com.jeluchu.jchucomponents.ui.composables.chips

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
Expand All @@ -24,53 +31,94 @@ import androidx.compose.ui.unit.sp
import com.jeluchu.jchucomponents.ui.extensions.modifier.cornerRadius
import com.jeluchu.jchucomponents.ui.foundation.text.MarqueeText
import com.jeluchu.jchucomponents.ui.themes.artichoke
import com.jeluchu.jchucomponents.ui.themes.cosmicLatte

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Type(
type: String,
text: String,
modifier: Modifier = Modifier,
textColor: Color = artichoke,
bgColor: Color = artichoke.copy(alpha = 0.1f),
shape: Shape = CircleShape,
colors: TypeColors = TypeColors(),
textAlign: TextAlign? = null,
fontWeight: FontWeight? = null,
fontSize: TextUnit = 12.sp,
isLongText: Boolean = false,
style: TextStyle = LocalTextStyle.current
) {
if (isLongText)
MarqueeText(
modifier = modifier
.padding(vertical = 4.dp)
.clip(10.cornerRadius())
.background(bgColor)
.padding(10.dp, 2.dp),
text = type,
style = style,
fontSize = fontSize,
color = textColor,
textAlign = textAlign
)
else
Text(
modifier = modifier
.padding(vertical = 4.dp)
.clip(10.cornerRadius())
.background(bgColor)
.padding(10.dp, 2.dp),
text = type,
style = style,
fontSize = fontSize,
textAlign = textAlign,
color = textColor
)
}
) = MarqueeText(
modifier = modifier
.clip(shape)
.background(colors.container)
.padding(10.dp, 2.dp),
text = text,
style = style,
fontSize = fontSize,
fontWeight = fontWeight,
gradientEdgeColor = colors.gradientEdge,
color = colors.content,
textAlign = textAlign
)

@Immutable
class TypeColors constructor(
val content: Color = artichoke,
val gradientEdge: Color = Color.White,
val container: Color = artichoke.copy(alpha = 0.1f),
)

@ExperimentalFoundationApi
@Preview
@Composable
fun VillagerTypePreview() {
Type(
"20/09",
Modifier
)
fun TypePreview() {
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text("A simple text")
Type("20/09")
Text("A long simple text with default gradients")
Type("The world is a Vampire! And this text is a example text of Marquee Type to check feature")
Text("A long simple text with custom gradients")
Type(
text = "The world is a Vampire! And this text is a example text of Marquee Type to check feature",
colors = TypeColors(
container = artichoke,
content = cosmicLatte,
gradientEdge = artichoke
)
)
Text("A long simple text with custom shape")
Type(
text = "The world is a Vampire! And this text is a example text of Marquee Type to check feature",
shape = 5.cornerRadius(),
colors = TypeColors(
container = artichoke,
content = cosmicLatte,
gradientEdge = artichoke
)
)

Text("An example in a Row Composable")
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
Type(
modifier = Modifier.weight(1f),
text = "The world is a Vampire! And this text is a example text of Marquee Type to check feature",
shape = 5.cornerRadius(),
colors = TypeColors(
container = artichoke,
content = cosmicLatte,
gradientEdge = artichoke
)
)
Type(
modifier = Modifier.weight(1f),
text = "The world is a Vampire! And this text is a example text of Marquee Type to check feature",
colors = TypeColors(
container = artichoke,
content = cosmicLatte,
gradientEdge = artichoke
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,147 @@ import kotlinx.coroutines.delay
*
*/

@Composable
fun MarqueeText(
text: String,
modifier: Modifier = Modifier,
textModifier: Modifier = Modifier,
gradientEdgeColor: Color = Color.White,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
val createText = @Composable { localModifier: Modifier ->
Text(
text,
textAlign = textAlign,
modifier = localModifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = 1,
onTextLayout = onTextLayout,
style = style,
)
}
var offset by remember { mutableStateOf(0) }
val textLayoutInfoState = remember { mutableStateOf<TextLayoutInfo?>(null) }
LaunchedEffect(textLayoutInfoState.value) {
val textLayoutInfo = textLayoutInfoState.value ?: return@LaunchedEffect
if (textLayoutInfo.textWidth <= textLayoutInfo.containerWidth) return@LaunchedEffect
val duration = 7500 * textLayoutInfo.textWidth / textLayoutInfo.containerWidth
val delay = 1000L

do {
val animation = TargetBasedAnimation(
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = duration,
delayMillis = 1000,
easing = LinearEasing,
),
repeatMode = RepeatMode.Restart
),
typeConverter = Int.VectorConverter,
initialValue = 0,
targetValue = -textLayoutInfo.textWidth
)
val startTime = withFrameNanos { it }
do {
val playTime = withFrameNanos { it } - startTime
offset = (animation.getValueFromNanos(playTime))
} while (!animation.isFinishedFromNanos(playTime))
delay(delay)
} while (true)
}

SubcomposeLayout(
modifier = modifier.clipToBounds()
) { constraints ->
val infiniteWidthConstraints = constraints.copy(maxWidth = Int.MAX_VALUE)
var mainText = subcompose(MarqueeLayers.MainText) {
createText(textModifier)
}.first().measure(infiniteWidthConstraints)

var gradient: Placeable? = null

var secondPlaceableWithOffset: Pair<Placeable, Int>? = null
if (mainText.width <= constraints.maxWidth) {
mainText = subcompose(MarqueeLayers.SecondaryText) {
createText(textModifier.fillMaxWidth())
}.first().measure(constraints)
textLayoutInfoState.value = null
} else {
val spacing = constraints.maxWidth * 2 / 3
textLayoutInfoState.value = TextLayoutInfo(
textWidth = mainText.width + spacing,
containerWidth = constraints.maxWidth
)
val secondTextOffset = mainText.width + offset + spacing
val secondTextSpace = constraints.maxWidth - secondTextOffset
if (secondTextSpace > 0) {
secondPlaceableWithOffset = subcompose(MarqueeLayers.SecondaryText) {
createText(textModifier)
}.first().measure(infiniteWidthConstraints) to secondTextOffset
}
gradient = subcompose(MarqueeLayers.EdgesGradient) {
Row {
GradientEdge(gradientEdgeColor, Color.Transparent)
Spacer(Modifier.weight(1f))
GradientEdge(Color.Transparent, gradientEdgeColor)
}
}.first().measure(constraints.copy(maxHeight = mainText.height))
}

layout(
width = constraints.maxWidth,
height = mainText.height
) {
mainText.place(offset, 0)
secondPlaceableWithOffset?.let {
it.first.place(it.second, 0)
}
gradient?.place(0, 0)
}
}
}

@Composable
private fun GradientEdge(
startColor: Color, endColor: Color,
) {
Box(
modifier = Modifier
.width(10.dp)
.fillMaxHeight()
.background(
brush = Brush.horizontalGradient(
0f to startColor, 1f to endColor,
)
)
)
}

private enum class MarqueeLayers { MainText, SecondaryText, EdgesGradient }
private data class TextLayoutInfo(val textWidth: Int, val containerWidth: Int)

fun ContentDrawScope.drawFadedEdge(
leftEdge: Boolean,
edgeWidth: Dp
Expand Down

0 comments on commit 4319d03

Please sign in to comment.