asegurate de que subimos todo
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,22 +3,356 @@ package com.fnregistry.citas_kt
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fn.compose.theme.FnRadius
|
||||||
|
import fn.compose.theme.FnSpacing
|
||||||
import fn.compose.theme.FnTheme
|
import fn.compose.theme.FnTheme
|
||||||
|
import fn.compose.ui.FnAlert
|
||||||
|
import fn.compose.ui.FnAlertVariant
|
||||||
|
import fn.compose.ui.FnAppShell
|
||||||
|
import fn.compose.ui.FnAvatar
|
||||||
|
import fn.compose.ui.FnAvatarSize
|
||||||
|
import fn.compose.ui.FnBadge
|
||||||
|
import fn.compose.ui.FnBadgeColor
|
||||||
|
import fn.compose.ui.FnButton
|
||||||
|
import fn.compose.ui.FnButtonVariant
|
||||||
|
import fn.compose.ui.FnCard
|
||||||
|
import fn.compose.ui.FnDialog
|
||||||
|
import fn.compose.ui.FnEmptyState
|
||||||
|
import fn.compose.ui.FnGroup
|
||||||
|
import fn.compose.ui.FnPageHeader
|
||||||
|
import fn.compose.ui.FnStack
|
||||||
|
import fn.compose.ui.FnTabs
|
||||||
|
import fn.compose.ui.FnText
|
||||||
|
import fn.compose.ui.FnTextSize
|
||||||
|
import fn.compose.ui.FnTitle
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
FnTheme {
|
FnTheme {
|
||||||
Surface(modifier = Modifier.fillMaxSize()) {
|
Surface(modifier = Modifier.fillMaxSize()) { CitasApp() }
|
||||||
Text("citas_kt ready")
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Profile(
|
||||||
|
val name: String,
|
||||||
|
val age: Int,
|
||||||
|
val city: String,
|
||||||
|
val bio: String,
|
||||||
|
val interests: List<String>,
|
||||||
|
val gradient: List<Color>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Match(val profile: Profile, val lastMessage: String, val unread: Int)
|
||||||
|
|
||||||
|
private val PROFILES = listOf(
|
||||||
|
Profile(
|
||||||
|
"Lucia", 27, "Madrid",
|
||||||
|
"Disenadora UX. Cafe de especialidad, montana y libros de no ficcion.",
|
||||||
|
listOf("Diseno", "Senderismo", "Lectura", "Cafe"),
|
||||||
|
listOf(Color(0xFFFF6B9D), Color(0xFFC06CFF)),
|
||||||
|
),
|
||||||
|
Profile(
|
||||||
|
"Marcos", 31, "Barcelona",
|
||||||
|
"Cocinero apasionado de la pasta fresca. Busco compania para descubrir mercados.",
|
||||||
|
listOf("Cocina", "Cine", "Viajes"),
|
||||||
|
listOf(Color(0xFF42A5F5), Color(0xFF26C6DA)),
|
||||||
|
),
|
||||||
|
Profile(
|
||||||
|
"Sofia", 24, "Valencia",
|
||||||
|
"Estudiante de biologia marina. Surf los domingos y plantas de interior.",
|
||||||
|
listOf("Surf", "Naturaleza", "Yoga", "Plantas"),
|
||||||
|
listOf(Color(0xFF66BB6A), Color(0xFF26A69A)),
|
||||||
|
),
|
||||||
|
Profile(
|
||||||
|
"Hugo", 29, "Bilbao",
|
||||||
|
"Ingeniero de software por el dia, musico amateur por la noche. Toco guitarra.",
|
||||||
|
listOf("Musica", "Tech", "Cervezas", "Pintxos"),
|
||||||
|
listOf(Color(0xFFFFB74D), Color(0xFFFF7043)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val MATCHES = listOf(
|
||||||
|
Match(PROFILES[0], "Que te parece quedar el sabado para tomar algo?", 2),
|
||||||
|
Match(PROFILES[2], "Jajaja eres muy gracioso, dime mas", 0),
|
||||||
|
Match(PROFILES[3], "Te paso la cancion que te decia", 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CitasApp() {
|
||||||
|
var tab by remember { mutableStateOf(0) }
|
||||||
|
FnAppShell(title = "Latido") { padding ->
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.padding(padding).fillMaxSize(),
|
||||||
|
gap = FnSpacing.xs,
|
||||||
|
) {
|
||||||
|
FnTabs(
|
||||||
|
tabs = listOf("Descubrir", "Matches", "Chats", "Perfil"),
|
||||||
|
selectedIndex = tab,
|
||||||
|
onTabSelected = { tab = it },
|
||||||
|
scrollable = false,
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = FnSpacing.md, vertical = FnSpacing.sm)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
when (tab) {
|
||||||
|
0 -> DiscoverTab()
|
||||||
|
1 -> MatchesTab()
|
||||||
|
2 -> ChatsTab()
|
||||||
|
3 -> ProfileTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DiscoverTab() {
|
||||||
|
var index by remember { mutableStateOf(0) }
|
||||||
|
var liked by remember { mutableStateOf<Profile?>(null) }
|
||||||
|
var passed by remember { mutableStateOf(0) }
|
||||||
|
var likes by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
if (index >= PROFILES.size) {
|
||||||
|
FnEmptyState(
|
||||||
|
title = "No quedan perfiles cerca",
|
||||||
|
description = "Has visto $passed pasados y $likes likes. Vuelve manana.",
|
||||||
|
icon = "💔",
|
||||||
|
action = {
|
||||||
|
FnButton(text = "Reiniciar", onClick = {
|
||||||
|
index = 0; passed = 0; likes = 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val p = PROFILES[index]
|
||||||
|
FnStack(modifier = Modifier.fillMaxSize(), gap = FnSpacing.md) {
|
||||||
|
FnCard(modifier = Modifier.fillMaxWidth(), padding = PaddingValues(0.dp)) {
|
||||||
|
FnStack(gap = FnSpacing.sm) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(280.dp)
|
||||||
|
.clip(RoundedCornerShape(topStart = FnRadius.md, topEnd = FnRadius.md))
|
||||||
|
.background(Brush.linearGradient(p.gradient)),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
FnAvatar(
|
||||||
|
initials = p.name.take(2).uppercase(),
|
||||||
|
size = FnAvatarSize.Lg,
|
||||||
|
backgroundColor = Color.White.copy(alpha = 0.25f),
|
||||||
|
textColor = Color.White,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.padding(horizontal = FnSpacing.md, vertical = FnSpacing.sm),
|
||||||
|
gap = FnSpacing.xs,
|
||||||
|
) {
|
||||||
|
FnTitle(text = "${p.name}, ${p.age}", order = 2)
|
||||||
|
FnText(text = "📍 ${p.city}", size = FnTextSize.Sm)
|
||||||
|
FnText(text = p.bio, size = FnTextSize.Sm)
|
||||||
|
FnGroup(gap = FnSpacing.xs) {
|
||||||
|
p.interests.forEach { tag ->
|
||||||
|
FnBadge(text = tag, color = FnBadgeColor.Brand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FnGroup(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
arrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
FnButton(
|
||||||
|
text = "✕ Pasar",
|
||||||
|
onClick = { passed++; index++ },
|
||||||
|
variant = FnButtonVariant.Outlined,
|
||||||
|
)
|
||||||
|
FnButton(
|
||||||
|
text = "⭐ Super",
|
||||||
|
onClick = { liked = p; likes++; index++ },
|
||||||
|
variant = FnButtonVariant.Secondary,
|
||||||
|
)
|
||||||
|
FnButton(
|
||||||
|
text = "♥ Like",
|
||||||
|
onClick = { liked = p; likes++; index++ },
|
||||||
|
variant = FnButtonVariant.Filled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FnText(
|
||||||
|
text = "${likes} likes · ${passed} pasados",
|
||||||
|
size = FnTextSize.Xs,
|
||||||
|
align = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FnDialog(
|
||||||
|
open = liked != null,
|
||||||
|
onDismiss = { liked = null },
|
||||||
|
title = "Es un match!",
|
||||||
|
description = "A ${liked?.name} tambien le gustas. Escribele ya.",
|
||||||
|
confirmText = "Enviar mensaje",
|
||||||
|
cancelText = "Seguir viendo",
|
||||||
|
onConfirm = { liked = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MatchesTab() {
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||||
|
gap = FnSpacing.md,
|
||||||
|
) {
|
||||||
|
FnPageHeader(title = "Tus matches", subtitle = "${MATCHES.size} personas conectadas")
|
||||||
|
MATCHES.forEach { m ->
|
||||||
|
FnCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
FnGroup(modifier = Modifier.fillMaxWidth(), gap = FnSpacing.md) {
|
||||||
|
FnAvatar(
|
||||||
|
initials = m.profile.name.take(2).uppercase(),
|
||||||
|
size = FnAvatarSize.Md,
|
||||||
|
backgroundColor = m.profile.gradient.first(),
|
||||||
|
textColor = Color.White,
|
||||||
|
)
|
||||||
|
FnStack(modifier = Modifier.weight(1f), gap = 4.dp) {
|
||||||
|
FnText(text = "${m.profile.name}, ${m.profile.age}", size = FnTextSize.Md)
|
||||||
|
FnText(text = m.profile.city, size = FnTextSize.Xs)
|
||||||
|
}
|
||||||
|
if (m.unread > 0) {
|
||||||
|
FnBadge(text = "${m.unread}", color = FnBadgeColor.Brand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ChatsTab() {
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||||
|
gap = FnSpacing.md,
|
||||||
|
) {
|
||||||
|
FnPageHeader(title = "Mensajes", subtitle = "Conversaciones activas")
|
||||||
|
if (MATCHES.isEmpty()) {
|
||||||
|
FnEmptyState(
|
||||||
|
title = "Sin mensajes todavia",
|
||||||
|
description = "Cuando alguien te de like apareceran aqui.",
|
||||||
|
icon = "💬",
|
||||||
|
)
|
||||||
|
return@FnStack
|
||||||
|
}
|
||||||
|
MATCHES.forEach { m ->
|
||||||
|
FnCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
FnGroup(modifier = Modifier.fillMaxWidth(), gap = FnSpacing.md) {
|
||||||
|
FnAvatar(
|
||||||
|
initials = m.profile.name.take(2).uppercase(),
|
||||||
|
size = FnAvatarSize.Md,
|
||||||
|
backgroundColor = m.profile.gradient.first(),
|
||||||
|
textColor = Color.White,
|
||||||
|
)
|
||||||
|
FnStack(modifier = Modifier.weight(1f), gap = 4.dp) {
|
||||||
|
FnGroup(modifier = Modifier.fillMaxWidth(), gap = FnSpacing.xs) {
|
||||||
|
FnText(text = m.profile.name, size = FnTextSize.Md, modifier = Modifier.weight(1f))
|
||||||
|
if (m.unread > 0) FnBadge(text = "${m.unread}", color = FnBadgeColor.Brand)
|
||||||
|
}
|
||||||
|
FnText(text = m.lastMessage, size = FnTextSize.Sm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ProfileTab() {
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||||
|
gap = FnSpacing.md,
|
||||||
|
) {
|
||||||
|
FnCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
FnStack(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
gap = FnSpacing.sm,
|
||||||
|
align = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
FnAvatar(
|
||||||
|
initials = "YO",
|
||||||
|
size = FnAvatarSize.Lg,
|
||||||
|
backgroundColor = Color(0xFFEC407A),
|
||||||
|
textColor = Color.White,
|
||||||
|
)
|
||||||
|
FnTitle(text = "Tu, 28", order = 2)
|
||||||
|
FnText(text = "Madrid · Premium", size = FnTextSize.Sm)
|
||||||
|
FnGroup(gap = FnSpacing.xs) {
|
||||||
|
FnBadge(text = "Verificado", color = FnBadgeColor.Green)
|
||||||
|
FnBadge(text = "Premium", color = FnBadgeColor.Brand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FnAlert(
|
||||||
|
title = "Completa tu perfil",
|
||||||
|
message = "Anade 2 fotos mas y duplica tus matches.",
|
||||||
|
variant = FnAlertVariant.Info,
|
||||||
|
)
|
||||||
|
FnCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
FnStack(gap = FnSpacing.sm) {
|
||||||
|
FnTitle(text = "Estadisticas", order = 3)
|
||||||
|
FnGroup(modifier = Modifier.fillMaxWidth(), arrangement = Arrangement.SpaceBetween) {
|
||||||
|
Stat("Likes dados", "47")
|
||||||
|
Stat("Matches", "${MATCHES.size}")
|
||||||
|
Stat("Vistas", "128")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FnButton(
|
||||||
|
text = "Cerrar sesion",
|
||||||
|
onClick = {},
|
||||||
|
variant = FnButtonVariant.Outlined,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Stat(label: String, value: String) {
|
||||||
|
FnStack(gap = 4.dp, align = Alignment.CenterHorizontally) {
|
||||||
|
FnTitle(text = value, order = 3, color = MaterialTheme.colorScheme.primary)
|
||||||
|
FnText(text = label, size = FnTextSize.Xs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user