Menemukan Lokasi ku menggunakan GPS, Monitoring Lokasi ku dan Tracking Location

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2026-01-12 20:26:21 +07:00
parent 575162379f
commit 0658054ac3
3 changed files with 183 additions and 81 deletions

View File

@ -58,5 +58,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest) 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") implementation("org.osmdroid:osmdroid-android:6.1.20")
} }

View File

@ -4,6 +4,8 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<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"

View File

@ -24,6 +24,30 @@ import kotlinx.coroutines.withContext
import org.osmdroid.events.MapEventsReceiver import org.osmdroid.events.MapEventsReceiver
import org.osmdroid.views.overlay.MapEventsOverlay import org.osmdroid.views.overlay.MapEventsOverlay
import java.util.Locale import java.util.Locale
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Layers
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import org.osmdroid.views.overlay.Polyline
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -42,113 +66,188 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun OsmMapScreen() { fun OsmMapScreen() {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() // Untuk menjalankan proses background (Geocoding)
// State // State UI
var isTracking by remember { mutableStateOf(false) }
var isNormalMode by remember { mutableStateOf(true) } var isNormalMode by remember { mutableStateOf(true) }
var selectedAddress by remember { mutableStateOf("Sentuh peta untuk cari alamat...") } var currentLat by remember { mutableStateOf(0.0) }
var currentLong by remember { mutableStateOf(0.0) }
var routePoints by remember { mutableStateOf(listOf<GeoPoint>()) }
// Kita simpan referensi MapView agar bisa dimanipulasi (tambah marker) dari luar AndroidView // Referensi Map & Marker User
var mapViewRef by remember { mutableStateOf<MapView?>(null) } var mapViewRef by remember { mutableStateOf<MapView?>(null) }
var userMarkerRef by remember { mutableStateOf<Marker?>(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( Scaffold(
bottomBar = { floatingActionButton = {
// Menampilkan Alamat Hasil Geocoding Column(
Surface( horizontalAlignment = Alignment.End,
shadowElevation = 8.dp, verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier.padding(bottom = 80.dp)
) { ) {
Text( FloatingActionButton(
text = selectedAddress, onClick = {
modifier = Modifier.padding(16.dp), isNormalMode = !isNormalMode
style = MaterialTheme.typography.bodyLarge 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)
} }
}, },
floatingActionButton = { containerColor = MaterialTheme.colorScheme.surfaceVariant
) { Icon(Icons.Default.LocationOn, "Lokasi") }
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { isNormalMode = !isNormalMode } 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
) { ) {
Text(text = if (isNormalMode) "Mode Topo" else "Mode Normal") Icon(if (isTracking) Icons.Default.Close else Icons.Default.PlayArrow, null)
Spacer(Modifier.width(8.dp))
Text(if (isTracking) "Stop" else "Mulai")
}
} }
} }
) { paddingValues -> ) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues).fillMaxSize()) { Box(modifier = Modifier.padding(paddingValues).fillMaxSize()) {
AndroidView( AndroidView(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
factory = { ctx -> factory = { ctx ->
MapView(ctx).apply { MapView(ctx).apply {
setMultiTouchControls(true) setMultiTouchControls(true)
val startPoint = GeoPoint(-6.175392, 106.827153)
controller.setZoom(18.0) controller.setZoom(18.0)
controller.setCenter(startPoint) controller.setCenter(GeoPoint(-6.175392, 106.827153)) // Default Monas
// --- LOGIKA SENTUH PETA (Poin 4) --- // --- BUAT MARKER MANUAL UNTUK USER ---
val eventReceiver = object : MapEventsReceiver { val marker = Marker(this)
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean { marker.title = "Saya di sini"
// 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.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
marker.title = "Lokasi Dipilih" // Gunakan icon default Pin (pasti muncul)
overlays.add(marker) // Atau load icon custom: marker.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_person)
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)
overlays.add(marker) // Tambahkan ke peta
userMarkerRef = marker // Simpan ke variabel agar bisa digerakkan nanti
mapViewRef = this mapViewRef = this
} }
}, },
update = { view -> update = { view ->
if (isNormalMode) { if (isNormalMode) view.setTileSource(TileSourceFactory.MAPNIK)
view.setTileSource(TileSourceFactory.MAPNIK) else view.setTileSource(TileSourceFactory.USGS_TOPO)
} 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")
}
}
} }
} }
} }