From 0658054ac39bc88d1b05a8c452dcb91466162454 Mon Sep 17 00:00:00 2001
From: Raihan Ariq <202310715297@mhs.ubharajaya.ac.id>
Date: Mon, 12 Jan 2026 20:26:21 +0700
Subject: [PATCH] Menemukan Lokasi ku menggunakan GPS, Monitoring Lokasi ku dan
Tracking Location
---
app/build.gradle.kts | 1 +
app/src/main/AndroidManifest.xml | 2 +
.../locationbasedservice/MainActivity.kt | 261 ++++++++++++------
3 files changed, 183 insertions(+), 81 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b58c16a..91206fe 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -58,5 +58,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+ implementation("androidx.compose.material:material-icons-extended:1.6.0")
implementation("org.osmdroid:osmdroid-android:6.1.20")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 76cccde..24ab9a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
+
+
()) }
- // Kita simpan referensi MapView agar bisa dimanipulasi (tambah marker) dari luar AndroidView
+ // Referensi Map & Marker User
var mapViewRef by remember { mutableStateOf(null) }
+ var userMarkerRef by remember { mutableStateOf(null) } // Marker khusus User
+
+ // --- FUNGSI DEBUGGING ---
+ fun showMessage(msg: String) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+
+ // --- LOGIKA UTAMA (UPDATE LOKASI) ---
+ fun updateLocationText(location: Location) {
+ currentLat = location.latitude
+ currentLong = location.longitude
+ val newGeoPoint = GeoPoint(location.latitude, location.longitude)
+
+ // 1. UPDATE POSISI MARKER USER (Solusi Titik Hilang)
+ userMarkerRef?.let { marker ->
+ marker.position = newGeoPoint
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ // Paksa peta refresh gambar
+ mapViewRef?.invalidate()
+ }
+
+ // 2. PINDAHKAN KAMERA (Ikuti user)
+ if (!isTracking) {
+ // Jika tidak sedang tracking, kamera ikuti user terus (opsional)
+ // mapViewRef?.controller?.animateTo(newGeoPoint)
+ } else {
+ // Jika sedang tracking, pasti ikuti user
+ mapViewRef?.controller?.animateTo(newGeoPoint)
+ }
+
+ // 3. GAMBAR JEJAK (TRACKING)
+ if (isTracking) {
+ if (routePoints.isEmpty() || newGeoPoint.distanceToAsDouble(routePoints.last()) > 5.0) {
+ val newList = routePoints.toMutableList()
+ newList.add(newGeoPoint)
+ routePoints = newList
+
+ mapViewRef?.let { map ->
+ val line = Polyline(map)
+ line.setPoints(newList)
+ line.color = android.graphics.Color.RED
+ line.width = 15f
+ // Hapus garis lama, gambar yang baru
+ map.overlays.removeAll { it is Polyline }
+ map.overlays.add(line)
+ // Pastikan marker user tetap paling atas (re-add atau biarkan urutan layer)
+ map.invalidate()
+ }
+ }
+ }
+ }
+
+ // --- LISTENER GPS ANDROID ---
+ val locationManager = remember { context.getSystemService(Context.LOCATION_SERVICE) as LocationManager }
+ val locationListener = remember {
+ object : LocationListener {
+ override fun onLocationChanged(location: Location) {
+ updateLocationText(location)
+ }
+ override fun onProviderEnabled(provider: String) {}
+ override fun onProviderDisabled(provider: String) {}
+ override fun onStatusChanged(provider: String?, status: Int, extras: android.os.Bundle?) {}
+ }
+ }
+
+ // --- LAUNCHER IZIN ---
+ val permissionLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted ->
+ if (isGranted) {
+ showMessage("Izin GPS Diterima. Menunggu sinyal...")
+ try {
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 2f, locationListener)
+ locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000L, 2f, locationListener)
+ } catch (e: SecurityException) { }
+ } else {
+ showMessage("Izin Ditolak!")
+ }
+ }
Scaffold(
- bottomBar = {
- // Menampilkan Alamat Hasil Geocoding
- Surface(
- shadowElevation = 8.dp,
- modifier = Modifier.fillMaxWidth()
- ) {
- Text(
- text = selectedAddress,
- modifier = Modifier.padding(16.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- }
- },
floatingActionButton = {
- ExtendedFloatingActionButton(
- onClick = { isNormalMode = !isNormalMode }
+ Column(
+ horizontalAlignment = Alignment.End,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.padding(bottom = 80.dp)
) {
- Text(text = if (isNormalMode) "Mode Topo" else "Mode Normal")
+ FloatingActionButton(
+ onClick = {
+ isNormalMode = !isNormalMode
+ showMessage("Mode: ${if(isNormalMode) "Normal" else "Topo"}")
+ },
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ ) { Icon(Icons.Default.Layers, "Peta") }
+
+ FloatingActionButton(
+ onClick = {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ showMessage("Mencari Lokasi...")
+ try {
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 2f, locationListener)
+ // Cek lokasi terakhir disimpan (Last Known)
+ val lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
+ ?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
+ if (lastKnown != null) {
+ updateLocationText(lastKnown)
+ mapViewRef?.controller?.animateTo(GeoPoint(lastKnown.latitude, lastKnown.longitude))
+ showMessage("Lokasi ditemukan!")
+ }
+ } catch (e: Exception) { }
+ } else {
+ permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+ },
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ ) { Icon(Icons.Default.LocationOn, "Lokasi") }
+
+ ExtendedFloatingActionButton(
+ onClick = {
+ if (currentLat == 0.0) showMessage("Tunggu Lokasi Dulu!")
+ else {
+ isTracking = !isTracking
+ showMessage(if(isTracking) "Mulai Tracking..." else "Tracking Stop")
+ }
+ },
+ containerColor = if (isTracking) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary
+ ) {
+ Icon(if (isTracking) Icons.Default.Close else Icons.Default.PlayArrow, null)
+ Spacer(Modifier.width(8.dp))
+ Text(if (isTracking) "Stop" else "Mulai")
+ }
}
}
) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues).fillMaxSize()) {
-
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
MapView(ctx).apply {
setMultiTouchControls(true)
- val startPoint = GeoPoint(-6.175392, 106.827153)
controller.setZoom(18.0)
- controller.setCenter(startPoint)
+ controller.setCenter(GeoPoint(-6.175392, 106.827153)) // Default Monas
- // --- LOGIKA SENTUH PETA (Poin 4) ---
- val eventReceiver = object : MapEventsReceiver {
- override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
- // 1. Hapus marker lama (opsional, biar tidak penuh)
- overlays.clear()
- // Jangan lupa tambahkan ulang event overlay ini agar bisa diklik lagi
- val eventsOverlay = MapEventsOverlay(this)
- overlays.add(eventsOverlay)
-
- // 2. Tambah Marker Baru di titik sentuh
- val marker = Marker(this@apply)
- marker.position = p
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
- marker.title = "Lokasi Dipilih"
- overlays.add(marker)
- invalidate() // Refresh peta
-
- // 3. Lakukan Geocoding (Poin 5)
- scope.launch(Dispatchers.IO) {
- try {
- val geocoder = Geocoder(ctx, Locale.getDefault())
- // Ambil max 1 hasil alamat
- val addresses = geocoder.getFromLocation(p.latitude, p.longitude, 1)
-
- val resultText = if (!addresses.isNullOrEmpty()) {
- val address = addresses[0]
- // Gabungkan baris alamat (misal: Jl. Sudirman, Jakarta)
- address.getAddressLine(0)
- } else {
- "Alamat tidak ditemukan"
- }
-
- // Update UI harus di Main Thread
- withContext(Dispatchers.Main) {
- selectedAddress = resultText
- marker.snippet = resultText
- marker.showInfoWindow() // Tampilkan balon info otomatis
- }
- } catch (e: Exception) {
- withContext(Dispatchers.Main) {
- selectedAddress = "Error: Cek koneksi internet"
- }
- }
- }
- return true
- }
-
- override fun longPressHelper(p: GeoPoint?): Boolean = false
- }
-
- // Pasang "jaring" penangkap sentuhan ke peta
- val eventsOverlay = MapEventsOverlay(eventReceiver)
- overlays.add(eventsOverlay)
+ // --- BUAT MARKER MANUAL UNTUK USER ---
+ val marker = Marker(this)
+ marker.title = "Saya di sini"
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+ // Gunakan icon default Pin (pasti muncul)
+ // Atau load icon custom: marker.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_person)
+ overlays.add(marker) // Tambahkan ke peta
+ userMarkerRef = marker // Simpan ke variabel agar bisa digerakkan nanti
mapViewRef = this
}
},
update = { view ->
- if (isNormalMode) {
- view.setTileSource(TileSourceFactory.MAPNIK)
- } else {
- view.setTileSource(TileSourceFactory.USGS_TOPO)
- }
+ if (isNormalMode) view.setTileSource(TileSourceFactory.MAPNIK)
+ else view.setTileSource(TileSourceFactory.USGS_TOPO)
}
)
+
+ // INFO PANEL
+ Card(
+ modifier = Modifier.align(Alignment.TopCenter).padding(top = 40.dp, start = 16.dp, end = 16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.9f))
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Text(text = if (isTracking) "REC: AKTIF" else "REC: STANDBY", fontWeight = FontWeight.Bold, color = if(isTracking) Color.Red else Color.Gray)
+ Text("Lat: $currentLat")
+ Text("Lng: $currentLong")
+ }
+ }
}
}
}
\ No newline at end of file