diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2c0511f..c53de8a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,6 +42,8 @@ android { } dependencies { + implementation("com.squareup.okhttp3:okhttp:4.11.0") + implementation("androidx.compose.material3:material3:1.1.1") implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt new file mode 100644 index 0000000..5dc6a7f --- /dev/null +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt @@ -0,0 +1,264 @@ +package id.ac.ubharajaya.panicbutton + +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody + +class AlertActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + AlertScreen() + } + } +} + +@Composable +fun AlertScreen() { + val context = androidx.compose.ui.platform.LocalContext.current + val sharedPreferences = context.getSharedPreferences("PanicButtonPrefs", Context.MODE_PRIVATE) + + // Ambil konfigurasi dari SharedPreferences + val ntfyUrl = remember { + sharedPreferences.getString("urlKey", null) + ?: "${sharedPreferences.getString("ntfyServer", "https://ntfy.sh")}/${sharedPreferences.getString("ntfyTopic", "ubahara-panic-button")}" + } + + var alertMessage by remember { mutableStateOf("") } + var showDialog by remember { mutableStateOf(false) } + var dialogMessage by remember { mutableStateOf("") } + var selectedAlertType by remember { mutableStateOf("") } + + val scrollState = rememberScrollState() + + // Dialog konfirmasi + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text("Status Pengiriman") }, + text = { Text(dialogMessage) }, + confirmButton = { + Button(onClick = { showDialog = false }) { + Text("OK") + } + } + ) + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "⚠ SISTEM ALERT DARURAT", + fontSize = 24.sp, + color = Color.Red, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = "Endpoint: $ntfyUrl", + fontSize = 12.sp, + color = Color.Gray, + modifier = Modifier.padding(bottom = 24.dp) + ) + + // Tombol Alert Cepat + val alertTypes = listOf( + "πŸ”₯ KEBAKARAN" to "fire", + "🌊 BANJIR/TSUNAMI" to "ocean", + "🌏 GEMPA BUMI" to "earth_asia", + "☒ BAHAYA RADIASI" to "radioactive", + "🚨 EVAKUASI SEGERA" to "rotating_light" + ) + + alertTypes.forEach { (label, tag) -> + Button( + onClick = { + selectedAlertType = label + sendQuickAlert(ntfyUrl, label, tag) { response -> + alertMessage = response + dialogMessage = response + showDialog = true + } + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Red + ), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + Text( + text = label, + color = Color.White, + fontSize = 18.sp, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + // Custom Alert dengan input + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF3E0)) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = "ALERT KUSTOM", + fontSize = 16.sp, + color = Color.Red + ) + Spacer(modifier = Modifier.height(8.dp)) + + var customMessage by remember { mutableStateOf("") } + + OutlinedTextField( + value = customMessage, + onValueChange = { customMessage = it }, + label = { Text("Pesan Alert") }, + modifier = Modifier.fillMaxWidth(), + minLines = 3 + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Button( + onClick = { + if (customMessage.isNotEmpty()) { + sendCustomAlert(ntfyUrl, customMessage) { response -> + alertMessage = response + dialogMessage = response + showDialog = true + customMessage = "" + } + } + }, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF6F00)), + modifier = Modifier.fillMaxWidth() + ) { + Text("Kirim Alert Kustom") + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + if (alertMessage.isNotEmpty()) { + Text( + text = alertMessage, + color = if (alertMessage.contains("berhasil")) Color.Green else Color.Red, + modifier = Modifier.padding(top = 16.dp) + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "TETAP TENANG DAN IKUTI PROSEDUR EVAKUASI", + color = Color.Red, + fontSize = 14.sp + ) + } +} + +fun sendQuickAlert(url: String, alertType: String, tag: String, onResult: (String) -> Unit) { + val client = OkHttpClient() + + val message = """ + ⚠ ALERT DARURAT ⚠ + + Jenis: $alertType + Waktu: ${java.text.SimpleDateFormat("dd/MM/yyyy HH:mm:ss", java.util.Locale.getDefault()).format(java.util.Date())} + + SEGERA LAKUKAN EVAKUASI! + """.trimIndent() + + val requestBody = RequestBody.create( + "text/plain".toMediaType(), + message + ) + + val request = Request.Builder() + .url(url) + .addHeader("Title", "🚨 ALERT DARURAT - $alertType") + .addHeader("Priority", "urgent") + .addHeader("Tags", "warning,alert,$tag,rotating_light") + .post(requestBody) + .build() + + Thread { + try { + val response = client.newCall(request).execute() + if (response.isSuccessful) { + onResult("βœ… Alert $alertType berhasil dikirim!") + } else { + onResult("❌ Gagal mengirim alert: ${response.code}") + } + } catch (e: Exception) { + onResult("❌ Error: ${e.message}") + } + }.start() +} + +fun sendCustomAlert(url: String, customMessage: String, onResult: (String) -> Unit) { + val client = OkHttpClient() + + val message = """ + πŸ“’ PESAN DARURAT KUSTOM + + $customMessage + + Waktu: ${java.text.SimpleDateFormat("dd/MM/yyyy HH:mm:ss", java.util.Locale.getDefault()).format(java.util.Date())} + """.trimIndent() + + val requestBody = RequestBody.create( + "text/plain".toMediaType(), + message + ) + + val request = Request.Builder() + .url(url) + .addHeader("Title", "πŸ“’ Pesan Darurat Kustom") + .addHeader("Priority", "high") + .addHeader("Tags", "warning,alert,loudspeaker") + .post(requestBody) + .build() + + Thread { + try { + val response = client.newCall(request).execute() + if (response.isSuccessful) { + onResult("βœ… Pesan kustom berhasil dikirim!") + } else { + onResult("❌ Gagal mengirim pesan: ${response.code}") + } + } catch (e: Exception) { + onResult("❌ Error: ${e.message}") + } + }.start() +} + 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 b5d3789..d4050f5 100644 --- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt @@ -1,6 +1,6 @@ package id.ac.ubharajaya.panicbutton - +import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity @@ -27,8 +27,6 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody -import java.util.concurrent.locks.Condition - class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -39,18 +37,93 @@ class MainActivity : ComponentActivity() { } } - @Composable fun MyApp() { val focusManager = LocalFocusManager.current + val context = LocalContext.current + val sharedPreferences = context.getSharedPreferences("PanicButtonPrefs", Context.MODE_PRIVATE) + var message by remember { mutableStateOf("Klik tombol untuk mengirim notifikasi") } var selectedConditions by remember { mutableStateOf(mutableSetOf()) } var additionalNotes by remember { mutableStateOf(TextFieldValue("")) } + var showDialog by remember { mutableStateOf(false) } + var dialogMessage by remember { mutableStateOf("") } + var showSettingsDialog by remember { mutableStateOf(false) } + + // Load saved configuration + var ntfyServer by remember { + mutableStateOf(sharedPreferences.getString("ntfyServer", "https://ntfy.sh") ?: "https://ntfy.sh") + } + var ntfyTopic by remember { + mutableStateOf(sharedPreferences.getString("ntfyTopic", "ubahara-panic-button") ?: "ubahara-panic-button") + } - // Agar bisa Scroll val scrollState = rememberScrollState() - val context = LocalContext.current + // Dialog konfirmasi + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text("Konfirmasi") }, + text = { Text(dialogMessage) }, + confirmButton = { + Button(onClick = { showDialog = false }) { + Text("OK") + } + } + ) + } + + // Dialog Settings + if (showSettingsDialog) { + var tempServer by remember { mutableStateOf(ntfyServer) } + var tempTopic by remember { mutableStateOf(ntfyTopic) } + + AlertDialog( + onDismissRequest = { showSettingsDialog = false }, + title = { Text("Pengaturan NTFY") }, + text = { + Column { + Text("Server NTFY:") + OutlinedTextField( + value = tempServer, + onValueChange = { tempServer = it }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + Text("Topic:") + OutlinedTextField( + value = tempTopic, + onValueChange = { tempTopic = it }, + modifier = Modifier.fillMaxWidth() + ) + } + }, + confirmButton = { + Button(onClick = { + ntfyServer = tempServer + ntfyTopic = tempTopic + // Save to SharedPreferences + with(sharedPreferences.edit()) { + putString("ntfyServer", tempServer) + putString("ntfyTopic", tempTopic) + // Gabungkan untuk kompatibilitas dengan AlertActivity + putString("urlKey", "$tempServer/$tempTopic") + apply() + } + showSettingsDialog = false + message = "Pengaturan berhasil disimpan" + }) { + Text("Simpan") + } + }, + dismissButton = { + TextButton(onClick = { showSettingsDialog = false }) { + Text("Batal") + } + } + ) + } Column( modifier = Modifier @@ -60,17 +133,37 @@ fun MyApp() { verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start ) { + // Header dengan tombol settings + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Terjadi Kondisi Darurat", + fontSize = 20.sp, + color = Color.Red + ) + Button( + onClick = { showSettingsDialog = true }, + colors = ButtonDefaults.buttonColors(containerColor = Color.Gray) + ) { + Text("βš™", fontSize = 18.sp) + } + } + + Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Terjadi Kondisi Darurat", - fontSize = 20.sp, - color = Color.Red, + text = "Server: $ntfyServer\nTopic: $ntfyTopic", + fontSize = 12.sp, + color = Color.Gray, modifier = Modifier.padding(bottom = 16.dp) ) listOf( - "\uD83D\uDD25 Kebakaran", "β›ˆ\uFE0F Banjir", "\uD83C\uDF0A Tsunami", "\uD83C\uDF0B Gunung Meletus", - "\uD83C\uDF0F Gempa Bumi", "\uD83D\uDC7F Huru hara", "\uD83D\uDC0D Binatang Buas", - "☒\uFE0F Radiasi Nuklir", "☣\uFE0F Biohazard" + "πŸ”₯ Kebakaran", "β›ˆ Banjir", "🌊 Tsunami", "πŸŒ‹ Gunung Meletus", + "🌏 Gempa Bumi", "πŸ‘Ώ Huru hara", "🐍 Binatang Buas", + "☒ Radiasi Nuklir", "☣ Biohazard" ).forEach { condition -> Row( modifier = Modifier @@ -119,18 +212,23 @@ fun MyApp() { val notes = additionalNotes.text val conditions = selectedConditions.joinToString(", ") val report = "Kondisi: $conditions\nCatatan: $notes" - sendNotification(conditions, report) { response -> + val fullUrl = "$ntfyServer/$ntfyTopic" + + sendNotification(fullUrl, conditions, report) { response -> message = response + dialogMessage = response + showDialog = true } }, - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + colors = ButtonDefaults.buttonColors(containerColor = Color.Red), + enabled = selectedConditions.isNotEmpty() ) { Text(text = "Kirim Laporan", color = Color.White) } Spacer(modifier = Modifier.height(16.dp)) Text( - text = "β€œJANGAN PANIK! SEGERA EVAKUASI\nDIRI ANDA KE TITIK KUMPUL”", + text = "JANGAN PANIK! SEGERA EVAKUASI DIRI ANDA KE TITIK KUMPUL", color = Color.Red, fontSize = 15.sp ) @@ -149,41 +247,29 @@ fun MyApp() { } } - -// Fungsi untuk mengirimkan HTTP request -fun sendNotification(condition: String, report: String, onResult: (String) -> Unit) { +fun sendNotification(url: String, condition: String, report: String, onResult: (String) -> Unit) { val client = OkHttpClient() - val url = "https://ntfy.ubharajaya.ac.id/panic-button" - - - // Mapping kondisi β†’ emoji/tag val tagMapping = mapOf( - "\uD83D\uDD25 Kebakaran" to "fire", - "β›ˆ\uFE0F Banjir" to "cloud_with_lightning_and_rain", - "\uD83C\uDF0A Tsunami" to "ocean", - "\uD83C\uDF0B Gunung Meletus" to "volcano", - "\uD83C\uDF0F Gempa Bumi" to "earth_asia", - "\uD83D\uDC7F Huru hara" to "imp", - "\uD83D\uDC0D Binatang Buas" to "snake", - "☒\uFE0F Radiasi Nuklir" to "radioactive", - "☣\uFE0F Biohazard" to "biohazard" + "πŸ”₯ Kebakaran" to "fire", + "β›ˆ Banjir" to "cloud_with_lightning_and_rain", + "🌊 Tsunami" to "ocean", + "πŸŒ‹ Gunung Meletus" to "volcano", + "🌏 Gempa Bumi" to "earth_asia", + "πŸ‘Ώ Huru hara" to "imp", + "🐍 Binatang Buas" to "snake", + "☒ Radiasi Nuklir" to "radioactive", + "☣ Biohazard" to "biohazard" ) - // Bersihkan kondisi: trim + filter val selectedList = condition .split(",") .map { it.trim() } .filter { it.isNotEmpty() } - // Format ulang kondisi val cleanConditionText = selectedList.joinToString(", ") - - // Ambil emoji untuk masing-masing kondisi val emojiTags = selectedList.mapNotNull { tagMapping[it] } - - // Tambahkan default tag: alert + warning - val finalTags = listOf("Alert") + emojiTags + val finalTags = listOf("alert", "warning") + emojiTags val finalReport = "Kondisi: $cleanConditionText\nCatatan: ${report.substringAfter("Catatan:")}" val requestBody = RequestBody.create( @@ -192,7 +278,7 @@ fun sendNotification(condition: String, report: String, onResult: (String) -> Un ) val request = Request.Builder() .url(url) - .addHeader("Title", "Alert") + .addHeader("Title", "⚠ ALERT - Kondisi Darurat") .addHeader("Priority", "urgent") .addHeader("Tags", finalTags.joinToString(",")) .post(requestBody) @@ -202,12 +288,12 @@ fun sendNotification(condition: String, report: String, onResult: (String) -> Un try { val response = client.newCall(request).execute() if (response.isSuccessful) { - onResult("Notifikasi berhasil dikirim!") + onResult("βœ… Notifikasi berhasil dikirim!\nLaporan telah terkirim ke sistem.") } else { - onResult("Gagal mengirim notifikasi: ${response.code}") + onResult("❌ Gagal mengirim notifikasi\nKode error: ${response.code}") } } catch (e: Exception) { - onResult("Error: ${e.message}") + onResult("❌ Error: ${e.message}") } }.start() } \ No newline at end of file diff --git a/app/src/main/res/drawable/jalur_evakuasi.png b/app/src/main/res/drawable/jalur_evakuasi.png index 3e9b5af..10d6f9d 100644 Binary files a/app/src/main/res/drawable/jalur_evakuasi.png and b/app/src/main/res/drawable/jalur_evakuasi.png differ