diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml
index 6aa0d3a..3b7d94b 100644
--- a/.idea/copilot.data.migration.agent.xml
+++ b/.idea/copilot.data.migration.agent.xml
@@ -22,6 +22,14 @@
+
+
+
+
+
+
+
+
@@ -36,6 +44,7 @@
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index eea4682..6921437 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -59,6 +59,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
//praktikum 1
implementation("com.squareup.okhttp3:okhttp:4.11.0")
- implementation("androidx.compose.material3:material3:1.1.1")
+ implementation("androidx.compose.material3:material3:1.2.0")
}
\ No newline at end of file
diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt
index be4d47b..f17c8f2 100644
--- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt
+++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt
@@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
-import androidx.compose.material3.MaterialTheme
+import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
@@ -12,8 +12,8 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
- MaterialTheme {
- MainScreen(viewModel)
+ PanicButtonTheme {
+ MainScreen(viewModel = viewModel)
}
}
}
diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt
index d86479c..f7cb2c6 100644
--- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt
+++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt
@@ -1,99 +1,144 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.*
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.CheckboxDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MainViewModel) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(18.dp),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Surface(
- color = MaterialTheme.colorScheme.surfaceVariant,
- tonalElevation = 4.dp,
- shape = RoundedCornerShape(10.dp),
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ bottomBar = {
+ PanicButton(onClick = { viewModel.sendAlert() })
+ }
+ ) { paddingValues ->
+ Column(
modifier = Modifier
- .fillMaxWidth(0.92f)
- .padding(horizontal = 12.dp)
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
) {
- Column(modifier = Modifier.padding(14.dp)) {
- Text(text = "Jenis Kondisi Darurat", fontSize = 18.sp, color = MaterialTheme.colorScheme.error)
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(
+ text = "Jenis Kondisi Darurat",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.primary
+ )
- Spacer(modifier = Modifier.height(10.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- // list options directly from viewModel
- for (opt in viewModel.options) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
+ viewModel.options.forEach { opt ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 6.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .background(
+ color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.1f),
+ shape = CircleShape
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(text = opt.icon, fontSize = 22.sp)
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Text(
+ text = opt.label,
+ modifier = Modifier.weight(1f),
+ style = MaterialTheme.typography.bodyLarge
+ )
+
+ Checkbox(
+ checked = viewModel.isChecked(opt.label),
+ onCheckedChange = { isChecked -> viewModel.setChecked(opt.label, isChecked) },
+ colors = CheckboxDefaults.colors(checkedColor = MaterialTheme.colorScheme.primary)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ OutlinedTextField(
+ value = viewModel.otherNote,
+ onValueChange = { viewModel.otherNote = it },
+ label = { Text("Catatan tambahan (opsional)") },
modifier = Modifier
.fillMaxWidth()
- .padding(vertical = 6.dp)
- ) {
- Box(
- modifier = Modifier
- .size(34.dp)
- .background(color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.06f), shape = CircleShape),
- contentAlignment = Alignment.Center
- ) {
- Text(text = opt.icon)
- }
+ .heightIn(min = 56.dp, max = 120.dp),
+ colors = TextFieldDefaults.colors(
+ focusedIndicatorColor = MaterialTheme.colorScheme.primary,
+ cursorColor = MaterialTheme.colorScheme.primary
+ )
+ )
- Spacer(modifier = Modifier.width(12.dp))
-
- Text(text = opt.label, modifier = Modifier.weight(1f))
-
- val checked = viewModel.isChecked(opt.label)
- Checkbox(
- checked = checked,
- onCheckedChange = { checkedValue -> viewModel.setChecked(opt.label, checkedValue) }
+ if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
+ Text(
+ "Catatan wajib diisi jika memilih 'Lainnya'",
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodySmall,
+ modifier = Modifier.padding(start = 4.dp, top = 4.dp)
)
}
}
-
- Spacer(modifier = Modifier.height(8.dp))
-
- OutlinedTextField(
- value = viewModel.otherNote,
- onValueChange = { newValue -> viewModel.otherNote = newValue },
- label = { Text("Catatan tambahan (opsional)") },
- modifier = Modifier
- .fillMaxWidth()
- .heightIn(min = 56.dp, max = 140.dp)
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
- Text("Catatan wajib jika Anda memilih 'Lainnya'", color = MaterialTheme.colorScheme.error)
- }
}
}
- Spacer(modifier = Modifier.height(20.dp))
-
- PanicButton(onClick = { viewModel.sendAlert() })
-
- Spacer(modifier = Modifier.height(14.dp))
-
- val dialog = viewModel.dialogMessage
- if (!dialog.isNullOrBlank()) {
+ val dialogMessage = viewModel.dialogMessage
+ if (!dialogMessage.isNullOrBlank()) {
AlertDialog(
onDismissRequest = { viewModel.clearDialog() },
- confirmButton = { TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") } },
- title = { Text("Notifikasi") },
- text = { Text(dialog) }
+ confirmButton = {
+ TextButton(onClick = { viewModel.clearDialog() }) {
+ Text("OK", color = MaterialTheme.colorScheme.primary)
+ }
+ },
+ title = { Text("Notifikasi", style = MaterialTheme.typography.titleMedium) },
+ text = { Text(dialogMessage, style = MaterialTheme.typography.bodyMedium) },
+ containerColor = MaterialTheme.colorScheme.surface
)
}
}
diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt
index 66a32dc..2ee6530 100644
--- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt
+++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt
@@ -1,66 +1,49 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
val options = listOf(
ReportOption("Kebakaran", "🔥"),
ReportOption("Banjir", "🌊"),
- ReportOption("Gempa Bumi", "🌍"),
- ReportOption("Huru Hara/Demonstrasi", "📣"),
- ReportOption("Lainnya", "📝")
+ ReportOption("Gempa Bumi", "🌋"),
+ ReportOption("Huru Hara/Demonstrasi", "💥"),
+ ReportOption("Lainnya", "✏️")
)
- // simple state holders
- var checkedMap by mutableStateOf(options.associate { it.label to false }.toMutableMap())
- private set
-
- var otherNote by mutableStateOf("")
-
- var dialogMessage by mutableStateOf(null)
-
- fun isChecked(label: String): Boolean = checkedMap[label] ?: false
- fun setChecked(label: String, checked: Boolean) { toggleOption(label, checked) }
-
- fun toggleOption(label: String, checked: Boolean) {
- val copy = checkedMap.toMutableMap()
- copy[label] = checked
- checkedMap = copy
+ // observable map so Compose recomposes on changes
+ private val _checkedState = mutableStateMapOf().apply {
+ options.forEach { put(it.label, false) }
}
- fun sendAlert(senderName: String = "Rakha adi saputro 202310715083") {
- val selected = checkedMap.filterValues { it }.keys.toList()
- if (selected.isEmpty()) {
- dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim."
- return
- }
- if (selected.contains("Lainnya") && otherNote.isBlank()) {
- dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan."
+ var otherNote by mutableStateOf("")
+ var dialogMessage by mutableStateOf(null)
+
+ fun isChecked(label: String): Boolean = _checkedState[label] == true
+
+ fun setChecked(label: String, isChecked: Boolean) {
+ _checkedState[label] = isChecked
+ }
+
+ fun sendAlert() {
+ val selectedOptions = options.filter { isChecked(it.label) }.map { it.label }
+
+ if (selectedOptions.isEmpty()) {
+ dialogMessage = "Pilih setidaknya satu kondisi darurat."
return
}
- val message = buildString {
- append("Jenis Laporan: ")
- append(selected.joinToString(", "))
- append("\nKeterangan: ")
- append(if (otherNote.isBlank()) "-" else otherNote.trim())
- append("\nPengirim: ")
- append(senderName)
+ if (isChecked("Lainnya") && otherNote.isBlank()) {
+ dialogMessage = "Catatan wajib diisi jika Anda memilih 'Lainnya'."
+ return
}
- viewModelScope.launch {
- try {
- val result = NotificationSender.sendNotification(message)
- dialogMessage = result
- } catch (e: Exception) {
- dialogMessage = "Error: ${e.message}"
- }
- }
+ // Simulate sending data
+ dialogMessage = "Laporan terkirim!\nKondisi: ${selectedOptions.joinToString() }"
}
fun clearDialog() {
diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Color.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Color.kt
index 8fc2706..d8e1c87 100644
--- a/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Color.kt
+++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Color.kt
@@ -2,10 +2,11 @@ package id.ac.ubharajaya.panicbutton.ui.theme
import androidx.compose.ui.graphics.Color
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
+val Blue_primary = Color(0xFF007BFF)
+val Blue_secondary = Color(0xFF00A2E8)
+val Dark_blue = Color(0xFF0056b3)
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
+val Grey_light = Color(0xFFF5F5F5)
+val Grey_dark = Color(0xFF333333)
+
+val Red_cancel = Color(0xFFDC3545)
diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Theme.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Theme.kt
index 1614f52..c25748d 100644
--- a/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Theme.kt
+++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/ui/theme/Theme.kt
@@ -1,53 +1,53 @@
package id.ac.ubharajaya.panicbutton.ui.theme
import android.app.Activity
-import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
+ primary = Blue_secondary,
+ secondary = Blue_primary,
+ background = Grey_dark,
+ surface = Color(0xFF1E1E1E),
+ onPrimary = Grey_dark,
+ onSecondary = Grey_dark,
+ onBackground = Grey_light,
+ onSurface = Grey_light,
+ error = Red_cancel
)
private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
+ primary = Blue_primary,
+ secondary = Blue_secondary,
+ tertiary = Dark_blue,
+ background = Grey_light,
+ surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
+ onBackground = Grey_dark,
+ onSurface = Grey_dark,
+ error = Red_cancel
)
@Composable
fun PanicButtonTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
+ dynamicColor: Boolean = false, // Disable dynamic color
content: @Composable () -> Unit
) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
-
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
+ val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
MaterialTheme(
@@ -55,4 +55,4 @@ fun PanicButtonTheme(
typography = Typography,
content = content
)
-}
\ No newline at end of file
+}