package com.parkplants.ui.addplant import android.graphics.Paint import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Handler import android.os.Looper import android.view.GestureDetector import android.view.MotionEvent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.MyLocation import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.parkplants.map.TilePrefetcher import com.parkplants.ui.main.OverlayState import org.osmdroid.config.Configuration import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.GeoPoint import org.osmdroid.views.MapView import org.osmdroid.views.overlay.GroundOverlay import org.osmdroid.views.overlay.Marker import org.osmdroid.views.overlay.Overlay import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay private const val PARK_LAT = 45.045076 private const val PARK_LNG = 39.035368 private const val MAX_ZOOM = 19.0 private const val DEFAULT_ZOOM = 17.0 @Composable fun MapPickerDialog( onCoordinatesSelected: (lat: Double, lng: Double) -> Unit, onDismiss: () -> Unit, overlayState: OverlayState? = null, ) { var selectedPoint by remember { mutableStateOf(null) } val mainHandler = remember { Handler(Looper.getMainLooper()) } // Храним ссылку на overlay чтобы отключить GPS при закрытии val locationOverlayRef = remember { mutableStateOf(null) } val mapViewRef = remember { mutableStateOf(null) } DisposableEffect(Unit) { onDispose { locationOverlayRef.value?.disableMyLocation() mapViewRef.value?.onPause() } } val context = LocalContext.current val isOffline = remember { val cm = context.getSystemService(ConnectivityManager::class.java) val caps = cm?.getNetworkCapabilities(cm.activeNetwork) caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) != true } Dialog( onDismissRequest = onDismiss, properties = DialogProperties( usePlatformDefaultWidth = false, dismissOnBackPress = true, dismissOnClickOutside = false, ), ) { Box(modifier = Modifier.fillMaxSize().background(Color(0xFF0F172A))) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> Configuration.getInstance().apply { userAgentValue = ctx.packageName osmdroidBasePath = TilePrefetcher.tileCacheDir(ctx).parentFile osmdroidTileCache = TilePrefetcher.tileCacheDir(ctx) } var currentMarker: Marker? = null val mapView = MapView(ctx).apply { setTileSource(TileSourceFactory.MAPNIK) setMultiTouchControls(true) isTilesScaledToDpi = true minZoomLevel = 2.0 maxZoomLevel = MAX_ZOOM // Открываемся по центру парка — GPS обновит при первом фиксе controller.setZoom(DEFAULT_ZOOM) controller.setCenter(GeoPoint(PARK_LAT, PARK_LNG)) } mapViewRef.value = mapView // ── GPS-индикатор позиции ───────────────────────────────── val locationProvider = GpsMyLocationProvider(ctx) val locationOverlay = MyLocationNewOverlay(locationProvider, mapView).apply { enableMyLocation() // При первом GPS-фиксе — центрируем карту и зумим на максимум runOnFirstFix { val loc = myLocation ?: return@runOnFirstFix mainHandler.post { mapView.controller.animateTo(loc) mapView.controller.setZoom(MAX_ZOOM) } } } locationOverlayRef.value = locationOverlay mapView.overlays.add(locationOverlay) // ── Обработчик двойного тапа ────────────────────────────── val gestureDetector = GestureDetector( ctx, object : GestureDetector.SimpleOnGestureListener() { override fun onDoubleTap(e: MotionEvent): Boolean { val geoPoint = mapView.projection.fromPixels( e.x.toInt(), e.y.toInt() ) as GeoPoint currentMarker?.let { mapView.overlays.remove(it) } val marker = Marker(mapView).apply { position = geoPoint setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) icon = createDotDrawable(ctx) title = null } mapView.overlays.add(marker) mapView.invalidate() currentMarker = marker mainHandler.postDelayed({ onCoordinatesSelected(geoPoint.latitude, geoPoint.longitude) onDismiss() }, 200) return true } } ) val touchOverlay = object : Overlay() { override fun onTouchEvent(event: MotionEvent, mapView: MapView): Boolean { gestureDetector.onTouchEvent(event) return false } } mapView.overlays.add(0, touchOverlay) mapView }, update = { mapView -> mapView.onResume() locationOverlayRef.value?.enableMyLocation() // ── Ground overlay (подложка) ───────────────────────────── mapView.overlays.removeIf { it is GroundOverlay } if (overlayState != null) { val cfg = overlayState.config val halfW = cfg.widthDeg / 2.0 val halfH = cfg.heightDeg / 2.0 val groundOverlay = GroundOverlay().apply { setImage(overlayState.bitmap) setPosition( GeoPoint(cfg.centerLat + halfH, cfg.centerLng - halfW), GeoPoint(cfg.centerLat + halfH, cfg.centerLng + halfW), GeoPoint(cfg.centerLat - halfH, cfg.centerLng + halfW), GeoPoint(cfg.centerLat - halfH, cfg.centerLng - halfW), ) setBearing(cfg.rotation) setTransparency(1f - cfg.opacity) } // Вставляем на индекс 1 — после touchOverlay (0), но под маркером mapView.overlays.add(1, groundOverlay) } mapView.invalidate() }, ) // Hint at top Box( modifier = Modifier .align(Alignment.TopCenter) .padding(top = 16.dp) .background(Color(0xCC000000), RoundedCornerShape(20.dp)) .padding(horizontal = 16.dp, vertical = 8.dp), ) { Text( "Дважды нажмите чтобы выбрать место", style = TextStyle(color = Color.White, fontSize = 14.sp), ) } // Кнопка "вернуться к моей позиции" Box( modifier = Modifier .align(Alignment.BottomEnd) .padding(bottom = 80.dp, end = 16.dp) .size(48.dp) .background(Color(0xCC1E293B), CircleShape), contentAlignment = Alignment.Center, ) { IconButton( onClick = { val loc = locationOverlayRef.value?.myLocation if (loc != null) { mapViewRef.value?.controller?.animateTo(loc) mapViewRef.value?.controller?.setZoom(MAX_ZOOM) } }, modifier = Modifier.fillMaxSize(), ) { Icon( Icons.Default.MyLocation, contentDescription = "Моя позиция", tint = Color(0xFF22C55E), modifier = Modifier.size(24.dp), ) } } // Offline badge if (isOffline) { Box( modifier = Modifier .align(Alignment.BottomStart) .padding(12.dp) .background(Color(0xCC1E293B), RoundedCornerShape(12.dp)) .padding(horizontal = 12.dp, vertical = 6.dp), ) { Text( "Офлайн — карта из кэша", style = TextStyle(color = Color(0xFF94A3B8), fontSize = 12.sp), ) } } // Close button Box( modifier = Modifier .align(Alignment.TopEnd) .padding(12.dp) .size(40.dp) .background(Color(0x99000000), CircleShape), contentAlignment = Alignment.Center, ) { IconButton(onClick = onDismiss, modifier = Modifier.fillMaxSize()) { Icon( Icons.Default.Close, contentDescription = "Закрыть карту", tint = Color.White, modifier = Modifier.size(22.dp), ) } } } } } private fun createDotDrawable(context: android.content.Context): android.graphics.drawable.Drawable { val size = (24 * context.resources.displayMetrics.density).toInt() val bitmap = android.graphics.Bitmap.createBitmap(size, size, android.graphics.Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(bitmap) val paintFill = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = android.graphics.Color.parseColor("#22C55E") style = Paint.Style.FILL } val paintStroke = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = android.graphics.Color.WHITE style = Paint.Style.STROKE strokeWidth = size * 0.12f } val radius = size / 2f - size * 0.08f canvas.drawCircle(size / 2f, size / 2f, radius, paintFill) canvas.drawCircle(size / 2f, size / 2f, radius, paintStroke) return android.graphics.drawable.BitmapDrawable(context.resources, bitmap) }