Perbaikan UI/UX
This commit is contained in:
parent
2365e0b907
commit
214af2312f
@ -26,6 +26,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@ -103,6 +104,15 @@ data class AssessmentEntry(
|
||||
val dateString: String = ""
|
||||
)
|
||||
|
||||
data class CognitiveTestEntry(
|
||||
val id: String = "",
|
||||
val userId: String = "",
|
||||
val testType: String = "",
|
||||
val score: Int = 0,
|
||||
val timestamp: com.google.firebase.Timestamp? = null,
|
||||
val dateString: String = ""
|
||||
)
|
||||
|
||||
data class Badge(
|
||||
val title: String,
|
||||
val description: String,
|
||||
@ -280,20 +290,27 @@ fun calculateDailyStreak(dates: List<Date>): Int {
|
||||
return streak
|
||||
}
|
||||
|
||||
fun getBadges(journals: List<JournalEntry>, assessments: List<AssessmentEntry>, streak: Int): List<Badge> {
|
||||
val totalActivities = journals.size + assessments.size
|
||||
fun getBadges(journals: List<JournalEntry>, assessments: List<AssessmentEntry>, cognitiveTests: List<CognitiveTestEntry>, streak: Int): List<Badge> {
|
||||
val totalActivities = journals.size + assessments.size + cognitiveTests.size
|
||||
val reflectionEntries = journals.count { it.type == "reflection" }
|
||||
|
||||
// Untuk demo, kita ambil tanggal hari ini jika lencana terbuka
|
||||
val today = SimpleDateFormat("d MMM yyyy", Locale("id", "ID")).format(Date())
|
||||
|
||||
val memoryTests = cognitiveTests.count { it.testType == "memory" }
|
||||
val focusTests = cognitiveTests.count { it.testType == "focus" }
|
||||
val reactionTests = cognitiveTests.count { it.testType == "reaction" }
|
||||
val logicalTests = cognitiveTests.count { it.testType == "logical" }
|
||||
|
||||
return listOf(
|
||||
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)
|
||||
Badge("Master Fokus", "Selesaikan total 10 aktivitas apa saja di dalam aplikasi.", Icons.Default.EmojiEvents, totalActivities >= 10, if(totalActivities >= 10) today else null),
|
||||
Badge("Ingatan Gajah", "Selesaikan 3 tes memori.", Icons.Default.Memory, memoryTests >= 3, if(memoryTests >= 3) today else null),
|
||||
Badge("Mata Elang", "Selesaikan 3 tes fokus.", Icons.Default.Visibility, focusTests >= 3, if(focusTests >= 3) today else null),
|
||||
Badge("Kilat Visual", "Selesaikan 3 tes kecepatan reaksi.", Icons.Default.Bolt, reactionTests >= 3, if(reactionTests >= 3) today else null),
|
||||
Badge("Logika Tajam", "Selesaikan 3 tes logika.", Icons.Default.Lightbulb, logicalTests >= 3, if(logicalTests >= 3) today else null)
|
||||
)
|
||||
}
|
||||
|
||||
@ -303,27 +320,34 @@ fun ProfileScreen(navController: NavController) {
|
||||
val auth = Firebase.auth
|
||||
val user = auth.currentUser
|
||||
val db = Firebase.firestore
|
||||
val context = LocalContext.current
|
||||
var journalList by remember { mutableStateOf<List<JournalEntry>>(emptyList()) }
|
||||
var assessmentList by remember { mutableStateOf<List<AssessmentEntry>>(emptyList()) }
|
||||
var cognitiveTestList by remember { mutableStateOf<List<CognitiveTestEntry>>(emptyList()) }
|
||||
var dailyStreak by remember { mutableIntStateOf(0) }
|
||||
var selectedBadge by remember { mutableStateOf<Badge?>(null) }
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var selectedReport by remember { mutableStateOf("Pilih Laporan") }
|
||||
val reportOptions = listOf("Mingguan", "Bulanan", "Tahunan")
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(user) {
|
||||
if (user != null) {
|
||||
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()
|
||||
val journalSnap = db.collection("journals").whereEqualTo("userId", user.uid).get().await()
|
||||
val assessmentSnap = db.collection("assessments").whereEqualTo("userId", user.uid).get().await()
|
||||
val cognitiveSnap = db.collection("cognitive_tests").whereEqualTo("userId", user.uid).get().await()
|
||||
|
||||
journalList = journalSnap.toObjects(JournalEntry::class.java)
|
||||
assessmentList = assessmentSnap.toObjects(AssessmentEntry::class.java)
|
||||
cognitiveTestList = cognitiveSnap.toObjects(CognitiveTestEntry::class.java)
|
||||
|
||||
val allDates = (journalList.mapNotNull { it.timestamp?.toDate() } +
|
||||
assessmentList.mapNotNull { it.timestamp?.toDate() })
|
||||
assessmentList.mapNotNull { it.timestamp?.toDate() } +
|
||||
cognitiveTestList.mapNotNull { it.timestamp?.toDate() })
|
||||
|
||||
dailyStreak = calculateDailyStreak(allDates)
|
||||
} catch (e: Exception) {
|
||||
@ -333,60 +357,137 @@ fun ProfileScreen(navController: NavController) {
|
||||
}
|
||||
}
|
||||
|
||||
val badges = remember(journalList, assessmentList, dailyStreak) {
|
||||
getBadges(journalList, assessmentList, dailyStreak)
|
||||
val badges = remember(journalList, assessmentList, cognitiveTestList, dailyStreak) {
|
||||
getBadges(journalList, assessmentList, cognitiveTestList, dailyStreak)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
contentPadding = PaddingValues(top = 16.dp, bottom = 80.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// User Info
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
if (user != null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 8.dp)) {
|
||||
AsyncImage(model = user.photoUrl, contentDescription = null, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Streak Card
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Default.LocalFireDepartment, null, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Badge Header
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
Text("Lencana Pencapaian", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth().padding(top = 8.dp))
|
||||
}
|
||||
|
||||
// Badges Grid items
|
||||
items(badges) { badge ->
|
||||
BadgeItem(badge = badge) {
|
||||
selectedBadge = badge
|
||||
}
|
||||
}
|
||||
|
||||
// Report Header
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
Text("Laporan Kesehatan Mental", style = MaterialTheme.typography.titleMedium, modifier = Modifier.fillMaxWidth().padding(top = 16.dp))
|
||||
}
|
||||
|
||||
// Report Dropdown
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
OutlinedCard(
|
||||
modifier = Modifier.fillMaxWidth().clickable { expanded = true },
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp).fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(selectedReport, fontWeight = FontWeight.SemiBold)
|
||||
Icon(if (expanded) Icons.Default.ArrowDropUp else Icons.Default.ArrowDropDown, contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier.fillMaxWidth(0.9f)
|
||||
) {
|
||||
reportOptions.forEach { option ->
|
||||
val description = when(option) {
|
||||
"Mingguan" -> "Analisis 7 hari terakhir"
|
||||
"Bulanan" -> "Analisis 30 hari terakhir"
|
||||
else -> "Tersedia di akhir tahun."
|
||||
}
|
||||
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
Text(option, fontWeight = FontWeight.Bold)
|
||||
Text(description, style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
if (option != "Tahunan") {
|
||||
selectedReport = option
|
||||
expanded = false
|
||||
} else {
|
||||
Toast.makeText(context, "Laporan tahunan tersedia di akhir tahun", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// History Button
|
||||
item(span = { GridItemSpan(3) }) {
|
||||
OutlinedButton(onClick = { navController.navigate("journal_history") }, modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)) {
|
||||
Text("Lihat Riwayat Jurnal Lengkap")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ?: "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())
|
||||
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 {
|
||||
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, onClick: () -> Unit) {
|
||||
fun BadgeItem(badge: Badge, modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = modifier.alpha(if (badge.isUnlocked) 1f else 0.4f),
|
||||
color = Color.Transparent,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.alpha(if (badge.isUnlocked) 1f else 0.4f)
|
||||
modifier = Modifier.padding(vertical = 8.dp).fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@ -398,12 +499,12 @@ fun BadgeItem(badge: Badge, onClick: () -> Unit) {
|
||||
) {
|
||||
Icon(
|
||||
badge.icon,
|
||||
contentDescription = badge.title,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
badge.title,
|
||||
fontSize = 12.sp,
|
||||
@ -412,35 +513,39 @@ fun BadgeItem(badge: Badge, onClick: () -> Unit) {
|
||||
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)
|
||||
shape = RoundedCornerShape(28.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
|
||||
) {
|
||||
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") }
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
IconButton(onClick = onDismiss) { Icon(Icons.Default.Close, null, tint = Color.White) }
|
||||
IconButton(onClick = { }) { Icon(Icons.Default.Share, null, tint = Color.White) }
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (badge.isUnlocked) MaterialTheme.colorScheme.primaryContainer else Color.LightGray.copy(alpha = 0.3f)),
|
||||
.background(if (badge.isUnlocked) MaterialTheme.colorScheme.primary.copy(alpha = 0.2f) else Color.DarkGray),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
badge.icon,
|
||||
contentDescription = badge.title,
|
||||
modifier = Modifier.size(60.dp),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(70.dp),
|
||||
tint = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray
|
||||
)
|
||||
}
|
||||
@ -453,87 +558,56 @@ fun BadgeDetailDialog(badge: Badge, onDismiss: () -> Unit) {
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = badge.dateUnlocked,
|
||||
color = Color.White,
|
||||
text = badge.dateUnlocked.uppercase(),
|
||||
color = Color(0xFFFFA500),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
fontWeight = FontWeight.Bold
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
fontWeight = FontWeight.ExtraBold
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = if (badge.isUnlocked) "Kamu meraih pencapaian" else "Pencapaian Terkunci",
|
||||
color = Color.LightGray,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Text(
|
||||
text = badge.title,
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = if (badge.isUnlocked) MaterialTheme.colorScheme.primary else Color.Gray
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = badge.description,
|
||||
color = Color.Gray,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
lineHeight = 20.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(50)
|
||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
||||
shape = RoundedCornerShape(25.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)
|
||||
) {
|
||||
Text("Tutup")
|
||||
Text("Tutup", fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun scheduleReminder(context: Context, hour: Int, minute: Int) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
val intent = Intent(context, MyReminderReceiver::class.java).apply { putExtra("title", "Waktunya Jurnaling!"); putExtra("message", "Luangkan waktu sejenak untuk refleksi diri.") }
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
val calendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, hour); set(Calendar.MINUTE, minute); set(Calendar.SECOND, 0); if (before(Calendar.getInstance())) add(Calendar.DATE, 1) }
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if(alarmManager.canScheduleExactAlarms()){
|
||||
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
|
||||
}
|
||||
} else {
|
||||
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
|
||||
}
|
||||
Toast.makeText(context, "Pengingat diatur!", Toast.LENGTH_SHORT).show()
|
||||
} catch (e: SecurityException) {
|
||||
Toast.makeText(context, "Izin alarm tidak diberikan", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateMentalHealthScore(sentimentLabel: String, sentimentScore: Float, indicators: Map<String, Float>): Int {
|
||||
var baseScore = 30.0
|
||||
when (sentimentLabel) { "sadness", "fear", "anger" -> baseScore += (sentimentScore * 10); "joy" -> baseScore -= (sentimentScore * 10) }
|
||||
@ -547,8 +621,8 @@ fun calculateMentalHealthScore(sentimentLabel: String, sentimentScore: Float, in
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
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 auth = Firebase.auth; 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") }, actions = { IconButton(onClick = { timePickerDialog.show() }) { Icon(Icons.Default.Alarm, contentDescription = "Set Reminder") } }) }) { padding ->
|
||||
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 auth = Firebase.auth; val db = Firebase.firestore; val scope = rememberCoroutineScope(); 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"); val reflectionQuestions = listOf("Satu hal kecil yang membuatmu tersenyum hari ini?", "Tantangan terbesar hari ini & solusinya?", "Satu hal yang ingin kamu perbaiki besok?")
|
||||
Scaffold(topBar = { TopAppBar(title = { Text("Jurnal & Refleksi") }) }) { padding ->
|
||||
Column(modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
TabRow(selectedTabIndex = selectedTab) { 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))
|
||||
@ -568,7 +642,28 @@ fun JournalScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class) @Composable fun AssessmentScreen() {
|
||||
fun getAssessmentColor(score: Int): Color {
|
||||
return when {
|
||||
score <= 4 -> Color(0xFF4CAF50) // Green (Normal)
|
||||
score <= 9 -> Color(0xFFFFEB3B) // Yellow (Ringan)
|
||||
score <= 14 -> Color(0xFFFF9800) // Orange (Sedang)
|
||||
else -> Color(0xFFF44336) // Red (Berat)
|
||||
}
|
||||
}
|
||||
|
||||
fun getIndicatorColor(value: Float): Color {
|
||||
return when (value.toInt()) {
|
||||
0 -> Color(0xFF4CAF50) // Green
|
||||
1 -> Color(0xFFFFEB3B) // Yellow
|
||||
2 -> Color(0xFFFF9800) // Orange
|
||||
3 -> Color(0xFFF44336) // Red
|
||||
else -> Color.Gray
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
@ -577,11 +672,27 @@ fun JournalScreen() {
|
||||
val db = Firebase.firestore
|
||||
val context = LocalContext.current
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
val summaryColor = getAssessmentColor(totalScore)
|
||||
|
||||
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 {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = summaryColor.copy(alpha = 0.2f))
|
||||
) {
|
||||
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 = {
|
||||
@ -609,31 +720,76 @@ fun JournalScreen() {
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !isSaving
|
||||
enabled = !isSaving,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = summaryColor)
|
||||
) {
|
||||
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(20.dp), color = Color.White)
|
||||
else Text("Selesai & Simpan")
|
||||
else Text("Selesai & Simpan", color = if (totalScore > 4 && totalScore <= 14) Color.Black else Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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)) } } }
|
||||
@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 -> ""
|
||||
}
|
||||
val indicatorColor = getIndicatorColor(value)
|
||||
|
||||
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)
|
||||
}
|
||||
Slider(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
valueRange = 0f..3f,
|
||||
steps = 2,
|
||||
colors = SliderDefaults.colors(
|
||||
thumbColor = indicatorColor,
|
||||
activeTrackColor = indicatorColor,
|
||||
inactiveTrackColor = indicatorColor.copy(alpha = 0.24f)
|
||||
)
|
||||
)
|
||||
Text(text = description, style = MaterialTheme.typography.bodySmall, modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveCognitiveResult(userId: String, testType: String, score: Int) {
|
||||
val db = Firebase.firestore
|
||||
val entry = hashMapOf(
|
||||
"userId" to userId,
|
||||
"testType" to testType,
|
||||
"score" to score,
|
||||
"timestamp" to FieldValue.serverTimestamp(),
|
||||
"dateString" to SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date())
|
||||
)
|
||||
db.collection("cognitive_tests").add(entry)
|
||||
}
|
||||
|
||||
@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) } } } }
|
||||
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.Default.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}"); Spacer(Modifier.height(16.dp)); Card(modifier = Modifier.fillMaxWidth()) { Text(q.question, modifier = Modifier.padding(16.dp)) }; 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); 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) }; val auth = Firebase.auth; Scaffold(topBar = { TopAppBar(title = { Text("Tes Logika") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.Default.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}"); Spacer(Modifier.height(16.dp)); Card(modifier = Modifier.fillMaxWidth()) { Text(q.question, modifier = Modifier.padding(16.dp)) }; 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; val uid = auth.currentUser?.uid; if (uid != null) saveCognitiveResult(uid, "logical", score) } }, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) { Text(option) } } } else { Text("Tes Selesai! Skor: $score/${questions.size}", style = MaterialTheme.typography.headlineMedium); Button(onClick = { navController.popBackStack() }) { Text("Kembali") } } } } }
|
||||
data class MemoryCard(val id: Int, val icon: ImageVector, var isFaceUp: Boolean = false, var isMatched: Boolean = false)
|
||||
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.Default.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"); 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"); 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) }; val auth = Firebase.auth; 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() } }; LaunchedEffect(cards) { if (gameState == MemoryGameState.PLAYING && cards.all { it.isMatched }) { gameState = MemoryGameState.FINISHED; val uid = auth.currentUser?.uid; if (uid != null) saveCognitiveResult(uid, "memory", moves) } }; Scaffold(topBar = { TopAppBar(title = { Text("Tes Memori") }, navigationIcon = { IconButton(onClick = {navController.popBackStack()}) { Icon(Icons.Default.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"); 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 (gameState == MemoryGameState.FINISHED) { Text("Selesai! Skor: $moves"); 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)) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { if(card.isFaceUp||card.isMatched) Icon(card.icon,null) } } }
|
||||
enum class FocusGameState { READY, PLAYING, FINISHED }
|
||||
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.Default.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"); Button(onClick = { gameState = FocusGameState.PLAYING }) { Text("Mulai") } } }; FocusGameState.PLAYING -> { Row { Text("Skor: $score"); Text("Waktu: $timeLeft") }; LazyVerticalGrid(columns = GridCells.Fixed(5)) { items(gridItems.indices.toList()) { index -> val item = gridItems[index]; Icon(imageVector = item.icon, null, modifier = Modifier.clickable { if (item.isDistractor) { score++; newLevel() } else { if (score > 0) score-- } }) } } }; FocusGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Waktu Habis!"); Text("Skor Akhir: $score"); 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) }; val auth = Firebase.auth; LaunchedEffect(gameState) { if (gameState == FocusGameState.PLAYING) { timeLeft = selectedDuration; while (timeLeft > 0) { delay(1000); timeLeft-- }; if (timeLeft == 0) { gameState = FocusGameState.FINISHED; val uid = auth.currentUser?.uid; if (uid != null) saveCognitiveResult(uid, "focus", score) } } 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.Default.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"); Button(onClick = { gameState = FocusGameState.PLAYING }) { Text("Mulai") } } }; FocusGameState.PLAYING -> { Row { Text("Skor: $score"); Spacer(Modifier.width(16.dp)); Text("Waktu: $timeLeft") }; LazyVerticalGrid(columns = GridCells.Fixed(5)) { items(gridItems.indices.toList()) { index -> val item = gridItems[index]; Icon(imageVector = item.icon, null, modifier = Modifier.size(48.dp).rotate(item.rotation).clickable { if (item.isDistractor) { score++; newLevel() } else { if (score > 0) score-- } }, tint = item.color) } } }; FocusGameState.FINISHED -> { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Waktu Habis!"); Text("Skor Akhir: $score"); 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 }
|
||||
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.Default.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 { Text("Tes Kecepatan Reaksi"); Button(onClick = { state = ReactionGameState.WAITING }) { Text("Mulai") } } }; ReactionGameState.WAITING -> { Text("Tunggu sampai hijau...") }; ReactionGameState.ACTION -> { Text("Tekan Sekarang!") }; ReactionGameState.FINISHED -> { Column { val resultText = if (reactionTime == -1L) "Terlalu Cepat!" else "${reactionTime} ms"; Text(resultText); 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 auth = Firebase.auth; val backgroundColor by animateColorAsState(targetValue = when (state) { ReactionGameState.WAITING -> Color.Red; ReactionGameState.ACTION -> Color.Green; else -> MaterialTheme.colorScheme.surface }, label=""); val onScreenClick: () -> Unit = { 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; val uid = auth.currentUser?.uid; if (uid != null) saveCognitiveResult(uid, "reaction", newTime.toInt()) }; 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.Default.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 { Text("Tes Kecepatan Reaksi"); Button(onClick = { state = ReactionGameState.WAITING }) { Text("Mulai") } } }; ReactionGameState.WAITING -> { Text("Tunggu sampai hijau...") }; ReactionGameState.ACTION -> { Text("Tekan Sekarang!") }; ReactionGameState.FINISHED -> { Column { val resultText = if (reactionTime == -1L) "Terlalu Cepat!" else "${reactionTime} ms"; Text(resultText); Button(onClick = { state = ReactionGameState.READY }) { Text("Coba Lagi") } } } } } } }
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.13.1"
|
||||
agp = "8.13.2"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user