This commit is contained in:
202310715066 NABILA SUWANDIRA 2026-01-12 17:15:00 +07:00
parent 23ff80ec12
commit 8253b4adeb
3 changed files with 72 additions and 51 deletions

View File

@ -1,5 +1,5 @@
package com.example.ppb_kelompok2 package com.example.ppb_kelompok2
// Yoseph & Team - Final Version with Guest Mode (No OAuth) // Yoseph & Team - Final Version with Gradient Background
import android.Manifest import android.Manifest
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
@ -43,6 +43,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
@ -75,7 +76,7 @@ import kotlin.random.Random
// Kunci API sekarang diambil dari BuildConfig, bukan hardcoded // Kunci API sekarang diambil dari BuildConfig, bukan hardcoded
const val HF_API_TOKEN = "Bearer ${BuildConfig.HF_API_KEY}" const val HF_API_TOKEN = "Bearer ${BuildConfig.HF_API_KEY}"
const val GUEST_USER_ID = "guest_user_123" // ID tetap untuk mode tanpa login const val GUEST_USER_ID = "guest_user_123"
data class JournalEntry( data class JournalEntry(
val id: String = "", val id: String = "",
@ -92,17 +93,33 @@ data class JournalEntry(
data class Badge(val title: String, val description: String, val icon: ImageVector, val isUnlocked: Boolean) data class Badge(val title: String, val description: String, val icon: ImageVector, val isUnlocked: Boolean)
@Composable
fun AppGradientBackground(content: @Composable () -> Unit) {
val gradient = Brush.verticalGradient(
colors = listOf(
Color(0xFFFFD1DC), // Pink Muda
Color(0xFFFF5252).copy(alpha = 0.3f), // Merah Lembut Transparan
Color.White // Putih
)
)
Box(modifier = Modifier.fillMaxSize().background(gradient)) {
content()
}
}
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
PPB_Kelompok2Theme { PPB_Kelompok2Theme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { AppGradientBackground {
Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) {
AppNavigationGraph() AppNavigationGraph()
} }
} }
} }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101) requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101)
@ -114,7 +131,6 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun AppNavigationGraph() { fun AppNavigationGraph() {
val navController = rememberNavController() val navController = rememberNavController()
// Langsung masuk ke "main" tanpa cek auth
NavHost(navController = navController, startDestination = "main") { NavHost(navController = navController, startDestination = "main") {
composable("main") { MainAppScreen() } composable("main") { MainAppScreen() }
} }
@ -132,7 +148,10 @@ val bottomNavItems = listOf(Screen.Journal, Screen.Assessment, Screen.CognitiveT
@Composable @Composable
fun MainAppScreen() { fun MainAppScreen() {
val navController = rememberNavController() val navController = rememberNavController()
Scaffold(bottomBar = { AppBottomNavigation(navController = navController) }) { innerPadding -> Scaffold(
bottomBar = { AppBottomNavigation(navController = navController) },
containerColor = Color.Transparent // Penting agar gradient di bawah terlihat
) { innerPadding ->
NavHost(navController = navController, startDestination = Screen.Journal.route, modifier = Modifier.padding(innerPadding)) { NavHost(navController = navController, startDestination = Screen.Journal.route, modifier = Modifier.padding(innerPadding)) {
composable(Screen.Journal.route) { JournalScreen() } composable(Screen.Journal.route) { JournalScreen() }
composable(Screen.Assessment.route) { AssessmentScreen() } composable(Screen.Assessment.route) { AssessmentScreen() }
@ -149,7 +168,7 @@ fun MainAppScreen() {
@Composable @Composable
fun AppBottomNavigation(navController: NavHostController) { fun AppBottomNavigation(navController: NavHostController) {
NavigationBar(containerColor = MaterialTheme.colorScheme.surface) { NavigationBar(containerColor = Color.White.copy(alpha = 0.8f)) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
bottomNavItems.forEach { screen -> bottomNavItems.forEach { screen ->
@ -245,7 +264,7 @@ fun ProfileScreen(navController: NavController) {
} }
} }
} }
item { Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.3f))) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.LocalFireDepartment, contentDescription = "Streak", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(40.dp)); Spacer(modifier = Modifier.width(16.dp)); Column { Text("$dailyStreak Hari", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); Text("Streak Jurnaling Harian", style = MaterialTheme.typography.bodySmall) } } } } item { Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.7f))) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.LocalFireDepartment, contentDescription = "Streak", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(40.dp)); Spacer(modifier = Modifier.width(16.dp)); Column { Text("$dailyStreak Hari", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); Text("Streak Jurnaling Harian", style = MaterialTheme.typography.bodySmall) } } } }
item { item {
Text("Lencana Pencapaian", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary) Text("Lencana Pencapaian", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary)
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 100.dp), contentPadding = PaddingValues(top = 8.dp), modifier = Modifier.height(120.dp).fillMaxWidth()) { LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 100.dp), contentPadding = PaddingValues(top = 8.dp), modifier = Modifier.height(120.dp).fillMaxWidth()) {
@ -256,9 +275,9 @@ fun ProfileScreen(navController: NavController) {
val currentMonth = Calendar.getInstance().get(Calendar.MONTH) val currentMonth = Calendar.getInstance().get(Calendar.MONTH)
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
val isReportAvailable = currentMonth == Calendar.DECEMBER && currentDay >= 15 val isReportAvailable = currentMonth == Calendar.DECEMBER && currentDay >= 15
Card(onClick = { /* Navigasi ke raport */ }, enabled = isReportAvailable, modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)) { 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) } } Card(onClick = { /* Navigasi ke raport */ }, enabled = isReportAvailable, modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.5f))) { 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) } }
} }
item { OutlinedButton(onClick = { navController.navigate("journal_history") }, modifier = Modifier.fillMaxWidth()) { Text("Lihat Riwayat Jurnal Lengkap") } } item { OutlinedButton(onClick = { navController.navigate("journal_history") }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary)) { Text("Lihat Riwayat Jurnal Lengkap") } }
} }
} }
@ -298,17 +317,20 @@ fun calculateMentalHealthScore(sentimentLabel: String, sentimentScore: Float, in
@Composable @Composable
fun JournalScreen() { fun JournalScreen() {
var selectedTab by remember { mutableIntStateOf(0) }; var journalText by remember { mutableStateOf("") }; var reflectionAnswers by remember { mutableStateOf(List(3) { "" }) }; var isSaving by remember { mutableStateOf(false) }; var detectedEmotion by remember { mutableStateOf<String?>(null) }; var detectedIssues by remember { mutableStateOf<String?>(null) }; var calculatedScoreFeedback by remember { mutableIntStateOf(-1) }; var isCriticalRisk by remember { mutableStateOf(false) }; val context = LocalContext.current; val db = Firebase.firestore; val scope = rememberCoroutineScope(); val reflectionQuestions = listOf("Satu hal kecil yang membuatmu tersenyum hari ini?", "Tantangan terbesar hari ini & solusinya?", "Satu hal yang ingin kamu perbaiki besok?"); val calendar = Calendar.getInstance(); val timePickerDialog = TimePickerDialog(context, { _, hour, minute -> scheduleReminder(context, hour, minute) }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); val depressionIndicators = listOf("suasana hati sedih", "kehilangan minat", "mudah marah", "perasaan bersalah", "perasaan tidak berharga", "sulit berkonsentrasi", "sulit mengambil keputusan", "pesimis", "menyalahkan diri sendiri", "kehilangan energi", "penurunan aktivitas", "menarik diri sosial", "gangguan tidur", "perubahan nafsu makan", "perubahan berat badan", "pikiran tentang kematian", "keinginan untuk mati", "pikiran bunuh diri", "rencana bunuh diri") var selectedTab by remember { mutableIntStateOf(0) }; var journalText by remember { mutableStateOf("") }; var reflectionAnswers by remember { mutableStateOf(List(3) { "" }) }; var isSaving by remember { mutableStateOf(false) }; var detectedEmotion by remember { mutableStateOf<String?>(null) }; var detectedIssues by remember { mutableStateOf<String?>(null) }; var calculatedScoreFeedback by remember { mutableIntStateOf(-1) }; var isCriticalRisk by remember { mutableStateOf(false) }; val context = LocalContext.current; val db = Firebase.firestore; val scope = rememberCoroutineScope(); val reflectionQuestions = listOf("Satu hal kecil yang membuatmu tersenyum hari ini?", "Tantangan terbesar hari ini & solusinya?", "Satu hal yang ingin kamu perbaiki besok?"); val calendar = Calendar.getInstance(); val timePickerDialog = TimePickerDialog(context, { _, hour, minute -> scheduleReminder(context, hour, minute) }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); val depressionIndicators = listOf("suasana hati sedih", "kehilangan minat", "mudah marah", "perasaan bersalah", "perasaan tidak berharga", "sulit berkonsentrasi", "sulit mengambil keputusan", "pesimis", "menyalahkan diri sendiri", "kehilangan energi", "penurunan aktivitas", "menarik diri sosial", "gangguan tidur", "perubahan nafsu makan", "perubahan berat badan", "pikiran tentang kematian", "keinginan untuk mati", "pikiran bunuh diri", "rencana bunuh diri")
Scaffold(topBar = { TopAppBar(title = { Text("Jurnal & Refleksi", color = MaterialTheme.colorScheme.primary) }, actions = { IconButton(onClick = { timePickerDialog.show() }) { Icon(Icons.Default.Alarm, contentDescription = "Set Reminder", tint = MaterialTheme.colorScheme.primary) } }) }) { padding -> Scaffold(
topBar = { TopAppBar(title = { Text("Jurnal & Refleksi", color = MaterialTheme.colorScheme.primary) }, actions = { IconButton(onClick = { timePickerDialog.show() }) { Icon(Icons.Default.Alarm, contentDescription = "Set Reminder", tint = MaterialTheme.colorScheme.primary) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)) },
containerColor = Color.Transparent
) { padding ->
Column(modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column(modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
TabRow(selectedTabIndex = selectedTab, containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.primary, indicator = { tabPositions -> TabRow(selectedTabIndex = selectedTab, containerColor = Color.White.copy(alpha = 0.5f), contentColor = MaterialTheme.colorScheme.primary, indicator = { tabPositions ->
TabRowDefaults.SecondaryIndicator(Modifier.tabIndicatorOffset(tabPositions[selectedTab]), color = MaterialTheme.colorScheme.primary) TabRowDefaults.SecondaryIndicator(Modifier.tabIndicatorOffset(tabPositions[selectedTab]), color = MaterialTheme.colorScheme.primary)
}) { Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }, text = { Text("Jurnal Bebas") }); Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }, text = { Text("Refleksi") }) } }) { Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }, text = { Text("Jurnal Bebas") }); Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }, text = { Text("Refleksi") }) }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
if (selectedTab == 0) { if (selectedTab == 0) {
Card(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), colors = if (isCriticalRisk) CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer) else CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.2f))) { Column(modifier = Modifier.padding(16.dp)) { Text("Analisis AI", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary); if (isCriticalRisk) { Text("⚠️ PERINGATAN RISIKO", color = MaterialTheme.colorScheme.error, fontWeight = FontWeight.Bold); Text("Terdeteksi sinyal bahaya. Anda tidak sendiri. Segera hubungi bantuan profesional.", style = MaterialTheme.typography.bodySmall); Spacer(Modifier.height(8.dp)) }; if (detectedEmotion != null) Text("Emosi: $detectedEmotion", color = MaterialTheme.colorScheme.secondary); if (detectedIssues != null) Text("Indikator: $detectedIssues", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary); if (calculatedScoreFeedback != -1) Text("Skor Depresi: $calculatedScoreFeedback/100", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary); if (detectedEmotion == null && detectedIssues == null) Text("Ceritakan harimu untuk analisis mendalam.", style = MaterialTheme.typography.bodySmall, color = Color.Gray) } } Card(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), colors = if (isCriticalRisk) CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer) else CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.8f))) { Column(modifier = Modifier.padding(16.dp)) { Text("Analisis AI", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary); if (isCriticalRisk) { Text("⚠️ PERINGATAN RISIKO", color = MaterialTheme.colorScheme.error, fontWeight = FontWeight.Bold); Text("Terdeteksi sinyal bahaya. Anda tidak sendiri. Segera hubungi bantuan profesional.", style = MaterialTheme.typography.bodySmall); Spacer(Modifier.height(8.dp)) }; if (detectedEmotion != null) Text("Emosi: $detectedEmotion", color = MaterialTheme.colorScheme.secondary); if (detectedIssues != null) Text("Indikator: $detectedIssues", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary); if (calculatedScoreFeedback != -1) Text("Skor Depresi: $calculatedScoreFeedback/100", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary); if (detectedEmotion == null && detectedIssues == null) Text("Ceritakan harimu untuk analisis mendalam.", style = MaterialTheme.typography.bodySmall, color = Color.Gray) } }
OutlinedTextField(value = journalText, onValueChange = { journalText = it }, label = { Text("Tuliskan perasaanmu di sini...") }, modifier = Modifier.fillMaxWidth().weight(1f), colors = OutlinedTextFieldDefaults.colors(focusedBorderColor = MaterialTheme.colorScheme.primary, cursorColor = MaterialTheme.colorScheme.primary)) OutlinedTextField(value = journalText, onValueChange = { journalText = it }, label = { Text("Tuliskan perasaanmu di sini...") }, modifier = Modifier.fillMaxWidth().weight(1f), colors = OutlinedTextFieldDefaults.colors(focusedBorderColor = MaterialTheme.colorScheme.primary, cursorColor = MaterialTheme.colorScheme.primary, focusedContainerColor = Color.White.copy(alpha = 0.6f), unfocusedContainerColor = Color.White.copy(alpha = 0.6f)))
} else { } else {
LazyColumn(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)) { items(reflectionQuestions.size) { index -> Text(reflectionQuestions[index], fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary); OutlinedTextField(value = reflectionAnswers[index], onValueChange = { newValue -> val newList = reflectionAnswers.toMutableList(); newList[index] = newValue; reflectionAnswers = newList }, modifier = Modifier.fillMaxWidth(), placeholder = { Text("Jawabanmu...") }, colors = OutlinedTextFieldDefaults.colors(focusedBorderColor = MaterialTheme.colorScheme.primary)) } } LazyColumn(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)) { items(reflectionQuestions.size) { index -> Text(reflectionQuestions[index], fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary); OutlinedTextField(value = reflectionAnswers[index], onValueChange = { newValue -> val newList = reflectionAnswers.toMutableList(); newList[index] = newValue; reflectionAnswers = newList }, modifier = Modifier.fillMaxWidth(), placeholder = { Text("Jawabanmu...") }, colors = OutlinedTextFieldDefaults.colors(focusedBorderColor = MaterialTheme.colorScheme.primary, focusedContainerColor = Color.White.copy(alpha = 0.6f), unfocusedContainerColor = Color.White.copy(alpha = 0.6f))) } }
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { val contentToSave = if (selectedTab == 0) journalText else reflectionAnswers.joinToString("\n\n") { it }; if (contentToSave.isBlank()) { Toast.makeText(context, "Isi tidak boleh kosong", Toast.LENGTH_SHORT).show(); return@Button }; isSaving = true; detectedEmotion = "Menganalisis..."; detectedIssues = ""; calculatedScoreFeedback = -1; isCriticalRisk = false Button(onClick = { val contentToSave = if (selectedTab == 0) journalText else reflectionAnswers.joinToString("\n\n") { it }; if (contentToSave.isBlank()) { Toast.makeText(context, "Isi tidak boleh kosong", Toast.LENGTH_SHORT).show(); return@Button }; isSaving = true; detectedEmotion = "Menganalisis..."; detectedIssues = ""; calculatedScoreFeedback = -1; isCriticalRisk = false
@ -320,26 +342,26 @@ 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<String, Float>().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", color = MaterialTheme.colorScheme.primary) }) }) { 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.primary)) { Column(modifier = Modifier.padding(16.dp)) { Text("Ringkasan Skor", style = MaterialTheme.typography.titleMedium, color = Color.White); Text("Total Skor: $totalScore", style = MaterialTheme.typography.headlineMedium, color = Color.White); Text("Tingkat: $assessmentLevel", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, color = Color.White) } } }; 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<String, Float>().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", color = MaterialTheme.colorScheme.primary) }, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)) }, containerColor = Color.Transparent) { 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.primary)) { Column(modifier = Modifier.padding(16.dp)) { Text("Ringkasan Skor", style = MaterialTheme.typography.titleMedium, color = Color.White); Text("Total Skor: $totalScore", style = MaterialTheme.typography.headlineMedium, color = Color.White); Text("Tingkat: $assessmentLevel", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, color = Color.White) } } }; 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") } } } } }
@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(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)) { Column(modifier = Modifier.padding(16.dp)) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text(indicatorName, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary); 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, colors = SliderDefaults.colors(thumbColor = MaterialTheme.colorScheme.primary, activeTrackColor = MaterialTheme.colorScheme.primary)); Text(text = description, style = MaterialTheme.typography.bodySmall, modifier = Modifier.align(Alignment.CenterHorizontally), color = MaterialTheme.colorScheme.secondary) } } } @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(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.6f))) { Column(modifier = Modifier.padding(16.dp)) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text(indicatorName, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary); 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, colors = SliderDefaults.colors(thumbColor = MaterialTheme.colorScheme.primary, activeTrackColor = MaterialTheme.colorScheme.primary)); Text(text = description, style = MaterialTheme.typography.bodySmall, modifier = Modifier.align(Alignment.CenterHorizontally), color = MaterialTheme.colorScheme.secondary) } } }
@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, color = MaterialTheme.colorScheme.primary); 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 CognitiveTestScreen(navController: NavController) { Box(Modifier.fillMaxSize()) { 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, color = MaterialTheme.colorScheme.primary); 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(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.4f))) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(icon, null, modifier = Modifier.size(40.dp), tint = MaterialTheme.colorScheme.primary); Spacer(Modifier.width(16.dp)); Column { Text(title, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary); Text(description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.secondary) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun TestCard(title: String, description: String, icon: ImageVector, route: String, navController: NavController) { Card(onClick = { navController.navigate(route) }, modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.7f))) { Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Icon(icon, null, modifier = Modifier.size(40.dp), tint = MaterialTheme.colorScheme.primary); Spacer(Modifier.width(16.dp)); Column { Text(title, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary); Text(description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.secondary) } } } }
data class LogicalQuestion(val question: String, val options: List<String>, val correctAnswer: Int) data class LogicalQuestion(val question: String, val options: List<String>, val correctAnswer: Int)
@OptIn(ExperimentalMaterial3Api::class) @Composable fun LogicalTestScreen(navController: NavController) { val questions = remember { listOf(LogicalQuestion("Pola: 2, 4, 8, 16, ?", listOf("24", "32", "30", "20"), 1), LogicalQuestion("Paus di air. Maka...", listOf("Paus ikan", "Paus bukan ikan", "Tak dapat disimpulkan"), 2)).shuffled() }; var currentQuestionIndex by remember { mutableIntStateOf(0) }; var score by remember { mutableIntStateOf(0) }; var isFinished by remember { mutableStateOf(false) }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Logika") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }) }) { padding -> Column(modifier = Modifier.padding(padding).padding(16.dp).fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { if (!isFinished) { val q = questions[currentQuestionIndex]; Text("Pertanyaan ${currentQuestionIndex + 1} / ${questions.size}", color = MaterialTheme.colorScheme.primary); Spacer(Modifier.height(16.dp)); Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.3f))) { Text(q.question, modifier = Modifier.padding(16.dp), color = MaterialTheme.colorScheme.primary) }; Spacer(Modifier.height(24.dp)); q.options.forEachIndexed { index, option -> Button(onClick = { if (index == q.correctAnswer) score++; if (currentQuestionIndex < questions.size - 1) currentQuestionIndex++ else isFinished = true }, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) { Text(option) } } } else { Text("Tes Selesai! Skor: $score/${questions.size}", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); Button(onClick = { navController.popBackStack() }) { Text("Kembali") } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun LogicalTestScreen(navController: NavController) { val questions = remember { listOf(LogicalQuestion("Pola: 2, 4, 8, 16, ?", listOf("24", "32", "30", "20"), 1), LogicalQuestion("Paus di air. Maka...", listOf("Paus ikan", "Paus bukan ikan", "Tak dapat disimpulkan"), 2)).shuffled() }; var currentQuestionIndex by remember { mutableIntStateOf(0) }; var score by remember { mutableIntStateOf(0) }; var isFinished by remember { mutableStateOf(false) }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Logika") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)) }, containerColor = Color.Transparent) { padding -> Column(modifier = Modifier.padding(padding).padding(16.dp).fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { if (!isFinished) { val q = questions[currentQuestionIndex]; Text("Pertanyaan ${currentQuestionIndex + 1} / ${questions.size}", color = MaterialTheme.colorScheme.primary); Spacer(Modifier.height(16.dp)); Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.8f))) { Text(q.question, modifier = Modifier.padding(16.dp), color = MaterialTheme.colorScheme.primary) }; Spacer(Modifier.height(24.dp)); q.options.forEachIndexed { index, option -> Button(onClick = { if (index == q.correctAnswer) score++; if (currentQuestionIndex < questions.size - 1) currentQuestionIndex++ else isFinished = true }, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) { Text(option) } } } else { Text("Tes Selesai! Skor: $score/${questions.size}", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); Button(onClick = { navController.popBackStack() }) { Text("Kembali") } } } } }
data class MemoryCard(val id: Int, val icon: ImageVector, var isFaceUp: Boolean = false, var isMatched: Boolean = false) data class MemoryCard(val id: Int, val icon: ImageVector, var isFaceUp: Boolean = false, var isMatched: Boolean = false)
enum class MemoryGameState { READY, PLAYING, FINISHED } enum class MemoryGameState { READY, PLAYING, FINISHED }
@OptIn(ExperimentalMaterial3Api::class) @Composable fun MemoryTestScreen(navController: NavController) { val icons = listOf(Icons.Default.Favorite, Icons.Default.Star, Icons.Default.ThumbUp, Icons.Default.Spa, Icons.Default.Cloud, Icons.Default.Anchor); var cards by remember { mutableStateOf((icons + icons).mapIndexed { i, icon -> MemoryCard(i, icon) }.shuffled()) }; var selectedCards by remember { mutableStateOf(listOf<MemoryCard>()) }; var moves by remember { mutableIntStateOf(0) }; var gameState by remember { mutableStateOf(MemoryGameState.READY) }; LaunchedEffect(selectedCards) { if (selectedCards.size == 2) { val (c1, c2) = selectedCards; if (c1.icon == c2.icon) { cards = cards.map { if (it.id == c1.id || it.id == c2.id) it.copy(isMatched = true) else it } } else { delay(1000); cards = cards.map { if (it.id == c1.id || it.id == c2.id) it.copy(isFaceUp = false) else it } }; selectedCards = emptyList() } }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Memori") }, navigationIcon = { IconButton(onClick = {navController.popBackStack()}) { Icon(Icons.AutoMirrored.Filled.ArrowBack,null)}})}) { p -> Column(Modifier.padding(p).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { if (gameState == MemoryGameState.READY) { Button(onClick = { gameState = MemoryGameState.PLAYING }) { Text("Mulai") } } else { Text("Gerakan: $moves", color = MaterialTheme.colorScheme.primary); LazyVerticalGrid(GridCells.Fixed(3)) { items(cards) { card -> MemoryCardView(card) { if (!card.isFaceUp && !card.isMatched && selectedCards.size < 2) { cards = cards.map { if (it.id == card.id) it.copy(isFaceUp = true) else it }; selectedCards = selectedCards + card; if (selectedCards.size == 1) moves++ } } } }; if (cards.all { it.isMatched }) { Text("Selesai! Skor: $moves", color = MaterialTheme.colorScheme.primary); Button(onClick = { cards = (icons + icons).mapIndexed{i,c->MemoryCard(i,c)}.shuffled(); moves=0; gameState=MemoryGameState.PLAYING }) { Text("Main Lagi") } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun MemoryTestScreen(navController: NavController) { val icons = listOf(Icons.Default.Favorite, Icons.Default.Star, Icons.Default.ThumbUp, Icons.Default.Spa, Icons.Default.Cloud, Icons.Default.Anchor); var cards by remember { mutableStateOf((icons + icons).mapIndexed { i, icon -> MemoryCard(i, icon) }.shuffled()) }; var selectedCards by remember { mutableStateOf(listOf<MemoryCard>()) }; var moves by remember { mutableIntStateOf(0) }; var gameState by remember { mutableStateOf(MemoryGameState.READY) }; LaunchedEffect(selectedCards) { if (selectedCards.size == 2) { val (c1, c2) = selectedCards; if (c1.icon == c2.icon) { cards = cards.map { if (it.id == c1.id || it.id == c2.id) it.copy(isMatched = true) else it } } else { delay(1000); cards = cards.map { if (it.id == c1.id || it.id == c2.id) it.copy(isFaceUp = false) else it } }; selectedCards = emptyList() } }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Memori") }, navigationIcon = { IconButton(onClick = {navController.popBackStack()}) { Icon(Icons.AutoMirrored.Filled.ArrowBack,null)}}, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent))}, containerColor = Color.Transparent) { p -> Column(Modifier.padding(p).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { if (gameState == MemoryGameState.READY) { Button(onClick = { gameState = MemoryGameState.PLAYING }) { Text("Mulai") } } else { Text("Gerakan: $moves", color = MaterialTheme.colorScheme.primary); LazyVerticalGrid(GridCells.Fixed(3)) { items(cards) { card -> MemoryCardView(card) { if (!card.isFaceUp && !card.isMatched && selectedCards.size < 2) { cards = cards.map { if (it.id == card.id) it.copy(isFaceUp = true) else it }; selectedCards = selectedCards + card; if (selectedCards.size == 1) moves++ } } } }; if (cards.all { it.isMatched }) { Text("Selesai! Skor: $moves", color = MaterialTheme.colorScheme.primary); Button(onClick = { cards = (icons + icons).mapIndexed{i,c->MemoryCard(i,c)}.shuffled(); moves=0; gameState=MemoryGameState.PLAYING }) { Text("Main Lagi") } } } } } }
@OptIn(ExperimentalMaterial3Api::class) @Composable fun MemoryCardView(card: MemoryCard, onClick: () -> Unit) { Card(onClick = onClick, modifier = Modifier.padding(4.dp).aspectRatio(1f), colors = CardDefaults.cardColors(containerColor = if(card.isFaceUp || card.isMatched) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.tertiary.copy(alpha = 0.5f))) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { if(card.isFaceUp||card.isMatched) Icon(card.icon,null, tint = Color.White) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun MemoryCardView(card: MemoryCard, onClick: () -> Unit) { Card(onClick = onClick, modifier = Modifier.padding(4.dp).aspectRatio(1f), colors = CardDefaults.cardColors(containerColor = if(card.isFaceUp || card.isMatched) MaterialTheme.colorScheme.primary else Color.White.copy(alpha = 0.6f))) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { if(card.isFaceUp||card.isMatched) Icon(card.icon,null, tint = Color.White) } } }
enum class FocusGameState { READY, PLAYING, FINISHED } enum class FocusGameState { READY, PLAYING, FINISHED }
data class FocusItem(val icon: ImageVector, val color: Color, val rotation: Float, val isDistractor: Boolean) data class FocusItem(val icon: ImageVector, val color: Color, val rotation: Float, val isDistractor: Boolean)
@OptIn(ExperimentalMaterial3Api::class) @Composable fun FocusTestScreen(navController: NavController) { var score by remember { mutableIntStateOf(0) }; var highScore by remember { mutableIntStateOf(0) }; val normalColor = MaterialTheme.colorScheme.onSurface; var gridItems by remember { mutableStateOf(generateFocusGrid(normalColor)) }; var gameState by remember { mutableStateOf(FocusGameState.READY) }; var selectedDuration by remember { mutableIntStateOf(15) }; var timeLeft by remember { mutableIntStateOf(selectedDuration) }; LaunchedEffect(gameState) { if (gameState == FocusGameState.PLAYING) { timeLeft = selectedDuration; while (timeLeft > 0) { delay(1000); timeLeft-- }; if (timeLeft == 0) gameState = FocusGameState.FINISHED } else if (gameState == FocusGameState.FINISHED) { if (score > highScore) { highScore = score } } }; fun newLevel() { gridItems = generateFocusGrid(normalColor) }; fun restartGame() { score = 0; gameState = FocusGameState.READY; newLevel() }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Fokus") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }) }) { innerPadding -> Column(modifier = Modifier.fillMaxSize().padding(innerPadding).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) { when (gameState) { FocusGameState.READY -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Tes Fokus", color = MaterialTheme.colorScheme.primary); Button(onClick = { gameState = FocusGameState.PLAYING }) { Text("Mulai") } } }; FocusGameState.PLAYING -> { Row { Text("Skor: $score", color = MaterialTheme.colorScheme.primary); Spacer(Modifier.width(16.dp)); Text("Waktu: $timeLeft", color = MaterialTheme.colorScheme.secondary) }; LazyVerticalGrid(columns = GridCells.Fixed(5)) { items(gridItems.indices.toList()) { index -> val item = gridItems[index]; Icon(imageVector = item.icon, null, tint = item.color, modifier = Modifier.clickable { if (item.isDistractor) { score++; newLevel() } else { if (score > 0) score-- } }.padding(8.dp).size(40.dp)) } } }; FocusGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Waktu Habis!", color = MaterialTheme.colorScheme.primary); Text("Skor Akhir: $score", style = MaterialTheme.typography.headlineMedium); Button(onClick = { restartGame() }) { Text("Coba Lagi") } } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun FocusTestScreen(navController: NavController) { var score by remember { mutableIntStateOf(0) }; var highScore by remember { mutableIntStateOf(0) }; val normalColor = MaterialTheme.colorScheme.onSurface; var gridItems by remember { mutableStateOf(generateFocusGrid(normalColor)) }; var gameState by remember { mutableStateOf(FocusGameState.READY) }; var selectedDuration by remember { mutableIntStateOf(15) }; var timeLeft by remember { mutableIntStateOf(selectedDuration) }; LaunchedEffect(gameState) { if (gameState == FocusGameState.PLAYING) { timeLeft = selectedDuration; while (timeLeft > 0) { delay(1000); timeLeft-- }; if (timeLeft == 0) gameState = FocusGameState.FINISHED } else if (gameState == FocusGameState.FINISHED) { if (score > highScore) { highScore = score } } }; fun newLevel() { gridItems = generateFocusGrid(normalColor) }; fun restartGame() { score = 0; gameState = FocusGameState.READY; newLevel() }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Fokus") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)) }, containerColor = Color.Transparent) { innerPadding -> Column(modifier = Modifier.fillMaxSize().padding(innerPadding).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) { when (gameState) { FocusGameState.READY -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Tes Fokus", color = MaterialTheme.colorScheme.primary); Button(onClick = { gameState = FocusGameState.PLAYING }) { Text("Mulai") } } }; FocusGameState.PLAYING -> { Row { Text("Skor: $score", color = MaterialTheme.colorScheme.primary); Spacer(Modifier.width(16.dp)); Text("Waktu: $timeLeft", color = MaterialTheme.colorScheme.secondary) }; LazyVerticalGrid(columns = GridCells.Fixed(5)) { items(gridItems.indices.toList()) { index -> val item = gridItems[index]; Icon(imageVector = item.icon, null, tint = item.color, modifier = Modifier.clickable { if (item.isDistractor) { score++; newLevel() } else { if (score > 0) score-- } }.padding(8.dp).size(40.dp)) } } }; FocusGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Waktu Habis!", color = MaterialTheme.colorScheme.primary); Text("Skor Akhir: $score", style = MaterialTheme.typography.headlineMedium); Button(onClick = { restartGame() }) { Text("Coba Lagi") } } } } } } }
private fun generateFocusGrid(normalColor: Color): List<FocusItem> { val gridSize = 25; val normalIcon = Icons.Default.Circle; val distractorIndex = Random.nextInt(gridSize); val distractorType = Random.nextInt(3); val distractor: FocusItem; val items = MutableList(gridSize) { FocusItem(normalIcon, normalColor, 0f, false) }; when (distractorType) { 0 -> { distractor = FocusItem(Icons.Default.Star, normalColor, 0f, true) }; 1 -> { distractor = FocusItem(normalIcon, Color.Red, 0f, true) }; else -> { distractor = FocusItem(Icons.Default.Navigation, normalColor, 90f, true); items.replaceAll { it.copy(icon = Icons.Default.Navigation) } } }; items[distractorIndex] = distractor; return items } private fun generateFocusGrid(normalColor: Color): List<FocusItem> { val gridSize = 25; val normalIcon = Icons.Default.Circle; val distractorIndex = Random.nextInt(gridSize); val distractorType = Random.nextInt(3); val distractor: FocusItem; val items = MutableList(gridSize) { FocusItem(normalIcon, normalColor, 0f, false) }; when (distractorType) { 0 -> { distractor = FocusItem(Icons.Default.Star, normalColor, 0f, true) }; 1 -> { distractor = FocusItem(normalIcon, Color.Red, 0f, true) }; else -> { distractor = FocusItem(Icons.Default.Navigation, normalColor, 90f, true); items.replaceAll { it.copy(icon = Icons.Default.Navigation) } } }; items[distractorIndex] = distractor; return items }
enum class ReactionGameState { READY, WAITING, ACTION, FINISHED } enum class ReactionGameState { READY, WAITING, ACTION, FINISHED }
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ReactionSpeedTestScreen(navController: NavController) { var state by remember { mutableStateOf(ReactionGameState.READY) }; var startTime by remember { mutableLongStateOf(0L) }; var reactionTime by remember { mutableLongStateOf(0L) }; var bestTime by remember { mutableStateOf<Long?>(null) }; val backgroundColor by animateColorAsState(targetValue = when (state) { ReactionGameState.WAITING -> Color.Red; ReactionGameState.ACTION -> Color.Green; else -> MaterialTheme.colorScheme.surface }, label=""); val onScreenClick = { when (state) { ReactionGameState.WAITING -> { reactionTime = -1; state = ReactionGameState.FINISHED }; ReactionGameState.ACTION -> { val newTime = System.currentTimeMillis() - startTime; reactionTime = newTime; if (bestTime == null || newTime < bestTime!!) { bestTime = newTime }; state = ReactionGameState.FINISHED }; else -> {} } }; LaunchedEffect(state) { if (state == ReactionGameState.WAITING) { delay(Random.nextLong(1500, 5500)); if (state == ReactionGameState.WAITING) { startTime = System.currentTimeMillis(); state = ReactionGameState.ACTION } } }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Kecepatan Reaksi") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }) }) { innerPadding -> Box(modifier = Modifier.fillMaxSize().padding(innerPadding).background(backgroundColor).clickable(enabled = state == ReactionGameState.WAITING || state == ReactionGameState.ACTION, onClick = onScreenClick), contentAlignment = Alignment.Center) { when (state) { ReactionGameState.READY -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Tes Kecepatan Reaksi", color = MaterialTheme.colorScheme.primary); Button(onClick = { state = ReactionGameState.WAITING }) { Text("Mulai") } } }; ReactionGameState.WAITING -> { Text("Tunggu sampai hijau...", color = Color.White, style = MaterialTheme.typography.headlineMedium) }; ReactionGameState.ACTION -> { Text("Tekan Sekarang!", color = Color.White, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold) }; ReactionGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { val resultText = if (reactionTime == -1L) "Terlalu Cepat!" else "${reactionTime} ms"; Text(resultText, style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); if(bestTime != null) Text("Terbaik: $bestTime ms", style = MaterialTheme.typography.bodySmall); Button(onClick = { state = ReactionGameState.READY }) { Text("Coba Lagi") } } } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun ReactionSpeedTestScreen(navController: NavController) { var state by remember { mutableStateOf(ReactionGameState.READY) }; var startTime by remember { mutableLongStateOf(0L) }; var reactionTime by remember { mutableLongStateOf(0L) }; var bestTime by remember { mutableStateOf<Long?>(null) }; val backgroundColor by animateColorAsState(targetValue = when (state) { ReactionGameState.WAITING -> Color.Red; ReactionGameState.ACTION -> Color.Green; else -> Color.Transparent }, label=""); val onScreenClick = { when (state) { ReactionGameState.WAITING -> { reactionTime = -1; state = ReactionGameState.FINISHED }; ReactionGameState.ACTION -> { val newTime = System.currentTimeMillis() - startTime; reactionTime = newTime; if (bestTime == null || newTime < bestTime!!) { bestTime = newTime }; state = ReactionGameState.FINISHED }; else -> {} } }; LaunchedEffect(state) { if (state == ReactionGameState.WAITING) { delay(Random.nextLong(1500, 5500)); if (state == ReactionGameState.WAITING) { startTime = System.currentTimeMillis(); state = ReactionGameState.ACTION } } }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Kecepatan Reaksi") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)) }, containerColor = backgroundColor) { innerPadding -> Box(modifier = Modifier.fillMaxSize().padding(innerPadding).clickable(enabled = state == ReactionGameState.WAITING || state == ReactionGameState.ACTION, onClick = onScreenClick), contentAlignment = Alignment.Center) { when (state) { ReactionGameState.READY -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Tes Kecepatan Reaksi", color = MaterialTheme.colorScheme.primary); Button(onClick = { state = ReactionGameState.WAITING }) { Text("Mulai") } } }; ReactionGameState.WAITING -> { Text("Tunggu sampai hijau...", color = Color.White, style = MaterialTheme.typography.headlineMedium) }; ReactionGameState.ACTION -> { Text("Tekan Sekarang!", color = Color.White, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold) }; ReactionGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { val resultText = if (reactionTime == -1L) "Terlalu Cepat!" else "${reactionTime} ms"; Text(resultText, style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary); if(bestTime != null) Text("Terbaik: $bestTime ms", style = MaterialTheme.typography.bodySmall); Button(onClick = { state = ReactionGameState.READY }) { Text("Coba Lagi") } } } } } } }
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun JournalHistoryScreen(navController: NavController) { fun JournalHistoryScreen(navController: NavHostController) {
val context = LocalContext.current val context = LocalContext.current
val db = Firebase.firestore val db = Firebase.firestore
var journalList by remember { mutableStateOf<List<JournalEntry>>(emptyList()) } var journalList by remember { mutableStateOf<List<JournalEntry>>(emptyList()) }
@ -404,9 +426,11 @@ fun JournalHistoryScreen(navController: NavController) {
IconButton(onClick = { navController.popBackStack() }) { IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali", tint = MaterialTheme.colorScheme.primary) Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali", tint = MaterialTheme.colorScheme.primary)
} }
} },
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
) )
} },
containerColor = Color.Transparent
) { paddingValues -> ) { paddingValues ->
if (isLoading) { if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@ -484,7 +508,7 @@ fun DeleteConfirmationDialog(journalEntry: JournalEntry, onConfirm: () -> Unit,
@Composable @Composable
fun JournalHistoryItem(journal: JournalEntry, modifier: Modifier = Modifier, onDelete: () -> Unit) { fun JournalHistoryItem(journal: JournalEntry, modifier: Modifier = Modifier, onDelete: () -> Unit) {
Card(modifier = modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)) { Card(modifier = modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.8f)), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Text(if (journal.type == "reflection") "Refleksi" else "Jurnal", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary) Text(if (journal.type == "reflection") "Refleksi" else "Jurnal", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary)
@ -517,7 +541,7 @@ fun TrendGraph(journals: List<JournalEntry>) {
} }
} else { } else {
val dataPoints = journals.sortedBy { it.timestamp?.seconds ?: 0 }.takeLast(7).map { it.mentalScore } val dataPoints = journals.sortedBy { it.timestamp?.seconds ?: 0 }.takeLast(7).map { it.mentalScore }
Card(modifier = Modifier.fillMaxWidth().height(220.dp), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f))) { Card(modifier = Modifier.fillMaxWidth().height(220.dp), colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.6f))) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
Text("Grafik Skor Depresi (7 Jurnal Terakhir)", color = primaryColor, fontWeight = FontWeight.Bold) Text("Grafik Skor Depresi (7 Jurnal Terakhir)", color = primaryColor, fontWeight = FontWeight.Bold)
Canvas(modifier = Modifier.fillMaxWidth().height(150.dp)) { Canvas(modifier = Modifier.fillMaxWidth().height(150.dp)) {

View File

@ -2,18 +2,17 @@ package com.example.ppb_kelompok2.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
// Light Pink & Red Theme // Pink Muda, Merah, dan Putih Theme
val PinkLight = Color(0xFFFFD1DC) // Pink Muda val PinkMuda = Color(0xFFFFD1DC) // Pink sangat muda
val PinkDeep = Color(0xFFFFB6C1) val MerahUtama = Color(0xFFFF5252) // Merah cerah
val RedSoft = Color(0xFFFF5252) // Merah Lembut val MerahTua = Color(0xFFD32F2F) // Merah gelap untuk teks/aksen
val RedDeep = Color(0xFFD32F2F) // Merah Tua val PutihMurni = Color(0xFFFFFFFF) // Putih
val PureWhite = Color(0xFFFFFFFF) val PinkAksen = Color(0xFFFFB6C1) // Pink untuk variasi
// Default Compose Colors (Keeping some for compatibility if needed) // Tetap pertahankan default jika diperlukan (opsional)
val Purple80 = Color(0xFFD0BCFF) val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC) val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8) val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4) val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71) val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260) val Pink40 = Color(0xFF7D5260)

View File

@ -13,9 +13,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = PinkDeep, primary = MerahUtama,
secondary = RedSoft, secondary = PinkAksen,
tertiary = PinkLight, tertiary = PinkMuda,
background = Color(0xFF1C1B1F), background = Color(0xFF1C1B1F),
surface = Color(0xFF1C1B1F), surface = Color(0xFF1C1B1F),
onPrimary = Color.White, onPrimary = Color.White,
@ -24,22 +24,21 @@ private val DarkColorScheme = darkColorScheme(
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(
primary = RedSoft, primary = MerahUtama,
secondary = PinkDeep, secondary = MerahTua,
tertiary = PinkLight, tertiary = PinkMuda,
background = PureWhite, background = PutihMurni,
surface = PureWhite, surface = PutihMurni,
onPrimary = Color.White, onPrimary = PutihMurni,
onSecondary = Color.White, onSecondary = PutihMurni,
onTertiary = Color.Black, onTertiary = Color.Black,
surfaceVariant = PinkLight surfaceVariant = PinkMuda
) )
@Composable @Composable
fun PPB_Kelompok2Theme( fun PPB_Kelompok2Theme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+ dynamicColor: Boolean = false,
dynamicColor: Boolean = false, // Turned off to force our custom theme
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val colorScheme = when { val colorScheme = when {
@ -47,7 +46,6 @@ fun PPB_Kelompok2Theme(
val context = LocalContext.current val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} }
darkTheme -> DarkColorScheme darkTheme -> DarkColorScheme
else -> LightColorScheme else -> LightColorScheme
} }