package com.shaarit.presentation.dashboard import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.shaarit.data.local.entity.ContentType import java.text.NumberFormat import java.util.concurrent.TimeUnit @OptIn(ExperimentalMaterial3Api::class) @Composable fun DashboardScreen( onNavigateBack: () -> Unit, viewModel: DashboardViewModel = hiltViewModel() ) { val stats by viewModel.stats.collectAsState() val tagStats by viewModel.tagStats.collectAsState() val contentTypeStats by viewModel.contentTypeStats.collectAsState() val activityData by viewModel.activityData.collectAsState() Scaffold( topBar = { TopAppBar( title = { Text("Tableau de bord") }, navigationIcon = { IconButton(onClick = onNavigateBack) { Icon(Icons.Default.ArrowBack, contentDescription = "Retour") } }, actions = { IconButton(onClick = { viewModel.refreshStats() }) { Icon(Icons.Default.Refresh, contentDescription = "Rafraîchir") } } ) } ) { padding -> LazyColumn( modifier = Modifier .fillMaxSize() .padding(padding), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Stats Cards Row item { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { StatCard( title = "Total", value = formatNumber(stats.totalLinks), icon = Icons.Default.Bookmark, color = MaterialTheme.colorScheme.primary, modifier = Modifier.weight(1f) ) StatCard( title = "Cette semaine", value = formatNumber(stats.linksThisWeek), icon = Icons.Default.Today, color = MaterialTheme.colorScheme.secondary, modifier = Modifier.weight(1f) ) StatCard( title = "Ce mois", value = formatNumber(stats.linksThisMonth), icon = Icons.Default.DateRange, color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.weight(1f) ) } } // Reading Time Stats item { ReadingTimeCard( totalReadingTimeMinutes = stats.totalReadingTimeMinutes, averageReadingTimeMinutes = stats.averageReadingTimeMinutes ) } // Content Type Distribution item { ContentTypeCard(contentTypeStats) } // Top Tags item { TopTagsCard(tagStats) } // Activity Overview item { ActivityCard(activityData) } } } } @Composable private fun StatCard( title: String, value: String, icon: ImageVector, color: Color, modifier: Modifier = Modifier ) { Card( modifier = modifier, colors = CardDefaults.cardColors( containerColor = color.copy(alpha = 0.1f) ) ) { Column( modifier = Modifier .padding(12.dp) .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = icon, contentDescription = null, tint = color, modifier = Modifier.size(24.dp) ) Spacer(modifier = Modifier.height(4.dp)) Text( text = value, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = color ) Text( text = title, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } @Composable private fun ReadingTimeCard( totalReadingTimeMinutes: Int, averageReadingTimeMinutes: Int ) { Card { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "Statistiques de lecture", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { ReadingTimeItem( label = "Temps total", minutes = totalReadingTimeMinutes, icon = Icons.Default.Schedule ) ReadingTimeItem( label = "Moyenne/article", minutes = averageReadingTimeMinutes, icon = Icons.Default.Timer ) } } } } @Composable private fun ReadingTimeItem( label: String, minutes: Int, icon: ImageVector ) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.height(4.dp)) Text( text = formatDuration(minutes), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Text( text = label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @Composable private fun ContentTypeCard( contentTypeStats: Map ) { Card { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "Liens par type", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(12.dp)) val total = contentTypeStats.values.sum().coerceAtLeast(1) for ((type, count) in contentTypeStats.toList().sortedByDescending { it.second }) { val percentage = (count * 100) / total ContentTypeBar( type = type, count = count, percentage = percentage ) Spacer(modifier = Modifier.height(8.dp)) } } } } @Composable private fun ContentTypeBar( type: ContentType, count: Int, percentage: Int ) { val (icon, label, color) = when (type) { ContentType.ARTICLE -> Triple(Icons.Default.Article, "Article", Color(0xFF4CAF50)) ContentType.VIDEO -> Triple(Icons.Default.PlayCircle, "Vidéo", Color(0xFFF44336)) ContentType.IMAGE -> Triple(Icons.Default.Image, "Image", Color(0xFF9C27B0)) ContentType.PODCAST -> Triple(Icons.Default.Audiotrack, "Podcast", Color(0xFFFF9800)) ContentType.PDF -> Triple(Icons.Default.PictureAsPdf, "PDF", Color(0xFFE91E63)) ContentType.REPOSITORY -> Triple(Icons.Default.Code, "Code", Color(0xFF607D8B)) ContentType.DOCUMENT -> Triple(Icons.Default.Description, "Document", Color(0xFF795548)) ContentType.SOCIAL -> Triple(Icons.Default.Share, "Social", Color(0xFF03A9F4)) ContentType.SHOPPING -> Triple(Icons.Default.ShoppingCart, "Shopping", Color(0xFF2196F3)) ContentType.NEWSLETTER -> Triple(Icons.Default.Email, "Newsletter", Color(0xFF9C27B0)) ContentType.UNKNOWN -> Triple(Icons.Default.Link, "Autre", Color(0xFF9E9E9E)) } Column { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = icon, contentDescription = null, tint = color, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) Text( text = label, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f) ) Text( text = "$count ($percentage%)", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } Spacer(modifier = Modifier.height(4.dp)) LinearProgressIndicator( progress = percentage / 100f, modifier = Modifier.fillMaxWidth(), color = color, trackColor = color.copy(alpha = 0.2f) ) } } @Composable private fun TopTagsCard( tagStats: List ) { Card { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "Tags les plus utilisés", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(12.dp)) if (tagStats.isEmpty()) { Text( text = "Aucun tag pour le moment", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } else { tagStats.take(10).forEach { stat -> TagStatRow(stat) if (stat != tagStats.take(10).last()) { Divider(modifier = Modifier.padding(vertical = 8.dp)) } } } } } } @Composable private fun TagStatRow(stat: TagStat) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Surface( color = MaterialTheme.colorScheme.primaryContainer, shape = MaterialTheme.shapes.small ) { Text( text = stat.name, style = MaterialTheme.typography.labelMedium, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), color = MaterialTheme.colorScheme.onPrimaryContainer ) } Spacer(modifier = Modifier.weight(1f)) Text( text = "${stat.count} lien${if (stat.count > 1) "s" else ""}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @Composable private fun ActivityCard( activityData: List ) { Card { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "Activité (30 derniers jours)", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(16.dp)) if (activityData.isEmpty()) { Text( text = "Pas d'activité récente", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } else { ActivityChart(activityData) } } } } @Composable private fun ActivityChart( data: List ) { val maxValue = data.maxOfOrNull { it.count }?.coerceAtLeast(1) ?: 1 Row( modifier = Modifier .fillMaxWidth() .height(100.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Bottom ) { data.takeLast(14).forEach { point -> val heightFraction = point.count.toFloat() / maxValue Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f) ) { Box( modifier = Modifier .width(8.dp) .fillMaxHeight(heightFraction.coerceAtLeast(0.05f)) .background( color = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.extraSmall ) ) Spacer(modifier = Modifier.height(4.dp)) Text( text = point.day.substring(0, 1), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } // Data classes @Immutable data class DashboardStats( val totalLinks: Int = 0, val linksThisWeek: Int = 0, val linksThisMonth: Int = 0, val totalReadingTimeMinutes: Int = 0, val averageReadingTimeMinutes: Int = 0 ) @Immutable data class TagStat( val name: String, val count: Int ) @Immutable data class ActivityPoint( val day: String, val count: Int ) // Helper functions private fun formatNumber(number: Int): String { return NumberFormat.getInstance().format(number) } private fun formatDuration(minutes: Int): String { return when { minutes < 60 -> "${minutes}m" minutes < 1440 -> { val hours = minutes / 60 val mins = minutes % 60 if (mins > 0) "${hours}h ${mins}m" else "${hours}h" } else -> { val days = minutes / 1440 "${days}j" } } }