forked from upstream-mirrors/OpenTracks
Merge upstream updates into local fork
This commit is contained in:
+5
-5
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:9.3.1'
|
classpath 'com.android.tools.build:gradle:9.4.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'de.dennisguse.opentracks'
|
applicationId 'de.dennisguse.opentracks'
|
||||||
versionCode 6698
|
versionCode 6751
|
||||||
versionName "v4.28.0"
|
versionName "v4.28.3"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -145,12 +145,12 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
implementation 'androidx.appcompat:appcompat:1.8.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.1.0'
|
implementation 'androidx.documentfile:documentfile:1.1.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.14.0'
|
implementation 'com.google.android.material:material:1.14.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.2'
|
||||||
implementation 'androidx.core:core:1.19.0'
|
implementation 'androidx.core:core:1.19.0'
|
||||||
implementation 'androidx.core:core-splashscreen:1.2.0'
|
implementation 'androidx.core:core-splashscreen:1.2.0'
|
||||||
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v4.28.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- HealthConnect: sending total distance, speed and elevation gain
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Gravel activity was not selectable
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Cleanup ContentProvider URLs
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v4.28.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Import: ignore GPX waypoints without time
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Only start GPS once
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v4.28.3: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Settings: allow to disable segment creation (max recording distance)
|
||||||
|
- Importer: match activity type case-insensitive
|
||||||
|
- GPX: export moving speed in summary
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- GPX: importer must respect namespace prefixes
|
||||||
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
v3.0.0: OpenTracks
|
v3.0.0: OpenTracks
|
||||||
|
|
||||||
Primer lanzamiento de OpenTracks (es decir, una bifurcación de MyTracks)
|
Primera versión de OpenTracks (es decir, una bifurcación dura de MyTracks)
|
||||||
|
|
||||||
Cambios:
|
Cambios:
|
||||||
- Cambio de nombre de MyTracks a OpenTracks
|
- Cambio de nombre de MyTracks a OpenTracks
|
||||||
- Se agregó un sensor de frecuencia cardíaca Bluetooth LE
|
- Se ha añadido el sensor de frecuencia cardíaca Bluetooth LE
|
||||||
- Se eliminaron todas las funciones que requieren acceso a Internet.
|
- Se han eliminado todas las funciones que requerían acceso a Internet
|
||||||
- Se eliminó la integración de Google Maps
|
- Se ha eliminado la integración con Google Maps
|
||||||
- Se eliminó el uso de los servicios de Google Play.
|
- Se ha eliminado el uso de los servicios de Google Play
|
||||||
- Se eliminaron los sensores ANT+
|
- Se han eliminado los sensores ANT+
|
||||||
- Se eliminaron los sensores clásicos de Bluetooth.
|
- Se han eliminado los sensores Bluetooth clásicos
|
||||||
- Se eliminó Protobuf
|
- Se ha eliminado Protobuf
|
||||||
- Se eliminaron la estimación de calorías y la estimación de actividad.
|
- Se han eliminado la estimación de calorías y la estimación de actividad
|
||||||
- Se eliminó la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones)
|
- Se ha eliminado la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones)
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ Correcciones de errores:
|
|||||||
- Se ha corregido la exportación a KML/KMZ
|
- Se ha corregido la exportación a KML/KMZ
|
||||||
|
|
||||||
Desarrollador:
|
Desarrollador:
|
||||||
- Las pruebas son ejecutables (pero muchas fallan)
|
- Las pruebas se pueden ejecutar (aunque muchas fallan)
|
||||||
- Se ha habilitado la integración continua con TravisCI (sin pruebas)
|
- Se ha habilitado la integración continua con TravisCI (sin pruebas)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
v3.1.0: OpenTracks
|
v3.1.0: OpenTracks
|
||||||
|
|
||||||
Cambios:
|
Cambios:
|
||||||
- Se agregó la capacidad de mostrar rutas en la aplicación de mapas (probado con OsmAnd y Maps.ME).
|
- Se ha añadido la opción de mostrar el recorrido en aplicaciones de mapas (probado con OsmAnd y Maps.ME)
|
||||||
- Se ha añadido la posibilidad de visualizar las estadísticas de seguimiento sin desbloquear el dispositivo durante el mismo.
|
- Se ha añadido la opción de mostrar las estadísticas del recorrido sin desbloquear el dispositivo mientras se realiza el seguimiento
|
||||||
- Se ha eliminado el modo horizontal de la vista de detalles de la pista (debido a un error).
|
- Se ha eliminado el modo horizontal para la vista detallada del recorrido (presentaba errores)
|
||||||
- El historial de sugerencias de búsqueda ha sido eliminado.
|
- Se ha eliminado el historial de sugerencias de búsqueda
|
||||||
|
|
||||||
Corrección de errores:
|
Corrección de errores:
|
||||||
- Solicitar permisos de almacenamiento para todas las importaciones/todas las exportaciones
|
- Se solicita permiso de almacenamiento para «Importar todo»/«Exportar todo»
|
||||||
|
|
||||||
Desarrolladores:
|
Desarrollador:
|
||||||
- Actualizar a Gradle 5.6
|
- Actualización a Gradle 5.6
|
||||||
- No minimice la versión de lanzamiento
|
- No se minimizan las compilaciones de lanzamiento
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.2.1: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Los ajustes ahora están en una sola página
|
||||||
|
|
||||||
|
Desarrollo:
|
||||||
|
-Los ajustes se migraron a Androidx compat (usa fragments)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v3.2.2: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Se eliminaron las estadísticas agregadas
|
||||||
|
- Migrado a Material Design
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se corrigió la exportación de datos de sensores
|
||||||
|
- Se corrigió la importación de datos de sensores incompletos
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se reemplazó la barra de acción (actionbar) por la barra de herramientas (toolbar)
|
||||||
|
- Migrado a androidx appcompat (incl. Material Design)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.2.3: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Compartir: usar Android Sharesheet
|
||||||
|
- Compartir: compartir múltiples pistas como archivos individuales
|
||||||
|
- Usar Android ShareSheet
|
||||||
|
- Sensor de frecuencia cardíaca (Bluetooth LE): reconexión automática
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Sensor de frecuencia cardíaca (Bluetooth LE): desconexión correcta
|
||||||
|
- La frecuencia cardíaca no siempre se mostraba durante la grabación
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se habilitaron las pruebas de instrumentación en TravisCI
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
v3.2.4: OpenTracks
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Caída/cierre inesperado (crash) en la actividad de ajustes
|
||||||
|
Se eliminó la posibilidad de cambiar la frecuencia de voz/fracción desde el menú contextual
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.2.5: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Deshabilitar el cambio de actividad
|
||||||
|
- Añadir permiso para Android 10: grabación de datos de ubicación
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Corregir el restablecimiento de los ajustes
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Preferencias
|
||||||
|
- si no se pueden cargar los Ajustes, restablecer todas las preferencias
|
||||||
|
- se movió el acceso a las preferencias a PreferencesUtil
|
||||||
|
- se movieron los valores por defecto de las preferencias del código a XML
|
||||||
|
- usar SummaryProvider
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v3.2.6: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Monitor de frecuencia cardíaca: escanear dispositivos disponibles
|
||||||
|
- Eliminar acceso directo a los ajustes de Bluetooth
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Preferencias: se movieron los valores por defecto de las preferencias del código a XML
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.7: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- La notificación muestra la precisión del GPS
|
||||||
|
- Mostrar notificación emergente (heads-up) si la precisión del GPS cae por debajo del umbral
|
||||||
|
- Mostrar siempre el gráfico tanto por tiempo como por distancia
|
||||||
|
- Se añadió ayuda (en Ajustes)
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- TrackDetailActivity: se deshabilitó el modo horizontal que no funcionaba (temporal)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.8: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Gráfico: zoom mediante gestos
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- La exportación a través de KMZ no exportaba correctamente los nombres de archivo de las imágenes
|
||||||
|
- Compartir múltiples pistas no incluía los archivos KMZ
|
||||||
|
- No se mostraba la interfaz de usuario de MarkerDetailActivity
|
||||||
|
- Los ajustes se cerraban inesperadamente si la preferencia de frecuencia cardíaca no estaba inicializada
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.9: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Opción de mantener la pantalla encendida durante la grabación
|
||||||
|
- Marcadores: se eliminaron por completo los marcadores de división (split markers)
|
||||||
|
- Los anuncios de voz ahora funcionan sin el permiso READ_PHONE_STATE
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Estadísticas: no se mostraba la frecuencia cardíaca
|
||||||
|
- Estadísticas: el tiempo se restablecía brevemente a 0s
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.2.10: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- ChartView ya no es configurable
|
||||||
|
- Después de reiniciar: se eliminó el reinicio automático de una grabación incompleta
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Los anuncios de voz usan MAY_DUCK (de nuevo)
|
||||||
|
- ChartView muestra marcadores (de nuevo)
|
||||||
|
- Se corrigió el diseño de grabación en horizontal (landscape recordingLayout) de TrackListActivity
|
||||||
|
- Se solicita el comportamiento heredado de la API de archivos (necesario para Android 10)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v3.2.11: OpenTracks
|
||||||
|
|
||||||
|
Error:
|
||||||
|
- La velocidad máxima es idéntica a la velocidad promedio
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Cierre inesperado (crash) al seleccionar el sensor de frecuencia cardíaca
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se reemplazó TabHost por TabsLayout
|
||||||
|
- Se eliminó el primer punto de ruta (waypoint) especial
|
||||||
|
- Se eliminaron las traducciones duplicadas
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v3.3.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios importantes:
|
||||||
|
- Fotos en almacenamiento privado (ocultas para otras apps)
|
||||||
|
NOTA: No se verán fotos de versiones anteriores
|
||||||
|
- Español (Latinoamérica) requiere Android 7.0 o superior
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Importar/exportar vía Storage Access Framework (sin permisos de almacenamiento)
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Limpieza de estructuras de datos internas
|
||||||
|
- Icono/nombre distintos para versión debug
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.3.1: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Android 10: permite conservar datos al desinstalar
|
||||||
|
- API Dashboard: icono de mapa para abrir la pista en app externa (ej. OSMDashboard)
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se corrigió el nombre de la app (roto en v3.3.0)
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Documentación de la API Dashboard en README_DEVELOPER.md
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.3.2: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Exportación: ahora sobrescribe los archivos
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se elimina siempre la notificación de grabación
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- En API 26+ se usa java.time para analizar y escribir fechas
|
||||||
|
- Refactorización de TrackRecordingService
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v3.4.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- API Dashboard: múltiples pistas
|
||||||
|
- Ayuda actualizada
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se corrigió el cierre inesperado al recrear fragmentos
|
||||||
|
- En pausa: evita que la app se detenga
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Pruebas habilitadas vía CI (Github Actions y TravisCI)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.5.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Marcador: seleccionar imágenes de la galería
|
||||||
|
- Marcador: compartir imagen
|
||||||
|
- Utilizar el nombre de la ruta como nombre de archivo al compartir
|
||||||
|
- Se han eliminado los valores mínimos y máximos de altitud (eran imprecisos)
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Los gráficos utilizan las unidades y la escala preferidas
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Base de datos: eliminar columnas en desuso
|
||||||
|
- Base de datos: añadir índices
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.6.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Ahora se pueden reanudar las rutas que se habían detenido
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- La importación de archivos KMZ no procesaba correctamente la URL de las fotos
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se ha simplificado el procesamiento de datos
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
v3.6.1: OpenTracks
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Importación de KMZ: guardar la ruta correcta de la imagen
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
v3.6.2: OpenTracks
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Los gráficos no se mostraban correctamente (problema introducido en la v3.6.0)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v3.7.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Informe de velocidad/ritmo por tipo de actividad
|
||||||
|
- Se ha añadido una solución provisional para el cambio de semana del GPS
|
||||||
|
- (Experimental) Compatibilidad con sensores de ciclismo Bluetooth LE (cadencia y velocidad)
|
||||||
|
- Se ha mejorado la importación de KMZ/KML
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Importación de KMZ: solo se importan las imágenes utilizadas
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se ha refactorizado TrackRecordingService
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.7.1: OpenTracks
|
||||||
|
|
||||||
|
Solución provisional:
|
||||||
|
- Ocultar el botón de inicio del GPS
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se ha corregido un puntero nulo
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
v3.7.2: OpenTracks
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se ha vuelto a añadir el botón «Iniciar GPS»
|
||||||
|
- El nombre de la ruta ya no se sobrescribe al final de una grabación
|
||||||
|
- Marcadores: al eliminar una foto, también se borra el archivo
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.7.3: OpenTracks
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- Se ha corregido el cálculo de la cadencia y la velocidad (mediante el sensor Bluetooth LE)
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se han eliminado las traducciones que no se utilizaban
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v3.7.4: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Formato de compartir/exportar configurable
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- El diámetro de la rueda no se almacenaba correctamente (para el sensor de velocidad)
|
||||||
|
- Ayuda: los enlaces son clicables
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se ha dividido TrackDetailActivity
|
||||||
|
- El sucesor de StatsFragment utiliza ConstraintLayout
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.7.5: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Se ha añadido un resumen de estadísticas agregadas (desde el menú de la pantalla principal)
|
||||||
|
- API del panel de control: se pasan los ajustes «mostrar en la pantalla de bloqueo» y «mantener la pantalla encendida»
|
||||||
|
- Se han actualizado las traducciones
|
||||||
|
- Importación: se ha flexibilizado el formato de fecha ISO 8601
|
||||||
|
- Mostrar ruta en el mapa: se puede seleccionar el formato de archivo y la apertura del panel de control funciona en Android R (API 30+)
|
||||||
|
|
||||||
|
Corrección de errores:
|
||||||
|
- El icono de la aplicación ahora se muestra correctamente
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.7.6: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Modo horizontal
|
||||||
|
- La interfaz de usuario para borrar rutas ahora es solo un indicador giratorio
|
||||||
|
- La velocidad se exporta e importa (GPX/KML/KMZ)
|
||||||
|
- El formato de exportación predeterminado es KMZ, incluyendo todos los datos y las imágenes
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se han sustituido las AsyncTasks por hilos
|
||||||
|
- Sincronización: se genera un UUID por cada ruta (aún no se exporta)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
v3.8.0: OpenTracks
|
||||||
|
|
||||||
|
Cambio importante:
|
||||||
|
- Importación/exportación de GPX de velocidad, cadencia y frecuencia cardíaca a través de TrackPointExtension/v2
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- API del panel de control: permite acceder a los marcadores (incluida una llamada de retorno para mostrarlos en OpenTracks)
|
||||||
|
- Nuevo icono de marcador (chincheta naranja, SVG)
|
||||||
|
- Los archivos que se van a importar se pueden seleccionar a través del gestor de archivos
|
||||||
|
|
||||||
|
Correcciones de errores:
|
||||||
|
- La interfaz de usuario de estadísticas agregadas respeta la frecuencia preferida
|
||||||
|
- Fallaba la edición de un marcador al cambiar la orientación
|
||||||
|
|
||||||
|
Desarrollador:
|
||||||
|
- Se ha refactorizado TrackRecordingService
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v3.8.1: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Durante la grabación: se ha añadido la opción de pantalla completa
|
||||||
|
- El panel de control se inicia en la misma tarea
|
||||||
|
|
||||||
|
Correcciones de errores:
|
||||||
|
- Los filtros de intención para importar archivos eran demasiado optimistas
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.9.0: OpenTracks
|
||||||
|
|
||||||
|
Cambios:
|
||||||
|
- Exportación de rutas con su UUID
|
||||||
|
- Se evita la importación de rutas ya existentes por su UUID (configurable; por defecto, activado)
|
||||||
|
- Ahora hay que mantener pulsado el botón de pausa/parada
|
||||||
|
- El preinicio del GPS muestra el estado de la señal
|
||||||
|
- Se han añadido nuevos iconos de actividad
|
||||||
|
- Sensor de velocidad: ahora se puede configurar el tamaño de la rueda en milímetros
|
||||||
|
|
||||||
|
Correcciones de errores:
|
||||||
|
- Se han corregido algunos fallos poco frecuentes (NPE)
|
||||||
|
- Se ha corregido el formato del ritmo
|
||||||
|
- Se ha cambiado la posición del icono de la aplicación en el lanzador
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
v4.14.1: OpenTracks
|
v4.14.1: OpenTracks
|
||||||
|
|
||||||
Cambios:
|
Cambios:
|
||||||
- Detección de inactividad se puede desactivar
|
- Se puede desactivar la detección de inactividad
|
||||||
|
|
||||||
Correcciones de errores:
|
Corrección de errores:
|
||||||
- UI para añadir imágenes a los marcadores estaba oculto
|
- La interfaz de usuario para añadir imágenes a los marcadores estaba oculta
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
Un compañero de seguimiento deportivo que respeta tu privacidad.
|
||||||
|
|
||||||
Funciones:
|
El deporte y las actividades al aire libre aportan felicidad, destreza y confianza.
|
||||||
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
|
|
||||||
* Marca lugares de interés, incluso agrega fotos a los marcadores
|
Cuida tu salud llevando un registro de tu entrenamiento.
|
||||||
* Pulsómetro (compatible con dispositivos Bluetooth LE)
|
Registra tus sesiones de carrera o caminata, y te ofrece un ciclocomputador con una pantalla más grande para el ciclismo.
|
||||||
* Exporta tus rutas como KML o GPX
|
Marca lugares interesantes a lo largo de tu recorrido con fotos.
|
||||||
* Completamente offline, no necesitas internet
|
Guarda estadísticas detalladas para su análisis.
|
||||||
|
Comparte solo los datos que quieras que vean los demás.
|
||||||
|
|
||||||
|
* Avisos de voz.
|
||||||
|
* Compatible con sensores Bluetooth LE: frecuencia cardíaca, velocidad y distancia (ciclismo), cadencia (ciclismo) y medidor de potencia (ciclismo).
|
||||||
|
* Desnivel positivo y negativo: mediante sensor barométrico.
|
||||||
|
* Altitud mostrada en EGM2008 (por encima del nivel medio del mar); exportada como WGS84.
|
||||||
|
* Exporta los datos como rutas en formato KMZ (incluidas fotos), KML o GPX.
|
||||||
|
* No requiere acceso a Internet ni permisos adicionales.
|
||||||
|
* Temas oscuro y claro, respetando la configuración del sistema.
|
||||||
|
* Sin anuncios.
|
||||||
|
|
||||||
|
Software libre / Software gratuito / Código abierto,
|
||||||
|
lo que significa que puedes utilizar, estudiar, modificar y compartir el código fuente.
|
||||||
|
Licencia Apache 2.0
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.0.0: OpenTracks
|
||||||
|
|
||||||
|
Rilisan pertama OpenTracks (fork keras dari MyTracks)
|
||||||
|
|
||||||
|
Perubahan:
|
||||||
|
- Perubahan merek MyTracks ke OpenTracks
|
||||||
|
- Menambahkan sensor detak jantung Bluetooth LE
|
||||||
|
- Menghapus semua fitur yang memerlukan koneksi internet
|
||||||
|
- Menghapus integrasi Google Maps
|
||||||
|
- Menghapus penggunaan layanan Google Play
|
||||||
|
- Menghapus sensor ANT+
|
||||||
|
- Menghapus sensor klasik Bluetooth
|
||||||
|
- Menghapus Protobuf
|
||||||
|
- Menghapus estimasi kalori dan aktivitas
|
||||||
|
- Menghapus API MyTracks Service (dengan kata lain, tidak ada integrasi dengan aplikasi lain)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
v3.0.1: OpenTracks
|
||||||
|
|
||||||
|
Perubahan:
|
||||||
|
- Bagikan penanda
|
||||||
|
- Perubahan struktur pengaturan
|
||||||
|
- Menghapus tombol kembali di bilah aksi
|
||||||
|
- Ekspor: menghapus pilihan pengaturan ukuran foto
|
||||||
|
- Ekspor: KML sebagai format sekarang dapat dipilih
|
||||||
|
|
||||||
|
Perbaikan Bug:
|
||||||
|
- direktori ekspor sekarang di /OpenTracks/
|
||||||
|
- mode lanskap tidak akan mogok
|
||||||
|
- memperbaiki ekspor KML/KMZ
|
||||||
|
|
||||||
|
Pengembang:
|
||||||
|
- Tes dapat dieksekusi (namun banyak yang gagal)
|
||||||
|
- Mengaktifkan integrasi terus menerus dengan TravisCI (tanpa tes)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.1.0: OpenTracks
|
||||||
|
|
||||||
|
Perubahan:
|
||||||
|
- Menambahkan opsi tampilan jalur salju di peta aplikasi (telah diuji dengan OsmAnd dan Maps.ME)
|
||||||
|
- Menambahkan ketika melacak, akan menampilkan statistik jalur tanpa perlu membuka kunci perangkat
|
||||||
|
- Menghapus mode landkap untuk tampilan detail jalur (tidak stabil)
|
||||||
|
- Menghapus riwayat saran penelusuran
|
||||||
|
|
||||||
|
Perbaikan Bug:
|
||||||
|
- Permintaan izin penyimpanan untuk impor/ekspor semua
|
||||||
|
|
||||||
|
Pengembang:
|
||||||
|
- Pembaruan ke Gradle 5.6
|
||||||
|
- Jangan mengecilkan (minify) rilisan build
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Teman pelacakan olahraga yang menghormati privasi Anda.
|
Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||||
|
|
||||||
Olahraga dan aktivitas luar ruangan memberikan kebahagiaan, keterampilan, dan kepercayaan diri.
|
Olahraga dan aktivitas luar ruangan memberikan kebahagiaan, keterampilan, dan kepercayaan diri.
|
||||||
|
|
||||||
@@ -11,12 +11,12 @@ Hanya membagikan data yang Anda ingin orang lain memilikinya.
|
|||||||
* Notifikasi suara.
|
* Notifikasi suara.
|
||||||
* Mendukung sensor Bluetooth LE: detak jantung, kecepatan dan jarak (bersepeda), irama (bersepeda), dan pengukur daya (bersepeda).
|
* Mendukung sensor Bluetooth LE: detak jantung, kecepatan dan jarak (bersepeda), irama (bersepeda), dan pengukur daya (bersepeda).
|
||||||
* Kenaikan dan penurunan ketinggian: melalui sensor barometrik.
|
* Kenaikan dan penurunan ketinggian: melalui sensor barometrik.
|
||||||
* Ketinggian yang ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84.
|
* Ketinggian ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84.
|
||||||
* Mengekspor data sebagai lintasan, baik sebagai KMZ (termasuk foto), KML, atau GPX.
|
* Mengekspor data sebagai lintasan, baik sebagai KMZ (termasuk foto), KML, atau GPX.
|
||||||
* Tidak ada akses Internet atau izin tambahan.
|
* Tidak ada akses Internet atau izin tambahan.
|
||||||
* Tema gelap dan terang, dengan memperhatikan pengaturan sistem.
|
* Tema gelap dan terang, dengan memperhatikan pengaturan sistem.
|
||||||
* Tanpa iklan.
|
* Tanpa iklan.
|
||||||
|
|
||||||
Perangkat lunak bebas / Perangkat Lunak Bebas / Sumber Terbuka
|
Perangkat Lunak Bebas / Sumber Terbuka
|
||||||
yang berarti Anda dapat menggunakan, mempelajari, mengubah, dan berbagi kode sumber.
|
yang berarti Anda dapat menggunakan, mempelajari, mengubah, dan berbagi kode sumber.
|
||||||
Berlisensi Apache 2.0
|
Berlisensi Apache 2.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Sebuah pelacak olahraga yang benar-benar menghormati privasi Anda.
|
Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
一個尊重您的隱私的運動蹤跡紀錄器。
|
一款尊重您隱私的運動蹤跡紀錄器。
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package de.dennisguse.opentracks.data.models;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ActivityTypeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_id() {
|
||||||
|
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("trail running"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_idOfEveryActivityType() {
|
||||||
|
for (ActivityType activityType : ActivityType.values()) {
|
||||||
|
assertEquals(activityType, ActivityType.findBy(activityType.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_synonymsOfEveryActivityType() {
|
||||||
|
for (ActivityType activityType : ActivityType.values()) {
|
||||||
|
for (String synonym : activityType.synonyms) {
|
||||||
|
assertEquals(activityType, ActivityType.findBy(synonym));
|
||||||
|
assertEquals(activityType, ActivityType.findBy(synonym.toUpperCase(Locale.ROOT).replace(' ', '_')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_ignoresCase() {
|
||||||
|
assertEquals(ActivityType.RUNNING, ActivityType.findBy("RUNNING"));
|
||||||
|
assertEquals(ActivityType.ATV, ActivityType.findBy("atv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_ignoresSeparators() {
|
||||||
|
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUNNING"));
|
||||||
|
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("mountain-biking"));
|
||||||
|
assertEquals(ActivityType.CROSS_COUNTRY_SKIING, ActivityType.findBy("cross_country_skiing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_ignoresSurroundingAndRepeatedWhitespace() {
|
||||||
|
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy(" Mountain Biking "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_synonym() {
|
||||||
|
assertEquals(ActivityType.RUNNING, ActivityType.findBy("treadmill"));
|
||||||
|
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUN"));
|
||||||
|
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("MOUNTAIN_BIKE"));
|
||||||
|
assertEquals(ActivityType.SWIMMING_OPEN, ActivityType.findBy("SWIMMING_OPENWATER"));
|
||||||
|
assertEquals(ActivityType.BIKING, ActivityType.findBy("bike"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_unknown() {
|
||||||
|
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy("hovercraft"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_null() {
|
||||||
|
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBy_blank() {
|
||||||
|
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
+180
-2
@@ -120,6 +120,184 @@ public class GPXTrackImporterTest {
|
|||||||
), importedTrackPoints);
|
), importedTrackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_missing_time() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_missing_time);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 2. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("", importedTrack.activityTypeLocalized());
|
||||||
|
assertEquals("", importedTrack.description());
|
||||||
|
assertEquals("20210907_213924.gpx", importedTrack.name());
|
||||||
|
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||||
|
|
||||||
|
// 3. trackstatistics
|
||||||
|
Statistics trackStatistics = importedTrack.statistics();
|
||||||
|
assertEquals(0, trackStatistics.maxSpeed().toMPS(), 0.01);
|
||||||
|
assertEquals(Duration.ofSeconds(0), trackStatistics.movingDuration());
|
||||||
|
|
||||||
|
// 4. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
assertEquals(1, importedTrackPoints.size());
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:12:00Z"),
|
||||||
|
30.14185982, -40.3863038, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
null))
|
||||||
|
), importedTrackPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_with_power_pwd() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_pwr);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 2. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("", importedTrack.activityTypeLocalized());
|
||||||
|
assertEquals("", importedTrack.description());
|
||||||
|
assertEquals("gpx with power", importedTrack.name());
|
||||||
|
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||||
|
|
||||||
|
// 3. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
assertEquals(3, importedTrackPoints.size());
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:10:19Z"),
|
||||||
|
30.14185982, -40.3863038, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
null),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(100f),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:11:07Z"),
|
||||||
|
30.14184657, -40.38670089, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
Speed.of(0.7976524233818054)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(110f),
|
||||||
|
null),
|
||||||
|
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:12:00Z"),
|
||||||
|
30.14185982, -40.3863038, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
Speed.of(0.7224021553993225)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(150f),
|
||||||
|
null)
|
||||||
|
), importedTrackPoints);
|
||||||
|
}
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_with_power_gpxpx() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_gpxpx);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 2. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("", importedTrack.activityTypeLocalized());
|
||||||
|
assertEquals("", importedTrack.description());
|
||||||
|
assertEquals("gpx with power", importedTrack.name());
|
||||||
|
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||||
|
|
||||||
|
// 3. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
assertEquals(3, importedTrackPoints.size());
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:10:19Z"),
|
||||||
|
30.14185982, -40.3863038, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
null),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(100f),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:11:07Z"),
|
||||||
|
30.14184657, -40.38670089, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
Speed.of(0.7976524233818054)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(110f),
|
||||||
|
null),
|
||||||
|
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2021-09-07T22:12:00Z"),
|
||||||
|
30.14185982, -40.3863038, null,
|
||||||
|
Altitude.WGS84.of(-5), null,
|
||||||
|
null,
|
||||||
|
Speed.of(0.7224021553993225)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new Power(150f),
|
||||||
|
null)
|
||||||
|
), importedTrackPoints);
|
||||||
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void gpx_speed_no_namespace() throws IOException {
|
public void gpx_speed_no_namespace() throws IOException {
|
||||||
@@ -285,8 +463,8 @@ public class GPXTrackImporterTest {
|
|||||||
public void importExportTest_timezone() throws IOException {
|
public void importExportTest_timezone() throws IOException {
|
||||||
// given
|
// given
|
||||||
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone);
|
||||||
try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) {
|
try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone)) {
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||||
|
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||||
|
<trk>
|
||||||
|
<name>20210907_213924.gpx</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time/>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:12:00Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
+2
-1
@@ -28,10 +28,11 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/
|
|||||||
<opentracks:typeTranslated><![CDATA[unknown]]></opentracks:typeTranslated>
|
<opentracks:typeTranslated><![CDATA[unknown]]></opentracks:typeTranslated>
|
||||||
<gpxtrkx:TrackStatsExtension>
|
<gpxtrkx:TrackStatsExtension>
|
||||||
<gpxtrkx:Distance>0.0</gpxtrkx:Distance>
|
<gpxtrkx:Distance>0.0</gpxtrkx:Distance>
|
||||||
<gpxtrkx:TimerTime>0</gpxtrkx:TimerTime>
|
<gpxtrkx:TotalElapsedTime>0</gpxtrkx:TotalElapsedTime>
|
||||||
<gpxtrkx:MovingTime>0</gpxtrkx:MovingTime>
|
<gpxtrkx:MovingTime>0</gpxtrkx:MovingTime>
|
||||||
<gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime>
|
<gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime>
|
||||||
<gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed>
|
<gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed>
|
||||||
|
<gpxtrkx:MovingSpeed>0.0</gpxtrkx:MovingSpeed>
|
||||||
</gpxtrkx:TrackStatsExtension>
|
</gpxtrkx:TrackStatsExtension>
|
||||||
</extensions>
|
</extensions>
|
||||||
<trkseg>
|
<trkseg>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||||
|
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gpxpx="http://www.garmin.com/xmlschemas/PowerExtension/v1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||||
|
<trk>
|
||||||
|
<name>gpx with power</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:10:19Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<gpxpx:PowerInWatts>100</gpxpx:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:11:07Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<gpxpx:PowerInWatts>110</gpxpx:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:12:00Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<gpxpx:PowerInWatts>150</gpxpx:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||||
|
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||||
|
<trk>
|
||||||
|
<name>gpx with power</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:10:19Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<pwr:PowerInWatts>100</pwr:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:11:07Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<pwr:PowerInWatts>110</pwr:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:12:00Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<extensions>
|
||||||
|
<pwr:PowerInWatts>150</pwr:PowerInWatts>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -71,6 +71,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
|
<uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
|
||||||
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
|
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
|
||||||
|
<uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
|
||||||
|
<uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
|
||||||
|
<uses-permission android:name="android.permission.health.WRITE_SPEED" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
@@ -14,13 +15,13 @@ public enum ActivityType {
|
|||||||
|
|
||||||
AIRPLANE("airplane", R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp, true),
|
AIRPLANE("airplane", R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||||
ATV("ATV", R.string.activity_type_atv, R.drawable.ic_activity_drive_24dp, true),
|
ATV("ATV", R.string.activity_type_atv, R.drawable.ic_activity_drive_24dp, true),
|
||||||
BIKING("biking", R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
BIKING("biking", List.of("bike", "indoor cycling"), R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||||
BLIMP("blimp", R.string.activity_type_blimp, ActivityIcon.ICON_UNKNOWN, true),
|
BLIMP("blimp", R.string.activity_type_blimp, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
BOAT("boat", R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp, true),
|
BOAT("boat", R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||||
CLIMBING("climbing", R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp, false),
|
CLIMBING("climbing", R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp, false),
|
||||||
COMMERCIAL_AIRPLANE("commercial airplane", R.string.activity_type_commercial_airplane, R.drawable.ic_activity_flight_24dp, true),
|
COMMERCIAL_AIRPLANE("commercial airplane", R.string.activity_type_commercial_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||||
CROSS_COUNTRY_SKIING("cross-country skiing", R.string.activity_type_cross_country_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
CROSS_COUNTRY_SKIING("cross-country skiing", R.string.activity_type_cross_country_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||||
CYCLING("cycling", R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
CYCLING("cycling", List.of("cycle"), R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||||
DIRT_BIKE("dirt bike", R.string.activity_type_dirt_bike, R.drawable.ic_activity_mtb_24dp, true),
|
DIRT_BIKE("dirt bike", R.string.activity_type_dirt_bike, R.drawable.ic_activity_mtb_24dp, true),
|
||||||
DONKEY_BACK_RIDING("donkey back riding", R.string.activity_type_donkey_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
DONKEY_BACK_RIDING("donkey back riding", R.string.activity_type_donkey_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
DRIVING("driving", R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp, true),
|
DRIVING("driving", R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp, true),
|
||||||
@@ -30,21 +31,21 @@ public enum ActivityType {
|
|||||||
FERRY("ferry", R.string.activity_type_ferry, R.drawable.ic_activity_boat_24dp, true),
|
FERRY("ferry", R.string.activity_type_ferry, R.drawable.ic_activity_boat_24dp, true),
|
||||||
FRISBEE("frisbee", R.string.activity_type_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
FRISBEE("frisbee", R.string.activity_type_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
GLIDING("gliding", R.string.activity_type_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
GLIDING("gliding", R.string.activity_type_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
GRAVEL_BIKING("gravel biking", R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true),
|
GRAVEL_BIKING("gravel biking", List.of("gravel bike"), R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||||
HANG_GLIDING("hang gliding", R.string.activity_type_hang_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
HANG_GLIDING("hang gliding", R.string.activity_type_hang_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
HELICOPTER("helicopter", R.string.activity_type_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
HELICOPTER("helicopter", R.string.activity_type_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
HIKING("hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
HIKING("hiking", List.of("hike"), R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||||
HORSE_BACK_RIDING("horse back riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
HORSE_BACK_RIDING("horse back riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
HOT_AIR_BALLOON("hot air balloon", R.string.activity_type_hot_air_balloon, ActivityIcon.ICON_UNKNOWN, true),
|
HOT_AIR_BALLOON("hot air balloon", R.string.activity_type_hot_air_balloon, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
ICE_SAILING("ice sailing", R.string.activity_type_ice_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
ICE_SAILING("ice sailing", R.string.activity_type_ice_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
INLINE_SKATING("inline skating", R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
INLINE_SKATING("inline skating", List.of("inline skate"), R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
||||||
KAYAKING("kayaking", R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp, true),
|
KAYAKING("kayaking", R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp, true),
|
||||||
KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true),
|
MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true),
|
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true),
|
||||||
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
|
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
|
||||||
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
MOUNTAIN_BIKING("mountain biking", List.of("mountain bike"), R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||||
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||||
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
|
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
|
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
@@ -53,46 +54,57 @@ public enum ActivityType {
|
|||||||
RC_BOAT("RC boat", R.string.activity_type_rc_boat, R.drawable.ic_activity_boat_24dp, true),
|
RC_BOAT("RC boat", R.string.activity_type_rc_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||||
RC_HELICOPTER("RC helicopter", R.string.activity_type_rc_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
RC_HELICOPTER("RC helicopter", R.string.activity_type_rc_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
RIDING("riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
RIDING("riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
ROAD_BIKING("road biking", R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
ROAD_BIKING("road biking", List.of("road bike"), R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||||
ROLLER_SKIING("roller skiing", R.string.activity_type_roller_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
ROLLER_SKIING("roller skiing", R.string.activity_type_roller_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
ROWING("rowing", R.string.activity_type_rowing, ActivityIcon.ICON_UNKNOWN, true),
|
ROWING("rowing", R.string.activity_type_rowing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
RUNNING("running", R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
RUNNING("running", List.of("run", "treadmill"), R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
||||||
SAILING("sailing", R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp, true),
|
SAILING("sailing", R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp, true),
|
||||||
KICKSCOOTER("kickscooter", R.string.activity_type_kickscooter, R.drawable.ic_activity_scooter_24dp, true),
|
KICKSCOOTER("kickscooter", R.string.activity_type_kickscooter, R.drawable.ic_activity_scooter_24dp, true),
|
||||||
SEAPLANE("seaplane", R.string.activity_type_seaplane, R.drawable.ic_activity_flight_24dp, true),
|
SEAPLANE("seaplane", R.string.activity_type_seaplane, R.drawable.ic_activity_flight_24dp, true),
|
||||||
SKATE_BOARDING("skateboarding", R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
SKATE_BOARDING("skateboarding", List.of("skateboard"), R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
||||||
SKATING("skating", R.string.activity_type_skating, ActivityIcon.ICON_UNKNOWN, true),
|
SKATING("skating", R.string.activity_type_skating, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
SKIING("skiing", R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
SKIING("skiing", R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||||
SKY_JUMPING("sky jumping", R.string.activity_type_sky_jumping, ActivityIcon.ICON_UNKNOWN, true),
|
SKY_JUMPING("sky jumping", R.string.activity_type_sky_jumping, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
SLED("sled", R.string.activity_type_sled, ActivityIcon.ICON_UNKNOWN, true),
|
SLED("sled", R.string.activity_type_sled, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
SNOW_BOARDING("snowboarding", R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
SNOW_BOARDING("snowboarding", List.of("snowboard"), R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
||||||
SNOW_SHOEING("snow shoeing", R.string.activity_type_snow_shoeing, ActivityIcon.ICON_UNKNOWN, true),
|
SNOW_SHOEING("snow shoeing", R.string.activity_type_snow_shoeing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
SPEED_WALKING("speed walking", R.string.activity_type_speed_walking, R.drawable.ic_activity_walk_24dp, false),
|
SPEED_WALKING("speed walking", R.string.activity_type_speed_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||||
STREET_RUNNING("street running", R.string.activity_type_street_running, R.drawable.ic_activity_run_24dp, false),
|
STREET_RUNNING("street running", R.string.activity_type_street_running, R.drawable.ic_activity_run_24dp, false),
|
||||||
SURFING("surfing", R.string.activity_type_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
SURFING("surfing", R.string.activity_type_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
TRACK_CYCLING("track cycling", R.string.activity_type_track_cycling, R.drawable.ic_activity_bike_24dp, true),
|
TRACK_CYCLING("track cycling", R.string.activity_type_track_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||||
TRACK_RUNNING("track running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
TRACK_RUNNING("track running", List.of("track run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||||
TRAIL_HIKING("trail hiking", R.string.activity_type_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
TRAIL_HIKING("trail hiking", R.string.activity_type_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||||
TRAIL_RUNNING("trail running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
TRAIL_RUNNING("trail running", List.of("trail run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||||
TRAIN("train", R.string.activity_type_train, ActivityIcon.ICON_UNKNOWN, true),
|
TRAIN("train", R.string.activity_type_train, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
ULTIMATE_FRISBEE("ultimate frisbee", R.string.activity_type_ultimate_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
ULTIMATE_FRISBEE("ultimate frisbee", R.string.activity_type_ultimate_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
WAKEBOARDING("wakeboarding", R.string.activity_type_wakeboarding, ActivityIcon.ICON_UNKNOWN, true),
|
WAKEBOARDING("wakeboarding", R.string.activity_type_wakeboarding, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
WALKING("walking", R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
WALKING("walking", List.of("walk"), R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||||
WATER_SKIING("water skiing", R.string.activity_type_water_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
WATER_SKIING("water skiing", R.string.activity_type_water_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
WIND_SURFING("wind surfing", R.string.activity_type_wind_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
WIND_SURFING("wind surfing", R.string.activity_type_wind_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||||
SWIMMING("swimming", R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
SWIMMING("swimming", List.of("swim"), R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
||||||
SWIMMING_OPEN("swimming in open water", R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
SWIMMING_OPEN("swimming in open water", List.of("swimming openwater", "open water swimming"), R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
||||||
WORKOUT("workout", R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp, false),
|
WORKOUT("workout", R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp, false),
|
||||||
UNKNOWN("unknown", R.string.activity_type_unknown, ActivityIcon.ICON_UNKNOWN, true);
|
UNKNOWN("unknown", R.string.activity_type_unknown, ActivityIcon.ICON_UNKNOWN, true);
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spellings used by other applications that {@link #normalize(String)} cannot map onto {@link #id}.
|
||||||
|
* Already normalized.
|
||||||
|
*/
|
||||||
|
final List<String> synonyms;
|
||||||
|
|
||||||
final int iconDrawableId;
|
final int iconDrawableId;
|
||||||
final boolean showSpeedPreferred;
|
final boolean showSpeedPreferred;
|
||||||
final int localizedStringId;
|
final int localizedStringId;
|
||||||
|
|
||||||
ActivityType(String id, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
ActivityType(String id, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||||
|
this(id, List.of(), localizedStringId, iconDrawableId, showSpeedPreferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityType(String id, List<String> synonyms, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.synonyms = synonyms;
|
||||||
this.localizedStringId = localizedStringId;
|
this.localizedStringId = localizedStringId;
|
||||||
this.iconDrawableId = iconDrawableId;
|
this.iconDrawableId = iconDrawableId;
|
||||||
this.showSpeedPreferred = showSpeedPreferred;
|
this.showSpeedPreferred = showSpeedPreferred;
|
||||||
@@ -121,12 +133,39 @@ public enum ActivityType {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowercases and reduces "_", "-" and whitespace runs to a single space.
|
||||||
|
* Lowercasing is locale-independent: under a Turkish locale the I of "RUNNING" would otherwise
|
||||||
|
* lowercase to a dotless i and stop matching "running".
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static String normalize(String activityTypeId) {
|
||||||
|
if (activityTypeId == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return activityTypeId.toLowerCase(Locale.ROOT)
|
||||||
|
.replaceAll("[\\s_-]+", " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves an activity type as written by another application; GPX's type element is free text.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static ActivityType findBy(String activityTypeId) {
|
public static ActivityType findBy(String activityTypeId) {
|
||||||
return Arrays.stream(ActivityType.values()).filter(
|
String normalized = normalize(activityTypeId);
|
||||||
it -> it.id.equals(activityTypeId)
|
if (normalized.isEmpty()) {
|
||||||
).findFirst()
|
return ActivityType.UNKNOWN;
|
||||||
.orElse(ActivityType.UNKNOWN);
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(ActivityType.values())
|
||||||
|
.filter(it -> normalize(it.id).equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseGet(() -> Arrays.stream(ActivityType.values())
|
||||||
|
.filter(it -> it.synonyms.contains(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(ActivityType.UNKNOWN));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
// List of tracks to be exported.
|
// List of tracks to be exported.
|
||||||
private ArrayList<ExportTask> exportTasks;
|
private final ArrayList<ExportTask> exportTasks = new ArrayList<>();
|
||||||
|
|
||||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||||
private final Handler conflictsHandler = new Handler();
|
private final Handler conflictsHandler = new Handler();
|
||||||
@@ -183,7 +183,7 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
||||||
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
||||||
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
||||||
exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
exportTasks.addAll(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||||
setProgress();
|
setProgress();
|
||||||
nextExport(null);
|
nextExport(null);
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,6 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
exportTasks = new ArrayList<>();
|
|
||||||
if (allInOneFile) {
|
if (allInOneFile) {
|
||||||
String filename = "OpenTracks-Backup";
|
String filename = "OpenTracks-Backup";
|
||||||
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::id).toList()));
|
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::id).toList()));
|
||||||
|
|||||||
@@ -282,10 +282,11 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
Statistics trackStatistics = track.statistics();
|
Statistics trackStatistics = track.statistics();
|
||||||
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
||||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.totalDistance().toM() + "</gpxtrkx:Distance>");
|
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.totalDistance().toM() + "</gpxtrkx:Distance>");
|
||||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TimerTime>");
|
printWriter.println("<gpxtrkx:TotalElapsedTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TotalElapsedTime>");
|
||||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.movingDuration().getSeconds() + "</gpxtrkx:MovingTime>");
|
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.movingDuration().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||||
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
||||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.maxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.maxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||||
|
printWriter.println("<gpxtrkx:MovingSpeed>" + trackStatistics.getAverageMovingSpeed().toMPS() + "</gpxtrkx:MovingSpeed>");
|
||||||
if (trackStatistics.altitudeGainLoss() != null) {
|
if (trackStatistics.altitudeGainLoss() != null) {
|
||||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.altitudeGainLoss().gain_m() + "</gpxtrkx:Ascent>");
|
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.altitudeGainLoss().gain_m() + "</gpxtrkx:Ascent>");
|
||||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.altitudeGainLoss().loss_m() + "</gpxtrkx:Descent>");
|
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.altitudeGainLoss().loss_m() + "</gpxtrkx:Descent>");
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.Locator;
|
import org.xml.sax.Locator;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
@@ -71,28 +74,44 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
private static final String TAG_TRACK_POINT = "trkpt";
|
private static final String TAG_TRACK_POINT = "trkpt";
|
||||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||||
private static final String TAG_TYPE = "type";
|
private static final String TAG_TYPE = "type";
|
||||||
private static final String TAG_TYPE_LOCALIZED = "opentracks:typeTranslated";
|
|
||||||
private static final String TAG_MARKER = "wpt";
|
private static final String TAG_MARKER = "wpt";
|
||||||
private static final String TAG_ID = "opentracks:trackid";
|
|
||||||
|
|
||||||
private static final String ATTRIBUTE_LAT = "lat";
|
private static final String ATTRIBUTE_LAT = "lat";
|
||||||
private static final String ATTRIBUTE_LON = "lon";
|
private static final String ATTRIBUTE_LON = "lon";
|
||||||
|
|
||||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
|
||||||
/**
|
/**
|
||||||
* Often speed is exported without the proper namespace.
|
* Often speed is exported without the proper namespace.
|
||||||
*/
|
*/
|
||||||
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
|
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
|
||||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
|
||||||
private static final String TAG_EXTENSION_TEMPERATURE = "gpxtpx:atemp";
|
|
||||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
|
||||||
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
|
||||||
|
|
||||||
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
|
/**
|
||||||
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
* Garmin Trackpoint Extensions.
|
||||||
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
*/
|
||||||
private static final String TAG_EXTENSION_ACCURACY_HORIZONTAL = "opentracks:accuracy_horizontal";
|
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
|
||||||
private static final String TAG_EXTENSION_ACCURACY_VERTICAL = "opentracks:accuracy_vertical";
|
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V2 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2";
|
||||||
|
private static final String TAG_GARMIN_TPX_EXTENSION_SPEED = "speed";
|
||||||
|
private static final String TAG_GARMIN_TPX_EXTENSION_HEARTRATE = "hr";
|
||||||
|
private static final String TAG_GARMIN_TPX_EXTENSION_TEMPERATURE = "atemp";
|
||||||
|
private static final String TAG_GARMIN_TPX_EXTENSION_CADENCE = "cad";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garmin Trackpoint Extensions.
|
||||||
|
*/
|
||||||
|
private static final String NS_GARMIN_POWER_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/PowerExtension/v1";
|
||||||
|
private static final String TAG_GARMIN_PWR_EXTENSION_POWER = "PowerInWatts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenTracks Extensions.
|
||||||
|
*/
|
||||||
|
private static final String NS_OPENTRACKS = "http://opentracksapp.com/xmlschemas/v1";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_TRACK_ID = "trackid";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED = "typeTranslated";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_GAIN = "gain";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_LOSS = "loss";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_DISTANCE = "distance";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL = "accuracy_horizontal";
|
||||||
|
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL = "accuracy_vertical";
|
||||||
|
|
||||||
private Locator locator;
|
private Locator locator;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@@ -142,8 +161,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) {
|
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||||
switch (tag) {
|
switch (qName) {
|
||||||
case TAG_MARKER:
|
case TAG_MARKER:
|
||||||
onMarkerStart(attributes);
|
onMarkerStart(attributes);
|
||||||
break;
|
break;
|
||||||
@@ -165,105 +184,128 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String tag) {
|
public void endElement(String uri, String localName, String qName) {
|
||||||
switch (tag) {
|
if (NS_GARMIN_TRACKPOINT_EXTENSION_V1.equals(uri) || NS_GARMIN_TRACKPOINT_EXTENSION_V2.equals(uri)) {
|
||||||
case TAG_GPX -> onFileEnd();
|
switch (localName) {
|
||||||
case TAG_MARKER -> onMarkerEnd();
|
case TAG_GARMIN_TPX_EXTENSION_SPEED -> {
|
||||||
case TAG_TRACK -> {
|
if (content != null) {
|
||||||
if (activityTypeLocalized == null) {
|
speed = content.trim();
|
||||||
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
}
|
||||||
activityTypeLocalized = activityType;
|
|
||||||
}
|
}
|
||||||
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
case TAG_GARMIN_TPX_EXTENSION_HEARTRATE -> {
|
||||||
zoneOffset = null;
|
if (content != null) {
|
||||||
}
|
heartrate = content.trim();
|
||||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
}
|
||||||
case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
|
|
||||||
case TAG_NAME -> {
|
|
||||||
if (content != null) {
|
|
||||||
name = content.trim();
|
|
||||||
}
|
}
|
||||||
}
|
case TAG_GARMIN_TPX_EXTENSION_TEMPERATURE -> {
|
||||||
case TAG_DESCRIPTION -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
temperature = content.trim();
|
||||||
description = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_GARMIN_TPX_EXTENSION_CADENCE -> {
|
||||||
case TAG_TYPE -> { //Track or Marker/WPT
|
if (content != null) {
|
||||||
if (content != null) {
|
cadence = content.trim();
|
||||||
// In older version this might be localized content.
|
}
|
||||||
activityType = content.trim();
|
|
||||||
markerType = content.trim();
|
|
||||||
}
|
}
|
||||||
|
default -> Log.w(TAG, "Unknown Garmin trackpoint extension tag " + localName);
|
||||||
}
|
}
|
||||||
case TAG_TYPE_LOCALIZED -> {
|
} else if (NS_GARMIN_POWER_EXTENSION_V1.equals(uri)) {
|
||||||
if (content != null) {
|
//noinspection SwitchStatementWithTooFewBranches
|
||||||
activityTypeLocalized = content.trim();
|
switch (localName) {
|
||||||
|
case TAG_GARMIN_PWR_EXTENSION_POWER -> {
|
||||||
|
if (content != null) {
|
||||||
|
power = content.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
default -> Log.w(TAG, "Unknown Garmin power extension tag " + localName);
|
||||||
}
|
}
|
||||||
case TAG_TIME -> {
|
} else if (NS_OPENTRACKS.equals(uri)) {
|
||||||
if (content != null) {
|
switch (localName) {
|
||||||
time = content.trim();
|
case TAG_OPENTRACKS_EXTENSION_TRACK_ID -> {
|
||||||
|
if (content != null) {
|
||||||
|
uuid = content.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED -> {
|
||||||
case TAG_ALTITUDE -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
activityTypeLocalized = content.trim();
|
||||||
altitude = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_GAIN -> {
|
||||||
case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
gain = content.trim();
|
||||||
speed = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_LOSS -> {
|
||||||
case TAG_EXTENSION_HEARTRATE -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
loss = content.trim();
|
||||||
heartrate = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_DISTANCE -> {
|
||||||
case TAG_EXTENSION_TEMPERATURE -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
sensorDistance = content.trim();
|
||||||
temperature = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||||
case TAG_EXTENSION_CADENCE -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
accuracyHorizontal = content.trim();
|
||||||
cadence = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL -> {
|
||||||
case TAG_EXTENSION_POWER -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
accuracyVertical = content.trim();
|
||||||
power = content.trim();
|
}
|
||||||
}
|
}
|
||||||
|
default -> Log.w(TAG, "Unknown OpenTracks extension tag " + localName);
|
||||||
}
|
}
|
||||||
case TAG_ID -> {
|
} else {
|
||||||
if (content != null) {
|
switch (qName) {
|
||||||
uuid = content.trim();
|
case TAG_GPX -> onFileEnd();
|
||||||
|
case TAG_MARKER -> onMarkerEnd();
|
||||||
|
case TAG_TRACK -> {
|
||||||
|
if (activityTypeLocalized == null) {
|
||||||
|
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
||||||
|
activityTypeLocalized = activityType;
|
||||||
|
}
|
||||||
|
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
||||||
|
zoneOffset = null;
|
||||||
}
|
}
|
||||||
}
|
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||||
case TAG_EXTENSION_GAIN -> {
|
case TAG_TRACK_POINT -> {
|
||||||
if (content != null) {
|
TrackPoint trackPoint = createTrackPoint();
|
||||||
gain = content.trim();
|
if (trackPoint != null) currentSegment.add(trackPoint);
|
||||||
}
|
}
|
||||||
}
|
case TAG_NAME -> {
|
||||||
case TAG_EXTENSION_LOSS -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
name = content.trim();
|
||||||
loss = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_DESCRIPTION -> {
|
||||||
case TAG_EXTENSION_DISTANCE -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
description = content.trim();
|
||||||
sensorDistance = content.trim();
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_TYPE -> { //Track or Marker/WPT
|
||||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
// In older version this might be localized content.
|
||||||
accuracyHorizontal = content.trim();
|
activityType = content.trim();
|
||||||
|
markerType = content.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
case TAG_TIME -> {
|
||||||
case TAG_EXTENSION_ACCURACY_VERTICAL -> {
|
if (content != null) {
|
||||||
if (content != null) {
|
time = content.trim();
|
||||||
accuracyVertical = content.trim();
|
}
|
||||||
|
}
|
||||||
|
case TAG_ALTITUDE -> {
|
||||||
|
if (content != null) {
|
||||||
|
altitude = content.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case TAG_EXTENSION_SPEED_COMPAT -> {
|
||||||
|
if (content != null) {
|
||||||
|
speed = content.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,8 +326,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
currentSegment.clear();
|
currentSegment.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private TrackPoint createTrackPoint() throws ParsingException {
|
private TrackPoint createTrackPoint() throws ParsingException {
|
||||||
|
if (TextUtils.isEmpty(time)) return null;
|
||||||
|
|
||||||
OffsetDateTime parsedTime;
|
OffsetDateTime parsedTime;
|
||||||
try {
|
try {
|
||||||
parsedTime = StringUtils.parseTime(time);
|
parsedTime = StringUtils.parseTime(time);
|
||||||
@@ -468,7 +512,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
|
|
||||||
TrackPoint trackPoint = createTrackPoint();
|
TrackPoint trackPoint = createTrackPoint();
|
||||||
|
if (trackPoint == null) {
|
||||||
|
Log.w(TAG, "Marker with no TrackPoint");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!trackPoint.position().hasLocation()) {
|
if (!trackPoint.position().hasLocation()) {
|
||||||
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position());
|
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position());
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
||||||
switch (tag) {
|
switch (qName) {
|
||||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||||
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, typeLocalized, and description.
|
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, typeLocalized, and description.
|
||||||
onMarkerStart();
|
onMarkerStart();
|
||||||
@@ -168,8 +168,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||||
switch (tag) {
|
switch (qName) {
|
||||||
case TAG_KML -> onFileEnd();
|
case TAG_KML -> onFileEnd();
|
||||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||||
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
||||||
|
|||||||
+13
-5
@@ -5,7 +5,10 @@ import android.content.Context;
|
|||||||
import androidx.health.connect.client.HealthConnectClient;
|
import androidx.health.connect.client.HealthConnectClient;
|
||||||
import androidx.health.connect.client.PermissionController;
|
import androidx.health.connect.client.PermissionController;
|
||||||
import androidx.health.connect.client.permission.HealthPermission;
|
import androidx.health.connect.client.permission.HealthPermission;
|
||||||
|
import androidx.health.connect.client.records.DistanceRecord;
|
||||||
|
import androidx.health.connect.client.records.ElevationGainedRecord;
|
||||||
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
||||||
|
import androidx.health.connect.client.records.SpeedRecord;
|
||||||
import androidx.work.Data;
|
import androidx.work.Data;
|
||||||
import androidx.work.ExistingWorkPolicy;
|
import androidx.work.ExistingWorkPolicy;
|
||||||
import androidx.work.OneTimeWorkRequest;
|
import androidx.work.OneTimeWorkRequest;
|
||||||
@@ -27,11 +30,13 @@ import kotlinx.coroutines.BuildersKt;
|
|||||||
*/
|
*/
|
||||||
public class HealthConnectUtils {
|
public class HealthConnectUtils {
|
||||||
|
|
||||||
static final String WRITE_EXERCISE_PERMISSION = HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ExerciseSessionRecord.class));
|
public static final Set<String> PERMISSIONS = Set.of(
|
||||||
static final String WRITE_EXERCISE_ROUTE_PERMISSION = HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE;
|
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ExerciseSessionRecord.class)),
|
||||||
|
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(DistanceRecord.class)),
|
||||||
//TODO Should be package private
|
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ElevationGainedRecord.class)),
|
||||||
public static final Set<String> PERMISSIONS = Set.of(WRITE_EXERCISE_PERMISSION, WRITE_EXERCISE_ROUTE_PERMISSION);
|
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(SpeedRecord.class)),
|
||||||
|
HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE
|
||||||
|
);
|
||||||
|
|
||||||
private HealthConnectUtils() {
|
private HealthConnectUtils() {
|
||||||
}
|
}
|
||||||
@@ -41,6 +46,9 @@ public class HealthConnectUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getGrantedPermissions(Context context) {
|
public static Set<String> getGrantedPermissions(Context context) {
|
||||||
|
if (!isAvailable(context))
|
||||||
|
return Set.of();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
||||||
PermissionController controller = client.getPermissionController();
|
PermissionController controller = client.getPermissionController();
|
||||||
|
|||||||
+89
-22
@@ -6,16 +6,22 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.health.connect.client.HealthConnectClient;
|
import androidx.health.connect.client.HealthConnectClient;
|
||||||
|
import androidx.health.connect.client.records.DistanceRecord;
|
||||||
|
import androidx.health.connect.client.records.ElevationGainedRecord;
|
||||||
import androidx.health.connect.client.records.ExerciseRoute;
|
import androidx.health.connect.client.records.ExerciseRoute;
|
||||||
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
||||||
|
import androidx.health.connect.client.records.Record;
|
||||||
|
import androidx.health.connect.client.records.SpeedRecord;
|
||||||
import androidx.health.connect.client.records.metadata.Device;
|
import androidx.health.connect.client.records.metadata.Device;
|
||||||
import androidx.health.connect.client.records.metadata.Metadata;
|
import androidx.health.connect.client.records.metadata.Metadata;
|
||||||
import androidx.health.connect.client.units.Length;
|
import androidx.health.connect.client.units.Length;
|
||||||
|
import androidx.health.connect.client.units.Velocity;
|
||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
@@ -111,27 +117,65 @@ public class HealthConnectWorker extends Worker {
|
|||||||
throw new RuntimeException("Track not found: " + trackId);
|
throw new RuntimeException("Track not found: " + trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExerciseRoute exerciseRoute = createExerciseRoute(contentProviderUtils, trackId);
|
|
||||||
|
|
||||||
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
||||||
|
Metadata metadata = Metadata.activelyRecorded(
|
||||||
|
new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL),
|
||||||
|
track.uuid().toString()
|
||||||
|
);
|
||||||
|
|
||||||
ExerciseSessionRecord record = new ExerciseSessionRecord(
|
List<Record> records = new ArrayList<>();
|
||||||
|
|
||||||
|
ExerciseData exerciseData = createExerciseData(contentProviderUtils, trackId);
|
||||||
|
|
||||||
|
//Avoid bug in HealthConnect: data must be within ExerciseSessionRecord.startTime and ExerciseSessionRecord.stopTime
|
||||||
|
List<ExerciseRoute.Location> locations = exerciseData.locations.stream()
|
||||||
|
.filter(i -> track.startTime().toInstant().isBefore(i.getTime()) && track.stopTime().toInstant().isAfter(i.getTime()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
records.add(new ExerciseSessionRecord(
|
||||||
track.startTime().toInstant(),
|
track.startTime().toInstant(),
|
||||||
track.startTime().getOffset(),
|
track.startTime().getOffset(),
|
||||||
track.stopTime().toInstant(),
|
track.stopTime().toInstant(),
|
||||||
track.stopTime().getOffset(),
|
track.stopTime().getOffset(),
|
||||||
Metadata.activelyRecorded(
|
metadata,
|
||||||
new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL),
|
|
||||||
track.uuid().toString()
|
|
||||||
),
|
|
||||||
toExerciseType(track.activityType()),
|
toExerciseType(track.activityType()),
|
||||||
track.name(),
|
track.name(),
|
||||||
track.description(),
|
track.description(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
exerciseRoute);
|
new ExerciseRoute(locations)));
|
||||||
|
|
||||||
|
records.add(new DistanceRecord(
|
||||||
|
track.startTime().toInstant(),
|
||||||
|
track.startTime().getOffset(),
|
||||||
|
track.stopTime().toInstant(),
|
||||||
|
track.stopTime().getOffset(),
|
||||||
|
Length.meters(track.statistics().totalDistance().toM()),
|
||||||
|
metadata
|
||||||
|
));
|
||||||
|
|
||||||
|
if (track.statistics().altitudeGainLoss() != null) {
|
||||||
|
records.add(new ElevationGainedRecord(
|
||||||
|
track.startTime().toInstant(),
|
||||||
|
track.startTime().getOffset(),
|
||||||
|
track.stopTime().toInstant(),
|
||||||
|
track.stopTime().getOffset(),
|
||||||
|
Length.meters(track.statistics().altitudeGainLoss().gain_m()),
|
||||||
|
metadata
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exerciseData.speeds.isEmpty()) {
|
||||||
|
records.add(new SpeedRecord(
|
||||||
|
track.startTime().toInstant(),
|
||||||
|
track.startTime().getOffset(),
|
||||||
|
track.stopTime().toInstant(),
|
||||||
|
track.stopTime().getOffset(),
|
||||||
|
exerciseData.speeds,
|
||||||
|
metadata
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
List<ExerciseSessionRecord> records = Collections.singletonList(record);
|
|
||||||
Object ignored = BuildersKt.runBlocking(
|
Object ignored = BuildersKt.runBlocking(
|
||||||
kotlin.coroutines.EmptyCoroutineContext.INSTANCE,
|
kotlin.coroutines.EmptyCoroutineContext.INSTANCE,
|
||||||
(Function2) (scope, cont) -> client.insertRecords(records, (Continuation) cont));
|
(Function2) (scope, cont) -> client.insertRecords(records, (Continuation) cont));
|
||||||
@@ -140,30 +184,53 @@ public class HealthConnectWorker extends Worker {
|
|||||||
return new ExportResult(track.name());
|
return new ExportResult(track.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExerciseRoute createExerciseRoute(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
private static ExerciseData createExerciseData(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||||
List<ExerciseRoute.Location> route = new ArrayList<>();
|
List<ExerciseRoute.Location> locations = new LinkedList<>();
|
||||||
|
List<SpeedRecord.Sample> speeds = new LinkedList<>();
|
||||||
|
|
||||||
try (TrackPointIterator iterator = contentProviderUtils.getTrackPointIterator(trackId, null)) {
|
try (TrackPointIterator iterator = contentProviderUtils.getTrackPointIterator(trackId, null)) {
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) { //TODO iterator.iterator.forEachRemaining()
|
||||||
TrackPoint trackPoint = iterator.next();
|
TrackPoint trackPoint = iterator.next();
|
||||||
if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue;
|
if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue; //TODO Include SEGMENT_START_AUTOMATIC?
|
||||||
|
|
||||||
Position position = trackPoint.position();
|
Position position = trackPoint.position();
|
||||||
|
|
||||||
route.add(new ExerciseRoute.Location(
|
if (position.hasLocation()) {
|
||||||
trackPoint.getTime(),
|
//ExcerciseRoute does not allow duplicated timestamps.
|
||||||
position.latitude(),
|
if (locations.stream().noneMatch(location -> location.getTime().equals(position.time()))) {
|
||||||
position.longitude(),
|
locations.add(new ExerciseRoute.Location(
|
||||||
position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null,
|
trackPoint.getTime(),
|
||||||
position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null,
|
position.latitude(),
|
||||||
position.altitude() != null ? Length.meters(position.altitude().toM()) : null)
|
position.longitude(),
|
||||||
);
|
position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null,
|
||||||
|
position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null,
|
||||||
|
position.altitude() != null ? Length.meters(position.altitude().toM()) : null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.hasSpeed()) {
|
||||||
|
speeds.add(new SpeedRecord.Sample(
|
||||||
|
trackPoint.getTime(),
|
||||||
|
Velocity.metersPerSecond(position.speed().toMPS())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Cadence
|
||||||
|
//TODO Power
|
||||||
|
//TODO Heartrate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ExerciseRoute(route);
|
return new ExerciseData(locations, speeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record ExerciseData(
|
||||||
|
List<ExerciseRoute.Location> locations,
|
||||||
|
List<SpeedRecord.Sample> speeds
|
||||||
|
) {}
|
||||||
|
|
||||||
|
|
||||||
private static int toExerciseType(ActivityType activityType) {
|
private static int toExerciseType(ActivityType activityType) {
|
||||||
return switch (activityType) {
|
return switch (activityType) {
|
||||||
case RUNNING, STREET_RUNNING, TRAIL_RUNNING, TRACK_RUNNING -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING;
|
case RUNNING, STREET_RUNNING, TRAIL_RUNNING, TRACK_RUNNING -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING;
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ public class GpsHandler extends SensorHandler<Location, Position> {
|
|||||||
gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval());
|
gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval());
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
if (driver != null) {
|
||||||
connect();
|
driver.updateDriverParameters(getContext(), sensorManager.getHandler());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implement
|
|||||||
|
|
||||||
private static final String TAG = SensorHandler.class.getSimpleName();
|
private static final String TAG = SensorHandler.class.getSimpleName();
|
||||||
|
|
||||||
private final SensorManager sensorManager;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Driver driver;
|
public Driver driver;
|
||||||
|
|
||||||
|
protected final SensorManager sensorManager;
|
||||||
|
|
||||||
protected Aggregator<AggregatorInput, AggregatorOutput> aggregator;
|
protected Aggregator<AggregatorInput, AggregatorOutput> aggregator;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -11,5 +11,9 @@ public interface Driver {
|
|||||||
//TODO Remove from interface?
|
//TODO Remove from interface?
|
||||||
boolean isConnected();
|
boolean isConnected();
|
||||||
|
|
||||||
|
default void updateDriverParameters(Context context, Handler handler) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
void disconnect();
|
void disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,16 +57,11 @@ public class GpsInternal implements Driver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
|
||||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
|
||||||
.setMaxUpdateDelayMillis(0)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Register for location updates " + context);
|
Log.i(TAG, "Register for location updates");
|
||||||
driverObserver.onConnected(null, null);
|
driverObserver.onConnected(null, null);
|
||||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, locationListenerCompat);
|
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat);
|
||||||
return;
|
return;
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||||
@@ -76,6 +71,16 @@ public class GpsInternal implements Driver {
|
|||||||
driverObserver.onDisconnected();
|
driverObserver.onDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"MissingPermission"})
|
||||||
|
@Override
|
||||||
|
public void updateDriverParameters(Context context, Handler handler) {
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
Log.i(TAG, "Update for location updates");
|
||||||
|
driverObserver.onConnected(null, null);
|
||||||
|
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"MissingPermission"})
|
@SuppressWarnings({"MissingPermission"})
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
@@ -87,9 +92,15 @@ public class GpsInternal implements Driver {
|
|||||||
driverObserver.onDisconnected();
|
driverObserver.onDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return locationManager != null;
|
return locationManager != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LocationRequestCompat createLocationRequest() {
|
||||||
|
return new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
||||||
|
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||||
|
.setMaxUpdateDelayMillis(0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized void endCurrentTrack() {
|
synchronized void endCurrentTrack() {
|
||||||
|
handler.removeCallbacks(ON_IDLE);
|
||||||
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
|
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
|
||||||
insertTrackPoint(segmentEnd, true);
|
insertTrackPoint(segmentEnd, true);
|
||||||
|
|
||||||
@@ -188,6 +189,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
}
|
}
|
||||||
|
|
||||||
RecordingData getDataForUI() {
|
RecordingData getDataForUI() {
|
||||||
|
if (trackId == null) {
|
||||||
|
throw new RuntimeException("Requesting data if not recording should not be done.");
|
||||||
|
}
|
||||||
|
|
||||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
||||||
TrackPoint trackPoint = current.first;
|
TrackPoint trackPoint = current.first;
|
||||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint);
|
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint);
|
||||||
@@ -196,8 +201,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
throw new RuntimeException("Data could not be loaded for TrackId " + trackId);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackBuilder trackBuilder = new TrackBuilder(track);
|
TrackBuilder trackBuilder = new TrackBuilder(track);
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
|
|||||||
setExportTrackFileFormatOptions();
|
setExportTrackFileFormatOptions();
|
||||||
setExportDirectorySummary();
|
setExportDirectorySummary();
|
||||||
setFilenameTemplate();
|
setFilenameTemplate();
|
||||||
|
|
||||||
findPreference("healthConnectPermissions").setSummaryProvider(preference -> {
|
|
||||||
//TODO This should be localized and prefixes trimmed.
|
|
||||||
return String.join(",", HealthConnectUtils.getGrantedPermissions(getActivity()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ public class PreferencesUtils {
|
|||||||
private static SharedPreferences sharedPreferences;
|
private static SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private static Resources resources;
|
private static Resources resources;
|
||||||
private static final long LONG_UNSET = Long.MIN_VALUE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called during application startup.
|
* Must be called during application startup.
|
||||||
@@ -560,6 +559,7 @@ public class PreferencesUtils {
|
|||||||
String[] entries = new String[entryValues.length];
|
String[] entries = new String[entryValues.length];
|
||||||
|
|
||||||
final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
|
final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
|
||||||
|
final int maxRecordingDistanceOff = Integer.parseInt(resources.getString(R.string.max_recording_distance_off));
|
||||||
UnitSystem unitSystem = getUnitSystem();
|
UnitSystem unitSystem = getUnitSystem();
|
||||||
|
|
||||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||||
@@ -576,6 +576,9 @@ public class PreferencesUtils {
|
|||||||
case METRIC, IMPERIAL_METER -> {
|
case METRIC, IMPERIAL_METER -> {
|
||||||
if (value == maxRecordingDistanceDefault) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||||
|
}
|
||||||
|
if (value == maxRecordingDistanceOff) {
|
||||||
|
entries[i] = resources.getString(R.string.value_off);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
@@ -583,6 +586,9 @@ public class PreferencesUtils {
|
|||||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
||||||
if (value == maxRecordingDistanceDefault) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||||
|
}
|
||||||
|
if (value == maxRecordingDistanceOff) {
|
||||||
|
entries[i] = resources.getString(R.string.value_off);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ limitations under the License.
|
|||||||
<string name="waypoint_type_water_fountain">συντριβάνι</string>
|
<string name="waypoint_type_water_fountain">συντριβάνι</string>
|
||||||
<string name="waypoint_type_waypoint">σημείο διαδρομής</string>
|
<string name="waypoint_type_waypoint">σημείο διαδρομής</string>
|
||||||
<string name="about_preference_title">Σχετικά</string>
|
<string name="about_preference_title">Σχετικά</string>
|
||||||
<string name="about_url">Ιστότοπους εγχειρήματος: <a href="%1$s">%1$s</a></string>
|
<string name="about_url">Ιστότοπος εγχειρήματος: <a href="%1$s">%1$s</a></string>
|
||||||
<string name="about_version_name">Όνομα έκδοσης: %1$s</string>
|
<string name="about_version_name">Όνομα έκδοσης: %1$s</string>
|
||||||
<string name="about_version_code">Κωδικός έκδοσης: %1$d</string>
|
<string name="about_version_code">Κωδικός έκδοσης: %1$d</string>
|
||||||
<string name="about_third_party_data">Δεδομένα από τρίτους</string>
|
<string name="about_third_party_data">Δεδομένα από τρίτους</string>
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
--><resources>
|
--><resources>
|
||||||
<string name="about_preference_title">Acerca de</string>
|
<string name="about_preference_title">Acerca de</string>
|
||||||
<string name="about_description"><i> OpenTracks </i> es una aplicación de seguimiento deportivo que respeta completamente su privacidad.</string>
|
<string name="about_description"><i>OpenTracks</i> ies una aplicación de seguimiento deportivo que respeta por completo tu privacidad.</string>
|
||||||
<string name="about_url">Página web del proyecto: <a href="%1$s">%1$s</a></string>
|
<string name="about_url">Página web del proyecto: <a href="%1$s">%1$s</a></string>
|
||||||
<string name="about_version_name">Nombre de la versión: %1$s</string>
|
<string name="about_version_name">Nombre de la versión: %1$s</string>
|
||||||
<string name="about_version_code">Código de versión: %1$d</string>
|
<string name="about_version_code">Código de versión: %1$d</string>
|
||||||
<string name="about_history">OpenTracks es una bifurcación de <a href="https://code.google.com/archive/p/mytracks/"> <i> MyTracks de Google </i> </a> (descontinuado en 2016).</string>
|
<string name="about_history">OpenTracks es una bifurcación de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a> (descontinuada en 2016).</string>
|
||||||
<string name="about_libraries">Bibliotecas</string>
|
<string name="about_libraries">Bibliotecas</string>
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<string name="help_feature_title">OpenTracks tiene las siguientes características:</string>
|
<string name="help_feature_title">OpenTracks tiene las siguientes características:</string>
|
||||||
@@ -30,10 +30,7 @@ limitations under the License.
|
|||||||
\n* funciona completamente sin conexión
|
\n* funciona completamente sin conexión
|
||||||
\n* mostrar pistas en un mapa (ver más abajo)</string>
|
\n* mostrar pistas en un mapa (ver más abajo)</string>
|
||||||
<string name="help_track_map_title">¿Cómo mostrar un recorrido en un mapa\?</string>
|
<string name="help_track_map_title">¿Cómo mostrar un recorrido en un mapa\?</string>
|
||||||
<string name="help_track_map_content"> Utilice el pequeño icono de mapa.
|
<string name="help_track_map_content">Utiliza el icono del mapa pequeño.\n a) (actualizaciones en tiempo real) Instala <a href="https://codeberg.org/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Ofrece mapas en línea o sin conexión a través de <a href="https://www.openstreetmap.org">OpenStreetMap</a>.\n En realidad, OSMDashboard es una aplicación independiente que solo puede acceder (temporalmente) a los datos de las rutas que se van a mostrar.\n b) A través de una aplicación de mapas normal (por ejemplo, <a href="http://osmand.net/">OsmAnd</a> y <a href="https://maps.me/">MAPS.ME</a>). Estas aplicaciones deben ser compatibles con el formato de archivo KMZ (solo ubicaciones y marcas de tiempo). De hecho, una aplicación debe ser compatible con <i>ACTION_VIEW</i> con el MIME <i>application/vnd.google-earth.kmz</i>.</string>
|
||||||
\na) (actualizaciones en vivo) Instale <a href="https://codeberg.org/OpenTracksApp/OSMDashboard"> OSMDashboard </a>. Proporciona mapas en línea o fuera de línea a través de <a href="https://www.openstreetmap.org"> OpenStreetMap </a>.
|
|
||||||
\nEn realidad, OSMDashboard es una aplicación separada que solo puede acceder (temporalmente) a los datos de los recorridos que se mostrarán.
|
|
||||||
\nb) A través de una aplicación de mapas normal (p. ej., <a href="http://osmand.net/"> OsmAnd </a> y <a href="https://maps.me/"> MAPS.ME </a>). Estas aplicaciones deben admitir el formato de archivo KMZ (solo ubicaciones y marcas de tiempo). De hecho, una aplicación debe admitir <i> ACTION_VIEW </i> con MIME <i> application / vnd.google-earth.kmz</i>. </string>
|
|
||||||
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
|
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
|
||||||
<string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, solo se puede acceder a ellos dentro de OpenTracks).</string>
|
<string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, solo se puede acceder a ellos dentro de OpenTracks).</string>
|
||||||
<string name="help_track_export_title">¿Cómo puedo exportar/compartir recorridos grabados\?</string>
|
<string name="help_track_export_title">¿Cómo puedo exportar/compartir recorridos grabados\?</string>
|
||||||
@@ -44,22 +41,9 @@ limitations under the License.
|
|||||||
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
|
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
|
||||||
<string name="help_data_storage_content"> Los datos del sensor se almacenan por ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que solo se almacena <i>una</i> medición de frecuencia cardíaca por ubicación. Si está parado o está en interiores, no se registrarán los datos del sensor (pero se mostrarán). </string>
|
<string name="help_data_storage_content"> Los datos del sensor se almacenan por ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que solo se almacena <i>una</i> medición de frecuencia cardíaca por ubicación. Si está parado o está en interiores, no se registrarán los datos del sensor (pero se mostrarán). </string>
|
||||||
<string name="help_gps_troubleshooting_title">¿OpenTracks no registra ubicaciones\?</string>
|
<string name="help_gps_troubleshooting_title">¿OpenTracks no registra ubicaciones\?</string>
|
||||||
<string name="help_gps_troubleshooting_content"> Lo que puedes hacer:
|
<string name="help_gps_troubleshooting_content">¿Qué puedes hacer?:\n a) comprueba si el GPS está activado,\n b) mejora la recepción del GPS (por ejemplo, sal al aire libre),\n c) compruébalo con la aplicación <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, y\n d) desactiva el modo de ahorro de energía.\n \n Si nada de esto funciona, por favor, informa de un error.</string>
|
||||||
\na) compruebe si el GPS está habilitado,
|
|
||||||
\nb) mejorar la recepción del GPS (por ejemplo, salir al aire libre),
|
|
||||||
\nc) verifique con la aplicación <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/"> GPSTest </a>, y
|
|
||||||
\nd) desactivar el ahorro de energía.
|
|
||||||
\n
|
|
||||||
\nSi todo no ayuda, informe un error. </string>
|
|
||||||
<string name="help_support_title">¿Cómo puedo ayudar a OpenTracks\?</string>
|
<string name="help_support_title">¿Cómo puedo ayudar a OpenTracks\?</string>
|
||||||
<string name="help_support_content"> Usted puede:
|
<string name="help_support_content">Puedes:\n a) dar a conocer OpenTracks,\n b) <a href="https://codeberg.org/OpenTracksApp/OpenTracks/issues">proponer nuevas funciones</a>,\n c) <a href="https://codeberg.org/OpenTracksApp/OpenTracks/issues">informar de errores</a>,\n d) colaborar en el desarrollo del software,\n e) comprarlo en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,\n f) hacer una donación, y\n g) hacer lo que creas que ayuda a OpenTracks.</string>
|
||||||
\na) haga correr la voz sobre OpenTracks,
|
|
||||||
\nb) <a href="https://codeberg.org/OpenTracksApp/OpenTracks/issues"> proponer funciones </a>,
|
|
||||||
\nc) <a href="https://codeberg.org/OpenTracksApp/OpenTracks/issues"> informar errores </a>,
|
|
||||||
\nd) ayuda en el desarrollo de software,
|
|
||||||
\ne) comprar en <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play Store </a>,
|
|
||||||
\nf) donar, y
|
|
||||||
\ng) haga lo que crea que ayuda a OpenTracks. </string>
|
|
||||||
<!-- Activity Type -->
|
<!-- Activity Type -->
|
||||||
<string name="activity_type_airplane">avión</string>
|
<string name="activity_type_airplane">avión</string>
|
||||||
<string name="activity_type_atv">vehículos todo terreno</string>
|
<string name="activity_type_atv">vehículos todo terreno</string>
|
||||||
@@ -655,4 +639,33 @@ limitations under the License.
|
|||||||
<string name="settings_locale_title">Idioma</string>
|
<string name="settings_locale_title">Idioma</string>
|
||||||
<string name="settings_locale_system_default">Predeterminado del sistema</string>
|
<string name="settings_locale_system_default">Predeterminado del sistema</string>
|
||||||
<string name="stats_gain_loss">Ganancia y pérdida</string>
|
<string name="stats_gain_loss">Ganancia y pérdida</string>
|
||||||
|
<string name="health_connect_permissions_rationale_title">Permisos de Health Connect</string>
|
||||||
|
<string name="health_connect_permissions_rationale_heading">¿Cómo usa OpenTracks Health Connect?</string>
|
||||||
|
<string name="health_connect_permissions_rationale_summary">Si se activa en los ajustes, OpenTracks registra los entrenamientos en Health Connect. OpenTracks no lee datos de Health Connect.</string>
|
||||||
|
<string name="activity_type_gravel_biking">ciclismo de grava</string>
|
||||||
|
<string name="description_sensor_temperature">Temperatura (Celsius)</string>
|
||||||
|
<string name="marker_detail_title">%1$d de %2$d: %3$s</string>
|
||||||
|
<string name="menu_export_all_to_health_connect">Exportar todo</string>
|
||||||
|
<string name="sensor_state_temperature">Termómetro</string>
|
||||||
|
<string name="settings_charts_title">Gráficos</string>
|
||||||
|
<string name="settings_chart_display_elevation_title">Mostrar altitud</string>
|
||||||
|
<string name="settings_chart_display_pace_or_speed_title">Ver ritmo o velocidad</string>
|
||||||
|
<string name="settings_chart_display_heart_rate_title">Ver pulso</string>
|
||||||
|
<string name="settings_recording_show_gps_disabled_warning_title">GPS desactivado</string>
|
||||||
|
<string name="settings_recording_show_gps_disabled_warning_summary">Aviso de GPS desactivado al grabar.</string>
|
||||||
|
<string name="settings_night_mode_option_system_dynamic">Sistema (Color dinámico)</string>
|
||||||
|
<string name="settings_night_mode_option_day_dynamic">Día (Color dinámico)</string>
|
||||||
|
<string name="settings_night_mode_option_night_dynamic">Noche (Color dinámico)</string>
|
||||||
|
<string name="settings_night_mode_option_night_oled_dynamic">Optimizado para OLED (Color dinámico)</string>
|
||||||
|
<string name="settings_announcements_segment_average_speed_pace">Velocidad/ritmo medio (tramo)</string>
|
||||||
|
<string name="settings_export_to_health_connect">Exportar a Health Connect</string>
|
||||||
|
<string name="stats_average_segment_pace">Ritmo medio del tramo</string>
|
||||||
|
<string name="stats_elapsed_segment_time">Tiempo de tramo transcurrido</string>
|
||||||
|
<string name="stats_sensors_temperature">Temperatura</string>
|
||||||
|
<string name="track_edit_time_offset">Diferencia horaria</string>
|
||||||
|
<string name="lap_heart_rate">Pulso (vuelta)</string>
|
||||||
|
<string name="health_connect_export_enabled_summary">Exportar la actividad a Health Connect al finalizar</string>
|
||||||
|
<string name="health_connect_not_available">Dispositivo no compatible con Health Connect</string>
|
||||||
|
<string name="health_connect_open_settings_title">Ajustes de Health Connect</string>
|
||||||
|
<string name="health_connect_open_settings_summary">Abrir los ajustes de Health Connect</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -598,4 +598,11 @@ limitations under the License.
|
|||||||
<string name="activity_type_gravel_biking">rattasõit kruusateedel</string>
|
<string name="activity_type_gravel_biking">rattasõit kruusateedel</string>
|
||||||
<string name="menu_export_all_to_health_connect">Ekspordi kõik</string>
|
<string name="menu_export_all_to_health_connect">Ekspordi kõik</string>
|
||||||
<string name="settings_export_to_health_connect">Ekspordi „Health Connect“ liidesesse</string>
|
<string name="settings_export_to_health_connect">Ekspordi „Health Connect“ liidesesse</string>
|
||||||
|
<string name="health_connect_export_enabled_summary">Peale salvestamise lõppemist ekspordi tegevus „Health Connect“ taustateenusesse</string>
|
||||||
|
<string name="health_connect_not_available">„Health Connect“ taustateenus pole selles seadmes saadaval</string>
|
||||||
|
<string name="health_connect_open_settings_title">„Health Connect“ taustateenuse seadistused</string>
|
||||||
|
<string name="health_connect_open_settings_summary">Ava „Health Connect“ taustateenuse seadistused</string>
|
||||||
|
<string name="health_connect_permissions_rationale_title">„Health Connect“ taustateenuse õigused</string>
|
||||||
|
<string name="health_connect_permissions_rationale_heading">Kuidas OpenTracks kasutab „Health Connect“ taustateenust</string>
|
||||||
|
<string name="health_connect_permissions_rationale_summary">Kui see on seadistustest lubatud, siis OpenTracks salvestab treeningute andmed ka „Health Connect“ taustateenusesse. OpenTracks ei loe andmeid „Health Connect“ taustateenusest.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
</resources>
|
||||||
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
--><resources>
|
--><resources>
|
||||||
<string name="about_preference_title">關於</string>
|
<string name="about_preference_title">關於</string>
|
||||||
<string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string>
|
<string name="about_description"><i>OpenTracks</i> 是一款完全尊重您隱私的運動追蹤應用程式。</string>
|
||||||
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
|
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
|
||||||
<string name="about_version_name">版本名稱:%1$s</string>
|
<string name="about_version_name">版本名稱:%1$s</string>
|
||||||
<string name="about_version_code">版本代號:%1$d</string>
|
<string name="about_version_code">版本代號:%1$d</string>
|
||||||
@@ -31,14 +31,11 @@ limitations under the License.
|
|||||||
\n * 完全離線工作
|
\n * 完全離線工作
|
||||||
\n * 在地圖上顯示蹤跡(見下文)</string>
|
\n * 在地圖上顯示蹤跡(見下文)</string>
|
||||||
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
|
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
|
||||||
<string name="help_track_map_content"> \u0020使用小的地圖圖示。
|
<string name="help_track_map_content">使用小型地圖圖示。\na)(即時更新)安裝 <a href="https://codeberg.org/OpenTracksApp/OSMDashboard">OSMDashboard</a>。它透過 <a href="https://www.openstreetmap.org">OpenStreetMap</a> 提供線上或離線地圖。\n實際上,OSMDashboard 是一個獨立的應用程式,只能(暫時)存取即將顯示的蹤跡資料。\nb) 透過一般地圖應用程式(例如 <a href="http://osmand.net/">OsmAnd</a> 和 <a href="https://maps.me/">MAPS.ME</a>)。這些應用程式必須支援 KMZ 檔案格式(僅包含位置與時間戳記)。實際上,應用程式需要支援 MIME 類型為 <i>application/vnd.google-earth.kmz</i> 的 <i>ACTION_VIEW</i>。</string>
|
||||||
\n \u0020a) (即時更新)安裝 <a href="https://codeberg.org/OpenTracksApp/OSMDashboard">OSMDashboard</a>。它透過 <a href="https://www.openstreetmap.org">OpenStreetMap</a> 提供線上或離線地圖。
|
|
||||||
\n \u0020實際上,OSMDashboard 是一個單獨的應用程式,只能(暫時)存取將要顯示的蹤跡資料。
|
|
||||||
\n \u0020b) \u0020透過一個普通的地圖應用程式(例如,<a href="http://osmand.net/">OsmAnd</a> 與 <a href="https://maps.me/">MAPS.ME</a>)。 這些 APP 需要支援 KMZ 檔案格式(只有位置與時間戳記)。 更準確地說,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。 \u0020</string>
|
|
||||||
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
|
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
|
||||||
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。</string>
|
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,僅限 OpenTracks 中存取)。</string>
|
||||||
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
|
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
|
||||||
<string name="help_track_export_content"> 可以從設定匯出蹤跡為 KMZ、KML 或 GPX。KMZ/KML 全部資料;GPX 只有位置。通過<i>分享圖示</i>以分享蹤跡為 KMZ。 </string>
|
<string name="help_track_export_content">可從「設定」中將蹤跡匯出為 KMZ、KML 或 GPX。KMZ/KML 包含所有資料;GPX 僅包含位置資訊。透過 <i>分享圖示</i> 分享的蹤跡會以 KMZ 格式分享。</string>
|
||||||
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
|
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
|
||||||
<string name="help_data_google_play_services_content">否</string>
|
<string name="help_data_google_play_services_content">否</string>
|
||||||
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
|
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
|
||||||
@@ -47,15 +44,9 @@ limitations under the License.
|
|||||||
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
|
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
|
||||||
</string>
|
</string>
|
||||||
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
|
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
|
||||||
<string name="help_data_storage_content"> 感測器資料儲存在每個記錄的位置。 就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。 如果您是站著不動或在室內,沒有感測器資料會被記錄(但它會被顯示)。 </string>
|
<string name="help_data_storage_content">感測器資料會儲存在每個記錄的位置中。以心率感測器為例,這表示每個位置只會儲存 <i>一筆</i> 心率測量值。如果您站著不動或位於室內,將不會記錄感測器資料(但仍會顯示)。</string>
|
||||||
<string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string>
|
<string name="help_gps_troubleshooting_title">OpenTracks 沒有記錄位置?</string>
|
||||||
<string name="help_gps_troubleshooting_content"> \u0020您能做什麼:
|
<string name="help_gps_troubleshooting_content">您可以進行以下操作:\na) 確認 GPS 是否已啟用;\nb) 改善 GPS 訊號接收(例如到戶外);\nc) 使用 <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app 進行檢查;以及\nd) 停用省電模式。\n\n如果上述方法都無法解決問題,請回報錯誤。</string>
|
||||||
\n 一)檢查 GPS 是否啟用,
|
|
||||||
\n 二)改善 GPS 收訊(例如,前往戶外),
|
|
||||||
\n 三)使用 <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app 檢查,以及
|
|
||||||
\n 四)停用省電。
|
|
||||||
\n
|
|
||||||
\n如果一切都沒有幫助,請回報 bug。 \u0020</string>
|
|
||||||
<string name="help_support_title">我如何支持 OpenTracks?</string>
|
<string name="help_support_title">我如何支持 OpenTracks?</string>
|
||||||
<string name="help_support_content">
|
<string name="help_support_content">
|
||||||
您可以:\n
|
您可以:\n
|
||||||
@@ -213,13 +204,13 @@ limitations under the License.
|
|||||||
<string name="generic_yesterday">昨日</string>
|
<string name="generic_yesterday">昨日</string>
|
||||||
<string name="generic_dismiss">關閉</string>
|
<string name="generic_dismiss">關閉</string>
|
||||||
<!-- Gps -->
|
<!-- Gps -->
|
||||||
<string name="gps_starting">正在啟用 GPS</string>
|
<string name="gps_starting">正在啟動 GPS</string>
|
||||||
<string name="gps_wait_for_better_signal">正在等待更清晰的 GPS 訊號</string>
|
<string name="gps_wait_for_better_signal">正在等待更清晰的 GPS 訊號</string>
|
||||||
<string name="gps_wait_for_signal">正在等待 GPS 訊號</string>
|
<string name="gps_wait_for_signal">正在等待 GPS 訊號</string>
|
||||||
<string name="gps_disabled_msg">GPS 禁用</string>
|
<string name="gps_disabled_msg">GPS 禁用</string>
|
||||||
<string name="gps_fixed_and_ready">GPS 已修復並準備就緒</string>
|
<string name="gps_fixed_and_ready">GPS 已修復並準備就緒</string>
|
||||||
<string name="gps_recording_status">%1$s:%2$s</string>
|
<string name="gps_recording_status">%1$s:%2$s</string>
|
||||||
<string name="gps_recording_without_signal">OpenTracks 紀錄中,不包含 GPS 資料。</string>
|
<string name="gps_recording_without_signal">OpenTracks 正在進行無 GPS 資料記錄。</string>
|
||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<string name="image_marker_count">標記數</string>
|
<string name="image_marker_count">標記數</string>
|
||||||
<string name="image_photo_marker">相片標記</string>
|
<string name="image_photo_marker">相片標記</string>
|
||||||
@@ -326,7 +317,7 @@ limitations under the License.
|
|||||||
<string name="settings_recording_fullscreen_on_while_recording_summary">當記錄時,在全螢幕中呈現。</string>
|
<string name="settings_recording_fullscreen_on_while_recording_summary">當記錄時,在全螢幕中呈現。</string>
|
||||||
<string name="settings_recording_default_activity_title">預設活動類型</string>
|
<string name="settings_recording_default_activity_title">預設活動類型</string>
|
||||||
<string name="settings_recording_location_frequency_summary">間隔 %1$s 檢查位置</string>
|
<string name="settings_recording_location_frequency_summary">間隔 %1$s 檢查位置</string>
|
||||||
<string name="settings_recording_max_recording_distance_summary">系統會在兩個地點之間的距離大於 %1$s 時建立新的區段</string>
|
<string name="settings_recording_max_recording_distance_summary">如果兩個地點之間的距離大於 %1$s,則建立一個新路段</string>
|
||||||
<string name="settings_recording_max_recording_distance_title">最遠距離</string>
|
<string name="settings_recording_max_recording_distance_title">最遠距離</string>
|
||||||
<string name="settings_recording_min_recording_distance_title">記錄位置的距離間隔</string>
|
<string name="settings_recording_min_recording_distance_title">記錄位置的距離間隔</string>
|
||||||
<string name="settings_recording_min_recording_interval_title">記錄位置的時間間隔</string>
|
<string name="settings_recording_min_recording_interval_title">記錄位置的時間間隔</string>
|
||||||
@@ -337,8 +328,8 @@ limitations under the License.
|
|||||||
<string name="settings_recording_track_name_title">預設蹤跡名稱</string>
|
<string name="settings_recording_track_name_title">預設蹤跡名稱</string>
|
||||||
<string name="settings_night_mode_title">界面主題</string>
|
<string name="settings_night_mode_title">界面主題</string>
|
||||||
<string name="settings_night_mode_option_system">系統</string>
|
<string name="settings_night_mode_option_system">系統</string>
|
||||||
<string name="settings_night_mode_option_day">日</string>
|
<string name="settings_night_mode_option_day">早</string>
|
||||||
<string name="settings_night_mode_option_night">夜</string>
|
<string name="settings_night_mode_option_night">晚</string>
|
||||||
<string name="settings_announcements_totaltime_frequency">時間間隔</string>
|
<string name="settings_announcements_totaltime_frequency">時間間隔</string>
|
||||||
<string name="settings_announcements_distance_frequency">單圈長度</string>
|
<string name="settings_announcements_distance_frequency">單圈長度</string>
|
||||||
<string name="settings_announcements_average_heart_rate">平均心率</string>
|
<string name="settings_announcements_average_heart_rate">平均心率</string>
|
||||||
@@ -383,12 +374,12 @@ limitations under the License.
|
|||||||
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
|
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
|
||||||
<string name="settings_public_api_package_title">公開 API 的套件名稱</string>
|
<string name="settings_public_api_package_title">公開 API 的套件名稱</string>
|
||||||
<string name="settings_public_api_enabled_title">公開 API</string>
|
<string name="settings_public_api_enabled_title">公開 API</string>
|
||||||
<string name="settings_public_api_enabled_summary_on">其他已安裝的應用程式可以開始或停止紀錄。</string>
|
<string name="settings_public_api_enabled_summary_on">其他已安裝的應用程式可以啟動或停止記錄。</string>
|
||||||
<string name="settings_public_api_enabled_summary_off">只有 OpenTracks 可以開始或停止紀錄。</string>
|
<string name="settings_public_api_enabled_summary_off">僅 OpenTracks 可以啟動和停止記錄。</string>
|
||||||
<string name="settings_public_api_disabled_toast">OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。</string>
|
<string name="settings_public_api_disabled_toast">OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。</string>
|
||||||
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</string>
|
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</string>
|
||||||
<string name="settings_public_api_dashboard_enabled_summary_on">開始紀錄的應用程式也可以存取紀錄的資料。</string>
|
<string name="settings_public_api_dashboard_enabled_summary_on">已啟動記錄的應用程式也可以存取已記錄的資料。</string>
|
||||||
<string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string>
|
<string name="settings_public_api_dashboard_enabled_summary_off">已記錄的資料不會自動與其他應用程式共用。</string>
|
||||||
<!-- Settings import/export -->
|
<!-- Settings import/export -->
|
||||||
<string name="settings_import_section">匯入</string>
|
<string name="settings_import_section">匯入</string>
|
||||||
<string name="settings_export_as_file_section">匯出</string>
|
<string name="settings_export_as_file_section">匯出</string>
|
||||||
@@ -546,26 +537,26 @@ limitations under the License.
|
|||||||
<string name="share_image_subject">我想與你分享一張圖片</string>
|
<string name="share_image_subject">我想與你分享一張圖片</string>
|
||||||
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
|
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
|
||||||
<string name="hold_to_stop">按住以停止</string>
|
<string name="hold_to_stop">按住以停止</string>
|
||||||
<string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string>
|
<string name="instant_export_enabled_summary">在記錄完成後將蹤跡匯出至儲存區</string>
|
||||||
<string name="instant_export_enabled_title">即時運動後匯出</string>
|
<string name="instant_export_enabled_title">即時運動後匯出</string>
|
||||||
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
|
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
|
||||||
<!-- Fields -->
|
<!-- Fields -->
|
||||||
<string name="field_set_primary">主要的</string>
|
<string name="field_set_primary">主要的</string>
|
||||||
<string name="field_set_secondary">次要的</string>
|
<string name="field_set_secondary">次要的</string>
|
||||||
<string name="field_remove_from_layout">移除</string>
|
<string name="field_remove_from_layout">移除</string>
|
||||||
<string name="no_compatible_file_manager_installed">沒有安裝相容的檔案管理器</string>
|
<string name="no_compatible_file_manager_installed">未安裝相容的檔案管理器</string>
|
||||||
<string name="error_crash_title">%1$s 已崩潰</string>
|
<string name="error_crash_title">%1$s 已崩潰</string>
|
||||||
<string name="share_crash_report">分享</string>
|
<string name="share_crash_report">分享</string>
|
||||||
<string name="report_bug">回報 bug</string>
|
<string name="report_bug">回報 bug</string>
|
||||||
<string name="show_on_dashboard">OpenTracks 儀表板 API</string>
|
<string name="show_on_dashboard">OpenTracks 儀表板 API</string>
|
||||||
<string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
|
<string name="show_on_dashboard_not_installed">未安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
|
||||||
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string>
|
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖功能。請安裝 OSMDashboard,以便在地圖上查看您的記錄。</string>
|
||||||
<string name="voice_on_device_speaker_title">使用裝置的揚聲器</string>
|
<string name="voice_on_device_speaker_title">使用裝置的揚聲器</string>
|
||||||
<string name="settings_stats_units_imperial_meter">英制(英里、公尺)</string>
|
<string name="settings_stats_units_imperial_meter">英制(英里、公尺)</string>
|
||||||
<string name="menu_export_all_one_file">全部匯出 (所有蹤跡於單一檔案)</string>
|
<string name="menu_export_all_one_file">全部匯出 (所有蹤跡於單一檔案)</string>
|
||||||
<string name="settings_recording_min_required_accuracy_summary">如果定位的精度大於 %1$s,則它將被忽略</string>
|
<string name="settings_recording_min_required_accuracy_summary">如果定位的精度大於 %1$s,則它將被忽略</string>
|
||||||
<string name="no_compatible_gallery_installed">沒有安裝相容的圖片庫應用程式</string>
|
<string name="no_compatible_gallery_installed">未安裝相容的圖片相簿應用程式</string>
|
||||||
<string name="no_compatible_camera_installed">沒有安裝相容的攝影機應用程式</string>
|
<string name="no_compatible_camera_installed">未安裝相容的相機應用程式</string>
|
||||||
<string name="settings_sensor_bluetooth_service_filter_title">篩選藍芽裝置</string>
|
<string name="settings_sensor_bluetooth_service_filter_title">篩選藍芽裝置</string>
|
||||||
<string name="settings_sensor_bluetooth_service_filter_off">顯示所有可發現的藍芽裝置</string>
|
<string name="settings_sensor_bluetooth_service_filter_off">顯示所有可發現的藍芽裝置</string>
|
||||||
<string name="voiceDistanceNauticalMilesPlural">{ n, plural, other {{n,number,#.0} 海里} }</string>
|
<string name="voiceDistanceNauticalMilesPlural">{ n, plural, other {{n,number,#.0} 海里} }</string>
|
||||||
@@ -608,12 +599,35 @@ limitations under the License.
|
|||||||
<string name="settings_announcements_lap_power">單圈功率</string>
|
<string name="settings_announcements_lap_power">單圈功率</string>
|
||||||
<string name="create_marker_error">無法建立標記。請再次嘗試。</string>
|
<string name="create_marker_error">無法建立標記。請再次嘗試。</string>
|
||||||
<string name="settings_theme_switch_restart">主題變更也許需要手動重新啟動。</string>
|
<string name="settings_theme_switch_restart">主題變更也許需要手動重新啟動。</string>
|
||||||
<string name="settings_night_mode_option_night_oled">夜(OLED 友善的)</string>
|
<string name="settings_night_mode_option_night_oled">暗色 OLED 友善</string>
|
||||||
<string name="power_x_watt">功率 {x, plural, =1 {1 瓦特} other {# 瓦特} }</string>
|
<string name="power_x_watt">功率 {x, plural, =1 {1 瓦特} other {# 瓦特} }</string>
|
||||||
<string name="settings_charts_title">圖表</string>
|
<string name="settings_charts_title">圖表</string>
|
||||||
<string name="stats_gain_loss">收益&損失</string>
|
<string name="stats_gain_loss">收益&損失</string>
|
||||||
<string name="settings_recording_show_gps_disabled_warning_title">GPS 禁用警告</string>
|
<string name="settings_recording_show_gps_disabled_warning_title">GPS 禁用警告</string>
|
||||||
<string name="settings_recording_show_gps_disabled_warning_summary">記錄時,如果 GPS 被禁用則顯示警告。</string>
|
<string name="settings_recording_show_gps_disabled_warning_summary">當記錄時,如果 GPS 被禁用則顯示警告。</string>
|
||||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||||
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
||||||
|
<string name="activity_type_gravel_biking">碎石路騎行</string>
|
||||||
|
<string name="description_sensor_temperature">溫度(攝氏度)</string>
|
||||||
|
<string name="menu_export_all_to_health_connect">匯出全部</string>
|
||||||
|
<string name="sensor_state_temperature">體溫計</string>
|
||||||
|
<string name="settings_night_mode_option_system_dynamic">系統(動態色彩)</string>
|
||||||
|
<string name="settings_night_mode_option_day_dynamic">早(動態色彩)</string>
|
||||||
|
<string name="settings_export_to_health_connect">匯出至 Health Connect</string>
|
||||||
|
<string name="settings_night_mode_option_night_dynamic">晚(動態色彩)</string>
|
||||||
|
<string name="settings_night_mode_option_night_oled_dynamic">暗色 OLED 友善(動態色彩)</string>
|
||||||
|
<string name="settings_announcements_segment_average_speed_pace">平均速度/配速(路段)</string>
|
||||||
|
<string name="stats_average_segment_pace">平均路段配速</string>
|
||||||
|
<string name="stats_elapsed_segment_time">路段經過時間</string>
|
||||||
|
<string name="stats_coordinates">緯度/經度</string>
|
||||||
|
<string name="stats_sensors_temperature">溫度</string>
|
||||||
|
<string name="track_edit_time_offset">時區偏移值</string>
|
||||||
|
<string name="health_connect_export_enabled_summary">在記錄完成後將活動匯出至 Health Connect</string>
|
||||||
|
<string name="health_connect_not_available">此設備不支援 Health Connect 功能</string>
|
||||||
|
<string name="health_connect_open_settings_title">Health Connect 設定</string>
|
||||||
|
<string name="health_connect_open_settings_summary">開啟 Health Connect 設定</string>
|
||||||
|
<string name="health_connect_permissions_rationale_title">Health Connect 權限</string>
|
||||||
|
<string name="health_connect_permissions_rationale_heading">OpenTracks 如何使用 Health Connect</string>
|
||||||
|
<string name="health_connect_permissions_rationale_summary">如果在設定中啟用此功能,OpenTracks 會將運動資料寫入 Health Connect。 OpenTracks 不會從 Health Connect 讀取資料。</string>
|
||||||
|
<string name="marker_detail_title">%2$d 個中的 %1$d:%3$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -81,7 +81,9 @@
|
|||||||
|
|
||||||
<string name="max_recording_distance_key" translatable="false">maxRecordingDistance</string>
|
<string name="max_recording_distance_key" translatable="false">maxRecordingDistance</string>
|
||||||
<string name="max_recording_distance_default" translatable="false">200</string>
|
<string name="max_recording_distance_default" translatable="false">200</string>
|
||||||
|
<string name="max_recording_distance_off" translatable="false">2147483647</string><!-- MAX_INT -->
|
||||||
<string-array name="max_recording_distance_values">
|
<string-array name="max_recording_distance_values">
|
||||||
|
<item>@string/max_recording_distance_off</item>
|
||||||
<item>50</item>
|
<item>50</item>
|
||||||
<item>100</item>
|
<item>100</item>
|
||||||
<item>@string/max_recording_distance_default</item>
|
<item>@string/max_recording_distance_default</item>
|
||||||
|
|||||||
@@ -65,14 +65,6 @@
|
|||||||
android:targetPackage="@string/applicationId" />
|
android:targetPackage="@string/applicationId" />
|
||||||
</Preference>
|
</Preference>
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:title="@string/health_connect_permissions_rationale_title"
|
|
||||||
android:key="healthConnectPermissions">
|
|
||||||
<intent
|
|
||||||
android:targetClass="de.dennisguse.opentracks.settings.HealthConnect$ConfigurePermissions"
|
|
||||||
android:targetPackage="@string/applicationId" />
|
|
||||||
</Preference>
|
|
||||||
|
|
||||||
<Preference android:title="@string/menu_export_all_to_health_connect">
|
<Preference android:title="@string/menu_export_all_to_health_connect">
|
||||||
<intent
|
<intent
|
||||||
android:targetClass="de.dennisguse.opentracks.settings.HealthConnect$ExportAll"
|
android:targetClass="de.dennisguse.opentracks.settings.HealthConnect$ExportAll"
|
||||||
|
|||||||
Reference in New Issue
Block a user