package com.rizoma.trazo.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.rizoma.trazo.data.TrazoSettings import com.rizoma.trazo.ime.KeyboardController import com.rizoma.trazo.ime.Layer import com.rizoma.trazo.ime.ShiftState import com.rizoma.trazo.ui.theme.NeonCian import com.rizoma.trazo.ui.theme.NeonRosa import com.rizoma.trazo.ui.theme.NeonVerde import com.rizoma.trazo.ui.theme.Tecla import com.rizoma.trazo.ui.theme.Tenue import com.rizoma.trazo.ui.theme.Vacio import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun KeyboardScreen(controller: KeyboardController, settings: TrazoSettings) { Column( Modifier .fillMaxWidth() .background(Vacio) // Evita que la barra de navegación del sistema tape la fila de funciones. .navigationBarsPadding() .padding(4.dp), ) { SuggestionBar(controller.suggestions, onPick = controller::commitSuggestion) if (controller.layer == Layer.EMOJIS) { EmojiPad( recents = settings.recentEmojis, onEmoji = controller::onEmoji, modifier = Modifier.fillMaxWidth().height(210.dp), ) Row(Modifier.fillMaxWidth().height(52.dp)) { FuncKey("ABC", NeonCian, Modifier.weight(1.4f), onClick = controller::toggleLayer) SpaceKey(Modifier.weight(3f), onSpace = controller::onSpace, onCursor = controller::moveCursor) BackspaceKey(Modifier.weight(1.4f), onBackspace = controller::onBackspace) } } else { val keys = controller.keys // Color de la pista de mayúsculas (R↑) según el estado del shift. val shiftAccent = when (controller.shift) { ShiftState.OFF -> Tenue ShiftState.ON -> NeonVerde ShiftState.LOCK -> NeonRosa } // Grid 3×3 (izquierda, ~¾) + columna de funciones (derecha, ~¼), como MessagEase. Row(Modifier.fillMaxWidth().height(210.dp)) { Column(Modifier.weight(3f).fillMaxHeight()) { for (row in 0 until 3) { Row(Modifier.fillMaxWidth().weight(1f)) { for (col in 0 until 3) { KeyCell( key = keys[row * 3 + col], shifted = controller.shifted, onChar = controller::onChar, onShift = controller::onShift, shiftAccent = shiftAccent, modifier = Modifier.weight(1f).fillMaxHeight(), ) } } } } // Columna derecha, arriba→abajo: emoticons, núm/alfa, borrar, enter. // (Mayúsculas ya no va aquí: vive en el swipe ↑ de la R.) Column(Modifier.weight(1f).fillMaxHeight()) { FuncKey("😊", NeonRosa, Modifier.fillMaxWidth().weight(1f), onClick = controller::showEmojis) FuncKey( if (controller.layer == Layer.LETTERS) "?123" else "ABC", NeonCian, Modifier.fillMaxWidth().weight(1f), onClick = controller::toggleLayer, ) BackspaceKey(Modifier.fillMaxWidth().weight(1f), onBackspace = controller::onBackspace) FuncKey("↵", NeonVerde, Modifier.fillMaxWidth().weight(1f), onClick = controller::onEnter) } } // Fila inferior: barra de espacio a todo el ancho. Row(Modifier.fillMaxWidth().height(56.dp)) { SpaceKey(Modifier.weight(1f), onSpace = controller::onSpace, onCursor = controller::moveCursor) } } } } @Composable private fun SuggestionBar(suggestions: List, onPick: (String) -> Unit) { Row( Modifier.fillMaxWidth().height(40.dp).padding(horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { suggestions.forEach { word -> Box( Modifier .weight(1f) .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(10.dp)) .clickable { onPick(word) }, contentAlignment = Alignment.Center, ) { Text( text = word, color = NeonVerde, fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } } } } @Composable private fun FuncKey(label: String, accent: Color, modifier: Modifier, onClick: () -> Unit) { Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .clickable(onClick = onClick), contentAlignment = Alignment.Center, ) { Text( text = label, color = accent, fontSize = if (label.length > 2) 14.sp else 20.sp, fontWeight = FontWeight.Medium, ) } } /** Espacio con swipe horizontal para mover el cursor; toque = espacio. */ @Composable private fun SpaceKey(modifier: Modifier, onSpace: () -> Unit, onCursor: (Int) -> Unit) { val step = with(LocalDensity.current) { 16.dp.toPx() } Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .pointerInput(Unit) { awaitEachGesture { val down = awaitFirstDown() var accum = 0f var moved = false while (true) { val event = awaitPointerEvent() val change = event.changes.firstOrNull { it.id == down.id } ?: break accum += change.positionChange().x change.consume() while (accum >= step) { onCursor(1); accum -= step; moved = true } while (accum <= -step) { onCursor(-1); accum += step; moved = true } if (!change.pressed) break } if (!moved) onSpace() } }, contentAlignment = Alignment.Center, ) { Text("espacio", color = Tenue, fontSize = 14.sp) } } /** Borrado con repetición al mantener pulsado. */ @Composable private fun BackspaceKey(modifier: Modifier, onBackspace: () -> Unit) { val scope = rememberCoroutineScope() Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .pointerInput(Unit) { detectTapGestures( onPress = { onBackspace() val job = scope.launch { delay(380) while (true) { onBackspace() delay(55) } } try { awaitRelease() } finally { job.cancel() } }, ) }, contentAlignment = Alignment.Center, ) { Text("⌫", color = Color.White, fontSize = 20.sp) } }