diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c728902..587a907 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) implementation("com.squareup.okhttp3:okhttp:4.11.0") - + implementation("com.google.android.gms:play-services-location:21.3.0") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e146bf..aa51bff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,11 +23,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/java/id/ac/ubharajaya/panicbutton/JalurEvakuasiActivity.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/JalurEvakuasiActivity.kt index 0a41d2d..6c55930 100644 --- a/app/src/main/java/id/ac/ubharajaya/panicbutton/JalurEvakuasiActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/JalurEvakuasiActivity.kt @@ -5,43 +5,48 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp class JalurEvakuasiActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val denah = intent.getStringExtra("denah") ?: "utara" + setContent { - JalurEvakuasiScreen(onClose = { finish() }) + JalurEvakuasiScreen(denah) } } } @Composable -fun JalurEvakuasiScreen(onClose: () -> Unit) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { +fun JalurEvakuasiScreen(denah: String) { - Image( - painter = painterResource(id = R.drawable.evakuasi), - contentDescription = "Jalur Evakuasi", - modifier = Modifier.fillMaxWidth() + val gambar = when (denah) { + "utara" -> R.drawable.evakuasiutara + "selatan" -> R.drawable.evakuasiselatan + "labkomp" -> R.drawable.evakuasi_lab_kompemrograman + else -> R.drawable.evakuasiutara + } + + Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { + + Text( + text = "Jalur Evakuasi ($denah)", + style = MaterialTheme.typography.headlineSmall ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(16.dp)) - Button(onClick = onClose) { - Text("Close") - } + Image( + painter = painterResource(id = gambar), + contentDescription = "Denah Evakuasi", + modifier = Modifier.fillMaxWidth() + ) } -} +} \ 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 cb95295..6cbd3ac 100644 --- a/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt @@ -1,9 +1,14 @@ + package id.ac.ubharajaya.panicbutton +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager +import android.location.Location import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -20,65 +25,100 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody class MainActivity : ComponentActivity() { + + private lateinit var fusedLocationClient: FusedLocationProviderClient + private var lastLocation: Location? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) + requestLocationPermission() + getLocation() + setContent { MyApp( onClose = { finish() }, - onOpenEvacuation = { - startActivity(Intent(this, JalurEvakuasiActivity::class.java)) + onSend = { report, callback -> + val loc = lastLocation + val locString = if (loc != null) { + "\nLokasi: https://maps.google.com/?q=${loc.latitude},${loc.longitude}" + } else { + "\nLokasi: Tidak tersedia" + } + + sendNotification(report + locString) { response -> + callback(response) + + // Setelah laporan terkirim, pindah ke pemilihan ruangan + startActivity(Intent(this, RuanganActivity::class.java)) + } } ) } } -} -// Emoji untuk tiap kondisi -fun getEmoji(condition: String): String { - return when (condition) { - "Kebakaran" -> "πŸ”₯" - "Banjir" -> "β›ˆοΈ" - "Tsunami" -> "🌊" - "Gunung Meletus" -> "πŸŒ‹" - "Gempa Bumi" -> "🌏" - "Huru Hara/Demonstrasi" -> "πŸ‘Ώ" - "Binatang Buas" -> "🐍" - "Radiasi Nuklir" -> "☒️" - "Biohazard" -> "☣️" - else -> "" + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + if (granted) getLocation() + } + + private fun requestLocationPermission() { + when { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED -> getLocation() + + else -> requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) + } + } + + private fun getLocation() { + try { + fusedLocationClient.lastLocation.addOnSuccessListener { loc: Location? -> + if (loc != null) lastLocation = loc + } + } catch (_: SecurityException) { } } } +// =========================================================== +// UI +// =========================================================== + @Composable -fun MyApp(onClose: () -> Unit, onOpenEvacuation: () -> Unit) { +fun MyApp( + onClose: () -> Unit, + onSend: (String, (String) -> Unit) -> Unit +) { val focusManager = LocalFocusManager.current var message by remember { mutableStateOf("Klik tombol untuk mengirim notifikasi") } var selectedConditions by remember { mutableStateOf(mutableSetOf()) } var additionalNotes by remember { mutableStateOf(TextFieldValue("")) } Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start + modifier = Modifier.fillMaxSize().padding(16.dp) ) { - // Judul + Text( text = "Terjadi Kondisi Darurat:", fontSize = 26.sp, fontWeight = FontWeight.Bold, - color = Color.Red, - modifier = Modifier.padding(bottom = 16.dp) + color = Color.Red ) - // Daftar kondisi + Spacer(modifier = Modifier.height(12.dp)) + listOf( "Kebakaran", "Banjir", @@ -102,28 +142,24 @@ fun MyApp(onClose: () -> Unit, onOpenEvacuation: () -> Unit) { if (contains(condition)) remove(condition) else add(condition) } } - .padding(vertical = 4.dp), + .padding(vertical = 3.dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox( checked = selectedConditions.contains(condition), onCheckedChange = { isChecked -> - focusManager.clearFocus() selectedConditions = selectedConditions.toMutableSet().apply { if (isChecked) add(condition) else remove(condition) } } ) - Text( - text = "$emoji $condition", - modifier = Modifier.padding(start = 8.dp) - ) + Text(text = "$emoji $condition", modifier = Modifier.padding(start = 6.dp)) } } - // Catatan tambahan - Spacer(modifier = Modifier.height(16.dp)) - Text(text = "Catatan tambahan:", fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(12.dp)) + + Text("Catatan tambahan:", fontWeight = FontWeight.Bold) BasicTextField( value = additionalNotes, @@ -131,97 +167,79 @@ fun MyApp(onClose: () -> Unit, onOpenEvacuation: () -> Unit) { modifier = Modifier .fillMaxWidth() .height(100.dp) - .padding(8.dp) - .border(1.dp, Color.Gray), + .border(1.dp, Color.Gray) + .padding(8.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) ) - // ================================================ - // Tombol Kirim + Lihat Jalur Evakuasi (Berdampingan) - // ================================================ Spacer(modifier = Modifier.height(16.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween + Button( + onClick = { + val report = + "Kondisi: ${selectedConditions.joinToString()}\nCatatan: ${additionalNotes.text}" + onSend(report) { msg -> message = msg } + }, + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { - - // Tombol Kirim Laporan - Button( - onClick = { - val notes = additionalNotes.text - val conditions = selectedConditions.joinToString(", ") { - "${getEmoji(it)} $it" - } - val report = "Kondisi: $conditions\nCatatan: $notes" - - sendNotification(report) { response -> - message = response - } - }, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) - ) { - Text(text = "Kirim Laporan", color = Color.White) - } - - Spacer(modifier = Modifier.width(12.dp)) - - // Tombol Jalur Evakuasi - Button( - onClick = onOpenEvacuation, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF0080FF)) - ) { - Text(text = "Lihat Jalur Evakuasi", color = Color.White) - } + Text("Kirim Laporan", color = Color.White) } - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "β€œJANGAN PANIK! SEGERA EVAKUASI\nDIRI ANDA KE TITIK KUMPUL”", - color = Color.Red, - fontSize = 15.sp - ) - Spacer(modifier = Modifier.height(20.dp)) - // Tombol Tutup (opsional) - Button(onClick = onClose) { - Text("Tutup") - } - - Spacer(modifier = Modifier.height(16.dp)) Text(text = message) + Spacer(modifier = Modifier.height(20.dp)) + + Button(onClick = onClose) { Text("Tutup") } } } -// =============================================================== -// Fungsi Kirim Notifikasi ke Server ntfy.ubharajaya.ac.id -// =============================================================== +// =========================================================== +// Emoji helper +// =========================================================== + +fun getEmoji(condition: String): String { + return when (condition) { + "Kebakaran" -> "πŸ”₯" + "Banjir" -> "🌊" + "Tsunami" -> "🌊" + "Gunung Meletus" -> "πŸŒ‹" + "Gempa Bumi" -> "🌎" + "Huru Hara/Demonstrasi" -> "⚠" + "Binatang Buas" -> "🐾" + "Radiasi Nuklir" -> "☒" + "Biohazard" -> "☣" + else -> "❗" + } +} + +// =========================================================== +// NOTIFICATION +// =========================================================== + fun sendNotification(report: String, onResult: (String) -> Unit) { val client = OkHttpClient() val url = "https://ntfy.ubharajaya.ac.id/panic-button" - val requestBody = RequestBody.create("text/plain".toMediaType(), report) + val body = RequestBody.create("text/plain".toMediaType(), report) val request = Request.Builder() .url(url) .addHeader("Title", "Alert") .addHeader("Priority", "urgent") - .post(requestBody) + .post(body) .build() Thread { try { val response = client.newCall(request).execute() - if (response.isSuccessful) { + if (response.isSuccessful) onResult("Notifikasi berhasil dikirim!") - } else { - onResult("Gagal mengirim notifikasi: ${response.code}") - } + else + onResult("Gagal mengirim: ${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/RuanganActivity.kt b/app/src/main/java/id/ac/ubharajaya/panicbutton/RuanganActivity.kt new file mode 100644 index 0000000..c48ebf4 --- /dev/null +++ b/app/src/main/java/id/ac/ubharajaya/panicbutton/RuanganActivity.kt @@ -0,0 +1,110 @@ +package id.ac.ubharajaya.panicbutton + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed + +class RuanganActivity : ComponentActivity() { + + private val denahUtara = listOf( + "R122","R121","R120","R119","R118","R117", + "R123","R124","R125","Toilet Pria Utara","Toilet Wanita Utara" + ) + + private val denahSelatan = listOf( + "R116","Biro Pemasaran","BK & Pusat Karir","R107","R108","R109", + "R111","R112","R106","R105","R104","R103","R102","R101", + "Ruang Laboratorium Teknik","Toilet Pria Selatan","Toilet Wanita Selatan" + ) + + private val denahLabKomp = listOf( + "R319","R318","R317","R316","R315","R320","R321","R322","R323","R324", + "Toilet Pria Lantai 3","Toilet Wanita Lantai 3" + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + PilihRuanganScreen { ruangan -> + val denah = when (ruangan) { + in denahUtara -> "utara" + in denahSelatan -> "selatan" + in denahLabKomp -> "labkomp" + else -> "utara" + } + + val intent = Intent(this, JalurEvakuasiActivity::class.java) + intent.putExtra("denah", denah) + startActivity(intent) + } + } + } +} + +@Composable +fun PilihRuanganScreen(onSelect: (String) -> Unit) { + + val semuaRuangan = listOf( + "Utara" to listOf( + "R122","R121","R120","R119","R118","R117", + "R123","R124","R125" + ), + "Selatan" to listOf( + "R116","Biro Pemasaran","BK & Pusat Karir", + "R107","R108","R109","R111","R112","R106","R105", + "R104","R103","R102","R101","Ruang Laboratorium Teknik" + ), + "Lab Komputer Pemrograman (Lantai 3)" to listOf( + "R319","R318","R317","R316","R315", + "R320","R321","R322","R323","R324" + ) + ) + + // -------------- FIX: SCROLLING DENGAN LAZYCOLUMN ---------------- + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + + item { + Text( + "Pilih Ruangan Anda", + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(12.dp)) + } + + semuaRuangan.forEach { (judul, rooms) -> + item { + Text(judul, style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(6.dp)) + } + + items(rooms.size) { index -> + val r = rooms[index] + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clickable { onSelect(r) } + ) { + Text(r, modifier = Modifier.padding(16.dp)) + } + } + + item { Spacer(modifier = Modifier.height(20.dp)) } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/evakuasi_lab_kompemrograman.jpg b/app/src/main/res/drawable/evakuasi_lab_kompemrograman.jpg new file mode 100644 index 0000000..8c951da Binary files /dev/null and b/app/src/main/res/drawable/evakuasi_lab_kompemrograman.jpg differ diff --git a/app/src/main/res/drawable/evakuasiselatan.jpg b/app/src/main/res/drawable/evakuasiselatan.jpg new file mode 100644 index 0000000..4a7a169 Binary files /dev/null and b/app/src/main/res/drawable/evakuasiselatan.jpg differ diff --git a/app/src/main/res/drawable/evakuasiutara.jpg b/app/src/main/res/drawable/evakuasiutara.jpg new file mode 100644 index 0000000..1b7aa65 Binary files /dev/null and b/app/src/main/res/drawable/evakuasiutara.jpg differ