feat: add gps

This commit is contained in:
rakha 2025-11-27 18:36:09 +07:00
parent 56ec2b4263
commit c1b2fc181b
4 changed files with 43 additions and 29 deletions

View File

@ -24,6 +24,10 @@
<option value="a20448f7-57cd-4a40-89b1-460451412588" /> <option value="a20448f7-57cd-4a40-89b1-460451412588" />
<option value="484561a1-1c08-44cd-bcd3-a53895fc3851" /> <option value="484561a1-1c08-44cd-bcd3-a53895fc3851" />
<option value="17ec376a-38fd-48ac-a4e1-52e9f7df3100" /> <option value="17ec376a-38fd-48ac-a4e1-52e9f7df3100" />
<option value="a0c23b11-ba1e-4275-a3e4-38b3c8fdbe00" />
<option value="3ac3800e-363e-4148-86b3-0d4f3b0a8b62" />
<option value="5460190d-c8ff-4194-9316-a6298c7cb54b" />
<option value="c640b33a-4bc6-4ee0-98ed-6a71e7270331" />
</set> </set>
</value> </value>
</entry> </entry>
@ -84,6 +88,9 @@
<option value="file://$PROJECT_DIR$/app/build.gradle.kts" /> <option value="file://$PROJECT_DIR$/app/build.gradle.kts" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt" /> <option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/NotificationSender.kt" /> <option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/NotificationSender.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/AndroidManifest.xml" />
</set> </set>
</entry> </entry>
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89" /> <entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89" />

View File

@ -2,6 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@ -34,5 +38,4 @@
android:label="Detail Peta" /> android:label="Detail Peta" />
</application> </application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest> </manifest>

View File

@ -1,6 +1,7 @@
package id.ac.ubharajaya.panicbutton package id.ac.ubharajaya.panicbutton
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
@ -11,16 +12,20 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private val requestPermissionLauncher = private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
// result handled inline when requesting // Inform user about permission result
if (isGranted) {
// User granted permission
// Ask user to press the panic button again to include location
if (::viewModel.isInitialized) viewModel.dialogMessage = "Izin lokasi diberikan. Tekan kembali untuk menyertakan koordinat."
} else {
if (::viewModel.isInitialized) viewModel.dialogMessage = "Izin lokasi ditolak. Laporan akan dikirim tanpa koordinat."
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -33,8 +38,8 @@ class MainActivity : ComponentActivity() {
startActivity(Intent(this, EvacuationMapsActivity::class.java)) startActivity(Intent(this, EvacuationMapsActivity::class.java))
} }
// onSendAlert will try to get location (if permission granted) and then call viewModel.sendAlert @SuppressLint("MissingPermission")
val onSendAlert = { fun handleSendAlert() {
// Check permission // Check permission
val fineGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED val fineGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
val coarseGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED val coarseGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
@ -44,28 +49,28 @@ class MainActivity : ComponentActivity() {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
// After permission flow, user will need to press the button again to actually send with location // After permission flow, user will need to press the button again to actually send with location
viewModel.dialogMessage = "Izin lokasi dibutuhkan untuk menyertakan koordinat. Silakan tekan lagi setelah mengizinkan lokasi." viewModel.dialogMessage = "Izin lokasi dibutuhkan untuk menyertakan koordinat. Silakan tekan lagi setelah mengizinkan lokasi."
return@let return
} }
// Try get last location asynchronously // Try get last location asynchronously via Task listeners
CoroutineScope(Dispatchers.Main).launch { fusedClient.lastLocation
try { .addOnSuccessListener { loc ->
val loc = fusedClient.lastLocation.await() // need extension await - we'll handle fallback
if (loc != null) { if (loc != null) {
viewModel.sendAlert(loc.latitude, loc.longitude) viewModel.sendAlert(loc.latitude, loc.longitude)
} else { } else {
// fallback: send without coordinates // fallback: send without coordinates
viewModel.sendAlert() viewModel.sendAlert()
} }
} catch (e: Exception) { }
.addOnFailureListener {
// If obtaining location fails, send without coordinates
viewModel.sendAlert() viewModel.sendAlert()
} }
}
} }
setContent { setContent {
MaterialTheme { MaterialTheme {
MainScreen(viewModel = viewModel, onOpenEvacMaps = openEvacMaps) MainScreen(viewModel = viewModel, onOpenEvacMaps = openEvacMaps, onSendAlert = ::handleSendAlert)
} }
} }
} }

View File

@ -12,7 +12,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
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
@ -22,9 +22,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.foundation.layout.IntrinsicSize
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -91,17 +89,18 @@ fun MainScreen(viewModel: MainViewModel, onOpenEvacMaps: () -> Unit, onSendAlert
} }
// Dialog // Dialog
val dialogMessage = viewModel.dialogMessage viewModel.dialogMessage
if (!dialogMessage.isNullOrBlank()) { ?.takeIf { it.isNotBlank() }
AlertDialog( ?.let { msg ->
onDismissRequest = { viewModel.clearDialog() }, AlertDialog(
confirmButton = { onDismissRequest = { viewModel.clearDialog() },
TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") } confirmButton = {
}, TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") }
title = { Text("🚨 Notifikasi", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) }, },
text = { Text(dialogMessage ?: "") } title = { Text("🚨 Notifikasi", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) },
) text = { Text(msg) }
} )
}
} }
} }