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