Compare commits

..

3 Commits

Author SHA1 Message Date
835b856792 Penyesuaian Code Settings Menu 2025-12-03 10:35:13 +07:00
3a5911c69f Merge remote-tracking branch 'origin/kelompok-1' into kelompok-1 2025-12-03 10:11:14 +07:00
0208835ce3 Revert "Menyesuaikan"
This reverts commit 36607418
2025-11-27 21:56:06 +07:00
3 changed files with 128 additions and 358 deletions

View File

@ -1,4 +0,0 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@ -3,15 +3,23 @@ package id.ac.ubharajaya.panicbutton
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -19,245 +27,141 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody 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() { class AlertActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
AlertScreen() SettingsScreen(onBack = { finish() })
} }
} }
} }
@Composable // ----------------------------------------------------------------------
fun AlertScreen() { // SETTINGS SCREEN
val context = androidx.compose.ui.platform.LocalContext.current // ----------------------------------------------------------------------
val sharedPreferences = context.getSharedPreferences("PanicButtonPrefs", Context.MODE_PRIVATE)
// Ambil konfigurasi dari SharedPreferences @OptIn(ExperimentalMaterial3Api::class)
val ntfyUrl = remember { @Composable
sharedPreferences.getString("urlKey", null) fun SettingsScreen(onBack: () -> Unit) {
?: "${sharedPreferences.getString("ntfyServer", "https://ntfy.sh")}/${sharedPreferences.getString("ntfyTopic", "ubahara-panic-button")}" 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 alertMessage by remember { mutableStateOf("") } var saveMessage by remember { mutableStateOf("") }
var showDialog by remember { mutableStateOf(false) }
var dialogMessage by remember { mutableStateOf("") }
var selectedAlertType by remember { mutableStateOf("") }
val scrollState = rememberScrollState() // ⬅️ TAMBAHAN: Menangani tombol kembali pada perangkat
BackHandler(onBack = onBack)
// Dialog konfirmasi Scaffold(
if (showDialog) { topBar = {
AlertDialog( TopAppBar(
onDismissRequest = { showDialog = false }, title = { Text("Pengaturan NTFY Endpoint") },
title = { Text("Status Pengiriman") }, navigationIcon = {
text = { Text(dialogMessage) }, IconButton(onClick = onBack) { // ⬅️ onBack menangani navigasi kembali ke MainScreen
confirmButton = { Icon(Icons.Filled.ArrowBack, contentDescription = "Kembali")
Button(onClick = { showDialog = false }) {
Text("OK")
} }
} }
) )
} }
) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .padding(innerPadding)
.padding(16.dp), .padding(16.dp)
verticalArrangement = Arrangement.Top, .verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.Start
) { ) {
Text( Text(
text = "⚠️ SISTEM ALERT DARURAT", text = "Konfigurasi Server Notifikasi",
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, fontSize = 18.sp,
modifier = Modifier.padding(vertical = 8.dp) modifier = Modifier.padding(bottom = 16.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( OutlinedTextField(
value = customMessage, value = serverText,
onValueChange = { customMessage = it }, onValueChange = { serverText = it },
label = { Text("Pesan Alert") }, label = { Text("Server NTFY (contoh: https://ntfy.sh)") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
minLines = 3 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
singleLine = true
) )
Spacer(modifier = Modifier.height(8.dp)) 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( Button(
onClick = { onClick = {
if (customMessage.isNotEmpty()) { saveNtfyConfig(
sendCustomAlert(ntfyUrl, customMessage) { response -> context,
alertMessage = response serverText.text.trim(),
dialogMessage = response topicText.text.trim()
showDialog = true )
customMessage = "" saveMessage = "Konfigurasi berhasil disimpan! URL: ${getNtfyUrl(context)}"
}
}
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF6F00)),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Kirim Alert Kustom") Text("Simpan Konfigurasi")
}
}
} }
Spacer(modifier = Modifier.height(16.dp))
if (alertMessage.isNotEmpty()) {
Text( Text(
text = alertMessage, text = saveMessage,
color = if (alertMessage.contains("berhasil")) Color.Green else Color.Red, color = if (saveMessage.contains("berhasil")) Color.Green else Color.Black,
modifier = Modifier.padding(top = 16.dp) modifier = Modifier.padding(top = 16.dp)
) )
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = "TETAP TENANG DAN IKUTI PROSEDUR EVAKUASI", text = "URL saat ini: ${getNtfyUrl(context)}",
color = Color.Red, style = TextStyle(color = Color.Gray, fontSize = 12.sp)
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()
} }

View File

@ -34,40 +34,7 @@ import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// 1. CONSTANTS & HELPER FUNCTIONS FOR SHAREDPREFERENCES // MAIN ACTIVITY CLASS
// ----------------------------------------------------------------------
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
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -80,7 +47,7 @@ class MainActivity : ComponentActivity() {
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// 3. MY APP (NAVIGATOR) // MY APP (NAVIGATOR)
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
sealed class ScreenState { sealed class ScreenState {
@ -133,7 +100,7 @@ fun MyApp() {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// 4. MAIN SCREEN // MAIN SCREEN
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@Composable @Composable
@ -246,104 +213,7 @@ fun MainScreen(
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// 5. SETTINGS SCREEN (DIPERBAIKI) // SEND NOTIFICATION FUNCTION
// ----------------------------------------------------------------------
@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
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
fun sendNotification(context: Context, condition: String, report: String, onResult: (String) -> Unit) { fun sendNotification(context: Context, condition: String, report: String, onResult: (String) -> Unit) {