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.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()
}

View File

@ -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) {