feat(ui): add checklist report types and 3D panic button; include notes/validation and formatted notification

This commit is contained in:
Rakha adi 2025-11-19 21:12:45 +07:00
parent ba36f0447b
commit 97d37a43ad
2 changed files with 181 additions and 81 deletions

29
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="pendingSessionIds">
<option value="bf5061b4-a1e9-4600-a273-ea8e51b2ce89" />
</option>
<option name="pendingTurns">
<map>
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89">
<value>
<set>
<option value="6dffb37e-ecb0-4c89-8cbe-e38ed347c397" />
<option value="f607f271-4885-4c4e-91da-e2caf2abd67a" />
<option value="39a0d9c4-a68f-4050-bb14-eda17dc695db" />
<option value="94d90579-6a6d-44a9-8b93-de694c0c38ef" />
</set>
</value>
</entry>
</map>
</option>
<pendingWorkingSetItems>
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89">
<set>
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt" />
</set>
</entry>
</pendingWorkingSetItems>
</component>
</project>

View File

@ -4,13 +4,14 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
@ -45,6 +46,11 @@ fun MyApp() {
// dialogMessage akan menampung hasil callback dari sendNotification; null = tidak tampil
var dialogMessage by remember { mutableStateOf<String?>(null) }
// Report options (checkbox-style)
val reportOptions = listOf("Kebakaran", "Banjir", "Gempa Bumi", "Huru Hara/Demostrasi", "Lainnya")
val checkedMap = remember { mutableStateMapOf<String, Boolean>().apply { reportOptions.forEach { put(it, false) } } }
var otherNote by remember { mutableStateOf("") }
// UI
Column(
modifier = Modifier
@ -53,17 +59,97 @@ fun MyApp() {
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// tombol panic
PanicButton(onClick = {
// Kirim HTTP request saat tombol ditekan
sendNotification { response ->
// tampilkan dialog ketika ada respon
dialogMessage = response
// Checklist area similar to the wireframe
Surface(
tonalElevation = 2.dp,
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
) {
Column(modifier = Modifier.padding(12.dp)) {
Text(text = "Terjadi Kondisi Darurat", fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.height(8.dp))
// Each option as a row with checkbox
reportOptions.forEach { option ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.toggleable(
value = checkedMap[option] ?: false,
onValueChange = { checked -> checkedMap[option] = checked }
)
) {
Checkbox(
checked = checkedMap[option] ?: false,
onCheckedChange = { checked -> checkedMap[option] = checked }
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = option)
}
})
}
// Custom Dialog untuk menampilkan status kirim notifikasi — styled sesuai tema panic
// Additional notes field — show always (wireframe shows a note field)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = otherNote,
onValueChange = { otherNote = it },
label = { Text("Catatan tambahan (opsional)") },
placeholder = { Text("Catatan tambahan ...") },
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
// small hint when Lainnya is checked but note empty
if ((checkedMap["Lainnya"] == true) && otherNote.isBlank()) {
Text(
text = "Catatan wajib jika Anda memilih 'Lainnya'",
color = Color(0xFFB71C1C),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 6.dp)
)
}
}
}
Spacer(modifier = Modifier.height(18.dp))
// Keep the existing nice PanicButton unchanged
PanicButton(onClick = {
// Validation: at least one option selected
val selected = reportOptions.filter { checkedMap[it] == true }
if (selected.isEmpty()) {
dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim."
return@PanicButton
}
if (selected.contains("Lainnya") && otherNote.isBlank()) {
dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan."
return@PanicButton
}
// build message: list selected options and the note
val message = buildString {
append("Jenis Laporan: ")
append(selected.joinToString(", "))
append("\n")
append("Keterangan: ")
if (selected.contains("Lainnya")) append(otherNote.trim()) else append("-")
append("\n")
append("Pengirim: Satrio Putra Wardani 202310715307")
}
sendNotification(message) { response -> dialogMessage = response }
})
Spacer(modifier = Modifier.height(16.dp))
// dialog hasil
if (dialogMessage != null) {
Dialog(onDismissRequest = { dialogMessage = null }) {
Box(
@ -131,6 +217,7 @@ fun MyApp() {
}
}
}
}
// New: 3D-styled PanicButton with press animation and glossy highlight
@ -206,34 +293,18 @@ fun PanicButton(
)
}
}
Spacer(modifier = Modifier.height(14.dp))
// label with 3D-like chip
Surface(
color = panicColor.copy(alpha = 0.10f),
shape = RoundedCornerShape(20.dp),
tonalElevation = 0.dp
) {
Text(
text = "PANIC",
color = panicColor,
modifier = Modifier
.padding(horizontal = 20.dp, vertical = 8.dp)
)
}
}
}
// Fungsi untuk mengirimkan HTTP request
fun sendNotification(onResult: (String) -> Unit) {
// Fungsi untuk mengirimkan HTTP request (mengikuti format pesan yang lebih rapi)
fun sendNotification(message: String, onResult: (String) -> Unit) {
val client = OkHttpClient()
val url = "https://ntfy.ubharajaya.ac.id/panic-button" // Ganti <your-topic> dengan topik Anda
val requestBody = "Notifikasi dari Satrio Putra Wardani 202310715307".toRequestBody(
val requestBody = message.toRequestBody(
"text/plain".toMediaType()
)