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
|
- **Sin permisos reales**: Simula ubicaciones sin solicitar permisos de ubicación al usuario
|
||||||
- 🗺️ **Última ubicación de Google**: Consulta y muestra la última posición guardada por Google (simulada)
|
- **Múltiples proveedores**: Establece ubicaciones simuladas en GPS, Network, Passive y Fused providers
|
||||||
- 🎲 **Ubicaciones aleatorias**: Genera posiciones aleatorias cerca de ciudades populares
|
- **Última ubicación de Google**: Consulta y simula la última posición guardada por Google
|
||||||
- 🌍 **Ubicaciones populares**: Lista predefinida de ciudades importantes
|
- **Ubicaciones populares**: Acceso rápido a ciudades españolas principales
|
||||||
- 📍 **Ubicación personalizada**: Permite ingresar coordenadas específicas
|
- **Ubicaciones aleatorias**: Genera coordenadas aleatorias cerca de ubicaciones base
|
||||||
- 💾 **Persistencia**: Guarda la última ubicación simulada
|
- **Ubicaciones personalizadas**: Permite establecer coordenadas específicas manualmente
|
||||||
|
- **Interfaz moderna**: Diseño Material 3 con Jetpack Compose
|
||||||
|
|
||||||
## 🏗️ Tecnologías
|
## 🛠️ Tecnologías
|
||||||
|
|
||||||
- **Kotlin** - Lenguaje principal
|
- **Kotlin**: Lenguaje principal
|
||||||
- **Jetpack Compose** - UI moderna y declarativa
|
- **Jetpack Compose**: UI moderna y declarativa
|
||||||
- **Material 3** - Diseño y componentes
|
- **Material 3**: Sistema de diseño de Google
|
||||||
- **Architecture Components** - ViewModel, StateFlow
|
- **LocationManager**: API nativa de Android para manejo de ubicaciones
|
||||||
- **SharedPreferences** - Almacenamiento local
|
- **Coroutines**: Programación asíncrona
|
||||||
- **Coroutines** - Programación asíncrona
|
- **StateFlow**: Manejo reactivo del estado
|
||||||
|
- **MVVM**: Arquitectura recomendada por Google
|
||||||
|
|
||||||
## 📱 Funcionalidades
|
## 🚀 Instalación
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
|
1. Clona el repositorio:
|
||||||
```bash
|
```bash
|
||||||
# Clonar el repositorio
|
git clone https://github.com/tuusuario/location-simulator.git
|
||||||
git clone https://git.manalejandro.com/ale/location.git
|
cd location-simulator
|
||||||
cd location
|
|
||||||
|
|
||||||
# Abrir en Android Studio
|
|
||||||
# File -> Open -> Seleccionar carpeta del proyecto
|
|
||||||
|
|
||||||
# Sincronizar dependencias
|
|
||||||
# Build -> Make Project
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuración
|
2. Abre el proyecto en Android Studio
|
||||||
No se requiere configuración adicional. La app funciona inmediatamente sin permisos especiales.
|
|
||||||
|
|
||||||
## 🎮 Uso
|
3. Sincroniza las dependencias de Gradle
|
||||||
|
|
||||||
1. **Consultar última ubicación**:
|
4. Ejecuta la aplicación en un dispositivo o emulador
|
||||||
- Toca "Última de Google" para obtener la posición guardada
|
|
||||||
- La app simula una consulta a los servicios de Google
|
|
||||||
|
|
||||||
2. **Generar nueva ubicación**:
|
## 📱 Uso
|
||||||
- Toca "Simular Nueva" para crear una ubicación actual
|
|
||||||
|
|
||||||
3. **Usar ubicaciones populares**:
|
### Ubicación Simulada
|
||||||
- Selecciona una ciudad para ir directamente
|
- **"Última de Google"**: Obtiene la última ubicación que Google tendría guardada
|
||||||
- Usa "Aleatorio" para generar posiciones cercanas
|
- **"Simular Nueva"**: Genera una nueva ubicación basada en la actual
|
||||||
|
|
||||||
4. **Ubicación personalizada**:
|
### Ubicaciones Populares
|
||||||
- Ingresa latitud y longitud manualmente
|
- Selecciona una ciudad española para establecer tu ubicación allí
|
||||||
- Toca "Simular Ubicación" para aplicar
|
- Usa "Aleatorio" para generar una ubicación cercana a la ciudad seleccionada
|
||||||
|
|
||||||
## 📂 Estructura del Proyecto
|
### Ubicación Personalizada
|
||||||
|
1. Introduce latitud y longitud manualmente
|
||||||
```
|
2. Presiona "Preparar Ubicación Personalizada"
|
||||||
app/src/main/java/com/manalejandro/location/
|
3. Confirma con "✓ Aceptar y Establecer" para aplicar a todos los proveedores
|
||||||
├── 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Arquitectura
|
## 🔧 Arquitectura
|
||||||
|
|
||||||
La aplicación sigue el patrón **MVVM (Model-View-ViewModel)**:
|
### Componentes Principales
|
||||||
|
|
||||||
- **View (Compose)**: Interfaz de usuario declarativa
|
- **MainActivity**: Actividad principal con interfaz Compose
|
||||||
- **ViewModel**: Manejo de estado y lógica de presentación
|
- **LocationService**: Servicio que maneja la lógica de ubicaciones simuladas
|
||||||
- **Model (LocationService)**: Simulación de servicios de ubicación
|
- **LocationViewModel**: ViewModel que gestiona el estado de la aplicación
|
||||||
|
- **LocationState**: Data class que representa el estado de la UI
|
||||||
|
|
||||||
### Flujo de Datos
|
### 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
|
La aplicación establece ubicaciones simuladas en todos los proveedores del sistema:
|
||||||
- **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
|
|
||||||
|
|
||||||
## 🧪 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
|
- **Desarrollo de Apps**: Testing de funcionalidades basadas en ubicación
|
||||||
# Ejecutar tests unitarios
|
- **QA Testing**: Pruebas de aplicaciones en diferentes ubicaciones
|
||||||
./gradlew test
|
- **Demostraciones**: Mostrar funcionalidades sin necesidad de moverse físicamente
|
||||||
|
- **Educación**: Aprender sobre APIs de ubicación de Android
|
||||||
|
|
||||||
# Ejecutar tests instrumentados
|
## 🔒 Seguridad
|
||||||
./gradlew connectedAndroidTest
|
|
||||||
```
|
- 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
|
## 🤝 Contribuir
|
||||||
|
|
||||||
|
Las contribuciones son bienvenidas. Para contribuir:
|
||||||
|
|
||||||
1. Fork el proyecto
|
1. Fork el proyecto
|
||||||
2. Crea una rama para tu feature (`git checkout -b feature/AmazingFeature`)
|
2. Crea una rama para tu feature (`git checkout -b feature/NuevaFuncionalidad`)
|
||||||
3. Commit tus cambios (`git commit -m 'Add some AmazingFeature'`)
|
3. Commit tus cambios (`git commit -m 'Agregar nueva funcionalidad'`)
|
||||||
4. Push a la rama (`git push origin feature/AmazingFeature`)
|
4. Push a la rama (`git push origin feature/NuevaFuncionalidad`)
|
||||||
5. Abre un Pull Request
|
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
|
## 📄 Licencia
|
||||||
|
|
||||||
Este proyecto está bajo la Licencia MIT. Ver el archivo `LICENSE` para más detalles.
|
Este proyecto está bajo la Licencia MIT. Ver el archivo `LICENSE` para más detalles.
|
||||||
|
|
||||||
## 👨💻 Autor
|
## 👨💻 Autor
|
||||||
|
|
||||||
**Manuel Alejandro**
|
**Alejandro** - [@manalejandro](https://github.com/manalejandro)
|
||||||
- Git: [@manalejandro](https://git.manalejandro.com/ale)
|
|
||||||
|
|
||||||
## 🙏 Agradecimientos
|
## ⭐ Agradecimientos
|
||||||
|
|
||||||
- Equipo de Android Developers
|
- Google por la documentación de LocationManager
|
||||||
- Comunidad de Kotlin
|
- La comunidad de Android por las mejores prácticas
|
||||||
- Material Design Team
|
- 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
|
### LocationViewModel
|
||||||
- Lanzamiento inicial
|
|
||||||
- Simulación de ubicaciones de Google
|
#### Estado de la Aplicación
|
||||||
- Ubicaciones populares predefinidas
|
|
||||||
- Ubicaciones personalizadas
|
```kotlin
|
||||||
- UI con Material 3
|
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
|
* Simula obtener la última ubicación conocida que Google tendría guardada
|
||||||
* Devuelve una ubicación predeterminada o la última simulada
|
* 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
|
* Guarda una ubicación simulada en todos los proveedores
|
||||||
*/
|
*/
|
||||||
suspend fun mockLocation(latitude: Double, longitude: Double): Boolean {
|
suspend fun mockLocation(latitude: Double, longitude: Double): Boolean {
|
||||||
return try {
|
// Usar el nuevo método que incluye LocationManager
|
||||||
val timestamp = System.currentTimeMillis()
|
return setMockLocationOnAllProviders(latitude, longitude)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,7 +246,7 @@ class LocationService(private val context: Context) {
|
|||||||
|
|
||||||
val coordinates = popularLocations[cityName]
|
val coordinates = popularLocations[cityName]
|
||||||
return if (coordinates != null) {
|
return if (coordinates != null) {
|
||||||
mockLocation(coordinates.first, coordinates.second)
|
setMockLocationOnAllProviders(coordinates.first, coordinates.second)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -287,4 +277,144 @@ class LocationService(private val context: Context) {
|
|||||||
null
|
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
|
// Mostrar errores
|
||||||
uiState.error?.let { error ->
|
uiState.error?.let { errorMessage ->
|
||||||
ErrorSection(
|
ErrorSection(
|
||||||
error = error,
|
error = errorMessage,
|
||||||
onDismiss = viewModel::clearError
|
onDismiss = viewModel::clearError
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -219,6 +219,7 @@ fun CustomLocationSection(
|
|||||||
) {
|
) {
|
||||||
var latitude by remember { mutableStateOf("") }
|
var latitude by remember { mutableStateOf("") }
|
||||||
var longitude by remember { mutableStateOf("") }
|
var longitude by remember { mutableStateOf("") }
|
||||||
|
var pendingLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) }
|
||||||
|
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(
|
Column(
|
||||||
@@ -237,7 +238,10 @@ fun CustomLocationSection(
|
|||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = latitude,
|
value = latitude,
|
||||||
onValueChange = { latitude = it },
|
onValueChange = {
|
||||||
|
latitude = it
|
||||||
|
pendingLocation = null // Limpiar ubicación pendiente al editar
|
||||||
|
},
|
||||||
label = { Text("Latitud") },
|
label = { Text("Latitud") },
|
||||||
placeholder = { Text("40.4168") },
|
placeholder = { Text("40.4168") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||||
@@ -246,7 +250,10 @@ fun CustomLocationSection(
|
|||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = longitude,
|
value = longitude,
|
||||||
onValueChange = { longitude = it },
|
onValueChange = {
|
||||||
|
longitude = it
|
||||||
|
pendingLocation = null // Limpiar ubicación pendiente al editar
|
||||||
|
},
|
||||||
label = { Text("Longitud") },
|
label = { Text("Longitud") },
|
||||||
placeholder = { Text("-3.7038") },
|
placeholder = { Text("-3.7038") },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||||
@@ -254,23 +261,74 @@ fun CustomLocationSection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
// Mostrar ubicación pendiente si existe
|
||||||
onClick = {
|
pendingLocation?.let { (lat, lng) ->
|
||||||
val lat = latitude.toDoubleOrNull()
|
Card(
|
||||||
val lng = longitude.toDoubleOrNull()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
if (lat != null && lng != null) {
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer)
|
||||||
onMockLocation(lat, lng)
|
) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
enabled = latitude.isNotBlank() && longitude.isNotBlank() && !uiState.isLoading,
|
}
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
// Botón para preparar la ubicación
|
||||||
Text("Establecer Ubicación Personalizada")
|
if (pendingLocation == null) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val lat = latitude.toDoubleOrNull()
|
||||||
|
val lng = longitude.toDoubleOrNull()
|
||||||
|
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("Preparar Ubicación Personalizada")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uiState.mockedLocation?.let { location ->
|
uiState.mockedLocation?.let { location ->
|
||||||
LocationDisplay(
|
LocationDisplay(
|
||||||
title = "Ubicación Guardada",
|
title = "Ubicación Establecida en Todos los Proveedores",
|
||||||
location = location
|
location = location
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user