diff --git a/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt b/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt index 48bf902..eec4ea7 100644 --- a/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt +++ b/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -30,6 +31,7 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -50,6 +52,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import androidx.core.content.ContextCompat import androidx.navigation.NavController import androidx.navigation.NavHostController @@ -66,6 +69,7 @@ import com.google.firebase.ktx.Firebase import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -90,7 +94,22 @@ data class JournalEntry( val dateString: String = "" ) -data class Badge(val title: String, val description: String, val icon: ImageVector, val isUnlocked: Boolean) +data class AssessmentEntry( + val id: String = "", + val userId: String = "", + val totalScore: Int = 0, + val level: String = "", + val timestamp: com.google.firebase.Timestamp? = null, + val dateString: String = "" +) + +data class Badge( + val title: String, + val description: String, + val icon: ImageVector, + val isUnlocked: Boolean, + val dateUnlocked: String? = null +) // --- Main Activity --- class MainActivity : ComponentActivity() { @@ -161,6 +180,9 @@ fun LoginScreen(navController: NavController) { Spacer(modifier = Modifier.height(8.dp)) Text("Lacak kesehatan mental, latih otak, dan temukan ketenangan.", style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 32.dp)) Spacer(modifier = Modifier.height(32.dp)) + + // Login Google dinonaktifkan sementara sesuai permintaan + /* if (isLoading) { CircularProgressIndicator() } else { @@ -170,6 +192,14 @@ fun LoginScreen(navController: NavController) { Text("Masuk dengan Google") } } + */ + Text("Opsi login sedang tidak tersedia", style = MaterialTheme.typography.bodyMedium, color = Color.Gray) + + // Tombol bypass untuk keperluan pengembangan jika diperlukan + Spacer(modifier = Modifier.height(16.dp)) + TextButton(onClick = { navController.navigate("main") { popUpTo("login") { inclusive = true } } }) { + Text("Masuk sebagai Tamu (Dev Mode)") + } } } @@ -222,23 +252,25 @@ fun AppBottomNavigation(navController: NavHostController) { } } -fun calculateDailyStreak(journals: List): Int { - if (journals.isEmpty()) return 0 - val entryDates = journals.map { it.timestamp?.toDate() ?: Date(0) }.map { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(it) }.distinct().sortedDescending() +fun calculateDailyStreak(dates: List): Int { + if (dates.isEmpty()) return 0 + val entryDates = dates.map { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(it) }.distinct().sortedDescending() var streak = 0 val calendar = Calendar.getInstance() val todayStr = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + if (todayStr in entryDates) { streak++ calendar.add(Calendar.DATE, -1) } else { calendar.add(Calendar.DATE, -1) val yesterdayStr = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) - if(yesterdayStr !in entryDates) return 0 + if (yesterdayStr !in entryDates) return 0 } - for (i in 1 until entryDates.size) { + + while (true) { val expectedDateStr = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) - if (entryDates.getOrNull(i) == expectedDateStr) { + if (expectedDateStr in entryDates) { streak++ calendar.add(Calendar.DATE, -1) } else { @@ -248,16 +280,20 @@ fun calculateDailyStreak(journals: List): Int { return streak } -fun getBadges(journals: List, streak: Int): List { - val totalEntries = journals.size +fun getBadges(journals: List, assessments: List, streak: Int): List { + val totalActivities = journals.size + assessments.size val reflectionEntries = journals.count { it.type == "reflection" } - val highStressEntries = journals.count { it.mentalScore > 60 } + + // Untuk demo, kita ambil tanggal hari ini jika lencana terbuka + val today = SimpleDateFormat("d MMM yyyy", Locale("id", "ID")).format(Date()) + return listOf( - Badge("Jurnalis Pertama", "Menulis jurnal pertamamu", Icons.Default.Edit, totalEntries >= 1), - Badge("Seminggu Penuh", "Streak jurnaling 7 hari", Icons.Default.CalendarToday, streak >= 7), - Badge("Reflektor", "Selesaikan 5 refleksi", Icons.Default.SelfImprovement, reflectionEntries >= 5), - Badge("Sadar Diri", "Menganalisis 10 jurnal", Icons.Default.Insights, totalEntries >= 10), - Badge("Pejuang Tangguh", "Mengatasi 3 hari skor tinggi", Icons.Default.Shield, highStressEntries >= 3) + Badge("Langkah Awal", "Lakukan aktivitas pertamamu untuk memulai perjalanan kesehatan mental.", Icons.Default.DirectionsRun, totalActivities >= 1, if(totalActivities >= 1) today else null), + Badge("Penulis Rutin", "Berhasil menulis 5 jurnal bebas sebagai bentuk ekspresi diri.", Icons.Default.EditNote, journals.size >= 5, if(journals.size >= 5) today else null), + Badge("Jiwa Reflektif", "Selesaikan 3 refleksi diri untuk mengenal dirimu lebih dalam.", Icons.Default.SelfImprovement, reflectionEntries >= 3, if(reflectionEntries >= 3) today else null), + Badge("Disiplin Diri", "Pertahankan aktivitas selama 7 hari tanpa terputus.", Icons.Default.LocalFireDepartment, streak >= 7, if(streak >= 7) today else null), + Badge("Pencari Jawaban", "Lakukan 5 penilaian harian untuk memantau kondisi mentalmu.", Icons.Default.Psychology, assessments.size >= 5, if(assessments.size >= 5) today else null), + Badge("Master Fokus", "Selesaikan total 10 aktivitas apa saja di dalam aplikasi.", Icons.Default.EmojiEvents, totalActivities >= 10, if(totalActivities >= 10) today else null) ) } @@ -268,43 +304,214 @@ fun ProfileScreen(navController: NavController) { val user = auth.currentUser val db = Firebase.firestore var journalList by remember { mutableStateOf>(emptyList()) } + var assessmentList by remember { mutableStateOf>(emptyList()) } var dailyStreak by remember { mutableIntStateOf(0) } - var badges by remember { mutableStateOf>(emptyList()) } + var selectedBadge by remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() + LaunchedEffect(user) { if (user != null) { - db.collection("journals").whereEqualTo("userId", user.uid).orderBy("timestamp").get().addOnSuccessListener { result -> - val journals = result.toObjects(JournalEntry::class.java) - journalList = journals - dailyStreak = calculateDailyStreak(journals) - badges = getBadges(journals, dailyStreak) + scope.launch { + val journalTask = db.collection("journals").whereEqualTo("userId", user.uid).get() + val assessmentTask = db.collection("assessments").whereEqualTo("userId", user.uid).get() + + try { + val journalSnap = journalTask.await() + val assessmentSnap = assessmentTask.await() + + journalList = journalSnap.toObjects(JournalEntry::class.java) + assessmentList = assessmentSnap.toObjects(AssessmentEntry::class.java) + + val allDates = (journalList.mapNotNull { it.timestamp?.toDate() } + + assessmentList.mapNotNull { it.timestamp?.toDate() }) + + dailyStreak = calculateDailyStreak(allDates) + } catch (e: Exception) { + Log.e("ProfileScreen", "Error loading data", e) + } } } } + + val badges = remember(journalList, assessmentList, dailyStreak) { + getBadges(journalList, assessmentList, dailyStreak) + } + + if (selectedBadge != null) { + BadgeDetailDialog(badge = selectedBadge!!, onDismiss = { selectedBadge = null }) + } + LazyColumn(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)) { - item { if (user != null) { Row(verticalAlignment = Alignment.CenterVertically) { AsyncImage(model = user.photoUrl, contentDescription = "Profile Picture", modifier = Modifier.size(64.dp).clip(CircleShape)); Spacer(modifier = Modifier.width(16.dp)); Column { Text(user.displayName ?: "Pengguna", style = MaterialTheme.typography.headlineSmall); Text(user.email ?: "", style = MaterialTheme.typography.bodyMedium) } } } } - item { Card(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.LocalFireDepartment, contentDescription = "Streak", tint = Color(0xFFFFA500), modifier = Modifier.size(40.dp)); Spacer(modifier = Modifier.width(16.dp)); Column { Text("$dailyStreak Hari", style = MaterialTheme.typography.headlineMedium); Text("Streak Jurnaling Harian", style = MaterialTheme.typography.bodySmall) } } } } + item { if (user != null) { Row(verticalAlignment = Alignment.CenterVertically) { AsyncImage(model = user.photoUrl, contentDescription = "Profile Picture", modifier = Modifier.size(64.dp).clip(CircleShape)); Spacer(modifier = Modifier.width(16.dp)); Column { Text(user.displayName ?: "Pengguna", style = MaterialTheme.typography.headlineSmall); Text(user.email ?: "Tamu", style = MaterialTheme.typography.bodyMedium) } } } else { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.AccountCircle, null, modifier = Modifier.size(64.dp)); Spacer(modifier = Modifier.width(16.dp)); Column { Text("Mode Tamu", style = MaterialTheme.typography.headlineSmall); Text("Masuk untuk simpan data", style = MaterialTheme.typography.bodyMedium) } } } } + + item { Card(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.LocalFireDepartment, contentDescription = "Streak", tint = Color(0xFFFFA500), modifier = Modifier.size(40.dp)); Spacer(modifier = Modifier.width(16.dp)); Column { Text("$dailyStreak Hari", style = MaterialTheme.typography.headlineMedium); Text("Streak Aktivitas Harian", style = MaterialTheme.typography.bodySmall) } } } } + item { Text("Lencana Pencapaian", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth()) - LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 100.dp), contentPadding = PaddingValues(top = 8.dp), modifier = Modifier.height(120.dp).fillMaxWidth()) { - items(badges) { BadgeItem(badge = it) } + Spacer(modifier = Modifier.height(8.dp)) + } + + item { + LazyVerticalGrid( + columns = GridCells.Fixed(3), + modifier = Modifier.height(240.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(badges) { badge -> + BadgeItem(badge = badge, onClick = { selectedBadge = badge }) + } } } + item { - val currentMonth = Calendar.getInstance().get(Calendar.MONTH) - val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - val isReportAvailable = currentMonth == Calendar.DECEMBER && currentDay >= 15 - Card(onClick = { /* Navigasi ke raport */ }, enabled = isReportAvailable, modifier = Modifier.fillMaxWidth()) { Column(Modifier.padding(16.dp)){ Text("Raport Tahunan", fontWeight = FontWeight.Bold); Text(if(isReportAvailable) "Raport tahun ini sudah tersedia!" else "Tersedia setiap 15 Desember.", style = MaterialTheme.typography.bodySmall) } } + Text("Laporan Kesehatan Mental", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth()) + Spacer(modifier = Modifier.height(8.dp)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ReportCard("Mingguan", "Analisis 7 hari", Modifier.weight(1f)) + ReportCard("Bulanan", "Analisis 30 hari", Modifier.weight(1f)) + } + Spacer(modifier = Modifier.height(8.dp)) + ReportCard("Tahunan", "Tersedia 15 Des", Modifier.fillMaxWidth(), enabled = false) } + item { OutlinedButton(onClick = { navController.navigate("journal_history") }, modifier = Modifier.fillMaxWidth()) { Text("Lihat Riwayat Jurnal Lengkap") } } } } @Composable -fun BadgeItem(badge: Badge) { - Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(8.dp).alpha(if (badge.isUnlocked) 1f else 0.4f)) { - Icon(badge.icon, contentDescription = badge.title, modifier = Modifier.size(48.dp), tint = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray) - Spacer(modifier = Modifier.height(4.dp)) - Text(badge.title, fontSize = 12.sp, textAlign = TextAlign.Center, lineHeight = 14.sp) +fun BadgeItem(badge: Badge, onClick: () -> Unit) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + .alpha(if (badge.isUnlocked) 1f else 0.4f) + ) { + Box( + modifier = Modifier + .size(64.dp) + .clip(CircleShape) + .background(if (badge.isUnlocked) MaterialTheme.colorScheme.primaryContainer else Color.LightGray.copy(alpha = 0.3f)) + .border(2.dp, if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + badge.icon, + contentDescription = badge.title, + modifier = Modifier.size(32.dp), + tint = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray + ) + } + Spacer(modifier = Modifier.height(6.dp)) + Text( + badge.title, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 14.sp + ) + } +} + +@Composable +fun BadgeDetailDialog(badge: Badge, onDismiss: () -> Unit) { + Dialog(onDismissRequest = onDismiss) { + Card( + shape = RoundedCornerShape(24.dp), + modifier = Modifier.fillMaxWidth().padding(16.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier.padding(24.dp).fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + IconButton(onClick = onDismiss) { Icon(Icons.Default.Close, contentDescription = "Close") } + } + + Box( + modifier = Modifier + .size(120.dp) + .clip(CircleShape) + .background(if (badge.isUnlocked) MaterialTheme.colorScheme.primaryContainer else Color.LightGray.copy(alpha = 0.3f)), + contentAlignment = Alignment.Center + ) { + Icon( + badge.icon, + contentDescription = badge.title, + modifier = Modifier.size(60.dp), + tint = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + if (badge.isUnlocked && badge.dateUnlocked != null) { + Surface( + color = Color(0xFF333333), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = badge.dateUnlocked, + color = Color.White, + fontSize = 12.sp, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), + fontWeight = FontWeight.Bold + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + + Text( + text = if (badge.isUnlocked) "Kamu meraih pencapaian" else "Pencapaian Terkunci", + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center + ) + + Text( + text = badge.title, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = badge.description, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Button( + onClick = onDismiss, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(50) + ) { + Text("Tutup") + } + } + } + } +} + +@Composable +fun ReportCard(title: String, subtitle: String, modifier: Modifier = Modifier, enabled: Boolean = true) { + Card( + onClick = { /* Navigasi ke laporan terkait */ }, + enabled = enabled, + modifier = modifier, + colors = if (enabled) CardDefaults.cardColors() else CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) + ) { + Column(Modifier.padding(12.dp)) { + Text(title, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleSmall) + Text(subtitle, style = MaterialTheme.typography.labelSmall, color = if(enabled) MaterialTheme.colorScheme.onSurfaceVariant else Color.Gray) + } } } @@ -361,7 +568,57 @@ fun JournalScreen() { } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AssessmentScreen() { val indicators = remember { listOf("Mood Sedih", "Rasa Bersalah", "Menarik Diri", "Sulit Konsentrasi", "Lelah", "Pikiran Bunuh Diri") }; val sliderValues = remember { mutableStateMapOf().apply { indicators.forEach { put(it, 0f) } } }; val totalScore = sliderValues.values.sum().toInt(); val assessmentLevel = when (totalScore) { in 0..4 -> "Normal"; in 5..9 -> "Ringan"; in 10..14 -> "Sedang"; else -> "Berat" }; Scaffold(topBar = { TopAppBar(title = { Text("Penilaian Harian") }) }) { innerPadding -> LazyColumn(modifier = Modifier.fillMaxSize().padding(innerPadding).padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { item { Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)) { Column(modifier = Modifier.padding(16.dp)) { Text("Ringkasan Skor", style = MaterialTheme.typography.titleMedium); Text("Total Skor: $totalScore", style = MaterialTheme.typography.headlineMedium); Text("Tingkat: $assessmentLevel", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) } } }; items(indicators) { indicator -> IndicatorItem(indicatorName = indicator, value = sliderValues[indicator] ?: 0f) { sliderValues[indicator] = it.roundToInt().toFloat() } }; item { Button(onClick = { /* Save logic */ }, modifier = Modifier.fillMaxWidth()) { Text("Selesai") } } } } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AssessmentScreen() { + val indicators = remember { listOf("Mood Sedih", "Rasa Bersalah", "Menarik Diri", "Sulit Konsentrasi", "Lelah", "Pikiran Bunuh Diri") } + val sliderValues = remember { mutableStateMapOf().apply { indicators.forEach { put(it, 0f) } } } + val totalScore = sliderValues.values.sum().toInt() + val assessmentLevel = when (totalScore) { in 0..4 -> "Normal"; in 5..9 -> "Ringan"; in 10..14 -> "Sedang"; else -> "Berat" } + val auth = Firebase.auth + val db = Firebase.firestore + val context = LocalContext.current + var isSaving by remember { mutableStateOf(false) } + + Scaffold(topBar = { TopAppBar(title = { Text("Penilaian Harian") }) }) { innerPadding -> + LazyColumn(modifier = Modifier.fillMaxSize().padding(innerPadding).padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + item { Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)) { Column(modifier = Modifier.padding(16.dp)) { Text("Ringkasan Skor", style = MaterialTheme.typography.titleMedium); Text("Total Skor: $totalScore", style = MaterialTheme.typography.headlineMedium); Text("Tingkat: $assessmentLevel", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) } } } + items(indicators) { indicator -> IndicatorItem(indicatorName = indicator, value = sliderValues[indicator] ?: 0f) { sliderValues[indicator] = it.roundToInt().toFloat() } } + item { + Button( + onClick = { + val user = auth.currentUser + if (user != null) { + isSaving = true + val entry = hashMapOf( + "userId" to user.uid, + "totalScore" to totalScore, + "level" to assessmentLevel, + "timestamp" to FieldValue.serverTimestamp(), + "dateString" to SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date()) + ) + db.collection("assessments").add(entry) + .addOnSuccessListener { + Toast.makeText(context, "Penilaian berhasil disimpan!", Toast.LENGTH_SHORT).show() + isSaving = false + } + .addOnFailureListener { + Toast.makeText(context, "Gagal menyimpan penilaian", Toast.LENGTH_SHORT).show() + isSaving = false + } + } else { + Toast.makeText(context, "Login dulu untuk menyimpan", Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = !isSaving + ) { + if (isSaving) CircularProgressIndicator(modifier = Modifier.size(20.dp), color = Color.White) + else Text("Selesai & Simpan") + } + } + } + } +} + @Composable fun IndicatorItem(indicatorName: String, value: Float, onValueChange: (Float) -> Unit) { val description = when (value.toInt()) { 0 -> "Tidak sama sekali"; 1 -> "Beberapa hari"; 2 -> "Separuh hari"; 3 -> "Hampir setiap hari"; else -> "" }; Card(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.padding(16.dp)) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text(indicatorName, style = MaterialTheme.typography.titleMedium); Text(text = value.toInt().toString(), style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary) }; Slider(value = value, onValueChange = onValueChange, valueRange = 0f..3f, steps = 2); Text(text = description, style = MaterialTheme.typography.bodySmall, modifier = Modifier.align(Alignment.CenterHorizontally)) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun CognitiveTestScreen(navController: NavController) { Column(modifier = Modifier.fillMaxSize().padding(16.dp).verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)) { Text("Pilih Tes Kognitif", style = MaterialTheme.typography.headlineSmall); TestCard("Tes Memori", "Uji memori jangka pendek", Icons.Default.Memory, "memory_test", navController); TestCard("Tes Fokus", "Uji fokus & atensi", Icons.Default.CenterFocusStrong, "focus_test", navController); TestCard("Tes Kecepatan Reaksi", "Uji refleks visual", Icons.Default.Speed, "reaction_test", navController); TestCard("Tes Logika", "Uji pola pikir & logika", Icons.Default.Psychology, "logical_test", navController) } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun TestCard(title: String, description: String, icon: ImageVector, route: String, navController: NavController) { Card(onClick = { navController.navigate(route) }, modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(icon, null, modifier = Modifier.size(40.dp)); Spacer(Modifier.width(16.dp)); Column { Text(title, style = MaterialTheme.typography.titleMedium); Text(description, style = MaterialTheme.typography.bodySmall, color = Color.Gray) } } } }