fix(home,onboarding): correct navigation and state handling

This commit is contained in:
null 2026-06-16 02:18:28 -05:00
parent 6f7ecccb8d
commit cce0349741
2 changed files with 106 additions and 39 deletions

View File

@ -1,5 +1,12 @@
package com.couplesconnect.app.ui.home package com.couplesconnect.app.ui.home
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -12,15 +19,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
@ -31,6 +41,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -102,10 +113,6 @@ private fun HomeContent(
streakCount = state.streakCount streakCount = state.streakCount
) )
if (!state.isPaired && !state.isLoading) {
InvitePartnerCard(onInvite = onInvite)
}
when { when {
state.isLoading -> LoadingHomeCard() state.isLoading -> LoadingHomeCard()
state.error != null -> ErrorHomeCard(message = state.error, onRefresh = onRefresh) state.error != null -> ErrorHomeCard(message = state.error, onRefresh = onRefresh)
@ -133,6 +140,16 @@ private fun HomeContent(
} }
} }
} }
if (!state.isPaired && !state.isLoading) {
PulsingInviteFab(
onClick = onInvite,
modifier = Modifier
.align(Alignment.BottomEnd)
.navigationBarsPadding()
.padding(24.dp)
)
}
} }
} }
@ -169,44 +186,95 @@ private fun HomeHeader(
} }
@Composable @Composable
private fun InvitePartnerCard(onInvite: () -> Unit) { private fun PulsingInviteFab(
Card( onClick: () -> Unit,
modifier = Modifier.fillMaxWidth(), modifier: Modifier = Modifier
shape = RoundedCornerShape(26.dp), ) {
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF4F0)), val infiniteTransition = rememberInfiniteTransition(label = "fab_pulse")
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
val ring1Scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 2.2f,
animationSpec = infiniteRepeatable(
animation = tween(1400, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "ring1"
)
val ring1Alpha by infiniteTransition.animateFloat(
initialValue = 0.6f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(1400, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "ring1a"
)
val ring2Scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 2.2f,
animationSpec = infiniteRepeatable(
animation = tween(1400, delayMillis = 500, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "ring2"
)
val ring2Alpha by infiniteTransition.animateFloat(
initialValue = 0.6f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(1400, delayMillis = 500, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "ring2a"
)
val fabScale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 1.08f,
animationSpec = infiniteRepeatable(
animation = tween(700, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "fabscale"
)
Box(
modifier = modifier.size(72.dp),
contentAlignment = Alignment.Center
) { ) {
Row( // Expanding ring 1
modifier = Modifier.padding(18.dp), Box(
horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier
verticalAlignment = Alignment.CenterVertically .size(56.dp)
.scale(ring1Scale)
.background(
color = Color(0xFFE07A5F).copy(alpha = ring1Alpha),
shape = CircleShape
)
)
// Expanding ring 2 (offset start)
Box(
modifier = Modifier
.size(56.dp)
.scale(ring2Scale)
.background(
color = Color(0xFFE07A5F).copy(alpha = ring2Alpha),
shape = CircleShape
)
)
// FAB
FloatingActionButton(
onClick = onClick,
modifier = Modifier.size(56.dp).scale(fabScale),
containerColor = Color(0xFFE07A5F),
contentColor = Color.White,
shape = CircleShape
) { ) {
Icon( Icon(
Icons.Filled.Favorite, Icons.Filled.Add,
contentDescription = null, contentDescription = "Invite partner",
tint = Color(0xFFE07A5F),
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
) )
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
text = "Invite your partner",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF27211F)
)
Text(
text = "Share a code to connect and start answering together.",
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF4E4642)
)
}
Button(
onClick = onInvite,
shape = RoundedCornerShape(14.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE07A5F))
) {
Text("Invite", color = Color.White)
}
} }
} }
} }

View File

@ -37,8 +37,7 @@ class OnboardingViewModel @Inject constructor(
val user = runCatching { userRepository.getUser(authState.userId) }.getOrNull() val user = runCatching { userRepository.getUser(authState.userId) }.getOrNull()
val destination = when { val destination = when {
user == null || user.displayName.isBlank() -> "create_profile" user == null || user.displayName.isBlank() -> "create_profile"
user.coupleId != null -> "home" else -> "home"
else -> "create_invite"
} }
_uiState.update { it.copy(isCheckingAuth = false, navigateTo = destination) } _uiState.update { it.copy(isCheckingAuth = false, navigateTo = destination) }
} }