kotlinandroid-jetpack-compose

Draw a line behind the Persona 5 messaging app


What my screen looks like. I had to use an offset for the lines to be drawn because it was as if both entries had the same y value.

I'm trying to recreate Persona 5's chat using Jetpack Compose + Kotlin in Android Studio. However, I don't know how to get the y value so that the lines I'm drawing connect between avatars regardless of the message size. Could anyone more knowledgeable about Android help?

How the line should look

fun Modifier.drawConnectingLine(
    entry1: Entry,
    entry2: Entry?
): Modifier {
    if (entry2 == null) return this

    return drawWithCache {
        val linePath = Path()
        val charCount = entry1.message.text.split("\n").size.toFloat()
        val bottomOffset = Offset(0f, 200+((charCount-1)*2000f))
        val topLeft = entry1.lineCoordinates.leftPoint
        val topRight = entry1.lineCoordinates.rightPoint
        val bottomLeft = entry2.lineCoordinates.leftPoint + bottomOffset
        val bottomRight = entry2.lineCoordinates.rightPoint + bottomOffset

        val shadowPaint = Paint().apply {
            color = android.graphics.Color.BLACK
            alpha = (0.5f * 255).toInt()
            maskFilter = BlurMaskFilter(4.dp.toPx(), BlurMaskFilter.Blur.NORMAL)
        }

        onDrawBehind {
            // monta o path
            with(linePath) {
                rewind()
                moveTo(topLeft.x, topLeft.y)           // canto superior esquerdo
                lineTo(topRight.x, topRight.y)         // canto superior direito
                lineTo(bottomRight.x, bottomRight.y)   // canto inferior direito
                lineTo(bottomLeft.x, bottomLeft.y)     // canto inferior esquerdo
                close()
            }

            // pontos de debug
            drawCircle(Color.Green, radius = 10f, center = entry1.lineCoordinates.leftPoint)
            drawCircle(Color.Blue, radius = 10f, center = entry1.lineCoordinates.rightPoint)
            drawCircle(Color.Black, radius = 10f, center = entry2.lineCoordinates.leftPoint)
            drawCircle(Color.White, radius = 10f, center = entry2.lineCoordinates.rightPoint)

            // desenha com Paint Android nativo (com blur)
            drawIntoCanvas { canvas ->
                val paint = android.graphics.Paint().apply {
                    color = android.graphics.Color.BLACK
                    alpha = (0.5f * 255).toInt()
                    strokeWidth = 8f
                    style = android.graphics.Paint.Style.STROKE
                    strokeCap = android.graphics.Paint.Cap.ROUND
                    maskFilter = BlurMaskFilter(6.dp.toPx(), BlurMaskFilter.Blur.NORMAL)
                }
                canvas.nativeCanvas.drawPath(linePath.asAndroidPath(), paint)
            }

            // desenha path "puro" Compose
            drawPath(
                linePath,
                Color.Black,
                style = Fill
            )
        }
    }
}
fun messagesToEntries(
    messages: List<Message>,
    avatarSize: Size,
    lineWidth: Float = 80f,             // largura da linha
    minShift: Float = 30f,               // deslocamento mínimo
    maxShift: Float = 50f,              // deslocamento máximo
    randomBetween: (Float, Float) -> Float = { min, max ->
        Random.nextFloat() * (max - min) + min
    }
): List<Entry> {
    return messages.mapIndexed { index, message ->
        val leftX = (avatarSize.width/2f) - (lineWidth / 2f)
        val rightX = leftX + lineWidth
        val y = avatarSize.height / 2f

        val direction = if (index % 2 == 0) 1f else -1f
        val horizontalShift = randomBetween(minShift, maxShift) * direction

        val lineCoordinates = LineCoordinates(
            leftPoint = Offset(leftX + horizontalShift, y),
            rightPoint = Offset(rightX + horizontalShift, y)
        )

        Entry(message, lineCoordinates)
    }
}

val listState = rememberLazyListState()
            LaunchedEffect(Unit)
            {
                viewModel.setMessages(viewModel.messages, avatarSize)
            }

            Box(modifier = Modifier.fillMaxSize())
            {
                LazyColumn (state = listState,
                    modifier = Modifier
                        .fillMaxSize(),
                        //.drawConnectingLines(uiState.entries),
                    verticalArrangement = Arrangement.spacedBy(16.dp),

                    )
                {
                    items(uiState.entries.size)
                    { index ->
                        val entry = uiState.entries[index]
                        val proxEntry = uiState.entries.getOrNull(index + 1)
                        Box(
                            modifier = Modifier
                                //.fillMaxWidth()
                                .then(
                                    Modifier.drawConnectingLine(entry, proxEntry).matchParentSize() // último não desenha
                                )
                        ) {
                            MessageRow(entry = entry, generalViewModel = viewModel)
                            //Não tá atualizando os valores
                            /*
                            if (prevEntry != null &&
                                entry.lineCoordinates.leftPoint.y != 0f &&
                                prevEntry.lineCoordinates.leftPoint.y != 0f
                            ) {

                                DebugLineCoordinates(
                                    entry = prevEntry,
                                    modifier = Modifier.matchParentSize(),
                                    color1 = Color.Blue,
                                    color2 = Color.Green
                                )

                            }*/
                            HorizontalCenterDebugCircle(modifier = Modifier.matchParentSize())
                        }


                    }
                }


Solution

  • I solved the problem by making the function that draws the messages also draw the line in the background and adding to the height y of the line coordinates the equivalent of the distance from the beginning of the message box to its center (as this is always fixed) + the total height of the box