package com.craigvg.lichun_android.ui.screens.dating import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.SearchOff import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.craigvg.lichun_android.domain.models.Person import com.craigvg.lichun_android.ui.screens.dating.components.EnhancedProfileCard import com.craigvg.lichun_android.ui.screens.dating.components.MatchCard import com.craigvg.lichun_android.ui.screens.dating.components.MatchExplanation import com.craigvg.lichun_android.ui.screens.dating.components.MatchExplanationToast import com.craigvg.lichun_android.ui.screens.dating.components.QuickActionsBar import com.craigvg.lichun_android.ui.theme.AppColors import com.craigvg.lichun_android.ui.theme.AppSpacing import com.craigvg.lichun_android.ui.theme.AppTypography import com.craigvg.lichun_android.viewmodel.PlayerViewModel import kotlin.math.abs import androidx.lifecycle.compose.collectAsStateWithLifecycle /** * Swipe dating screen with card stack * Ported from iOS SwipeDatingView.swift */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun SwipeDatingScreen( playerViewModel: PlayerViewModel = hiltViewModel(), onBack: () -> Unit = {}, onMatch: (Person) -> Unit = {} ) { var currentCandidate by remember { mutableStateOf(null) } var targetOffsetX by remember { mutableFloatStateOf(0f) } var targetOffsetY by remember { mutableFloatStateOf(0f) } var isDragging by remember { mutableStateOf(false) } var isLoading by remember { mutableStateOf(true) } var showMatchAnimation by remember { mutableStateOf(false) } var matchedPerson by remember { mutableStateOf(null) } var matchExplanation by remember { mutableStateOf(null) } val offsetX by animateFloatAsState( targetValue = targetOffsetX, animationSpec = if (isDragging) snap() else spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMedium ), label = "offsetX" ) val offsetY by animateFloatAsState( targetValue = targetOffsetY, animationSpec = if (isDragging) snap() else spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMedium ), label = "offsetY" ) val swipeThreshold = 150f val rotationAngle = (offsetX / 20f).coerceIn(-15f, 15f) LaunchedEffect(Unit) { playerViewModel.fetchSwipeCharacter() } val swipeCharacter by playerViewModel.swipeCharacter.collectAsStateWithLifecycle() LaunchedEffect(swipeCharacter) { swipeCharacter?.let { currentCandidate = it isLoading = false } } Scaffold( topBar = { TopAppBar( title = { Text("Find a Match", style = AppTypography.headline) }, navigationIcon = { IconButton(onClick = onBack) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = AppColors.primaryText ) } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = AppColors.surfaceElevated ) ) }, containerColor = AppColors.background ) { paddingValues -> Box( modifier = Modifier .fillMaxSize() .padding(paddingValues), contentAlignment = Alignment.Center ) { if (isLoading) { CircularProgressIndicator(color = AppColors.primary) } else if (currentCandidate != null) { Box( modifier = Modifier .fillMaxWidth() .padding(AppSpacing.md), contentAlignment = Alignment.Center ) { // Background card (next in stack) Card( modifier = Modifier .fillMaxWidth(0.9f) .aspectRatio(0.7f) .scale(0.95f), shape = androidx.compose.foundation.shape.RoundedCornerShape(AppSpacing.largeCornerRadius), colors = CardDefaults.cardColors(containerColor = AppColors.surfaceSubtle) ) {} // Current card currentCandidate?.let { candidate -> EnhancedProfileCard( person = candidate, offsetX = offsetX, offsetY = offsetY, rotationAngle = rotationAngle, onDragStart = { isDragging = true }, onDrag = { dragX, dragY -> targetOffsetX += dragX targetOffsetY += dragY }, onDragEnd = { isDragging = false val candidateId = currentCandidate?.id ?: return@EnhancedProfileCard when { targetOffsetX > swipeThreshold -> { playerViewModel.swipeRight(candidateId) matchedPerson = currentCandidate showMatchAnimation = true targetOffsetX = 0f targetOffsetY = 0f isLoading = true playerViewModel.fetchSwipeCharacter() } targetOffsetX < -swipeThreshold -> { playerViewModel.swipeLeft(candidateId) targetOffsetX = 0f targetOffsetY = 0f isLoading = true playerViewModel.fetchSwipeCharacter() } else -> { targetOffsetX = 0f targetOffsetY = 0f } } } ) } // Swipe indicators if (abs(offsetX) > 50) { Box( modifier = Modifier .align(if (offsetX > 0) Alignment.TopStart else Alignment.TopEnd) .padding(AppSpacing.lg) ) { Text( text = if (offsetX > 0) "LIKE" else "PASS", style = AppTypography.title, color = if (offsetX > 0) AppColors.success else AppColors.error, modifier = Modifier.rotate(if (offsetX > 0) -15f else 15f) ) } } } // Action buttons QuickActionsBar( onPass = { currentCandidate?.let { candidate -> playerViewModel.swipeLeft(candidate.id) isLoading = true playerViewModel.fetchSwipeCharacter() } }, onLike = { currentCandidate?.let { candidate -> playerViewModel.swipeRight(candidate.id) matchedPerson = candidate showMatchAnimation = true isLoading = true playerViewModel.fetchSwipeCharacter() } }, modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = AppSpacing.xl) ) // Match animation overlay if (showMatchAnimation) { MatchCard( matchedPerson = matchedPerson, onDismiss = { showMatchAnimation = false // Show explanation toast after match card dismissed matchedPerson?.let { person -> val currentPerson = playerViewModel.person.value matchExplanation = MatchExplanation.generate( person = person, currentPersonInterests = currentPerson.interests ) onMatch(person) } matchedPerson = null } ) } // Match explanation toast matchExplanation?.let { explanation -> MatchExplanationToast( explanation = explanation, onDismiss = { matchExplanation = null } ) } } else { // No more candidates Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(AppSpacing.xl) ) { Icon( imageVector = Icons.Default.SearchOff, contentDescription = null, tint = AppColors.disabledText, modifier = Modifier.size(64.dp) ) Spacer(modifier = Modifier.height(AppSpacing.md)) Text( text = "No more matches nearby", style = AppTypography.headline, color = AppColors.secondaryText, textAlign = TextAlign.Center ) Text( text = "Check back later for new people", style = AppTypography.body, color = AppColors.disabledText, textAlign = TextAlign.Center ) } } } } }