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"