From 835b856792ffe6cdb8d80c4e01afb4c64f9c7e94 Mon Sep 17 00:00:00 2001 From: Raihan Ariq <202310715297@mhs.ubharajaya.ac.id> Date: Wed, 3 Dec 2025 10:35:13 +0700 Subject: [PATCH] Penyesuaian Code Settings Menu --- .../ubharajaya/panicbutton/AlertActivity.kt | 344 +++++++----------- .../ac/ubharajaya/panicbutton/MainActivity.kt | 138 +------ 2 files changed, 128 insertions(+), 354 deletions(-) diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt index 39158de..fba73f9 100644 --- a/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/AlertActivity.kt @@ -3,15 +3,23 @@ package id.ac.ubharajaya.panicbutton import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack 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.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import okhttp3.MediaType.Companion.toMediaType @@ -19,245 +27,141 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody +// ---------------------------------------------------------------------- +// 1. CONSTANTS & HELPER FUNCTIONS FOR SHAREDPREFERENCES +// ---------------------------------------------------------------------- + +const val PREFS_NAME = "AppPrefs" +const val KEY_NTFY_SERVER = "ntfyServer" +const val KEY_NTFY_TOPIC = "ntfyTopic" + +const val DEFAULT_NTFY_SERVER = "https://ntfy.ubharajaya.ac.id" +const val DEFAULT_NTFY_TOPIC = "panic-button" + + +fun getPrefs(context: Context) = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + +fun saveNtfyConfig(context: Context, server: String, topic: String) { + getPrefs(context).edit().apply { + putString(KEY_NTFY_SERVER, server) + putString(KEY_NTFY_TOPIC, topic) + apply() + } +} + +fun getNtfyUrl(context: Context): String { + val prefs = getPrefs(context) + val server = prefs.getString(KEY_NTFY_SERVER, DEFAULT_NTFY_SERVER) + val topic = prefs.getString(KEY_NTFY_TOPIC, DEFAULT_NTFY_TOPIC) + + val cleanServer = server?.trimEnd('/') ?: DEFAULT_NTFY_SERVER.trimEnd('/') + val cleanTopic = topic?.trimStart('/') ?: DEFAULT_NTFY_TOPIC.trimStart('/') + + return "$cleanServer/$cleanTopic" +} + + class AlertActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - AlertScreen() + SettingsScreen(onBack = { finish() }) } } } +// ---------------------------------------------------------------------- +// SETTINGS SCREEN +// ---------------------------------------------------------------------- + +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun AlertScreen() { - val context = androidx.compose.ui.platform.LocalContext.current - val sharedPreferences = context.getSharedPreferences("PanicButtonPrefs", Context.MODE_PRIVATE) +fun SettingsScreen(onBack: () -> Unit) { + val context = LocalContext.current + val prefs = remember { getPrefs(context) } - // Ambil konfigurasi dari SharedPreferences - val ntfyUrl = remember { - sharedPreferences.getString("urlKey", null) - ?: "${sharedPreferences.getString("ntfyServer", "https://ntfy.sh")}/${sharedPreferences.getString("ntfyTopic", "ubahara-panic-button")}" + // Inisialisasi state dari SharedPreferences + var serverText by remember { + mutableStateOf(TextFieldValue(prefs.getString(KEY_NTFY_SERVER, DEFAULT_NTFY_SERVER) ?: DEFAULT_NTFY_SERVER)) + } + var topicText by remember { + mutableStateOf(TextFieldValue(prefs.getString(KEY_NTFY_TOPIC, DEFAULT_NTFY_TOPIC) ?: DEFAULT_NTFY_TOPIC)) } - var alertMessage by remember { mutableStateOf("") } - var showDialog by remember { mutableStateOf(false) } - var dialogMessage by remember { mutableStateOf("") } - var selectedAlertType by remember { mutableStateOf("") } + var saveMessage by remember { mutableStateOf("") } - val scrollState = rememberScrollState() + // ⬅️ TAMBAHAN: Menangani tombol kembali pada perangkat + BackHandler(onBack = onBack) - // 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 + Scaffold( + topBar = { + TopAppBar( + title = { Text("Pengaturan NTFY Endpoint") }, + navigationIcon = { + IconButton(onClick = onBack) { // ⬅️ onBack menangani navigasi kembali ke MainScreen + Icon(Icons.Filled.ArrowBack, contentDescription = "Kembali") } - }, - 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) ) } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(16.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.Start + ) { + Text( + text = "Konfigurasi Server Notifikasi", + fontSize = 18.sp, + modifier = Modifier.padding(bottom = 16.dp) + ) - Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = serverText, + onValueChange = { serverText = it }, + label = { Text("Server NTFY (contoh: https://ntfy.sh)") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + singleLine = true + ) - Text( - text = "TETAP TENANG DAN IKUTI PROSEDUR EVAKUASI", - color = Color.Red, - fontSize = 14.sp - ) + OutlinedTextField( + value = topicText, + onValueChange = { topicText = it }, + label = { Text("Topic (contoh: panic-button)") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + singleLine = true + ) + + Button( + onClick = { + saveNtfyConfig( + context, + serverText.text.trim(), + topicText.text.trim() + ) + saveMessage = "Konfigurasi berhasil disimpan! URL: ${getNtfyUrl(context)}" + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Simpan Konfigurasi") + } + + Text( + text = saveMessage, + color = if (saveMessage.contains("berhasil")) Color.Green else Color.Black, + modifier = Modifier.padding(top = 16.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "URL saat ini: ${getNtfyUrl(context)}", + style = TextStyle(color = Color.Gray, fontSize = 12.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() } \ 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 c72002a..32b8495 100644 --- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt @@ -34,40 +34,7 @@ import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.ui.text.TextStyle // ---------------------------------------------------------------------- -// 1. CONSTANTS & HELPER FUNCTIONS FOR SHAREDPREFERENCES -// ---------------------------------------------------------------------- - -const val PREFS_NAME = "AppPrefs" -const val KEY_NTFY_SERVER = "ntfyServer" -const val KEY_NTFY_TOPIC = "ntfyTopic" - -const val DEFAULT_NTFY_SERVER = "https://ntfy.ubharajaya.ac.id" -const val DEFAULT_NTFY_TOPIC = "panic-button" - - -fun getPrefs(context: Context) = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - -fun saveNtfyConfig(context: Context, server: String, topic: String) { - getPrefs(context).edit().apply { - putString(KEY_NTFY_SERVER, server) - putString(KEY_NTFY_TOPIC, topic) - apply() - } -} - -fun getNtfyUrl(context: Context): String { - val prefs = getPrefs(context) - val server = prefs.getString(KEY_NTFY_SERVER, DEFAULT_NTFY_SERVER) - val topic = prefs.getString(KEY_NTFY_TOPIC, DEFAULT_NTFY_TOPIC) - - val cleanServer = server?.trimEnd('/') ?: DEFAULT_NTFY_SERVER.trimEnd('/') - val cleanTopic = topic?.trimStart('/') ?: DEFAULT_NTFY_TOPIC.trimStart('/') - - return "$cleanServer/$cleanTopic" -} - -// ---------------------------------------------------------------------- -// 2. MAIN ACTIVITY CLASS +// MAIN ACTIVITY CLASS // ---------------------------------------------------------------------- class MainActivity : ComponentActivity() { @@ -80,7 +47,7 @@ class MainActivity : ComponentActivity() { } // ---------------------------------------------------------------------- -// 3. MY APP (NAVIGATOR) +// MY APP (NAVIGATOR) // ---------------------------------------------------------------------- sealed class ScreenState { @@ -133,7 +100,7 @@ fun MyApp() { // ---------------------------------------------------------------------- -// 4. MAIN SCREEN +// MAIN SCREEN // ---------------------------------------------------------------------- @Composable @@ -246,104 +213,7 @@ fun MainScreen( // ---------------------------------------------------------------------- -// 5. SETTINGS SCREEN (DIPERBAIKI) -// ---------------------------------------------------------------------- - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SettingsScreen(onBack: () -> Unit) { - val context = LocalContext.current - val prefs = remember { getPrefs(context) } - - // Inisialisasi state dari SharedPreferences - var serverText by remember { - mutableStateOf(TextFieldValue(prefs.getString(KEY_NTFY_SERVER, DEFAULT_NTFY_SERVER) ?: DEFAULT_NTFY_SERVER)) - } - var topicText by remember { - mutableStateOf(TextFieldValue(prefs.getString(KEY_NTFY_TOPIC, DEFAULT_NTFY_TOPIC) ?: DEFAULT_NTFY_TOPIC)) - } - - var saveMessage by remember { mutableStateOf("") } - - // ⬅️ TAMBAHAN: Menangani tombol kembali pada perangkat - BackHandler(onBack = onBack) - - Scaffold( - topBar = { - TopAppBar( - title = { Text("Pengaturan NTFY Endpoint") }, - navigationIcon = { - IconButton(onClick = onBack) { // ⬅️ onBack menangani navigasi kembali ke MainScreen - Icon(Icons.Filled.ArrowBack, contentDescription = "Kembali") - } - } - ) - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(16.dp) - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.Start - ) { - Text( - text = "Konfigurasi Server Notifikasi", - fontSize = 18.sp, - modifier = Modifier.padding(bottom = 16.dp) - ) - - OutlinedTextField( - value = serverText, - onValueChange = { serverText = it }, - label = { Text("Server NTFY (contoh: https://ntfy.sh)") }, - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - singleLine = true - ) - - OutlinedTextField( - value = topicText, - onValueChange = { topicText = it }, - label = { Text("Topic (contoh: panic-button)") }, - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - singleLine = true - ) - - Button( - onClick = { - saveNtfyConfig( - context, - serverText.text.trim(), - topicText.text.trim() - ) - saveMessage = "Konfigurasi berhasil disimpan! URL: ${getNtfyUrl(context)}" - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Simpan Konfigurasi") - } - - Text( - text = saveMessage, - color = if (saveMessage.contains("berhasil")) Color.Green else Color.Black, - modifier = Modifier.padding(top = 16.dp) - ) - - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "URL saat ini: ${getNtfyUrl(context)}", - style = TextStyle(color = Color.Gray, fontSize = 12.sp) - ) - } - } -} - - -// ---------------------------------------------------------------------- -// 6. SEND NOTIFICATION FUNCTION +// SEND NOTIFICATION FUNCTION // ---------------------------------------------------------------------- fun sendNotification(context: Context, condition: String, report: String, onResult: (String) -> Unit) {