package app.closer.ui.activity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.closer.domain.model.ActivityItem import app.closer.core.navigation.AppRoute import app.closer.data.remote.FirestoreActivityDataSource import androidx.compose.ui.res.painterResource import app.closer.R import app.closer.ui.components.BrandIllustration import app.closer.domain.repository.AuthRepository import app.closer.ui.components.CloserCard import app.closer.ui.components.CloserHeartLoader import app.closer.ui.settings.SettingsSubpage import app.closer.ui.theme.CloserPalette import app.closer.ui.theme.closerCardColor import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.text.DateFormat import java.util.Date import javax.inject.Inject data class ActivityUiState( val isLoading: Boolean = true, val items: List = emptyList() ) @HiltViewModel class ActivityViewModel @Inject constructor( private val authRepository: AuthRepository, private val activityDataSource: FirestoreActivityDataSource ) : ViewModel() { private val _uiState = MutableStateFlow(ActivityUiState()) val uiState: StateFlow = _uiState.asStateFlow() init { val uid = authRepository.currentUserId if (uid == null) { _uiState.update { it.copy(isLoading = false) } } else { viewModelScope.launch { activityDataSource.observeActivity(uid).collect { items -> _uiState.update { it.copy(isLoading = false, items = items) } } } // Opening the feed clears the unread badge — best effort. viewModelScope.launch { runCatching { activityDataSource.markAllRead(uid) } } } } } @Composable fun ActivityScreen( onNavigate: (String) -> Unit = {}, viewModel: ActivityViewModel = hiltViewModel() ) { val state by viewModel.uiState.collectAsState() SettingsSubpage(title = "Together", onBack = { onNavigate("back") }) { padding -> if (state.isLoading) { Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) { CloserHeartLoader() } } else if (state.items.isEmpty()) { ActivityEmptyState(modifier = Modifier.padding(padding)) } else { LazyColumn( modifier = Modifier .fillMaxSize() .padding(padding) .padding(horizontal = 20.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(state.items, key = { it.id }) { item -> val route = routeForActivityType(item.type) ActivityRow(item, onClick = route?.let { { onNavigate(it) } }) } } } } } /** * Maps an activity item's type to the relevant hub to open on tap. Affection/reminder items * (thinking_of_you, gentle_reminder, streak, reengagement, …) carry no deeper target, so they * return null and the row stays non-tappable. */ private fun routeForActivityType(type: String): String? { val t = type.lowercase() return when { "message" in t || "chat" in t -> AppRoute.MESSAGES "game" in t -> AppRoute.PLAY "capsule" in t -> AppRoute.MEMORY_LANE "challenge" in t -> AppRoute.CONNECTION_CHALLENGES "date" in t -> AppRoute.DATE_MATCHES "answer" in t || "reveal" in t || "question" in t || "daily" in t -> AppRoute.DAILY_QUESTION else -> null } } @Composable private fun ActivityRow(item: ActivityItem, onClick: (() -> Unit)? = null) { CloserCard( modifier = Modifier .fillMaxWidth() .let { if (onClick != null) it.clickable(onClick = onClick) else it }, containerColor = if (item.read) closerCardColor() else CloserPalette.PurpleSoft ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = item.title.ifBlank { "Shared moment" }, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface ) if (item.body.isNotBlank()) { Text( text = item.body, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } if (item.createdAt > 0L) { Text( text = DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(item.createdAt)), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } @Composable private fun ActivityEmptyState(modifier: Modifier = Modifier) { Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Column( modifier = Modifier.padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally ) { BrandIllustration( res = R.drawable.illustration_together_empty, contentDescription = null, modifier = Modifier.size(180.dp) ) Text( text = "Your story, together", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 16.dp) ) Text( text = "Every answer you reveal, every game you play, every little milestone — they'll gather here, just for the two of you.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 6.dp) ) } } }