From eb12f138b0e836b2160f029d569113111bc5090f Mon Sep 17 00:00:00 2001 From: Ahmar Rafly <202310715320@mhs.ubharajaya.ac.id> Date: Mon, 12 Jan 2026 23:13:51 +0700 Subject: [PATCH] Perbaikan tampilan --- .idea/.name | 1 + .idea/deploymentTargetSelector.xml | 8 + app/src/main/AndroidManifest.xml | 8 +- app/src/main/assets/index.html | 893 ++++++++++++++++++ .../com/example/ppb_kelompok2/MainActivity.kt | 881 +---------------- gradle/libs.versions.toml | 2 +- 6 files changed, 946 insertions(+), 847 deletions(-) create mode 100644 .idea/.name create mode 100644 app/src/main/assets/index.html diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..76d0635 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +PPB_Kelompok2 \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..93e513f 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c56ca6..8f23866 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + android:theme="@style/Theme.PPB_Kelompok2" + android:usesCleartextTraffic="true"> + android:theme="@style/Theme.PPB_Kelompok2" + android:configChanges="orientation|screenSize|keyboardHidden"> diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html new file mode 100644 index 0000000..443ec6c --- /dev/null +++ b/app/src/main/assets/index.html @@ -0,0 +1,893 @@ + + + + + + PsyJournal React + + + + + + + + + +
+ + + + diff --git a/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt b/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt index 3904f8e..c48fc14 100644 --- a/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt +++ b/app/src/main/java/com/example/ppb_kelompok2/MainActivity.kt @@ -1,868 +1,61 @@ package com.example.ppb_kelompok2 -// Yoseph + +import android.annotation.SuppressLint import android.os.Bundle +import android.view.ViewGroup +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -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.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import androidx.navigation.NavHostController -import androidx.navigation.compose.* -import com.example.ppb_kelompok2.ui.theme.PPB_Kelompok2Theme -import kotlinx.coroutines.delay -import kotlin.math.roundToInt -import kotlin.random.Random +import androidx.compose.ui.viewinterop.AndroidView -// --- Main Activity --- class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - PPB_Kelompok2Theme { - Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - AppNavigationGraph() - } + Surface(modifier = Modifier.fillMaxSize()) { + WebViewContainer() } } } } -// --- Navigation Graph --- +@SuppressLint("SetJavaScriptEnabled") @Composable -fun AppNavigationGraph() { - val navController = rememberNavController() - NavHost(navController = navController, startDestination = "login") { - composable("login") { LoginScreen(navController = navController) } - composable("main") { MainAppScreen() } - } -} - - -// --- Login Screen --- -@Composable -fun LoginScreen(navController: NavController) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("MindTrack AI", style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - Text( - "Lacak kesehatan mental Anda dengan kekuatan AI.", - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = 32.dp) - ) - Spacer(modifier = Modifier.height(32.dp)) - Button( - onClick = { navController.navigate("main") { - popUpTo("login") { inclusive = true } - } }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp) - ) { - Icon(Icons.Default.AccountCircle, contentDescription = "Google Icon") // Placeholder for Google Icon - Spacer(modifier = Modifier.width(8.dp)) - Text("Masuk dengan Google") - } - } -} - -// --- Main App Structure (with Bottom Navigation) --- -sealed class Screen(val route: String, val label: String, val icon: ImageVector) { - object Journal : Screen("journal", "Jurnal", Icons.Default.Book) - object Assessment : Screen("assessment", "Penilaian", Icons.Default.Checklist) - object CognitiveTest : Screen("cognitive_test", "Tes Kognitif", Icons.Default.SportsEsports) - object History : Screen("history", "Riwayat & Grafik", Icons.Default.BarChart) -} - -val bottomNavItems = listOf( - Screen.Journal, - Screen.Assessment, - Screen.CognitiveTest, - Screen.History -) - -@Composable -fun MainAppScreen() { - val navController = rememberNavController() - Scaffold( - bottomBar = { AppBottomNavigation(navController = navController) } - ) { innerPadding -> - AppNavHost(navController = navController, modifier = Modifier.padding(innerPadding)) - } -} - -@Composable -fun AppBottomNavigation(navController: NavHostController) { - NavigationBar { - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route - - bottomNavItems.forEach { screen -> - NavigationBarItem( - icon = { Icon(screen.icon, contentDescription = screen.label) }, - label = { Text(screen.label) }, - selected = currentRoute == screen.route, - onClick = { - navController.navigate(screen.route) { - popUpTo(navController.graph.startDestinationId) { saveState = true } - launchSingleTop = true - restoreState = true - } - } - ) - } - } -} - -@Composable -fun AppNavHost(navController: NavHostController, modifier: Modifier = Modifier) { - NavHost( - navController = navController, - startDestination = Screen.Journal.route, - modifier = modifier.fillMaxSize() - ) { - composable(Screen.Journal.route) { JournalScreen() } - composable(Screen.Assessment.route) { AssessmentScreen() } - composable(Screen.CognitiveTest.route) { CognitiveTestScreen(navController) } - composable(Screen.History.route) { HistoryScreen() } - composable("memory_test") { MemoryTestScreen(navController) } - composable("focus_test") { FocusTestScreen(navController) } - composable("reaction_test") { ReactionSpeedTestScreen(navController) } - } -} - -// --- App Screens --- - -@Composable -fun JournalScreen() { - var journalText by remember { mutableStateOf("") } - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Card(modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp)) { - Column(modifier = Modifier.padding(16.dp)) { - Text("Analisis AI", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - Text("Sentimen: Netral") - Text("Emosi Terdeteksi: Tenang") - } - } - OutlinedTextField( - value = journalText, - onValueChange = { journalText = it }, - label = { Text("Tuliskan perasaanmu di sini...") }, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) - Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { /* TODO: Implement save logic */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Simpan Jurnal") - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AssessmentScreen() { - val indicators = remember { - listOf( - "Mood Sedih", "Rasa Bersalah", "Menarik Diri Sosial", - "Sulit Konsentrasi", "Kelelahan", "Pikiran Bunuh Diri" - ) - } - - val sliderValues = remember { - mutableStateMapOf().apply { - indicators.forEach { indicator -> - put(indicator, 0f) - } - } - } - - val totalScore = sliderValues.values.sum().toInt() - val assessmentLevel = when (totalScore) { - in 0..4 -> "Normal" - in 5..9 -> "Ringan" - in 10..14 -> "Sedang" - else -> "Berat" - } - - Scaffold( - topBar = { - TopAppBar(title = { Text("Penilaian Harian") }) - } - ) { innerPadding -> - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - item { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) - ) { - Column( - modifier = Modifier.padding(16.dp), - horizontalAlignment = Alignment.Start - ) { - Text("Ringkasan Skor", style = MaterialTheme.typography.titleMedium) - Spacer(modifier = Modifier.height(8.dp)) - Text("Total Skor: $totalScore", style = MaterialTheme.typography.headlineMedium) - Text("Tingkat: $assessmentLevel", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) - } - } - } - - items(indicators) { indicatorName -> - IndicatorItem( - indicatorName = indicatorName, - value = sliderValues[indicatorName] ?: 0f, - onValueChange = { - sliderValues[indicatorName] = it.roundToInt().toFloat() - } +fun WebViewContainer() { + AndroidView( + factory = { context -> + WebView(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT ) - } - - item { - Button( - onClick = { /* TODO: Implement finish logic */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Selesai") + + // Konfigurasi WebView agar React berjalan lancar + settings.apply { + javaScriptEnabled = true + domStorageEnabled = true + allowFileAccess = true + allowContentAccess = true + loadWithOverviewMode = true + useWideViewPort = true + cacheMode = WebSettings.LOAD_DEFAULT + mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } + + webViewClient = WebViewClient() + + // Memuat file index.html dari folder assets + loadUrl("file:///android_asset/index.html") } - } - } -} - -@Composable -fun IndicatorItem(indicatorName: String, value: Float, onValueChange: (Float) -> Unit) { - val description = when (value.toInt()) { - 0 -> "Tidak sama sekali" - 1 -> "Beberapa hari" - 2 -> "Lebih dari separuh hari" - 3 -> "Hampir setiap hari" - else -> "" - } - - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text(indicatorName, style = MaterialTheme.typography.titleMedium) - Text( - text = value.toInt().toString(), - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary - ) - } - Spacer(modifier = Modifier.height(8.dp)) - - Slider( - value = value, - onValueChange = onValueChange, - valueRange = 0f..3f, - steps = 2, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = description, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - } - } -} - - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun CognitiveTestScreen(navController: NavController) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text("Pilih Tes Kognitif", style = MaterialTheme.typography.headlineSmall) - - @Composable - fun TestCard(title: String, description: String, icon: ImageVector, route: String) { - Card( - onClick = { navController.navigate(route) }, - modifier = Modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(icon, contentDescription = 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) - } - } - } - } - - TestCard( - title = "Tes Memori", - description = "Uji memori jangka pendek Anda", - icon = Icons.Default.Memory, - route = "memory_test" - ) - TestCard( - title = "Tes Fokus", - description = "Uji kemampuan fokus & atensi", - icon = Icons.Default.CenterFocusStrong, - route = "focus_test" - ) - TestCard( - title = "Tes Kecepatan Reaksi", - description = "Uji kecepatan reaksi visual Anda", - icon = Icons.Default.Speed, - route = "reaction_test" - ) - } -} - -// --- Cognitive Test Screens --- - -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(createShuffledCards(icons)) } - var selectedCards by remember { mutableStateOf(listOf()) } - var moves by remember { mutableIntStateOf(0) } - var bestScore by remember { mutableStateOf(null) } - var gameState by remember { mutableStateOf(MemoryGameState.READY) } - - LaunchedEffect(cards.all { it.isMatched }) { - if (cards.all { it.isMatched } && gameState == MemoryGameState.PLAYING) { - gameState = MemoryGameState.FINISHED - if (bestScore == null || moves < bestScore!!) { - bestScore = moves - } - } - } - - LaunchedEffect(selectedCards) { - if (selectedCards.size == 2) { - val (first, second) = selectedCards - if (first.icon == second.icon) { - cards = cards.map { if (it.id == first.id || it.id == second.id) it.copy(isMatched = true) else it } - } else { - delay(1000) - cards = cards.map { if (it.id == first.id || it.id == second.id) it.copy(isFaceUp = false) else it } - } - selectedCards = listOf() - } - } - - fun restartGame() { - moves = 0 - cards = createShuffledCards(icons) - gameState = MemoryGameState.PLAYING - } - - Scaffold( - topBar = { - TopAppBar( - title = { Text("Tes Memori") }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Default.ArrowBack, contentDescription = "Kembali") - } - } - ) - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - when (gameState) { - MemoryGameState.READY -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text("Tes Memori", style = MaterialTheme.typography.headlineMedium) - bestScore?.let { - Text("Skor Terbaik: $it gerakan", style = MaterialTheme.typography.titleMedium) - } - Text( - "Tes ini menguji memori jangka pendek Anda. Cocokkan semua kartu dengan jumlah gerakan sesedikit mungkin.", - textAlign = TextAlign.Center - ) - Button(onClick = { gameState = MemoryGameState.PLAYING }) { - Text("Mulai") - } - } - } - - MemoryGameState.PLAYING, MemoryGameState.FINISHED -> { - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) { - Text("Gerakan: $moves", style = MaterialTheme.typography.bodyLarge) - bestScore?.let { - Text("Skor Terbaik: $it", style = MaterialTheme.typography.bodyLarge) - } - } - Spacer(modifier = Modifier.height(16.dp)) - LazyVerticalGrid( - columns = GridCells.Fixed(3), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(cards) { card -> - MemoryCardView(card = card, onCardClicked = { - if (gameState == MemoryGameState.PLAYING && !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++ - } - }) - } - } - Spacer(modifier = Modifier.weight(1f)) - if (gameState == MemoryGameState.FINISHED) { - Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)){ - Text("Selesai!", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.primary) - Button(onClick = { restartGame() }) { - Icon(Icons.Default.Refresh, contentDescription = "Coba Lagi") - Spacer(modifier = Modifier.width(8.dp)) - Text("Coba Lagi") - } - } - } - } - } - } - } -} - -fun createShuffledCards(icons: List): List { - return (icons + icons).mapIndexed { index, icon -> MemoryCard(id = index, icon = icon) }.shuffled() -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MemoryCardView(card: MemoryCard, onCardClicked: () -> Unit) { - Card( - onClick = onCardClicked, - modifier = Modifier.aspectRatio(1f), - enabled = !card.isMatched, - colors = CardDefaults.cardColors( - containerColor = if (card.isFaceUp || card.isMatched) MaterialTheme.colorScheme.surfaceVariant else MaterialTheme.colorScheme.primary - ) - ) { - Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { - if (card.isFaceUp || card.isMatched) { - Icon(card.icon, contentDescription = null, modifier = Modifier.size(40.dp)) - } - } - } -} - -enum class FocusGameState { READY, PLAYING, FINISHED } - -@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, contentDescription = "Kembali") - } - } - ) - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - when (gameState) { - FocusGameState.READY -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text("Tes Fokus", style = MaterialTheme.typography.headlineMedium) - if (highScore > 0) { - Text("Skor Tertinggi: $highScore", style = MaterialTheme.typography.titleMedium) - } - Text( - "Tes ini menguji kemampuan fokus dan atensi Anda untuk mengidentifikasi perbedaan visual dengan cepat.", - textAlign = TextAlign.Center - ) - Text("Pilih durasi waktu:") - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - val durations = listOf(15, 30, 60, 120) - durations.forEach { duration -> - val isSelected = selectedDuration == duration - OutlinedButton( - onClick = { selectedDuration = duration }, - colors = if (isSelected) ButtonDefaults.outlinedButtonColors(containerColor = MaterialTheme.colorScheme.primaryContainer) else ButtonDefaults.outlinedButtonColors() - ) { - Text("${duration}s") - } - } - } - Button(onClick = { gameState = FocusGameState.PLAYING }) { - Text("Mulai") - } - } - } - FocusGameState.PLAYING -> { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceAround, - verticalAlignment = Alignment.CenterVertically - ) { - Text("Skor: $score", style = MaterialTheme.typography.bodyLarge) - Text("Waktu: $timeLeft", style = MaterialTheme.typography.bodyLarge) - } - Spacer(modifier = Modifier.height(16.dp)) - LazyVerticalGrid( - columns = GridCells.Fixed(5), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(gridItems.indices.toList()) { index -> - val item = gridItems[index] - Icon( - imageVector = item.icon, - contentDescription = null, - modifier = Modifier - .size(40.dp) - .rotate(item.rotation) - .clickable { - if (item.isDistractor) { - score++ - newLevel() - } else { - if (score > 0) score-- - } - }, - tint = item.color - ) - } - } - } - FocusGameState.FINISHED -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text("Waktu Habis!", style = MaterialTheme.typography.headlineMedium) - Text("Skor Akhir: $score", style = MaterialTheme.typography.bodyLarge) - Text("Skor Tertinggi: $highScore", style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold) - Button(onClick = { restartGame() }) { - Text("Coba Lagi") - } - } - } - } - } - } -} - -data class FocusItem(val icon: ImageVector, val color: Color, val rotation: Float, val isDistractor: Boolean) - -private fun generateFocusGrid(normalColor: Color): List { - 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 -> { // Different Icon - distractor = FocusItem(Icons.Default.Star, normalColor, 0f, true) - } - 1 -> { // Different Color - distractor = FocusItem(normalIcon, Color.Red, 0f, true) - } - else -> { // Different Rotation - distractor = FocusItem(normalIcon, normalColor, 90f, true) - // Use an icon that shows rotation - 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(null) } - - val backgroundColor by animateColorAsState( - targetValue = when (state) { - ReactionGameState.WAITING -> Color.Red.copy(alpha = 0.8f) - ReactionGameState.ACTION -> Color.Green.copy(alpha = 0.8f) - else -> MaterialTheme.colorScheme.surface }, - animationSpec = tween(300), - label = "ReactionBackgroundColor" + modifier = Modifier.fillMaxSize() ) - - val onScreenClick = { - when (state) { - ReactionGameState.WAITING -> { - reactionTime = -1 // Too soon - state = ReactionGameState.FINISHED - } - ReactionGameState.ACTION -> { - val newReactionTime = System.currentTimeMillis() - startTime - reactionTime = newReactionTime - if (bestTime == null || newReactionTime < bestTime!!) { - bestTime = newReactionTime - } - state = ReactionGameState.FINISHED - } - else -> { /* Clicks handled by buttons in READY and FINISHED states */ } - } - } - - LaunchedEffect(state) { - if (state == ReactionGameState.WAITING) { - delay(Random.nextLong(1500, 5500)) - if (state == ReactionGameState.WAITING) { // Ensure state hasn't changed - startTime = System.currentTimeMillis() - state = ReactionGameState.ACTION - } - } - } - - Scaffold( - topBar = { - TopAppBar( - title = { Text("Tes Kecepatan Reaksi") }, - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Default.ArrowBack, contentDescription = "Kembali") - } - } - ) - } - ) { 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, - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.padding(16.dp) - ) { - Text("Tes Kecepatan Reaksi", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onSurface) - bestTime?.let { - Text("Waktu Terbaik: $it ms", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface) - } - Text( - "Tes ini mengukur kecepatan reaksi visual Anda. Tunggu layar berubah menjadi hijau, lalu tekan secepat mungkin.", - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = 16.dp), - color = MaterialTheme.colorScheme.onSurface - ) - Button(onClick = { state = ReactionGameState.WAITING }) { - Text("Mulai") - } - } - } - ReactionGameState.WAITING -> { - Text("Tunggu sampai hijau...", fontSize = 24.sp, color = Color.White, fontWeight = FontWeight.Bold) - } - ReactionGameState.ACTION -> { - Text("Tekan Sekarang!", fontSize = 24.sp, color = Color.White, fontWeight = FontWeight.Bold) - } - ReactionGameState.FINISHED -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.padding(16.dp) - ) { - val resultText = if (reactionTime == -1L) "Terlalu Cepat!" else "${reactionTime} ms" - Text(resultText, fontSize = 48.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) - bestTime?.let { - Text("Waktu Terbaik: $it ms", fontSize = 20.sp, color = MaterialTheme.colorScheme.onSurface) - } - Button(onClick = { state = ReactionGameState.READY }) { - Text("Coba Lagi") - } - } - } - } - } - } -} - - -@Composable -fun HistoryScreen() { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - item { - Text("Riwayat & Grafik", style = MaterialTheme.typography.headlineSmall) - } - item { - GraphCard(title = "Tren Mood Mingguan") - } - item { - GraphCard(title = "Perkembangan Skor Depresi") - } - item { - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - Text("Insight AI", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - Text("AI menemukan pola bahwa mood Anda cenderung menurun di akhir pekan.", style = MaterialTheme.typography.bodyMedium) - } - } - } - } -} - -@Composable -fun GraphCard(title: String) { - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - Box( - modifier = Modifier - .fillMaxWidth() - .height(150.dp) - .background(Color.LightGray.copy(alpha = 0.5f)) - ) { - Text("Area Grafik", modifier = Modifier.align(Alignment.Center), color = Color.Gray) //warna abu - } - } - } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7e6b91..4623cb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"