273
README.md
273
README.md
@@ -1,202 +1,175 @@
|
||||
# Location Simulator Android App 📍
|
||||
# 📍 Simulador de Ubicación Android
|
||||
|
||||
Una aplicación Android desarrollada en Kotlin que simula la funcionalidad de ubicaciones de Google **sin necesidad de permisos reales de ubicación**. Perfecta para desarrollo, testing y demostraciones.
|
||||
Una aplicación Android desarrollada en Kotlin que permite simular ubicaciones usando la API de Google sin necesidad de permisos de ubicación reales. Perfecta para testing y desarrollo de aplicaciones basadas en ubicación.
|
||||
|
||||

|
||||

|
||||
|
||||
## 🎯 Características
|
||||
## ✨ Características
|
||||
|
||||
- ✅ **Sin permisos de ubicación**: Simula ubicaciones sin acceso real al GPS
|
||||
- 🗺️ **Última ubicación de Google**: Consulta y muestra la última posición guardada por Google (simulada)
|
||||
- 🎲 **Ubicaciones aleatorias**: Genera posiciones aleatorias cerca de ciudades populares
|
||||
- 🌍 **Ubicaciones populares**: Lista predefinida de ciudades importantes
|
||||
- 📍 **Ubicación personalizada**: Permite ingresar coordenadas específicas
|
||||
- 💾 **Persistencia**: Guarda la última ubicación simulada
|
||||
- **Sin permisos reales**: Simula ubicaciones sin solicitar permisos de ubicación al usuario
|
||||
- **Múltiples proveedores**: Establece ubicaciones simuladas en GPS, Network, Passive y Fused providers
|
||||
- **Última ubicación de Google**: Consulta y simula la última posición guardada por Google
|
||||
- **Ubicaciones populares**: Acceso rápido a ciudades españolas principales
|
||||
- **Ubicaciones aleatorias**: Genera coordenadas aleatorias cerca de ubicaciones base
|
||||
- **Ubicaciones personalizadas**: Permite establecer coordenadas específicas manualmente
|
||||
- **Interfaz moderna**: Diseño Material 3 con Jetpack Compose
|
||||
|
||||
## 🏗️ Tecnologías
|
||||
## 🛠️ Tecnologías
|
||||
|
||||
- **Kotlin** - Lenguaje principal
|
||||
- **Jetpack Compose** - UI moderna y declarativa
|
||||
- **Material 3** - Diseño y componentes
|
||||
- **Architecture Components** - ViewModel, StateFlow
|
||||
- **SharedPreferences** - Almacenamiento local
|
||||
- **Coroutines** - Programación asíncrona
|
||||
- **Kotlin**: Lenguaje principal
|
||||
- **Jetpack Compose**: UI moderna y declarativa
|
||||
- **Material 3**: Sistema de diseño de Google
|
||||
- **LocationManager**: API nativa de Android para manejo de ubicaciones
|
||||
- **Coroutines**: Programación asíncrona
|
||||
- **StateFlow**: Manejo reactivo del estado
|
||||
- **MVVM**: Arquitectura recomendada por Google
|
||||
|
||||
## 📱 Funcionalidades
|
||||
|
||||
### 1. Ubicación Simulada
|
||||
- **Última de Google**: Obtiene la última ubicación que Google tendría guardada
|
||||
- **Simular Nueva**: Genera una nueva ubicación actual simulada
|
||||
- Muestra coordenadas, precisión y timestamp
|
||||
|
||||
### 2. Ubicaciones Populares
|
||||
Ciudades predefinidas disponibles:
|
||||
- 🇪🇸 Madrid, España
|
||||
- 🇫🇷 París, Francia
|
||||
- 🇬🇧 Londres, Reino Unido
|
||||
- 🇺🇸 Nueva York, Estados Unidos
|
||||
- 🇯🇵 Tokio, Japón
|
||||
- 🇧🇷 São Paulo, Brasil
|
||||
- 🇦🇺 Sídney, Australia
|
||||
|
||||
Cada ciudad permite:
|
||||
- Ir directamente a esa ubicación
|
||||
- Generar una posición aleatoria cercana (radio de 1km)
|
||||
|
||||
### 3. Ubicación Personalizada
|
||||
- Ingreso manual de latitud y longitud
|
||||
- Validación de coordenadas
|
||||
- Guardado automático de la nueva posición
|
||||
|
||||
## 🚀 Instalación y Configuración
|
||||
|
||||
### Prerrequisitos
|
||||
- Android Studio Arctic Fox o superior
|
||||
- SDK mínimo: API 24 (Android 7.0)
|
||||
- SDK objetivo: API 36
|
||||
|
||||
### Clonar e Instalar
|
||||
## 🚀 Instalación
|
||||
|
||||
1. Clona el repositorio:
|
||||
```bash
|
||||
# Clonar el repositorio
|
||||
git clone https://git.manalejandro.com/ale/location.git
|
||||
cd location
|
||||
|
||||
# Abrir en Android Studio
|
||||
# File -> Open -> Seleccionar carpeta del proyecto
|
||||
|
||||
# Sincronizar dependencias
|
||||
# Build -> Make Project
|
||||
git clone https://github.com/tuusuario/location-simulator.git
|
||||
cd location-simulator
|
||||
```
|
||||
|
||||
### Configuración
|
||||
No se requiere configuración adicional. La app funciona inmediatamente sin permisos especiales.
|
||||
2. Abre el proyecto en Android Studio
|
||||
|
||||
## 🎮 Uso
|
||||
3. Sincroniza las dependencias de Gradle
|
||||
|
||||
1. **Consultar última ubicación**:
|
||||
- Toca "Última de Google" para obtener la posición guardada
|
||||
- La app simula una consulta a los servicios de Google
|
||||
4. Ejecuta la aplicación en un dispositivo o emulador
|
||||
|
||||
2. **Generar nueva ubicación**:
|
||||
- Toca "Simular Nueva" para crear una ubicación actual
|
||||
## 📱 Uso
|
||||
|
||||
3. **Usar ubicaciones populares**:
|
||||
- Selecciona una ciudad para ir directamente
|
||||
- Usa "Aleatorio" para generar posiciones cercanas
|
||||
### Ubicación Simulada
|
||||
- **"Última de Google"**: Obtiene la última ubicación que Google tendría guardada
|
||||
- **"Simular Nueva"**: Genera una nueva ubicación basada en la actual
|
||||
|
||||
4. **Ubicación personalizada**:
|
||||
- Ingresa latitud y longitud manualmente
|
||||
- Toca "Simular Ubicación" para aplicar
|
||||
### Ubicaciones Populares
|
||||
- Selecciona una ciudad española para establecer tu ubicación allí
|
||||
- Usa "Aleatorio" para generar una ubicación cercana a la ciudad seleccionada
|
||||
|
||||
## 📂 Estructura del Proyecto
|
||||
|
||||
```
|
||||
app/src/main/java/com/manalejandro/location/
|
||||
├── MainActivity.kt # Actividad principal y UI
|
||||
├── LocationViewModel.kt # Lógica de negocio y estado
|
||||
├── LocationService.kt # Servicios de ubicación simulados
|
||||
└── ui/theme/ # Tema y estilos Material 3
|
||||
├── Color.kt
|
||||
├── Theme.kt
|
||||
└── Type.kt
|
||||
```
|
||||
### Ubicación Personalizada
|
||||
1. Introduce latitud y longitud manualmente
|
||||
2. Presiona "Preparar Ubicación Personalizada"
|
||||
3. Confirma con "✓ Aceptar y Establecer" para aplicar a todos los proveedores
|
||||
|
||||
## 🔧 Arquitectura
|
||||
|
||||
La aplicación sigue el patrón **MVVM (Model-View-ViewModel)**:
|
||||
### Componentes Principales
|
||||
|
||||
- **View (Compose)**: Interfaz de usuario declarativa
|
||||
- **ViewModel**: Manejo de estado y lógica de presentación
|
||||
- **Model (LocationService)**: Simulación de servicios de ubicación
|
||||
- **MainActivity**: Actividad principal con interfaz Compose
|
||||
- **LocationService**: Servicio que maneja la lógica de ubicaciones simuladas
|
||||
- **LocationViewModel**: ViewModel que gestiona el estado de la aplicación
|
||||
- **LocationState**: Data class que representa el estado de la UI
|
||||
|
||||
### Flujo de Datos
|
||||
|
||||
```
|
||||
UI (Compose) ↔ ViewModel ↔ LocationService ↔ SharedPreferences
|
||||
UI (Compose) → ViewModel → LocationService → LocationManager + SharedPreferences
|
||||
```
|
||||
|
||||
## 🎨 Diseño
|
||||
## 📋 Proveedores Soportados
|
||||
|
||||
- **Material 3 Design**: Componentes modernos y accesibles
|
||||
- **Responsive**: Adaptable a diferentes tamaños de pantalla
|
||||
- **Dark/Light Theme**: Soporte automático según configuración del sistema
|
||||
- **Cards y Sections**: Organización clara de funcionalidades
|
||||
La aplicación establece ubicaciones simuladas en todos los proveedores del sistema:
|
||||
|
||||
## 🧪 Testing
|
||||
- **GPS_PROVIDER**: Simulación de GPS con alta precisión (5m)
|
||||
- **NETWORK_PROVIDER**: Simulación de ubicación por red (20m de precisión)
|
||||
- **PASSIVE_PROVIDER**: Proveedor pasivo (15m de precisión)
|
||||
- **FUSED_PROVIDER**: Simulación del proveedor fusionado (8m de precisión)
|
||||
|
||||
La aplicación incluye pruebas básicas:
|
||||
## 🎯 Casos de Uso
|
||||
|
||||
```bash
|
||||
# Ejecutar tests unitarios
|
||||
./gradlew test
|
||||
- **Desarrollo de Apps**: Testing de funcionalidades basadas en ubicación
|
||||
- **QA Testing**: Pruebas de aplicaciones en diferentes ubicaciones
|
||||
- **Demostraciones**: Mostrar funcionalidades sin necesidad de moverse físicamente
|
||||
- **Educación**: Aprender sobre APIs de ubicación de Android
|
||||
|
||||
# Ejecutar tests instrumentados
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
## 🔒 Seguridad
|
||||
|
||||
- No requiere permisos de ubicación reales
|
||||
- Las ubicaciones se almacenan localmente en SharedPreferences
|
||||
- No envía datos a servidores externos
|
||||
- Código completamente open source
|
||||
|
||||
## 🤝 Contribuir
|
||||
|
||||
Las contribuciones son bienvenidas. Para contribuir:
|
||||
|
||||
1. Fork el proyecto
|
||||
2. Crea una rama para tu feature (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit tus cambios (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push a la rama (`git push origin feature/AmazingFeature`)
|
||||
2. Crea una rama para tu feature (`git checkout -b feature/NuevaFuncionalidad`)
|
||||
3. Commit tus cambios (`git commit -m 'Agregar nueva funcionalidad'`)
|
||||
4. Push a la rama (`git push origin feature/NuevaFuncionalidad`)
|
||||
5. Abre un Pull Request
|
||||
|
||||
## 📝 Casos de Uso
|
||||
|
||||
### Para Desarrolladores
|
||||
- Testing de aplicaciones que requieren ubicación
|
||||
- Desarrollo sin depender de ubicación real
|
||||
- Demostraciones en entornos controlados
|
||||
|
||||
### Para QA/Testing
|
||||
- Verificar comportamiento con diferentes ubicaciones
|
||||
- Probar funcionalidades geográficas
|
||||
- Testing sin moverse físicamente
|
||||
|
||||
### Para Educación
|
||||
- Enseñar conceptos de geolocalización
|
||||
- Demostrar APIs de ubicación
|
||||
- Workshops y tutoriales
|
||||
|
||||
## 🔒 Privacidad
|
||||
|
||||
Esta aplicación **NO**:
|
||||
- Accede a tu ubicación real
|
||||
- Requiere permisos de ubicación
|
||||
- Envía datos a servidores externos
|
||||
- Almacena información personal
|
||||
|
||||
Toda la funcionalidad es local y simulada.
|
||||
|
||||
## 📄 Licencia
|
||||
|
||||
Este proyecto está bajo la Licencia MIT. Ver el archivo `LICENSE` para más detalles.
|
||||
|
||||
## 👨💻 Autor
|
||||
|
||||
**Manuel Alejandro**
|
||||
- Git: [@manalejandro](https://git.manalejandro.com/ale)
|
||||
**Alejandro** - [@manalejandro](https://github.com/manalejandro)
|
||||
|
||||
## 🙏 Agradecimientos
|
||||
## ⭐ Agradecimientos
|
||||
|
||||
- Equipo de Android Developers
|
||||
- Comunidad de Kotlin
|
||||
- Material Design Team
|
||||
- Google por la documentación de LocationManager
|
||||
- La comunidad de Android por las mejores prácticas
|
||||
- Material Design por las guías de UI/UX
|
||||
|
||||
---
|
||||
|
||||
⭐ Si este proyecto te ha sido útil, ¡no olvides darle una estrella!
|
||||
## 📚 API Reference
|
||||
|
||||
## 📸 Screenshots
|
||||
### LocationService
|
||||
|
||||
> Agrega capturas de pantalla de tu aplicación aquí
|
||||
#### Métodos Principales
|
||||
|
||||
## 🔄 Changelog
|
||||
```kotlin
|
||||
suspend fun getLastKnownLocation(): Location?
|
||||
suspend fun getCurrentLocation(): Location?
|
||||
suspend fun mockLocation(latitude: Double, longitude: Double): Boolean
|
||||
suspend fun setMockLocationOnAllProviders(latitude: Double, longitude: Double): Boolean
|
||||
fun getPopularLocations(): List<Pair<String, Location>>
|
||||
fun setupTestProviders()
|
||||
fun cleanupTestProviders()
|
||||
```
|
||||
|
||||
### v1.0.0
|
||||
- Lanzamiento inicial
|
||||
- Simulación de ubicaciones de Google
|
||||
- Ubicaciones populares predefinidas
|
||||
- Ubicaciones personalizadas
|
||||
- UI con Material 3
|
||||
### LocationViewModel
|
||||
|
||||
#### Estado de la Aplicación
|
||||
|
||||
```kotlin
|
||||
data class LocationState(
|
||||
val currentLocation: Location? = null,
|
||||
val mockedLocation: Location? = null,
|
||||
val popularLocations: List<Pair<String, Location>> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val hasLocationPermission: Boolean = true
|
||||
)
|
||||
```
|
||||
|
||||
## 🐛 Problemas Conocidos
|
||||
|
||||
- En algunos emuladores, los proveedores de prueba pueden no funcionar correctamente
|
||||
- La funcionalidad de LocationManager requiere que la app esté en modo debug
|
||||
|
||||
## 🔮 Roadmap
|
||||
|
||||
- [ ] Soporte para rutas simuladas
|
||||
- [ ] Exportar/importar ubicaciones
|
||||
- [ ] Historial de ubicaciones
|
||||
- [ ] Widget de acceso rápido
|
||||
- [ ] Soporte para coordenadas UTM
|
||||
- [ ] Integración con mapas
|
||||
|
||||
## 📞 Soporte
|
||||
|
||||
Si tienes preguntas o problemas:
|
||||
|
||||
1. Revisa los [Issues existentes](https://github.com/tuusuario/location-simulator/issues)
|
||||
2. Crea un nuevo Issue con detalles del problema
|
||||
3. Contacta al desarrollador
|
||||
|
||||
---
|
||||
|
||||
**⚠️ Nota**: Esta aplicación está diseñada para propósitos de desarrollo y testing. No usar para evadir restricciones geográficas o con fines maliciosos.
|
||||
|
||||
@@ -17,6 +17,8 @@ class LocationService(private val context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
/**
|
||||
* Simula obtener la última ubicación conocida que Google tendría guardada
|
||||
* Devuelve una ubicación predeterminada o la última simulada
|
||||
@@ -65,20 +67,8 @@ class LocationService(private val context: Context) {
|
||||
* Guarda una ubicación simulada en todos los proveedores
|
||||
*/
|
||||
suspend fun mockLocation(latitude: Double, longitude: Double): Boolean {
|
||||
return try {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
// Guardar la ubicación en todos los proveedores
|
||||
ALL_PROVIDERS.forEach { provider ->
|
||||
saveLocationForProvider(provider, latitude, longitude, timestamp)
|
||||
}
|
||||
|
||||
// Simular un pequeño delay
|
||||
delay(500)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
// Usar el nuevo método que incluye LocationManager
|
||||
return setMockLocationOnAllProviders(latitude, longitude)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,7 +246,7 @@ class LocationService(private val context: Context) {
|
||||
|
||||
val coordinates = popularLocations[cityName]
|
||||
return if (coordinates != null) {
|
||||
mockLocation(coordinates.first, coordinates.second)
|
||||
setMockLocationOnAllProviders(coordinates.first, coordinates.second)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -287,4 +277,144 @@ class LocationService(private val context: Context) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establece una ubicación simulada usando LocationManager en todos los proveedores del sistema
|
||||
*/
|
||||
suspend fun setMockLocationOnAllProviders(latitude: Double, longitude: Double): Boolean {
|
||||
return try {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
// Guardar la ubicación en SharedPreferences para todos los proveedores
|
||||
ALL_PROVIDERS.forEach { provider ->
|
||||
saveLocationForProvider(provider, latitude, longitude, timestamp)
|
||||
}
|
||||
|
||||
// Establecer la ubicación simulada usando LocationManager para cada proveedor disponible
|
||||
ALL_PROVIDERS.forEach { provider ->
|
||||
try {
|
||||
if (isProviderAvailable(provider)) {
|
||||
setMockLocationForProvider(provider, latitude, longitude, timestamp)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Continuar con otros proveedores si uno falla
|
||||
android.util.Log.w("LocationService", "Error setting mock location for $provider: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
delay(500)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LocationService", "Error setting mock location on all providers", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establece una ubicación simulada para un proveedor específico usando LocationManager
|
||||
*/
|
||||
private fun setMockLocationForProvider(provider: String, latitude: Double, longitude: Double, timestamp: Long) {
|
||||
if (provider == "fused") {
|
||||
// El proveedor fused no es directamente accesible vía LocationManager
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val location = Location(provider).apply {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
time = timestamp
|
||||
accuracy = when (provider) {
|
||||
LocationManager.GPS_PROVIDER -> 5.0f
|
||||
LocationManager.NETWORK_PROVIDER -> 20.0f
|
||||
LocationManager.PASSIVE_PROVIDER -> 15.0f
|
||||
else -> 10.0f
|
||||
}
|
||||
// Establecer campos adicionales para hacer la ubicación más realista
|
||||
bearing = 0.0f
|
||||
speed = 0.0f
|
||||
altitude = 650.0 // Altura aproximada de Madrid
|
||||
}
|
||||
|
||||
// Habilitar ubicaciones simuladas para el proveedor
|
||||
if (locationManager.isProviderEnabled(provider)) {
|
||||
locationManager.setTestProviderEnabled(provider, true)
|
||||
locationManager.setTestProviderLocation(provider, location)
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
android.util.Log.w("LocationService", "Permission denied for mock location on $provider")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
android.util.Log.w("LocationService", "Provider $provider not available for mock location")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LocationService", "Error setting test provider location for $provider", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si un proveedor está disponible
|
||||
*/
|
||||
private fun isProviderAvailable(provider: String): Boolean {
|
||||
if (provider == "fused") return true // Siempre consideramos fused como disponible
|
||||
|
||||
return try {
|
||||
locationManager.isProviderEnabled(provider) ||
|
||||
locationManager.allProviders.contains(provider)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura los proveedores de prueba necesarios
|
||||
*/
|
||||
fun setupTestProviders() {
|
||||
ALL_PROVIDERS.forEach { provider ->
|
||||
if (provider == "fused") return@forEach // Skip fused provider
|
||||
|
||||
try {
|
||||
// Eliminar proveedor de prueba existente si existe
|
||||
try {
|
||||
locationManager.removeTestProvider(provider)
|
||||
} catch (e: Exception) {
|
||||
// Ignorar si no existe
|
||||
}
|
||||
|
||||
// Agregar proveedor de prueba
|
||||
locationManager.addTestProvider(
|
||||
provider,
|
||||
false, // requiresNetwork
|
||||
false, // requiresSatellite
|
||||
false, // requiresCell
|
||||
false, // hasMonetaryCost
|
||||
true, // supportsAltitude
|
||||
true, // supportsSpeed
|
||||
true, // supportsBearing
|
||||
android.location.Criteria.POWER_MEDIUM, // powerRequirement
|
||||
android.location.Criteria.ACCURACY_FINE // accuracy
|
||||
)
|
||||
|
||||
locationManager.setTestProviderEnabled(provider, true)
|
||||
} catch (e: SecurityException) {
|
||||
android.util.Log.w("LocationService", "Permission denied for test provider $provider")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("LocationService", "Error setting up test provider $provider", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia los proveedores de prueba
|
||||
*/
|
||||
fun cleanupTestProviders() {
|
||||
ALL_PROVIDERS.forEach { provider ->
|
||||
if (provider == "fused") return@forEach // Skip fused provider
|
||||
|
||||
try {
|
||||
locationManager.setTestProviderEnabled(provider, false)
|
||||
locationManager.removeTestProvider(provider)
|
||||
} catch (e: Exception) {
|
||||
// Ignorar errores al limpiar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +88,9 @@ fun LocationApp() {
|
||||
)
|
||||
|
||||
// Mostrar errores
|
||||
uiState.error?.let { error ->
|
||||
uiState.error?.let { errorMessage ->
|
||||
ErrorSection(
|
||||
error = error,
|
||||
error = errorMessage,
|
||||
onDismiss = viewModel::clearError
|
||||
)
|
||||
}
|
||||
@@ -219,6 +219,7 @@ fun CustomLocationSection(
|
||||
) {
|
||||
var latitude by remember { mutableStateOf("") }
|
||||
var longitude by remember { mutableStateOf("") }
|
||||
var pendingLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) }
|
||||
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
@@ -237,7 +238,10 @@ fun CustomLocationSection(
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = latitude,
|
||||
onValueChange = { latitude = it },
|
||||
onValueChange = {
|
||||
latitude = it
|
||||
pendingLocation = null // Limpiar ubicación pendiente al editar
|
||||
},
|
||||
label = { Text("Latitud") },
|
||||
placeholder = { Text("40.4168") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
@@ -246,7 +250,10 @@ fun CustomLocationSection(
|
||||
|
||||
OutlinedTextField(
|
||||
value = longitude,
|
||||
onValueChange = { longitude = it },
|
||||
onValueChange = {
|
||||
longitude = it
|
||||
pendingLocation = null // Limpiar ubicación pendiente al editar
|
||||
},
|
||||
label = { Text("Longitud") },
|
||||
placeholder = { Text("-3.7038") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
@@ -254,23 +261,74 @@ fun CustomLocationSection(
|
||||
)
|
||||
}
|
||||
|
||||
// Mostrar ubicación pendiente si existe
|
||||
pendingLocation?.let { (lat, lng) ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Ubicación Lista para Establecer",
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
text = "Lat: ${String.format("%.6f", lat)}, Lng: ${String.format("%.6f", lng)}",
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
onMockLocation(lat, lng)
|
||||
pendingLocation = null
|
||||
latitude = ""
|
||||
longitude = ""
|
||||
},
|
||||
enabled = !uiState.isLoading,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("✓ Aceptar y Establecer")
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { pendingLocation = null },
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("✗ Cancelar")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Botón para preparar la ubicación
|
||||
if (pendingLocation == null) {
|
||||
Button(
|
||||
onClick = {
|
||||
val lat = latitude.toDoubleOrNull()
|
||||
val lng = longitude.toDoubleOrNull()
|
||||
if (lat != null && lng != null) {
|
||||
onMockLocation(lat, lng)
|
||||
if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
||||
pendingLocation = Pair(lat, lng)
|
||||
}
|
||||
},
|
||||
enabled = latitude.isNotBlank() && longitude.isNotBlank() && !uiState.isLoading,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Establecer Ubicación Personalizada")
|
||||
Text("Preparar Ubicación Personalizada")
|
||||
}
|
||||
}
|
||||
|
||||
uiState.mockedLocation?.let { location ->
|
||||
LocationDisplay(
|
||||
title = "Ubicación Guardada",
|
||||
title = "Ubicación Establecida en Todos los Proveedores",
|
||||
location = location
|
||||
)
|
||||
}
|
||||
|
||||
Referencia en una nueva incidencia
Block a user