Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23f47f10f8 | |||
| 251fa17487 | |||
| bae3373fe9 | |||
| 0a685a66e3 | |||
| 5ca9ca857a | |||
| d9dfa6d746 | |||
| 3a5220546e | |||
| 047d5c2340 | |||
| 725c21c0a1 | |||
| 7939e39fc1 | |||
| a133bd8f9e | |||
| 1f9790cba8 | |||
| a3ba1e1490 | |||
| accb30152f | |||
| dc7daa6e8c | |||
| 1d8e788d53 | |||
| 568b908b26 | |||
| 3ab63adb34 | |||
| 91692f345e | |||
| dbdb98a682 | |||
| df66cd5753 | |||
| 6e39dd37f8 | |||
| 7212f7be94 | |||
| 77fe494e6c | |||
| 2f491e8c10 | |||
| f4fe7b1ab3 | |||
| 7e444cbf31 | |||
| ed6ab9c53c | |||
| f91ca3154c | |||
| bbfab1b201 | |||
| 1f2e3bcf70 | |||
| d1f3791f97 | |||
| 490b95a001 | |||
| 12e6f03098 | |||
| d86f42e2e9 | |||
| 360d59d3de | |||
| b617420290 | |||
| 53b267150b | |||
| 0dfd55ed52 | |||
| daabd5381d | |||
| a842129e37 | |||
| e8b9c2bbc3 | |||
| e55924dae3 | |||
| e3a8143d58 | |||
| 30310bb874 | |||
| 9f377b172d | |||
| 23175902fb | |||
| 706bd8b01f | |||
| dc4ac07e11 | |||
| 5be4129ba9 | |||
| f75a48ccab | |||
| 2815aa4b62 | |||
| 0383b0e1c7 | |||
| 1601741495 | |||
| be1fae956b | |||
| c7557d80bb | |||
| ac28124664 | |||
| 1deaf7650e |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Free</th>
|
<th>Free</th>
|
||||||
<th>Paid</th>
|
<th>Paid (same as free)</th>
|
||||||
<th>Donations</th>
|
<th>Donations</th>
|
||||||
|
<th>Translations</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -24,40 +25,54 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.transifex.com/opentracks/opentracks/">
|
||||||
|
Transifex
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
* __Tracking:__ track your sport and outdoor activities
|
* __Tracking:__ track your sport and outdoor activities
|
||||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||||
* __Heart rate:__ supports Bluetooth LE sensors
|
* __Bluetooth LE sensors:__
|
||||||
* __Cycling (speed and cadence)__: supports Bluetooth LE sensors
|
* heart rate
|
||||||
|
* cycling: speed and cadence
|
||||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||||
* __No Internet access:__ Internet is not used
|
* __No Internet access:__ Internet is not used
|
||||||
* __Voice announcements__
|
* __Voice announcements__
|
||||||
* __No advertising__
|
* __No advertising__
|
||||||
* __Privacy:__
|
* __Privacy:__
|
||||||
* _does not contain any in-app analytics_
|
* _does not contain any in-app analytics_
|
||||||
* _does not have Google Play Services_
|
* _does not use Google Play Services_
|
||||||
* _has no cloud integration_
|
* _has no cloud integration_
|
||||||
|
|
||||||
__Only required permissions:__
|
__Only required permissions:__
|
||||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||||
|
|
||||||
_Please note:_
|
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
|
||||||
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
|
||||||
|
|
||||||
## Custom Dashboards
|
_Please note:_
|
||||||
|
_OpenTracks_ does not contain a _map_ (as this would required Internet access).
|
||||||
|
|
||||||
|
## Custom Dashboards (incl. Map)
|
||||||
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
||||||
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
||||||
|
The developer documentation is in [README_API.md](README_API.md).
|
||||||
|
|
||||||
|
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)).
|
||||||
|
However, this is rather slow and does not provide updates while recording.
|
||||||
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
<div>
|
<div>
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||||
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_5.png">
|
||||||
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_6.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
@@ -78,7 +93,7 @@ Rework of _OpenTracks_ included:
|
|||||||
* removing calorie estimation and activity estimation,
|
* removing calorie estimation and activity estimation,
|
||||||
* removing support for ANT+ and Classic Bluetooth,
|
* removing support for ANT+ and Classic Bluetooth,
|
||||||
* adding support for Bluetooth LE heart rate sensors,
|
* adding support for Bluetooth LE heart rate sensors,
|
||||||
* removing Protobuf (store sensor data in SQLite columns directly), and
|
* removing Protobuf (store sensosensorr data in SQLite columns directly), and
|
||||||
* removing Android Service API for other apps.
|
* removing Android Service API for other apps.
|
||||||
|
|
||||||
Artwork, logos and user interface remained more or less unchanged.
|
Artwork, logos and user interface remained more or less unchanged.
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ Although this might be (mostly) sufficient, it has some limitations.
|
|||||||
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
|
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
|
||||||
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
|
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
|
||||||
|
|
||||||
_NOTE:_ for showing a track on a map a workaround was implemented in OpenTracks.
|
|
||||||
Data could be exported as KMZ and presented in 3rd-party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)).
|
|
||||||
|
|
||||||
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
|
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Overview of tested Bluetooth LE sensors
|
||||||
|
|
||||||
|
## HRS: Heartrate Service
|
||||||
|
* Decathlon|Geonaute|Kalenji Dual HRM Belt
|
||||||
|
* Polar H7
|
||||||
|
* Polar OH1
|
||||||
|
Has updatable firmware that requires a user account at polar.com
|
||||||
|
* Wahoo Tickr (Model: WFBTHR02)
|
||||||
|
|
||||||
|
## CSCP: Cycling Cadence and Speed Service
|
||||||
|
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
|
||||||
|
However, often only one value is provided.
|
||||||
|
|
||||||
|
### Speed
|
||||||
|
* Wahoo Speed (Model: WFRPMSPD)
|
||||||
|
|
||||||
|
### Cadence
|
||||||
|
* Wahoo Cadence (Model: WFPODCAD2)
|
||||||
|
This sensor reports cadence data as speed.
|
||||||
|
A workaround is in place.
|
||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,25 +50,30 @@ def getVersionName = { ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
applicationLogo: "@mipmap/ic_launcher",
|
||||||
applicationName: "@string/app_name"
|
applicationName: "@string/app_name"
|
||||||
]
|
]
|
||||||
|
|
||||||
versionCode 3369
|
versionCode 3427
|
||||||
//versionCode getVersionCode() //Useful for debugging.
|
//versionCode getVersionCode() //Useful for debugging.
|
||||||
versionName "v3.7.0"
|
versionName "v3.7.5"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -78,7 +83,7 @@ android {
|
|||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix "-debug"
|
versionNameSuffix "-debug"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
applicationLogo: "@drawable/ic_logo_black_24dp",
|
applicationLogo: "@mipmap/ic_launcher_debug",
|
||||||
applicationName: "OpenTracks (Debug)"
|
applicationName: "OpenTracks (Debug)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -100,8 +105,10 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
@@ -113,5 +120,4 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,34 @@ from every activity showing a menu.
|
|||||||
endlegend
|
endlegend
|
||||||
|
|
||||||
[*] --> TrackListActivity
|
[*] --> TrackListActivity
|
||||||
TrackListActivity --> TrackDetailActivity : Start recording
|
TrackListActivity --> TrackRecordingActivity : Start recording
|
||||||
TrackListActivity --> SearchListActivity
|
TrackListActivity --> SearchListActivity
|
||||||
TrackListActivity --> MarkerListActivity : Show all markers
|
TrackListActivity --> MarkerListActivity : Show all markers
|
||||||
TrackListActivity --> TrackDetailActivity : Show track
|
TrackListActivity --> TrackRecordedActivity : Show track
|
||||||
TrackListActivity --> TrackDeleteActivity
|
TrackListActivity --> TrackDeleteActivity
|
||||||
|
|
||||||
SearchListActivity --> MarkerDetailActivity : Search result
|
SearchListActivity --> MarkerDetailActivity : Search result
|
||||||
SearchListActivity --> TrackDetailActivity : Search result
|
SearchListActivity --> TrackRecordedActivity : Search result
|
||||||
|
|
||||||
TrackDetailActivity --> TrackDeleteActivity
|
TrackRecordedActivity --> TrackDeleteActivity
|
||||||
TrackDetailActivity --> TrackEditActivity
|
TrackRecordedActivity --> TrackEditActivity
|
||||||
TrackDetailActivity --> MarkerListActivity
|
TrackRecordedActivity --> MarkerListActivity
|
||||||
TrackDetailActivity --> MarkerEditActivity : Add marker
|
TrackRecordedActivity --> MarkerEditActivity : Add marker
|
||||||
TrackDetailActivity --> CameraIntent : Add marker (with photo)
|
TrackRecordedActivity --> CameraIntent : Add marker (with photo)
|
||||||
TrackDetailActivity --> ShareFileIntent
|
TrackRecordedActivity --> ShareFileIntent
|
||||||
TrackDetailActivity --> DashboardIntent : Database cursor
|
TrackRecordedActivity --> DashboardIntent : Database cursor
|
||||||
TrackDetailActivity --> TrackOnMapIntent : KMZ
|
TrackRecordedActivity --> TrackOnMapIntent : KMZ
|
||||||
|
TrackRecordedActivity --> TrackRecordingActivity
|
||||||
|
TrackRecordedActivity --> SettingsActivity
|
||||||
|
TrackRecordedActivity --> TrackListActivity : Back button
|
||||||
|
|
||||||
|
TrackRecordingActivity --> TrackOnMapIntent
|
||||||
|
TrackRecordingActivity --> MarkerEditActivity : Add marker
|
||||||
|
TrackRecordingActivity --> MarkerListActivity
|
||||||
|
TrackRecordingActivity --> TrackEditActivity
|
||||||
|
TrackRecordingActivity --> SettingsActivity
|
||||||
|
TrackRecordingActivity --> TrackRecordedActivity : Stop recording
|
||||||
|
TrackRecordingActivity --> TrackListActivity : Back button
|
||||||
|
|
||||||
MarkerListActivity --> MarkerDetailActivity
|
MarkerListActivity --> MarkerDetailActivity
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
@startuml
|
||||||
|
SensorManager --> ElevationSumManager
|
||||||
|
LocationManager --> TrackRecordingService
|
||||||
|
ElevationSumManager --> TrackRecordingService
|
||||||
|
|
||||||
|
TrackRecordingService --> TrackRecordingServiceConnection
|
||||||
|
TrackRecordingService --> Database
|
||||||
|
|
||||||
|
Database --> CustomContentProvider
|
||||||
|
CustomContentProvider --> TrackDataHub
|
||||||
|
CustomContentProvider --> ShareContentProvider
|
||||||
|
TrackDataHub --> StatisticsRecordingFragment : TrackPoints, TrackStatistics, Waypoints (push)
|
||||||
|
TrackDataHub --> StatisticsRecordedFragment : TrackPoints, TrackStatistics, Waypoints (push)
|
||||||
|
TrackDataHub --> ChartFragment --> ChartView
|
||||||
|
|
||||||
|
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, elevation gain; pull 1s)
|
||||||
|
TrackRecordingServiceConnection --> TrackController : Start time (pull, onStart)
|
||||||
|
@enduml
|
||||||
@@ -4,6 +4,7 @@ Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
|||||||
Funktionen & Features:
|
Funktionen & Features:
|
||||||
* zeichnet Sport und andere Outdooraktivitäten auf
|
* zeichnet Sport und andere Outdooraktivitäten auf
|
||||||
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
|
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
|
||||||
* zeichnet deine Herzfrequenz auf (unterstützt Bluetooth-LE-Sensoren)
|
* zeichnet deine Herzfrequenz auf (Bluetooth LE)
|
||||||
|
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren (Bluetooth LE)
|
||||||
* exportiert deine Daten als KML oder GPX
|
* exportiert deine Daten als KML oder GPX
|
||||||
* funktioniert ganz ohne Internet
|
* funktioniert ganz ohne Internet
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.7.1: OpenTracks
|
||||||
|
|
||||||
|
Workaround:
|
||||||
|
- Hide start GPS button
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed null pointer
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
v3.7.2: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Re-added start GPS button
|
||||||
|
- Track name is not overwritten at the end of a recording
|
||||||
|
- Markers: removing a photo also deletes the file
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.7.3: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed computation of cadence and speed (via Bluetooth LE sensor)
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Removed unused translations
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v3.7.4: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Sharing/export format configurable
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Wheel diameter not stored correctly (for speed sensor)
|
||||||
|
- Help: links are clickable
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Split TrackDetailActivity
|
||||||
|
- StatsFragement successor uses ConstraintLayout
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.7.5: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Added aggregated statistics summary (from main the screen's menu)
|
||||||
|
- Dashboard API: pass "show on lock screen"- and "keep screen on"-setting
|
||||||
|
- Updated translations
|
||||||
|
- Import: be lenient with ISO8601 date format
|
||||||
|
- Show track on map: file format can be selected and opening dashboard works on Android R (API 30+)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- App icon now correctly implemented
|
||||||
@@ -3,6 +3,8 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
|||||||
Features:
|
Features:
|
||||||
* Sport tracking: track your sport and outdoor activities
|
* Sport tracking: track your sport and outdoor activities
|
||||||
* Photos and markers: mark interesting locations while tracking
|
* Photos and markers: mark interesting locations while tracking
|
||||||
* Heart rate: supports Bluetooth LE sensors
|
* Supports Bluetooth LE sensors:
|
||||||
|
* heart rate
|
||||||
|
* cycling: speed and cadence
|
||||||
* Export data: export tracks either as KML or GPX
|
* Export data: export tracks either as KML or GPX
|
||||||
* No Internet access: Internet is not used
|
* No Internet access: Internet is not used
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#Sun Apr 12 18:41:12 CEST 2020
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -129,6 +130,7 @@ fi
|
|||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -175,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%*
|
|||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||||
|
private static final String TEST_NAME_NEW = "Test Name new!";
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
@@ -625,7 +626,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint() {
|
public void testUpdateWaypoint() {
|
||||||
@@ -641,11 +642,120 @@ public class CustomContentProviderUtilsTest {
|
|||||||
// Update
|
// Update
|
||||||
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
contentProviderUtils.updateWaypoint(waypoint);
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_withPhoto() throws IOException {
|
||||||
|
// tests after update waypoint with photo the photo remains in the storage.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert at first.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
|
// Update
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setName(TEST_NAME_NEW);
|
||||||
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
|
||||||
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertTrue(waypoint.hasPhoto());
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
|
||||||
|
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert at first.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
|
// Update
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setName(TEST_NAME_NEW);
|
||||||
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
|
waypoint.setPhotoUrl(null);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
|
||||||
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertFalse(waypoint.hasPhoto());
|
||||||
|
Assert.assertFalse(dir.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
|
||||||
|
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert two waypoints with photos.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
otherWaypoint.setDescription(TEST_DESC);
|
||||||
|
otherWaypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
long otherWaypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(otherWaypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(2, dir.list().length);
|
||||||
|
|
||||||
|
// Update one waypoint deleting photo.
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setPhotoUrl(null);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertFalse(waypoint.hasPhoto());
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
|
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|||||||
public class ShareContentProviderTest {
|
public class ShareContentProviderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateandParseURI() {
|
public void testCreateandParseURI_invalid() {
|
||||||
|
Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1");
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(new long[]{}, ShareContentProvider.parseURI(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateandParseURI_valid() {
|
||||||
long[] trackIds = {1, 3, 5};
|
long[] trackIds = {1, 3, 5};
|
||||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import de.dennisguse.opentracks.util.UintUtils;
|
|||||||
public class SensorDataCyclingTest {
|
public class SensorDataCyclingTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compute_cadence() {
|
public void compute_cadence_1() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
||||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
||||||
@@ -24,6 +24,19 @@ public class SensorDataCyclingTest {
|
|||||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_cadence_2() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 6184);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 8016);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(33.53, current.getCadence_rpm(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compute_cadence_sameCount() {
|
public void compute_cadence_sameCount() {
|
||||||
// given
|
// given
|
||||||
@@ -77,6 +90,19 @@ public class SensorDataCyclingTest {
|
|||||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_speed() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
|
||||||
|
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous, 2150);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(1.20, current.getSpeed_mps(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compute_speed_rollOverCount() {
|
public void compute_speed_rollOverCount() {
|
||||||
// given
|
// given
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class ExportImportTest {
|
|||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -169,7 +169,7 @@ public class ExportImportTest {
|
|||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class PreferencesUtilsTest {
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ExportTrackFileFormat_ok() {
|
||||||
|
// given
|
||||||
|
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||||
|
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ExportTrackFileFormat_invalid() {
|
||||||
|
// given
|
||||||
|
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||||
|
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ExportTrackFileFormat_noValue() {
|
||||||
|
// given
|
||||||
|
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||||
|
editor.clear();
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,6 +128,7 @@ public class StringUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTime_timezone() {
|
public void testGetTime_timezone() {
|
||||||
|
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||||
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
|
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
|
||||||
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
|
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||||
assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
|
assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class AggregatedStatisticsTest {
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TrackStatistics object.
|
||||||
|
*
|
||||||
|
* @param totalDistance distance in meters.
|
||||||
|
* @param totalTime total time in milliseconds.
|
||||||
|
* @return TrackStatistics object.
|
||||||
|
*/
|
||||||
|
private static Track createTrack(Context context, long totalDistance, long totalTime, String category) {
|
||||||
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
statistics.setStartTime_ms(1000L); // Resulting start time
|
||||||
|
statistics.setStopTime_ms(1000L + totalTime);
|
||||||
|
statistics.setTotalTime(totalTime);
|
||||||
|
statistics.setMovingTime(totalTime);
|
||||||
|
statistics.setTotalDistance(totalDistance);
|
||||||
|
statistics.setTotalElevationGain(50.0);
|
||||||
|
statistics.setMaxSpeed(50.0); // Resulting max speed
|
||||||
|
statistics.setMaxElevation(1250.0);
|
||||||
|
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||||
|
|
||||||
|
Track track = new Track();
|
||||||
|
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||||
|
track.setCategory(category);
|
||||||
|
track.setTrackStatistics(statistics);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
long totalTime = 2400000;
|
||||||
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
|
Track track = createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(track);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||||
|
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_mountainBiking() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
long totalTime = 2400000;
|
||||||
|
String mountainBiking = context.getString(R.string.activity_type_mountain_biking);
|
||||||
|
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(track);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(mountainBiking) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_trailRunning() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
long totalTime = 2400000;
|
||||||
|
String trailRunning = context.getString(R.string.activity_type_trail_running);
|
||||||
|
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(track);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(trailRunning) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_twoBikingTracks() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
long totalTime = 2400000;
|
||||||
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
|
Track[] tracks = new Track[2];
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
tracks[i] = createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||||
|
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||||
|
Assert.assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
|
||||||
|
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime * 2L, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_threeDifferentTracks() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
|
||||||
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
|
String running = context.getString(R.string.activity_type_running);
|
||||||
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
|
long totalTime = 2400000;
|
||||||
|
Track[] tracks = new Track[]{
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, running),
|
||||||
|
createTrack(context, totalDistance, totalTime, walking)
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||||
|
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||||
|
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(3, aggregatedStatistics.getCount());
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(running) != null);
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
|
||||||
|
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_severalTracksWithSeveralActivities() {
|
||||||
|
// given
|
||||||
|
// 10km in 40 minutes.
|
||||||
|
long totalDistance = 10000;
|
||||||
|
long totalTime = 2400000;
|
||||||
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
|
String running = context.getString(R.string.activity_type_running);
|
||||||
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
|
String driving = context.getString(R.string.activity_type_driving);
|
||||||
|
Track[] tracks = new Track[]{
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, running),
|
||||||
|
createTrack(context, totalDistance, totalTime, walking),
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, running),
|
||||||
|
createTrack(context, totalDistance, totalTime, walking),
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
|
createTrack(context, totalDistance, totalTime, driving),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
for (Track track : tracks) {
|
||||||
|
aggregatedStatistics.aggregate(track);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 4 sports.
|
||||||
|
Assert.assertEquals(4, aggregatedStatistics.getCount());
|
||||||
|
|
||||||
|
// There is a map for every sport.
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(running) != null);
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
|
||||||
|
Assert.assertTrue(aggregatedStatistics.get(driving) != null);
|
||||||
|
|
||||||
|
// Number of tracks by sport.
|
||||||
|
Assert.assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
|
||||||
|
Assert.assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
|
||||||
|
Assert.assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
|
||||||
|
Assert.assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
|
||||||
|
|
||||||
|
// Biking.
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime * 5, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running.
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walking.
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driving.
|
||||||
|
{
|
||||||
|
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
|
||||||
|
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
|
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,14 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
<!-- Permission to initialize services -->
|
|
||||||
|
<supports-screens
|
||||||
|
android:anyDensity="true"
|
||||||
|
android:largeScreens="true"
|
||||||
|
android:normalScreens="true"
|
||||||
|
android:smallScreens="true"
|
||||||
|
android:xlargeScreens="true" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@@ -79,7 +86,11 @@ limitations under the License.
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".TrackDetailActivity"
|
android:name=".TrackRecordedActivity"
|
||||||
|
android:screenOrientation="userPortrait" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".TrackRecordingActivity"
|
||||||
android:screenOrientation="userPortrait" />
|
android:screenOrientation="userPortrait" />
|
||||||
|
|
||||||
<activity android:name=".TrackEditActivity" />
|
<activity android:name=".TrackEditActivity" />
|
||||||
@@ -94,12 +105,30 @@ limitations under the License.
|
|||||||
android:value=".SearchListActivity" />
|
android:value=".SearchListActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".AggregatedStatisticsActivity"
|
||||||
|
android:screenOrientation="userPortrait"
|
||||||
|
android:label="@string/menu_aggregated_statistics"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.exporter.ExportActivity"
|
android:name=".io.file.exporter.ExportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent" />
|
android:theme="@style/ThemeCustomTransparent" />
|
||||||
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
<activity android:name=".settings.SettingsActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".ShowOnMapProxyActivity$KMZ">
|
||||||
|
<intent-filter android:label="@string/open_track_as_KMZ">
|
||||||
|
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".ShowOnMapProxyActivity$GPX">
|
||||||
|
<intent-filter android:label="@string/open_track_as_GPX">
|
||||||
|
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".content.provider.ShareContentProvider"
|
android:name=".content.provider.ShareContentProvider"
|
||||||
android:authorities="${applicationId}.content"
|
android:authorities="${applicationId}.content"
|
||||||
@@ -124,12 +153,7 @@ limitations under the License.
|
|||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/recording_service" />
|
android:label="@string/recording_service" />
|
||||||
</application>
|
</application>
|
||||||
<supports-screens
|
|
||||||
android:anyDensity="true"
|
|
||||||
android:largeScreens="true"
|
|
||||||
android:normalScreens="true"
|
|
||||||
android:smallScreens="true"
|
|
||||||
android:xlargeScreens="true" />
|
|
||||||
<!-- Permissions to record locations -->
|
<!-- Permissions to record locations -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.location.gps"
|
android:name="android.hardware.location.gps"
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.ViewUtils;
|
||||||
|
|
||||||
public class AboutActivity extends AbstractActivity {
|
public class AboutActivity extends AbstractActivity {
|
||||||
|
|
||||||
@@ -27,6 +29,8 @@ public class AboutActivity extends AbstractActivity {
|
|||||||
|
|
||||||
TextView textURL = findViewById(R.id.about_app_url);
|
TextView textURL = findViewById(R.id.about_app_url);
|
||||||
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
||||||
|
|
||||||
|
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getLayoutResId() {
|
protected int getLayoutResId() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract class for the following common tasks across
|
* An abstract class for the following common tasks across
|
||||||
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and
|
||||||
* {@link SearchListActivity}:
|
* {@link SearchListActivity}:
|
||||||
* <p>
|
* <p>
|
||||||
* - share track <br>
|
* - share track <br>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
||||||
|
|
||||||
|
public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
private ListView listView;
|
||||||
|
private AggregatedStatisticsAdapter adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
listView = findViewById(R.id.aggregated_stats_list);
|
||||||
|
|
||||||
|
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||||
|
viewModel.getAggregatedStats().observe(this, new Observer<AggregatedStatistics>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(AggregatedStatistics aggregatedStatistics) {
|
||||||
|
if (aggregatedStatistics != null) {
|
||||||
|
adapter = new AggregatedStatisticsAdapter(getApplicationContext(), aggregatedStatistics);
|
||||||
|
listView.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutResId() {
|
||||||
|
return R.layout.aggregated_stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.ViewUtils;
|
||||||
|
|
||||||
public class HelpActivity extends AbstractActivity {
|
public class HelpActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getLayoutResId() {
|
protected int getLayoutResId() {
|
||||||
return R.layout.help;
|
return R.layout.help;
|
||||||
|
|||||||
@@ -109,12 +109,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleteMarkerDone() {
|
public void onDeleteMarkerDone() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(this::finish);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -315,6 +315,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypoint.setDescription(waypointDescription.getText().toString());
|
waypoint.setDescription(waypointDescription.getText().toString());
|
||||||
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||||
|
|
||||||
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
new ContentProviderUtils(this).updateWaypoint(this, waypoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,12 +82,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> MarkerListActivity.this.invalidateOptionsMenu());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
MarkerListActivity.this.invalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -153,12 +153,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> arrayAdapter.notifyDataSetChanged());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
arrayAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -214,11 +209,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
|
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
|
||||||
if (markerId != null) {
|
if (markerId != null) {
|
||||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||||
} else {
|
} else {
|
||||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
}
|
}
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
@@ -346,12 +341,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
||||||
|
|
||||||
// Use the UI thread to display the results
|
// Use the UI thread to display the results
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
arrayAdapter.clear();
|
||||||
public void run() {
|
arrayAdapter.addAll(displayResults);
|
||||||
arrayAdapter.clear();
|
|
||||||
arrayAdapter.addAll(displayResults);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,12 +440,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleteMarkerDone() {
|
public void onDeleteMarkerDone() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> handleIntent(getIntent()));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
handleIntent(getIntent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -463,11 +450,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDeleted() {
|
protected void onDeleted() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> handleIntent(getIntent()));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
handleIntent(getIntent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to convert IntentDashboardUtils.startDashboard-requests into {@link TrackFileFormat}.
|
||||||
|
*/
|
||||||
|
public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private TrackFileFormat trackFileFormat;
|
||||||
|
|
||||||
|
protected ShowOnMapProxyActivity(TrackFileFormat trackFileFormat) {
|
||||||
|
this.trackFileFormat = trackFileFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
long[] trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
|
||||||
|
|
||||||
|
showTrackTrackfileFormat(this, trackFileFormat, trackIds);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send intent to show tracks on a map (needs an another app) as KMZ.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @param trackIds the track ids
|
||||||
|
*/
|
||||||
|
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, long[] trackIds) {
|
||||||
|
if (trackIds.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
||||||
|
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat);
|
||||||
|
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
||||||
|
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
|
context.startActivity(Intent.createChooser(intent, context.getString(R.string.open_track_as_trackfileformat, trackFileFormat.getExtension())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KMZ extends ShowOnMapProxyActivity {
|
||||||
|
public KMZ() {
|
||||||
|
super(TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GPX extends ShowOnMapProxyActivity {
|
||||||
|
public GPX() {
|
||||||
|
super(TrackFileFormat.GPX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for communication between activities that use {@link de.dennisguse.opentracks.content.TrackDataHub} and their fragments that need thi data hub.
|
||||||
|
*/
|
||||||
|
public interface TrackActivityDataHubInterface {
|
||||||
|
TrackDataHub getTrackDataHub();
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
@@ -143,14 +144,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
TrackListActivity.this.invalidateOptionsMenu();
|
||||||
public void run() {
|
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||||
TrackListActivity.this.invalidateOptionsMenu();
|
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
trackController.update(isRecording, recordingTrackPaused);
|
||||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
|
||||||
trackController.update(isRecording, recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,15 +172,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
/*
|
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||||
* After binding changes (e.g., becomes available), update the total time in trackController.
|
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
|
||||||
*/
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!startGps) {
|
if (!startGps) {
|
||||||
return;
|
return;
|
||||||
@@ -205,7 +196,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class);
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
} else if (recordingTrackPaused) {
|
} else if (recordingTrackPaused) {
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
@@ -250,9 +241,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long trackId) {
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
Intent newIntent;
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
|
if (trackId == recordingTrackId) {
|
||||||
|
// Is recording -> open record activity.
|
||||||
|
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||||
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
} else {
|
||||||
|
// Not recording -> open detail activity.
|
||||||
|
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||||
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
}
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -351,7 +350,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||||
|
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -397,6 +395,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.track_list_aggregated_stats:
|
||||||
|
intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
case R.id.track_list_markers:
|
case R.id.track_list_markers:
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -480,7 +482,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
Intent intent;
|
Intent intent;
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
case R.id.list_context_menu_show_on_map:
|
case R.id.list_context_menu_show_on_map:
|
||||||
IntentUtils.showTrackOnMap(this, trackIds);
|
IntentDashboardUtils.startDashboard(this, trackIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_share:
|
case R.id.list_context_menu_share:
|
||||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An activity to show the track detail, record a new track or resumes an existing one.
|
||||||
|
*
|
||||||
|
* @author Leif Hendrik Wilden
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class TrackRecordedActivity extends AbstractListActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||||
|
|
||||||
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
|
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
|
|
||||||
|
// The following are set in onCreate.
|
||||||
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
private TrackDataHub trackDataHub;
|
||||||
|
private ViewPager pager;
|
||||||
|
|
||||||
|
// From intent.
|
||||||
|
private long trackId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
handleIntent(getIntent());
|
||||||
|
|
||||||
|
trackDataHub = new TrackDataHub(this);
|
||||||
|
|
||||||
|
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return new StatisticsRecordedFragment();
|
||||||
|
case 1:
|
||||||
|
return ChartFragment.newInstance(false);
|
||||||
|
case 2:
|
||||||
|
return ChartFragment.newInstance(true);
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return getString(R.string.track_detail_stats_tab);
|
||||||
|
case 1:
|
||||||
|
return getString(R.string.settings_chart_by_time);
|
||||||
|
case 2:
|
||||||
|
return getString(R.string.settings_chart_by_distance);
|
||||||
|
}
|
||||||
|
return "Unknown Tab";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pager = findViewById(R.id.track_detail_activity_view_pager);
|
||||||
|
pager.setAdapter(adapter);
|
||||||
|
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
|
||||||
|
tabs.setupWithViewPager(pager);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
pager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
trackDataHub.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
this.invalidateOptionsMenu();
|
||||||
|
|
||||||
|
if (trackId != -1L) {
|
||||||
|
trackDataHub.loadTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
trackDataHub.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutResId() {
|
||||||
|
return R.layout.track_detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
setIntent(intent);
|
||||||
|
handleIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
setTitle(track != null ? track.getName() : "");
|
||||||
|
return super.onPrepareOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
Intent intent;
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.track_detail_share:
|
||||||
|
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||||
|
intent = Intent.createChooser(intent, null);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_menu_show_on_map:
|
||||||
|
IntentDashboardUtils.startDashboard(this, trackId);
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_markers:
|
||||||
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
|
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_edit:
|
||||||
|
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_delete:
|
||||||
|
deleteTracks(new long[]{trackId});
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_resume_track:
|
||||||
|
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||||
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(newIntent);
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
case R.id.track_detail_settings:
|
||||||
|
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||||
|
// Not needed.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDeleted() {
|
||||||
|
runOnUiThread(this::finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link TrackDataHub}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TrackDataHub getTrackDataHub() {
|
||||||
|
return trackDataHub;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIntent(Intent intent) {
|
||||||
|
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
|
|
||||||
|
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||||
|
if (markerId != -1L) {
|
||||||
|
// Use the trackId from the marker
|
||||||
|
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||||
|
if (waypoint == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trackId = waypoint.getTrackId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackId == -1L) {
|
||||||
|
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void chooseActivityType(String category) {
|
||||||
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChooseActivityTypeDone(String iconValue) {
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
|
||||||
|
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -38,15 +22,14 @@ import com.google.android.material.tabs.TabLayout;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
@@ -58,12 +41,11 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class TrackDetailActivity extends AbstractListActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
|
||||||
|
|
||||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
private static final String TAG = TrackRecordingActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
|
|
||||||
@@ -86,12 +68,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (is available), update the total time in trackController.
|
// After binding changes (is available), update the total time in trackController.
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
trackController.update(isRecording(), recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (recordingTrackId == -1L) {
|
if (recordingTrackId == -1L) {
|
||||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
@@ -115,7 +92,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A recording track is on.
|
// A recording track is on.
|
||||||
Toast.makeText(TrackDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(TrackRecordingActivity.this, msg, Toast.LENGTH_SHORT).show();
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackController.update(true, false);
|
trackController.update(true, false);
|
||||||
trackController.onResume(true, recordingTrackPaused);
|
trackController.onResume(true, recordingTrackPaused);
|
||||||
@@ -126,43 +103,37 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingActivity.this);
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
TrackRecordingActivity.this.invalidateOptionsMenu();
|
||||||
public void run() {
|
trackController.update(true, recordingTrackPaused);
|
||||||
TrackDetailActivity.this.invalidateOptionsMenu();
|
|
||||||
boolean isRecording = isRecording();
|
|
||||||
trackController.update(isRecording, recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private MenuItem insertMarkerMenuItem;
|
private MenuItem insertMarkerMenuItem;
|
||||||
private MenuItem markerListMenuItem;
|
private MenuItem markerListMenuItem;
|
||||||
private MenuItem shareMenuItem;
|
|
||||||
private MenuItem resumeMenuItem;
|
|
||||||
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -184,8 +155,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private final OnClickListener stopListener = new OnClickListener() {
|
private final OnClickListener stopListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
trackRecordingServiceConnection.stopRecording(TrackDetailActivity.this, true);
|
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true);
|
||||||
|
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||||
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(newIntent);
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
updateMenuItems(true);
|
updateMenuItems(true);
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,12 +188,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return new StatisticsRecordingFragment();
|
||||||
case 1:
|
case 1:
|
||||||
return ChartFragment.newInstance(false);
|
return ChartFragment.newInstance(false);
|
||||||
case 2:
|
case 2:
|
||||||
return ChartFragment.newInstance(true);
|
return ChartFragment.newInstance(true);
|
||||||
default: //0
|
default:
|
||||||
return new StatsFragment();
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +231,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setLockscreenPolicy() {
|
private void setLockscreenPolicy() {
|
||||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this)
|
||||||
&& PreferencesUtils.isRecording(this);
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
@@ -266,7 +244,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setScreenOnPolicy() {
|
private void setScreenOnPolicy() {
|
||||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this)
|
||||||
&& PreferencesUtils.isRecording(this);
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
if (keepScreenOn) {
|
if (keepScreenOn) {
|
||||||
@@ -296,10 +274,10 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
if (trackId != -1L) {
|
if (trackId != -1L) {
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
trackController.onResume(true, recordingTrackPaused);
|
||||||
} else {
|
|
||||||
startRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -324,7 +302,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getLayoutResId() {
|
protected int getLayoutResId() {
|
||||||
return R.layout.track_detail;
|
return R.layout.track_record;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -336,12 +314,10 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
getMenuInflater().inflate(R.menu.track_record, menu);
|
||||||
|
|
||||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
|
||||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||||
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -362,13 +338,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_share:
|
|
||||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
|
||||||
intent = Intent.createChooser(intent, null);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_menu_show_on_map:
|
case R.id.track_detail_menu_show_on_map:
|
||||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
IntentDashboardUtils.startDashboard(this, trackId);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_markers:
|
case R.id.track_detail_markers:
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
@@ -380,12 +351,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_delete:
|
|
||||||
deleteTracks(new long[]{trackId});
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_resume_track:
|
|
||||||
startRecording();
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_settings:
|
case R.id.track_detail_settings:
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -395,57 +360,25 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
|
||||||
return trackRecordingServiceConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDeleted() {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link TrackDataHub}.
|
* Gets the {@link TrackDataHub}.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public TrackDataHub getTrackDataHub() {
|
public TrackDataHub getTrackDataHub() {
|
||||||
return trackDataHub;
|
return trackDataHub;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
|
||||||
if (markerId != -1L) {
|
|
||||||
// Use the trackId from the marker
|
|
||||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
|
||||||
if (waypoint == null) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackId = waypoint.getTrackId();
|
|
||||||
if (trackId == -1L) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
// Use the last track if markerId is not set
|
track = contentProviderUtils.getLastTrack();
|
||||||
if (markerId == -1L) {
|
if (track != null) {
|
||||||
track = contentProviderUtils.getLastTrack();
|
trackId = track.getId();
|
||||||
if (track != null) {
|
return;
|
||||||
trackId = track.getId();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -455,20 +388,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
* Updates the menu items.
|
* Updates the menu items.
|
||||||
*/
|
*/
|
||||||
private void updateMenuItems(boolean isPaused) {
|
private void updateMenuItems(boolean isPaused) {
|
||||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
insertMarkerMenuItem.setVisible(!isPaused);
|
||||||
shareMenuItem.setVisible(!isRecording());
|
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
||||||
String title;
|
|
||||||
if (isRecording()) {
|
|
||||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
|
||||||
resumeMenuItem.setVisible(false);
|
|
||||||
} else {
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
|
||||||
title = track != null ? track.getName() : "";
|
|
||||||
// Only visible if there aren't other tracks that is recording.
|
|
||||||
resumeMenuItem.setVisible(recordingTrackId == -1L);
|
|
||||||
}
|
|
||||||
setTitle(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void chooseActivityType(String category) {
|
public void chooseActivityType(String category) {
|
||||||
@@ -482,10 +404,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRecording() {
|
|
||||||
return trackId == recordingTrackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startRecording() {
|
private void startRecording() {
|
||||||
trackRecordingServiceConnection.startAndBind(this);
|
trackRecordingServiceConnection.startAndBind(this);
|
||||||
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||||
|
|
||||||
|
public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
private AggregatedStatistics aggregatedStatistics;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
|
||||||
|
this.context = context;
|
||||||
|
this.aggregatedStatistics = aggregatedStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
if (aggregatedStatistics != null) {
|
||||||
|
return aggregatedStatistics.getCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AggregatedStatistics.AggregatedStatistic getItem(int position) {
|
||||||
|
return aggregatedStatistics.getItem(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
// Not important because data are not database register but cooked data.
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
AggregatedStatistics.AggregatedStatistic aggregatedStats = getItem(position);
|
||||||
|
ViewHolder viewHolder;
|
||||||
|
|
||||||
|
if (isSpeedSport(position)) {
|
||||||
|
viewHolder = new ViewSpeedHolder();
|
||||||
|
} else {
|
||||||
|
viewHolder = new ViewPaceHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
||||||
|
viewHolder.setValues(convertView, getIcon(position), getName(position), aggregatedStats);
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSpeedSport(int position) {
|
||||||
|
String category = aggregatedStatistics.getSportName(position);
|
||||||
|
return TrackIconUtils.isSpeedIcon(context, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getIcon(int position) {
|
||||||
|
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistics.getSportName(position));
|
||||||
|
return TrackIconUtils.getIconDrawable(iconValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName(int position) {
|
||||||
|
return aggregatedStatistics.getSportName(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewHolder {
|
||||||
|
protected ImageView sportIcon;
|
||||||
|
protected TextView typeLabel;
|
||||||
|
protected TextView numTracks;
|
||||||
|
protected TextView distance;
|
||||||
|
protected TextView distanceUnit;
|
||||||
|
protected TextView time;
|
||||||
|
protected boolean metricsUnits;
|
||||||
|
|
||||||
|
public ViewHolder() {
|
||||||
|
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||||
|
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
||||||
|
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
||||||
|
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
||||||
|
distance = view.findViewById(R.id.aggregated_stats_distance);
|
||||||
|
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
|
||||||
|
time = view.findViewById(R.id.aggregated_stats_time);
|
||||||
|
|
||||||
|
sportIcon.setImageResource(iconDrawable);
|
||||||
|
typeLabel.setText(name);
|
||||||
|
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStats.getCountTracks())));
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStats.getTrackStatistics().getTotalDistance(), metricsUnits);
|
||||||
|
distance.setText(parts.first);
|
||||||
|
distanceUnit.setText(parts.second);
|
||||||
|
|
||||||
|
time.setText(StringUtils.formatElapsedTime(aggregatedStats.getTrackStatistics().getMovingTime()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewSpeedHolder extends ViewHolder {
|
||||||
|
private TextView avgSpeed;
|
||||||
|
private TextView avgSpeedUnit;
|
||||||
|
private TextView avgSpeedLabel;
|
||||||
|
private TextView maxSpeed;
|
||||||
|
private TextView maxSpeedUnit;
|
||||||
|
private TextView maxSpeedLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||||
|
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||||
|
|
||||||
|
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||||
|
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||||
|
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||||
|
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||||
|
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||||
|
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, true);
|
||||||
|
avgSpeed.setText(parts.first);
|
||||||
|
avgSpeedUnit.setText(parts.second);
|
||||||
|
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, true);
|
||||||
|
maxSpeed.setText(parts.first);
|
||||||
|
maxSpeedUnit.setText(parts.second);
|
||||||
|
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewPaceHolder extends ViewHolder {
|
||||||
|
private TextView avgPace;
|
||||||
|
private TextView avgPaceUnit;
|
||||||
|
private TextView avgPaceLabel;
|
||||||
|
private TextView maxPace;
|
||||||
|
private TextView maxPaceUnit;
|
||||||
|
private TextView maxPaceLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||||
|
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||||
|
|
||||||
|
avgPace = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||||
|
avgPaceUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||||
|
avgPaceLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||||
|
|
||||||
|
maxPace = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||||
|
maxPaceUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||||
|
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, false);
|
||||||
|
avgPace.setText(parts.first);
|
||||||
|
avgPaceUnit.setText(parts.second);
|
||||||
|
avgPaceLabel.setText(R.string.stats_average_moving_pace);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, false);
|
||||||
|
maxPace.setText(parts.first);
|
||||||
|
maxPaceUnit.setText(parts.second);
|
||||||
|
maxPaceLabel.setText(R.string.stats_fastest_pace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,12 +144,9 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
|
|
||||||
PreferencesUtils.register(context, this);
|
PreferencesUtils.register(context, this);
|
||||||
onSharedPreferenceChanged(null, null);
|
onSharedPreferenceChanged(null, null);
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (started) {
|
||||||
public void run() {
|
loadDataForAll();
|
||||||
if (started) {
|
|
||||||
loadDataForAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -178,16 +175,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadTrack(final long trackId) {
|
public void loadTrack(final long trackId) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (trackId == selectedTrackId) {
|
||||||
public void run() {
|
Log.i(TAG, "Not reloading track " + trackId);
|
||||||
if (trackId == selectedTrackId) {
|
return;
|
||||||
Log.i(TAG, "Not reloading track " + trackId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedTrackId = trackId;
|
|
||||||
loadDataForAll();
|
|
||||||
}
|
}
|
||||||
|
selectedTrackId = trackId;
|
||||||
|
loadDataForAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,13 +191,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
* @param trackDataListener the track data listener
|
* @param trackDataListener the track data listener
|
||||||
*/
|
*/
|
||||||
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||||
public void run() {
|
if (started) {
|
||||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
loadDataForListener(trackDataListener);
|
||||||
if (started) {
|
|
||||||
loadDataForListener(trackDataListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,12 +205,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
* @param trackDataListener the track data listener
|
* @param trackDataListener the track data listener
|
||||||
*/
|
*/
|
||||||
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
trackDataManager.unregisterTrackDataListener(trackDataListener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,15 +224,12 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||||
public void run() {
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
}
|
||||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||||
}
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -289,7 +272,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
if (isOnlyListener) {
|
if (isOnlyListener) {
|
||||||
resetSamplingState();
|
resetSamplingState();
|
||||||
}
|
}
|
||||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.emptySet();
|
||||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -382,17 +382,7 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
public void deleteWaypoint(Context context, long waypointId) {
|
public void deleteWaypoint(Context context, long waypointId) {
|
||||||
final Waypoint waypoint = getWaypoint(waypointId);
|
final Waypoint waypoint = getWaypoint(waypointId);
|
||||||
if (waypoint != null && waypoint.hasPhoto()) {
|
deleteWaypointPhoto(context, waypoint);
|
||||||
Uri uri = waypoint.getPhotoURI();
|
|
||||||
File file = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), uri);
|
|
||||||
if (file.exists()) {
|
|
||||||
File parent = file.getParentFile();
|
|
||||||
file.delete();
|
|
||||||
if (parent.listFiles().length == 0) {
|
|
||||||
parent.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,14 +504,38 @@ public class ContentProviderUtils {
|
|||||||
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
|
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete waypoint's photo if any.
|
||||||
|
*
|
||||||
|
* @param context the context object.
|
||||||
|
* @param waypoint the waypoint object.
|
||||||
|
*/
|
||||||
|
private void deleteWaypointPhoto(Context context, Waypoint waypoint) {
|
||||||
|
if (waypoint != null && waypoint.hasPhoto()) {
|
||||||
|
Uri uri = waypoint.getPhotoURI();
|
||||||
|
File file = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), uri);
|
||||||
|
if (file.exists()) {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
file.delete();
|
||||||
|
if (parent.listFiles().length == 0) {
|
||||||
|
parent.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a waypoint.
|
* Updates a waypoint.
|
||||||
* Returns true if successful.
|
* Returns true if successful.
|
||||||
*
|
*
|
||||||
* @param waypoint the waypoint
|
* @param updatedWaypoint the waypoint with updated data.
|
||||||
*/
|
*/
|
||||||
public boolean updateWaypoint(Waypoint waypoint) {
|
public boolean updateWaypoint(Context context, Waypoint updatedWaypoint) {
|
||||||
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())});
|
Waypoint savedWaypoint = getWaypoint(updatedWaypoint.getId());
|
||||||
|
if (!updatedWaypoint.hasPhoto()) {
|
||||||
|
deleteWaypointPhoto(context, savedWaypoint);
|
||||||
|
}
|
||||||
|
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())});
|
||||||
return rows == 1;
|
return rows == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,5 +910,4 @@ public class ContentProviderUtils {
|
|||||||
public static String[] parseTrackIdsFromUri(Uri url) {
|
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
|
|
||||||
static long[] parseURI(Uri uri) {
|
static long[] parseURI(Uri uri) {
|
||||||
List<String> uriPaths = uri.getPathSegments();
|
List<String> uriPaths = uri.getPathSegments();
|
||||||
if (uriPaths == null || uriPaths.size() < 2) {
|
if (uriPaths == null || uriPaths.size() < 3) {
|
||||||
Log.d(TAG, "URI does not contain any trackIds.");
|
Log.d(TAG, "URI does not contain any trackIds.");
|
||||||
return new long[]{};
|
return new long[]{};
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks, null);
|
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks);
|
||||||
|
|
||||||
PipeDataWriter pipeDataWriter = new PipeDataWriter<String>() {
|
PipeDataWriter pipeDataWriter = new PipeDataWriter<String>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ public final class SensorDataCycling {
|
|||||||
|
|
||||||
public void compute(Cadence previous) {
|
public void compute(Cadence previous) {
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
long timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024 * UnitConversions.S_TO_MS;
|
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||||
if (timeDiff_ms <= 0) {
|
if (timeDiff_ms <= 0) {
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
cadence_rpm = INVALID_VALUE_FLOAT;
|
cadence_rpm = INVALID_VALUE_FLOAT;
|
||||||
} else {
|
} else {
|
||||||
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
||||||
float cadence_ms = crankDiff / (float) timeDiff_ms;
|
float cadence_ms = crankDiff / timeDiff_ms;
|
||||||
cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ public final class SensorDataCycling {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "cadence=" + getCadence_rpm() + "_" + getCrankRevolutionsTime();
|
return "cadence=" + getCadence_rpm() + " time=" + getCrankRevolutionsTime() + " count=" + getCrankRevolutionsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,7 +125,7 @@ public final class SensorDataCycling {
|
|||||||
|
|
||||||
public void compute(Speed previous, int wheel_circumference_mm) {
|
public void compute(Speed previous, int wheel_circumference_mm) {
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
long timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024 * UnitConversions.S_TO_MS;
|
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||||
if (timeDiff_ms <= 0) {
|
if (timeDiff_ms <= 0) {
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
speed_mps = INVALID_VALUE_FLOAT;
|
speed_mps = INVALID_VALUE_FLOAT;
|
||||||
@@ -140,7 +140,7 @@ public final class SensorDataCycling {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "speed=" + getSpeed_mps() + "_" + getWheelRevolutionsTime();
|
return "speed=" + getSpeed_mps() + " time=" + getWheelRevolutionsTime() + " count=" + getWheelRevolutionsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class SensorDataSet {
|
public final class SensorDataSet {
|
||||||
|
|
||||||
private SensorDataHeartRate heartRate;
|
private SensorDataHeartRate heartRate;
|
||||||
@@ -31,10 +28,6 @@ public final class SensorDataSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void set(SensorData data) {
|
public void set(SensorData data) {
|
||||||
if (data == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data instanceof SensorDataHeartRate) {
|
if (data instanceof SensorDataHeartRate) {
|
||||||
this.heartRate = (SensorDataHeartRate) data;
|
this.heartRate = (SensorDataHeartRate) data;
|
||||||
return;
|
return;
|
||||||
@@ -48,10 +41,6 @@ public final class SensorDataSet {
|
|||||||
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data instanceof SensorDataCycling.CadenceAndSpeed) {
|
|
||||||
set(((SensorDataCycling.CadenceAndSpeed) data).getCadence());
|
|
||||||
set(((SensorDataCycling.CadenceAndSpeed) data).getSpeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
||||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||||
import de.dennisguse.opentracks.chart.ChartView;
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
@@ -90,12 +90,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
if (metricUnits != chartView.getMetricUnits()) {
|
if (metricUnits != chartView.getMetricUnits()) {
|
||||||
chartView.setMetricUnits(metricUnits);
|
chartView.setMetricUnits(metricUnits);
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.requestLayout();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,12 +103,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
chartView.setReportSpeed(reportSpeed);
|
chartView.setReportSpeed(reportSpeed);
|
||||||
chartView.applyReportSpeed();
|
chartView.applyReportSpeed();
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.requestLayout();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,12 +208,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
||||||
pendingPoints.clear();
|
pendingPoints.clear();
|
||||||
chartView.reset();
|
chartView.reset();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.resetScroll();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.resetScroll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -286,7 +277,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||||
*/
|
*/
|
||||||
private synchronized void resumeTrackDataHub() {
|
private synchronized void resumeTrackDataHub() {
|
||||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
|
||||||
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,15 +80,12 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
|||||||
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
new Thread(new Runnable() {
|
new Thread(() -> {
|
||||||
@Override
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||||
public void run() {
|
for (long markerId : markerIds) {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||||
for (long markerId : markerIds) {
|
|
||||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
|
||||||
}
|
|
||||||
caller.onDeleteMarkerDone();
|
|
||||||
}
|
}
|
||||||
|
caller.onDeleteMarkerDone();
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.fragments;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A DialogFragment to select a file type, gpx, kml, etc.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public class FileTypeDialogFragment extends DialogFragment {
|
|
||||||
|
|
||||||
private static final String FILE_TYPE_DIALOG_TAG = "fileType";
|
|
||||||
private static final String KEY_TITLE_ID = "titleId";
|
|
||||||
private static final String KEY_OPTION_ID = "optionId";
|
|
||||||
|
|
||||||
private FileTypeCaller caller;
|
|
||||||
|
|
||||||
public static void showDialog(FragmentManager fragmentManager, int titleId, int optionId) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putInt(KEY_TITLE_ID, titleId);
|
|
||||||
bundle.putInt(KEY_OPTION_ID, optionId);
|
|
||||||
|
|
||||||
FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment();
|
|
||||||
fileTypeDialogFragment.setArguments(bundle);
|
|
||||||
fileTypeDialogFragment.show(fragmentManager, FILE_TYPE_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(@NonNull Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
try {
|
|
||||||
caller = (FileTypeCaller) context;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException(context + " must implement " + FileTypeCaller.class.getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
int optionId = getArguments().getInt(KEY_OPTION_ID);
|
|
||||||
final int titleId = getArguments().getInt(KEY_TITLE_ID);
|
|
||||||
|
|
||||||
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX};
|
|
||||||
String[] choices = new String[trackFileFormats.length];
|
|
||||||
for (int i = 0; i < choices.length; i++) {
|
|
||||||
TrackFileFormat trackFileFormat = trackFileFormats[i];
|
|
||||||
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
|
||||||
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
|
|
||||||
String trackFileFormatDisplayName = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId));
|
|
||||||
choices[i] = getString(optionId, trackFileFormatDisplayName);
|
|
||||||
}
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setNegativeButton(R.string.generic_cancel, new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
onDismissed();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
|
||||||
caller.onFileTypeDone(trackFileFormats[position]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setSingleChoiceItems(choices, 0, null)
|
|
||||||
.setTitle(titleId)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel(@NonNull DialogInterface dialog) {
|
|
||||||
onDismissed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDismissed() {
|
|
||||||
dismiss();
|
|
||||||
caller.onDismissed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for caller of this dialog fragment.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public interface FileTypeCaller {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when file type selection is done.
|
|
||||||
*/
|
|
||||||
void onFileTypeDone(TrackFileFormat trackFileFormat);
|
|
||||||
|
|
||||||
void onDismissed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||||
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
|
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment to display track statistics to the user.
|
||||||
|
*
|
||||||
|
* @author Sandor Dornbush
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class StatisticsRecordedFragment extends Fragment implements TrackDataListener {
|
||||||
|
|
||||||
|
private TrackStatistics trackStatistics = null;
|
||||||
|
private String category = "";
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
|
if (isResumed()) {
|
||||||
|
getActivity().runOnUiThread(() -> {
|
||||||
|
if (isResumed()) {
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.statistics_recorded, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Views */
|
||||||
|
private TextView totalTimeValueView;
|
||||||
|
private TextView distanceValue;
|
||||||
|
private TextView distanceUnit;
|
||||||
|
private View activityLabel;
|
||||||
|
private Spinner activitySpinner;
|
||||||
|
private TextView movingTimeValue;
|
||||||
|
private TextView speedAvgLabel;
|
||||||
|
private TextView speedAvgValue;
|
||||||
|
private TextView speedAvgUnit;
|
||||||
|
private TextView speedMaxLabel;
|
||||||
|
private TextView speedMaxValue;
|
||||||
|
private TextView speedMaxUnit;
|
||||||
|
private TextView speedMovingLabel;
|
||||||
|
private TextView speedMovingValue;
|
||||||
|
private TextView speedMovingUnit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
|
||||||
|
|
||||||
|
distanceValue = view.findViewById(R.id.stats_distance_value);
|
||||||
|
distanceUnit = view.findViewById(R.id.stats_distance_unit);
|
||||||
|
|
||||||
|
activityLabel = view.findViewById(R.id.stats_activity_type_label);
|
||||||
|
activitySpinner = view.findViewById(R.id.stats_activity_type_icon);
|
||||||
|
|
||||||
|
movingTimeValue = view.findViewById(R.id.stats_moving_time_value);
|
||||||
|
|
||||||
|
speedAvgLabel = view.findViewById(R.id.stats_average_speed_label);
|
||||||
|
speedAvgValue = view.findViewById(R.id.stats_average_speed_value);
|
||||||
|
speedAvgUnit = view.findViewById(R.id.stats_average_speed_unit);
|
||||||
|
|
||||||
|
speedMaxLabel = view.findViewById(R.id.stats_max_speed_label);
|
||||||
|
speedMaxValue = view.findViewById(R.id.stats_max_speed_value);
|
||||||
|
speedMaxUnit = view.findViewById(R.id.stats_max_speed_unit);
|
||||||
|
|
||||||
|
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
||||||
|
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||||
|
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||||
|
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||||
|
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||||
|
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||||
|
|
||||||
|
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
|
||||||
|
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||||
|
|
||||||
|
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
|
||||||
|
trackDataHub.unregisterTrackDataListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
|
||||||
|
totalTimeValueView = null;
|
||||||
|
|
||||||
|
distanceValue = null;
|
||||||
|
distanceUnit = null;
|
||||||
|
|
||||||
|
activityLabel = null;
|
||||||
|
activitySpinner = null;
|
||||||
|
|
||||||
|
movingTimeValue = null;
|
||||||
|
|
||||||
|
speedAvgLabel = null;
|
||||||
|
speedAvgValue = null;
|
||||||
|
speedAvgUnit = null;
|
||||||
|
|
||||||
|
speedMaxLabel = null;
|
||||||
|
speedMaxValue = null;
|
||||||
|
speedMaxUnit = null;
|
||||||
|
|
||||||
|
speedMovingLabel = null;
|
||||||
|
speedMovingValue = null;
|
||||||
|
speedMovingUnit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTrackUpdated(final Track track) {
|
||||||
|
if (isResumed()) {
|
||||||
|
getActivity().runOnUiThread(() -> {
|
||||||
|
if (isResumed()) {
|
||||||
|
trackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||||
|
category = track != null ? track.getCategory() : "";
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearTrackPoints() {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewTrackPointsDone() {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearWaypoints() {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewWaypoint(Waypoint wpt) {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewWaypointsDone() {
|
||||||
|
// We don't care.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||||
|
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||||
|
|
||||||
|
// Set total distance
|
||||||
|
{
|
||||||
|
double totalDistance = trackStatistics == null ? Double.NaN : trackStatistics.getTotalDistance();
|
||||||
|
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||||
|
|
||||||
|
distanceValue.setText(parts.first);
|
||||||
|
distanceUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set activity type
|
||||||
|
{
|
||||||
|
activityLabel.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
activitySpinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||||
|
activitySpinner.setEnabled(false);
|
||||||
|
if (trackIconValue != null) {
|
||||||
|
TrackIconUtils.setIconSpinner(activitySpinner, trackIconValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set time and start datetime
|
||||||
|
if (trackStatistics != null) {
|
||||||
|
movingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
|
||||||
|
totalTimeValueView.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set average speed/pace
|
||||||
|
{
|
||||||
|
double speed = trackStatistics != null ? trackStatistics.getAverageSpeed() : Double.NaN;
|
||||||
|
speedAvgLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedAvgValue.setText(parts.first);
|
||||||
|
speedAvgUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set max speed/pace
|
||||||
|
{
|
||||||
|
double speed = trackStatistics == null ? Double.NaN : trackStatistics.getMaxSpeed();
|
||||||
|
|
||||||
|
speedMaxLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedMaxValue.setText(parts.first);
|
||||||
|
speedMaxUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set moving speed/pace
|
||||||
|
{
|
||||||
|
double speed = trackStatistics != null ? trackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||||
|
|
||||||
|
speedMovingLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedMovingValue.setText(parts.first);
|
||||||
|
speedMovingUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.fragments;
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@@ -31,15 +15,18 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.constraintlayout.widget.Group;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
@@ -57,9 +44,9 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class StatsFragment extends Fragment implements TrackDataListener {
|
public class StatisticsRecordingFragment extends Fragment implements TrackDataListener {
|
||||||
|
|
||||||
private static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
private static final String TAG = StatisticsRecordingFragment.class.getSimpleName();
|
||||||
|
|
||||||
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND_MS;
|
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND_MS;
|
||||||
|
|
||||||
@@ -81,12 +68,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
updateUI();
|
||||||
if (isResumed()) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -109,14 +93,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.stats, container, false);
|
return inflater.inflate(R.layout.statistics_recording, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Views */
|
/* Views */
|
||||||
private View heartRateContainer;
|
private View sensorHorizontalLine;
|
||||||
|
private Group heartRateGroup;
|
||||||
private TextView heartRateValueView;
|
private TextView heartRateValueView;
|
||||||
private TextView heartRateSensorView;
|
private TextView heartRateSensorView;
|
||||||
private View cadenceContainer;
|
private Group cadenceGroup;
|
||||||
private TextView cadenceValueView;
|
private TextView cadenceValueView;
|
||||||
private TextView cadenceSensorView;
|
private TextView cadenceSensorView;
|
||||||
|
|
||||||
@@ -147,16 +132,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
private TextView speedMovingLabel;
|
private TextView speedMovingLabel;
|
||||||
private TextView speedMovingValue;
|
private TextView speedMovingValue;
|
||||||
private TextView speedMovingUnit;
|
private TextView speedMovingUnit;
|
||||||
private View elevationSeparator;
|
private Group elevationGroup;
|
||||||
private View elevationContainer;
|
|
||||||
private View speedContainer;
|
|
||||||
private TextView speedLabel;
|
private TextView speedLabel;
|
||||||
private TextView speedValue;
|
private TextView speedValue;
|
||||||
private TextView speedUnit;
|
private TextView speedUnit;
|
||||||
private TextView elevationValue;
|
private TextView elevationValue;
|
||||||
private TextView elevationUnit;
|
private TextView elevationUnit;
|
||||||
private View coordinateSeparator;
|
private Group coordinateGroup;
|
||||||
private View coordinateContainer;
|
|
||||||
private TextView latitudeValue;
|
private TextView latitudeValue;
|
||||||
private TextView longitudeValue;
|
private TextView longitudeValue;
|
||||||
|
|
||||||
@@ -164,11 +146,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
heartRateContainer = view.findViewById(R.id.stats_sensor_heart_rate_container);
|
sensorHorizontalLine = view.findViewById(R.id.stats_sensor_horizontal_line);
|
||||||
|
|
||||||
|
heartRateGroup = view.findViewById(R.id.stats_sensor_heart_rate_group);
|
||||||
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
|
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
|
||||||
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||||
|
|
||||||
cadenceContainer = view.findViewById(R.id.stats_sensor_cadence_container);
|
cadenceGroup = view.findViewById(R.id.stats_sensor_cadence_group);
|
||||||
cadenceValueView = view.findViewById(R.id.stats_sensor_cadence_value);
|
cadenceValueView = view.findViewById(R.id.stats_sensor_cadence_value);
|
||||||
cadenceSensorView = view.findViewById(R.id.stats_sensor_cadence_sensor_value);
|
cadenceSensorView = view.findViewById(R.id.stats_sensor_cadence_sensor_value);
|
||||||
|
|
||||||
@@ -194,10 +178,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||||
|
|
||||||
elevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
elevationGroup = view.findViewById(R.id.stats_elevation_current_group);
|
||||||
elevationContainer = view.findViewById(R.id.stats_elevation_container);
|
|
||||||
|
|
||||||
speedContainer = view.findViewById(R.id.stats_speed);
|
|
||||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||||
@@ -205,8 +187,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||||
|
|
||||||
coordinateSeparator = view.findViewById(R.id.stats_coordinate_separator);
|
coordinateGroup = view.findViewById(R.id.stats_coordinate_group);
|
||||||
coordinateContainer = view.findViewById(R.id.stats_coordinate_container);
|
|
||||||
|
|
||||||
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
||||||
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
||||||
@@ -226,7 +207,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -235,7 +216,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -275,10 +256,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
heartRateContainer = null;
|
sensorHorizontalLine = null;
|
||||||
|
|
||||||
|
heartRateGroup = null;
|
||||||
heartRateValueView = null;
|
heartRateValueView = null;
|
||||||
heartRateSensorView = null;
|
heartRateSensorView = null;
|
||||||
cadenceContainer = null;
|
cadenceGroup = null;
|
||||||
cadenceValueView = null;
|
cadenceValueView = null;
|
||||||
cadenceSensorView = null;
|
cadenceSensorView = null;
|
||||||
|
|
||||||
@@ -304,10 +287,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
speedMovingValue = null;
|
speedMovingValue = null;
|
||||||
speedMovingUnit = null;
|
speedMovingUnit = null;
|
||||||
|
|
||||||
elevationSeparator = null;
|
elevationGroup = null;
|
||||||
elevationContainer = null;
|
|
||||||
|
|
||||||
speedContainer = null;
|
|
||||||
speedLabel = null;
|
speedLabel = null;
|
||||||
speedValue = null;
|
speedValue = null;
|
||||||
speedUnit = null;
|
speedUnit = null;
|
||||||
@@ -315,8 +296,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
elevationValue = null;
|
elevationValue = null;
|
||||||
elevationUnit = null;
|
elevationUnit = null;
|
||||||
|
|
||||||
coordinateSeparator = null;
|
coordinateGroup = null;
|
||||||
coordinateContainer = null;
|
|
||||||
|
|
||||||
latitudeValue = null;
|
latitudeValue = null;
|
||||||
longitudeValue = null;
|
longitudeValue = null;
|
||||||
@@ -325,14 +305,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(final Track track) {
|
public void onTrackUpdated(final Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||||
if (isResumed()) {
|
category = track != null ? track.getCategory() : "";
|
||||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
updateUI();
|
||||||
category = track != null ? track.getCategory() : "";
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -356,24 +333,21 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onNewTrackPointsDone() {
|
public void onNewTrackPointsDone() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||||
if (isResumed()) {
|
lastTrackPoint = null;
|
||||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
}
|
||||||
|
|
||||||
|
if (lastTrackPoint != null) {
|
||||||
|
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
||||||
|
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
|
||||||
|
|
||||||
|
if (!hasFix || !hasGoodFix) {
|
||||||
lastTrackPoint = null;
|
lastTrackPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastTrackPoint != null) {
|
|
||||||
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
|
||||||
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
|
|
||||||
|
|
||||||
if (!hasFix || !hasGoodFix) {
|
|
||||||
lastTrackPoint = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setLocationValues();
|
|
||||||
}
|
}
|
||||||
|
setLocationValues();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -399,7 +373,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||||
*/
|
*/
|
||||||
private synchronized void resumeTrackDataHub() {
|
private synchronized void resumeTrackDataHub() {
|
||||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
trackDataHub = ((TrackRecordingActivity) getActivity()).getTrackDataHub();
|
||||||
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,65 +416,79 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
SensorDataSet sensorDataSet = null;
|
SensorDataSet sensorDataSet = null;
|
||||||
if (trackRecordingService == null) {
|
if (trackRecordingService == null) {
|
||||||
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
Log.d(TAG, "Cannot get the track recording service.");
|
||||||
} else {
|
} else {
|
||||||
sensorDataSet = trackRecordingService.getSensorData();
|
sensorDataSet = trackRecordingService.getSensorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
setHeartRateSensorData(sensorDataSet);
|
||||||
setCadenceSensorData(sensorDataSet, isSelectedTrackRecording());
|
setCadenceSensorData(sensorDataSet);
|
||||||
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
||||||
int isVisible = View.VISIBLE;
|
int isVisible = View.VISIBLE;
|
||||||
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
if (PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
||||||
isVisible = View.INVISIBLE;
|
isVisible = View.GONE;
|
||||||
}
|
}
|
||||||
heartRateContainer.setVisibility(isVisible);
|
heartRateGroup.setVisibility(isVisible);
|
||||||
|
setVisibilitySensorHorizontalLine();
|
||||||
|
|
||||||
if (isRecording) {
|
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
String sensorName = getContext().getString(R.string.value_unknown);
|
||||||
String sensorName = getContext().getString(R.string.value_unknown);
|
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
SensorDataHeartRate data = sensorDataSet.getHeartRate();
|
||||||
sensorName = sensorDataSet.getHeartRate().getSensorName();
|
|
||||||
if (sensorDataSet.getHeartRate().hasHeartRate_bpm() && sensorDataSet.getHeartRate().isRecent()) {
|
sensorName = data.getSensorName();
|
||||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getHeartRate().getHeartRate_bpm(), 0);
|
if (data.hasHeartRate_bpm() && data.isRecent()) {
|
||||||
}
|
sensorValue = StringUtils.formatDecimal(data.getHeartRate_bpm(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
heartRateSensorView.setText(sensorName);
|
|
||||||
heartRateValueView.setText(sensorValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
heartRateSensorView.setText(sensorName);
|
||||||
|
heartRateValueView.setText(sensorValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCadenceSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
private void setCadenceSensorData(SensorDataSet sensorDataSet) {
|
||||||
int isVisible = View.VISIBLE;
|
int isVisible = View.VISIBLE;
|
||||||
if (!isRecording || PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
if (PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
||||||
isVisible = View.INVISIBLE;
|
isVisible = View.GONE;
|
||||||
}
|
}
|
||||||
cadenceContainer.setVisibility(isVisible);
|
cadenceGroup.setVisibility(isVisible);
|
||||||
|
setVisibilitySensorHorizontalLine();
|
||||||
|
|
||||||
if (isRecording) {
|
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
String sensorName = getContext().getString(R.string.value_unknown);
|
||||||
String sensorName = getContext().getString(R.string.value_unknown);
|
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
||||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
SensorDataCycling.Cadence data = sensorDataSet.getCyclingCadence();
|
||||||
sensorName = sensorDataSet.getCyclingCadence().getSensorName();
|
sensorName = data.getSensorName();
|
||||||
if (sensorDataSet.getCyclingCadence().hasCadence_rpm() && sensorDataSet.getCyclingCadence().isRecent()) {
|
|
||||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getCyclingCadence().getCadence_rpm(), 0);
|
if (data.hasCadence_rpm() && data.isRecent()) {
|
||||||
}
|
sensorValue = StringUtils.formatDecimal(data.getCadence_rpm(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
cadenceSensorView.setText(sensorName);
|
|
||||||
cadenceValueView.setText(sensorValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cadenceSensorView.setText(sensorName);
|
||||||
|
cadenceValueView.setText(sensorValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If cadence and hear rate groups are invisible then sensor horizontal line hast to be invisible too.
|
||||||
|
*/
|
||||||
|
private void setVisibilitySensorHorizontalLine() {
|
||||||
|
if (cadenceGroup.getVisibility() != View.VISIBLE && heartRateGroup.getVisibility() != View.VISIBLE) {
|
||||||
|
sensorHorizontalLine.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
||||||
if (sensorDataSet.getCyclingSpeed().hasSpeed_mps() && sensorDataSet.getCyclingSpeed().isRecent()) {
|
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
||||||
setSpeed(sensorDataSet.getCyclingSpeed().getSpeed_mps());
|
|
||||||
|
if (data.hasSpeed_mps() && data.isRecent()) {
|
||||||
|
setSpeed(data.getSpeed_mps());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,7 +562,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
// Make elevation visible?
|
// Make elevation visible?
|
||||||
{
|
{
|
||||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
elevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,18 +591,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
private void setLocationValues() {
|
private void setLocationValues() {
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
boolean isRecording = isSelectedTrackRecording();
|
|
||||||
|
|
||||||
// Set speed/pace
|
// Set speed/pace
|
||||||
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||||
if (isRecording) {
|
setSpeed(speed);
|
||||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
|
||||||
setSpeed(speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set elevation
|
// Set elevation
|
||||||
boolean showElevation = isRecording && PreferencesUtils.isShowStatsElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
elevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (showElevation) {
|
if (showElevation) {
|
||||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
||||||
@@ -625,10 +609,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set coordinate
|
// Set coordinate
|
||||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
|
boolean showCoordinate = PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||||
|
|
||||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
coordinateGroup.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
|
||||||
if (showCoordinate) {
|
if (showCoordinate) {
|
||||||
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
|
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
|
||||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||||
@@ -12,7 +12,6 @@ import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
|
|||||||
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
|
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporterListener;
|
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,8 +73,8 @@ public enum TrackFileFormat {
|
|||||||
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
|
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,8 +100,8 @@ public enum TrackFileFormat {
|
|||||||
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
|
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -134,8 +133,8 @@ public enum TrackFileFormat {
|
|||||||
return MIME_KMZ;
|
return MIME_KMZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -163,8 +162,8 @@ public enum TrackFileFormat {
|
|||||||
return MIME_KMZ;
|
return MIME_KMZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -197,10 +196,10 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
||||||
|
|
||||||
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, boolean exportPhotos) {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
|
||||||
|
|
||||||
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
@@ -210,10 +209,10 @@ public enum TrackFileFormat {
|
|||||||
*/
|
*/
|
||||||
public abstract String getMimeType();
|
public abstract String getMimeType();
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
|
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
|
||||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,26 +24,25 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.DialogUtils;
|
import de.dennisguse.opentracks.util.DialogUtils;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity for saving tracks to the external storage.
|
* An activity for saving tracks to the external storage.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ExportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
|
public class ExportActivity extends FragmentActivity {
|
||||||
|
|
||||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||||
|
|
||||||
private static final int DIALOG_PROGRESS_ID = 0;
|
private static final int DIALOG_PROGRESS_ID = 0;
|
||||||
private static final int DIALOG_RESULT_ID = 1;
|
private static final int DIALOG_RESULT_ID = 1;
|
||||||
@@ -56,65 +55,50 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
private int processedTrackCount;
|
private int processedTrackCount;
|
||||||
private int totalTrackCount;
|
private int totalTrackCount;
|
||||||
|
|
||||||
private Uri directoryUri;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
|
||||||
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
super.onActivityResult(requestCode, resultCode, resultData);
|
super.onActivityResult(requestCode, resultCode, resultData);
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
directoryUri = resultData.getData();
|
Uri directoryUri = resultData.getData();
|
||||||
fileTypeDialogStart();
|
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
|
DocumentFile exportDirectory = pickedDirectory.findFile(FileUtils.EXPORT_DIR);
|
||||||
|
if (exportDirectory == null) {
|
||||||
|
exportDirectory = pickedDirectory.createDirectory(FileUtils.EXPORT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(this);
|
||||||
|
|
||||||
|
directoryDisplayName = FileUtils.getPath(exportDirectory);
|
||||||
|
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, exportDirectory);
|
||||||
|
exportAsyncTask.execute();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileTypeDialogStart() {
|
|
||||||
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.export_all_title, R.string.export_all_option);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
|
||||||
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
|
||||||
DocumentFile exportDirectory = pickedDirectory.findFile(FileUtils.EXPORT_DIR);
|
|
||||||
if (exportDirectory == null) {
|
|
||||||
exportDirectory = pickedDirectory.createDirectory(FileUtils.EXPORT_DIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
directoryDisplayName = FileUtils.getPath(exportDirectory);
|
|
||||||
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, exportDirectory);
|
|
||||||
exportAsyncTask.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDismissed() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Dialog onCreateDialog(int id) {
|
protected Dialog onCreateDialog(int id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case DIALOG_PROGRESS_ID:
|
case DIALOG_PROGRESS_ID:
|
||||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
||||||
R.string.export_external_storage_progress_message,
|
R.string.export_progress_message,
|
||||||
new DialogInterface.OnCancelListener() {
|
new DialogInterface.OnCancelListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(DialogInterface dialog) {
|
public void onCancel(DialogInterface dialog) {
|
||||||
exportAsyncTask.cancel(true);
|
exportAsyncTask.cancel(true);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}, directoryDisplayName);
|
}, directoryDisplayName);
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
@@ -128,11 +112,11 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
iconId = R.drawable.ic_dialog_success_24dp;
|
iconId = R.drawable.ic_dialog_success_24dp;
|
||||||
titleId = R.string.generic_success_title;
|
titleId = R.string.generic_success_title;
|
||||||
message = getString(
|
message = getString(
|
||||||
R.string.export_external_storage_success, totalTracks, directoryDisplayName);
|
R.string.export_success, totalTracks, directoryDisplayName);
|
||||||
} else {
|
} else {
|
||||||
iconId = R.drawable.ic_dialog_error_24dp;
|
iconId = R.drawable.ic_dialog_error_24dp;
|
||||||
titleId = R.string.generic_error_title;
|
titleId = R.string.generic_error_title;
|
||||||
message = getString(R.string.export_external_storage_error, processedTrackCount, totalTracks, directoryDisplayName);
|
message = getString(R.string.export_error, processedTrackCount, totalTracks, directoryDisplayName);
|
||||||
}
|
}
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
|
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
|
||||||
.setIcon(iconId).setMessage(message)
|
.setIcon(iconId).setMessage(message)
|
||||||
@@ -140,13 +124,13 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
@Override
|
@Override
|
||||||
public void onCancel(DialogInterface dialog) {
|
public void onCancel(DialogInterface dialog) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int arg1) {
|
public void onClick(DialogInterface dialog, int arg1) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}).setTitle(titleId);
|
}).setTitle(titleId);
|
||||||
return builder.create();
|
return builder.create();
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.exporter;
|
package de.dennisguse.opentracks.io.file.exporter;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
@@ -45,13 +44,10 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final DocumentFile directory;
|
private final DocumentFile directory;
|
||||||
private final Context context;
|
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private ExportActivity exportActivity;
|
private ExportActivity exportActivity;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
private boolean completed = false;
|
|
||||||
|
|
||||||
private int processedTrackCount = 0;
|
private int processedTrackCount = 0;
|
||||||
private int totalTrackCount = 0;
|
private int totalTrackCount = 0;
|
||||||
|
|
||||||
@@ -66,8 +62,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
this.exportActivity = exportActivity;
|
this.exportActivity = exportActivity;
|
||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
context = exportActivity.getApplicationContext();
|
contentProviderUtils = new ContentProviderUtils(exportActivity);
|
||||||
contentProviderUtils = new ContentProviderUtils(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,7 +94,6 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
completed = true;
|
|
||||||
if (exportActivity != null) {
|
if (exportActivity != null) {
|
||||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||||
}
|
}
|
||||||
@@ -107,7 +101,6 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
completed = true;
|
|
||||||
if (exportActivity != null) {
|
if (exportActivity != null) {
|
||||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||||
}
|
}
|
||||||
@@ -119,17 +112,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
* @param track the track
|
* @param track the track
|
||||||
*/
|
*/
|
||||||
private Boolean exportTrack(Track track) {
|
private Boolean exportTrack(Track track) {
|
||||||
TrackExporterListener trackExporterListener = new TrackExporterListener() {
|
TrackExporter trackExporter = trackFileFormat.newTrackExporter(exportActivity, new Track[]{track});
|
||||||
@Override
|
|
||||||
public void onProgressUpdate(int number, int max) {
|
|
||||||
//Update the progress dialog once every 500 points.
|
|
||||||
if (number % 500 == 0) {
|
|
||||||
publishProgress(number, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, trackExporterListener);
|
|
||||||
|
|
||||||
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
||||||
|
|
||||||
@@ -139,8 +122,8 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
|
try (OutputStream outputStream = exportActivity.getContentResolver().openOutputStream(file.getUri())) {
|
||||||
if (trackExporter.writeTrack(context, outputStream)) {
|
if (trackExporter.writeTrack(exportActivity, outputStream)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (!file.delete()) {
|
if (!file.delete()) {
|
||||||
@@ -167,6 +150,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
totalTrackCount = cursor.getCount();
|
totalTrackCount = cursor.getCount();
|
||||||
|
|
||||||
for (int i = 0; i < totalTrackCount; i++) {
|
for (int i = 0; i < totalTrackCount; i++) {
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private final Track[] tracks;
|
private final Track[] tracks;
|
||||||
private final TrackWriter trackWriter;
|
private final TrackWriter trackWriter;
|
||||||
private final TrackExporterListener trackExporterListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -52,13 +51,11 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
* @param contentProviderUtils the content provider utils
|
* @param contentProviderUtils the content provider utils
|
||||||
* @param trackWriter the track writer
|
* @param trackWriter the track writer
|
||||||
* @param tracks the tracks
|
* @param tracks the tracks
|
||||||
* @param trackExporterListener the track export listener
|
|
||||||
*/
|
*/
|
||||||
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks) {
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
this.tracks = tracks;
|
this.tracks = tracks;
|
||||||
this.trackWriter = trackWriter;
|
this.trackWriter = trackWriter;
|
||||||
this.trackExporterListener = trackExporterListener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -158,9 +155,6 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
// Write the current trackPoint
|
// Write the current trackPoint
|
||||||
trackWriter.writeTrackPoint(trackPoint);
|
trackWriter.writeTrackPoint(trackPoint);
|
||||||
if (trackExporterListener != null) {
|
|
||||||
trackExporterListener.onProgressUpdate(locationNumber, trackPointIterator.getCount());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (wroteSegment) {
|
if (wroteSegment) {
|
||||||
trackWriter.writeCloseSegment();
|
trackWriter.writeCloseSegment();
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.io.file.exporter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for {@link TrackExporter} progress.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public interface TrackExporterListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to update progress.
|
|
||||||
*
|
|
||||||
* @param number the number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s written
|
|
||||||
* @param max the maximum number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s in a {@link de.dennisguse.opentracks.content.data.Track}, for calculation of
|
|
||||||
* completion percentage
|
|
||||||
*/
|
|
||||||
void onProgressUpdate(int number, int max);
|
|
||||||
}
|
|
||||||
@@ -424,7 +424,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
timeValue = StringUtils.parseTime(time);
|
timeValue = StringUtils.parseTime(time);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (Exception e) {
|
||||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,26 +24,25 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.DialogUtils;
|
import de.dennisguse.opentracks.util.DialogUtils;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to import files from the external storage.
|
* An activity to import files from the external storage.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ImportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
|
public class ImportActivity extends FragmentActivity {
|
||||||
|
|
||||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||||
|
|
||||||
private static final int DIALOG_PROGRESS_ID = 0;
|
private static final int DIALOG_PROGRESS_ID = 0;
|
||||||
private static final int DIALOG_RESULT_ID = 1;
|
private static final int DIALOG_RESULT_ID = 1;
|
||||||
@@ -56,8 +55,6 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
private int importedTrackCount;
|
private int importedTrackCount;
|
||||||
private int totalTrackCount;
|
private int totalTrackCount;
|
||||||
|
|
||||||
private Uri directoryUri;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -65,43 +62,28 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
super.onActivityResult(requestCode, resultCode, resultData);
|
super.onActivityResult(requestCode, resultCode, resultData);
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
directoryUri = resultData.getData();
|
Uri directoryUri = resultData.getData();
|
||||||
fileTypeDialogStart();
|
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
|
|
||||||
|
directoryDisplayName = FileUtils.getPath(pickedDirectory);
|
||||||
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(this);
|
||||||
|
|
||||||
|
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
|
||||||
|
importAsyncTask.execute();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void fileTypeDialogStart() {
|
|
||||||
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.import_selection_title, R.string.import_selection_option);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
|
||||||
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
|
||||||
|
|
||||||
directoryDisplayName = FileUtils.getPath(pickedDirectory);
|
|
||||||
|
|
||||||
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
|
|
||||||
importAsyncTask.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDismissed() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Dialog onCreateDialog(int id) {
|
protected Dialog onCreateDialog(int id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
@@ -112,7 +94,7 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
public void onCancel(DialogInterface dialog) {
|
public void onCancel(DialogInterface dialog) {
|
||||||
importAsyncTask.cancel(true);
|
importAsyncTask.cancel(true);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}, directoryDisplayName);
|
}, directoryDisplayName);
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
@@ -142,13 +124,13 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
@Override
|
@Override
|
||||||
public void onCancel(DialogInterface dialogInterface) {
|
public void onCancel(DialogInterface dialogInterface) {
|
||||||
dialogInterface.dismiss();
|
dialogInterface.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int which) {
|
public void onClick(DialogInterface dialogInterface, int which) {
|
||||||
dialogInterface.dismiss();
|
dialogInterface.dismiss();
|
||||||
onDismissed();
|
finish();
|
||||||
}
|
}
|
||||||
}).setTitle(titleId).create();
|
}).setTitle(titleId).create();
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.importer;
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
@@ -46,39 +45,23 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final DocumentFile directory;
|
private final DocumentFile directory;
|
||||||
// TODO Can be removed, or?
|
|
||||||
private Context context;
|
|
||||||
// TODO Use weak reference
|
// TODO Use weak reference
|
||||||
private ImportActivity importActivity;
|
private ImportActivity importActivity;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
// true if the AsyncTask has completed
|
|
||||||
private boolean completed;
|
|
||||||
|
|
||||||
private int importTrackCount;
|
private int importTrackCount;
|
||||||
|
|
||||||
private int totalTrackCount;
|
private int totalTrackCount;
|
||||||
|
private long lastSuccessfulTrackId;
|
||||||
|
|
||||||
// the last successfully imported track id
|
|
||||||
private long trackId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an AsyncTask.
|
|
||||||
*
|
|
||||||
* @param importActivity the activity currently associated with this AsyncTask
|
|
||||||
* @param trackFileFormat the track file format
|
|
||||||
* @param directory path to import GPX files
|
|
||||||
*/
|
|
||||||
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||||
this.importActivity = importActivity;
|
this.importActivity = importActivity;
|
||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
context = importActivity.getApplicationContext();
|
|
||||||
|
|
||||||
completed = false;
|
|
||||||
importTrackCount = 0;
|
importTrackCount = 0;
|
||||||
totalTrackCount = 0;
|
totalTrackCount = 0;
|
||||||
trackId = -1L;
|
lastSuccessfulTrackId = -1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,8 +73,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of files.
|
* Gets a list of files.
|
||||||
* If importAll is true, returns a list of the files under the path directory.
|
|
||||||
* If importAll is false, returns a list containing just the path file.
|
|
||||||
*/
|
*/
|
||||||
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
|
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
|
||||||
List<DocumentFile> files = new ArrayList<>();
|
List<DocumentFile> files = new ArrayList<>();
|
||||||
@@ -119,7 +100,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
completed = true;
|
|
||||||
if (importActivity != null) {
|
if (importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||||
}
|
}
|
||||||
@@ -127,8 +107,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
context = null;
|
|
||||||
completed = true;
|
|
||||||
if (importActivity != null) {
|
if (importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||||
importActivity = null;
|
importActivity = null;
|
||||||
@@ -190,9 +168,9 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
|
try (InputStream inputStream = importActivity.getContentResolver().openInputStream(file.getUri())) {
|
||||||
trackId = trackImporter.importFile(inputStream);
|
lastSuccessfulTrackId = trackImporter.importFile(inputStream);
|
||||||
return trackId != -1L;
|
return lastSuccessfulTrackId != -1L;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
|
||||||
import de.dennisguse.opentracks.TrackListActivity;
|
import de.dennisguse.opentracks.TrackListActivity;
|
||||||
|
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
@@ -150,12 +150,7 @@ public class TrackRecordingService extends Service {
|
|||||||
if (locationExecutorService == null || locationExecutorService.isShutdown() || locationExecutorService.isTerminated()) {
|
if (locationExecutorService == null || locationExecutorService.isShutdown() || locationExecutorService.isTerminated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
locationExecutorService.submit(new Runnable() {
|
locationExecutorService.submit(() -> onLocationChangedAsync(location));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
onLocationChangedAsync(location);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -461,12 +456,6 @@ public class TrackRecordingService extends Service {
|
|||||||
// Update the recording track time
|
// Update the recording track time
|
||||||
updateTrackTotalTime(track);
|
updateTrackTotalTime(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTrackStatistics().getStartTime_ms());
|
|
||||||
if (trackName != null && !trackName.equals(track.getName())) {
|
|
||||||
track.setName(trackName);
|
|
||||||
contentProviderUtils.updateTrack(track);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
endRecording(true);
|
endRecording(true);
|
||||||
}
|
}
|
||||||
@@ -482,8 +471,9 @@ public class TrackRecordingService extends Service {
|
|||||||
// Update database
|
// Update database
|
||||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
if (lastTrackPoint != null) {
|
||||||
|
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||||
|
}
|
||||||
insertTrackPoint(track, TrackPoint.createPause());
|
insertTrackPoint(track, TrackPoint.createPause());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,10 +738,10 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.addParentStack(TrackDetailActivity.class)
|
.addParentStack(TrackRecordingActivity.class)
|
||||||
.addNextIntent(intent)
|
.addNextIntent(intent)
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,14 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
|
|
||||||
private TrackRecordingServiceInterface trackRecordingService;
|
private TrackRecordingServiceInterface trackRecordingService;
|
||||||
|
|
||||||
|
public TrackRecordingServiceConnection() {
|
||||||
|
callback = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param callback the callback to invoke when the service binding changes
|
* @param callback the callback to invoke when the service binding changes
|
||||||
*/
|
*/
|
||||||
public TrackRecordingServiceConnection(Runnable callback) {
|
public TrackRecordingServiceConnection(@NonNull Runnable callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||||
@@ -31,11 +32,11 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
||||||
*
|
* <p>
|
||||||
* Note: should only be instantiated once.
|
* Note: should only be instantiated once.
|
||||||
*
|
* <p>
|
||||||
* TODO: listen for Bluetooth enabled/disabled events.
|
* TODO: listen for Bluetooth enabled/disabled events.
|
||||||
*
|
* <p>
|
||||||
* TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
|
* TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
|
||||||
* We establish two GATT separate GATT connections (as if two different sensors were used).
|
* We establish two GATT separate GATT connections (as if two different sensors were used).
|
||||||
* However, it is not clear if this is allowed.
|
* However, it is not clear if this is allowed.
|
||||||
@@ -148,20 +149,25 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onChanged(de.dennisguse.opentracks.content.sensor.SensorData sensorData) {
|
public synchronized void onChanged(SensorData sensorData) {
|
||||||
if (sensorData instanceof SensorDataCycling.Cadence) {
|
if (sensorData instanceof SensorDataCycling.Cadence) {
|
||||||
if (sensorData.equals(sensorDataSet.getCyclingCadence())) {
|
SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence();
|
||||||
|
Log.d(TAG, "previous " + previous + "; current" + sensorData);
|
||||||
|
|
||||||
|
if (sensorData.equals(previous)) {
|
||||||
Log.d(TAG, "onChanged: cadence data repeated.");
|
Log.d(TAG, "onChanged: cadence data repeated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
((SensorDataCycling.Cadence) sensorData).compute(sensorDataSet.getCyclingCadence());
|
((SensorDataCycling.Cadence) sensorData).compute(previous);
|
||||||
}
|
}
|
||||||
if (sensorData instanceof SensorDataCycling.Speed) {
|
if (sensorData instanceof SensorDataCycling.Speed) {
|
||||||
if (sensorData.equals(sensorDataSet.getCyclingSpeed())) {
|
SensorDataCycling.Speed previous = sensorDataSet.getCyclingSpeed();
|
||||||
|
Log.d(TAG, "previous " + previous + "; current" + sensorData);
|
||||||
|
if (sensorData.equals(previous)) {
|
||||||
Log.d(TAG, "onChanged: speed data repeated.");
|
Log.d(TAG, "onChanged: speed data repeated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
((SensorDataCycling.Speed) sensorData).compute(sensorDataSet.getCyclingSpeed(), PreferencesUtils.getWheelCircumference(context));
|
((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
sensorDataSet.set(sensorData);
|
sensorDataSet.set(sensorData);
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import androidx.preference.ListPreference;
|
|||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
|
||||||
@@ -57,20 +60,10 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(() -> updateReset());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateReset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
getActivity().runOnUiThread(() -> updateUnits());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateUnits();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -89,6 +82,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
|
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setExportTrackFileFormatOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -172,5 +167,24 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
|
|
||||||
HackUtils.invalidatePreference(statsRatePreferences);
|
HackUtils.invalidatePreference(statsRatePreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setExportTrackFileFormatOptions() {
|
||||||
|
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX};
|
||||||
|
String[] entries = new String[trackFileFormats.length];
|
||||||
|
String[] entryValues = new String[trackFileFormats.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < entries.length; i++) {
|
||||||
|
TrackFileFormat trackFileFormat = trackFileFormats[i];
|
||||||
|
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
||||||
|
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
|
||||||
|
entries[i] = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId));
|
||||||
|
entryValues[i] = trackFileFormat.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
ListPreference listPreference = findPreference(getString(R.string.export_trackfileformat_key));
|
||||||
|
listPreference.setEntries(entries);
|
||||||
|
listPreference.setEntryValues(entryValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,10 @@ public class BluetoothUtils {
|
|||||||
final Object mutex = new Object();
|
final Object mutex = new Object();
|
||||||
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
handler.post(new Runnable() {
|
handler.post(() -> {
|
||||||
@Override
|
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
||||||
public void run() {
|
synchronized (mutex) {
|
||||||
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
mutex.notify();
|
||||||
synchronized (mutex) {
|
|
||||||
mutex.notify();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class DialogUtils {
|
|||||||
* @param formatArgs the format arguments for the message id
|
* @param formatArgs the format arguments for the message id
|
||||||
*/
|
*/
|
||||||
private static ProgressDialog createProgressDialog(boolean spinner, final Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
|
private static ProgressDialog createProgressDialog(boolean spinner, final Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
|
||||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
final ProgressDialog progressDialog = new ProgressDialog(context, R.style.ThemeCustomNotTransparent);
|
||||||
progressDialog.setCancelable(true);
|
progressDialog.setCancelable(true);
|
||||||
progressDialog.setCanceledOnTouchOutside(false);
|
progressDialog.setCanceledOnTouchOutside(false);
|
||||||
progressDialog.setIcon(R.drawable.ic_dialog_info_24dp);
|
progressDialog.setIcon(R.drawable.ic_dialog_info_24dp);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
@@ -21,32 +23,61 @@ public class IntentDashboardUtils {
|
|||||||
|
|
||||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||||
|
|
||||||
|
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||||
|
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||||
|
|
||||||
|
private static int TRACK_URI_INDEX = 0;
|
||||||
|
private static int TRACKPOINTS_URI_INDEX = 1;
|
||||||
|
|
||||||
private IntentDashboardUtils() {
|
private IntentDashboardUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean startDashboard(Context context, long[] trackIds) {
|
/**
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @param trackIds the track ids
|
||||||
|
*/
|
||||||
|
public static void startDashboard(Context context, long[] trackIds) {
|
||||||
|
if (trackIds.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||||
|
|
||||||
uris.add(0, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
uris.add(1, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||||
|
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||||
|
|
||||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||||
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(context));
|
||||||
|
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(context));
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
ClipData clipData = ClipData.newRawUri(null, uris.get(0));
|
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||||
clipData.addItem(new ClipData.Item(uris.get(1)));
|
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||||
intent.setClipData(clipData);
|
intent.setClipData(clipData);
|
||||||
|
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
context.startActivity(intent);
|
||||||
context.startActivity(Intent.createChooser(intent, null));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void startDashboard(Context context, long trackId) {
|
||||||
|
startDashboard(context, new long[]{trackId});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] extractTrackIdsFromIntent(@NonNull Intent intent) {
|
||||||
|
final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD);
|
||||||
|
final Uri tracksUri = uris.get(TRACK_URI_INDEX);
|
||||||
|
|
||||||
|
String[] trackIdsString = ContentProviderUtils.parseTrackIdsFromUri(tracksUri);
|
||||||
|
long[] trackIds = new long[trackIdsString.length];
|
||||||
|
for (int i = 0; i < trackIdsString.length; i++) {
|
||||||
|
trackIds[i] = Long.parseLong(trackIdsString[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackIds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,12 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
@@ -35,11 +33,9 @@ import java.util.Date;
|
|||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
|
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for creating intents.
|
* Utilities for creating intents.
|
||||||
@@ -92,9 +88,10 @@ public class IntentUtils {
|
|||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
|
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), PreferencesUtils.getExportTrackFileFormat(context));
|
||||||
uris.add(uriAndMime.first);
|
uris.add(uriAndMime.first);
|
||||||
mime = uriAndMime.second;
|
mime = uriAndMime.second;
|
||||||
}
|
}
|
||||||
@@ -152,42 +149,11 @@ public class IntentUtils {
|
|||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(uri));
|
intent.setData(Uri.parse(uri));
|
||||||
|
|
||||||
try {
|
|
||||||
context.startActivity(intent);
|
context.startActivity(Intent.createChooser(intent, null));
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
Toast.makeText(context, context.getString(R.string.app_not_installed_show_on_map), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send intent to show tracks on a map (needs an another app).
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param trackIds the track ids
|
|
||||||
*/
|
|
||||||
public static void showTrackOnMap(Context context, long[] trackIds) {
|
|
||||||
if (trackIds.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IntentDashboardUtils.startDashboard(context, trackIds)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
|
||||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
|
||||||
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
|
||||||
context.startActivity(intent);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, context.getString(R.string.app_not_installed_show_on_map), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a take picture request to the camera app.
|
* Sends a take picture request to the camera app.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities to access preferences stored in {@link SharedPreferences}.
|
* Utilities to access preferences stored in {@link SharedPreferences}.
|
||||||
@@ -320,6 +321,16 @@ public class PreferencesUtils {
|
|||||||
return getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
return getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TrackFileFormat getExportTrackFileFormat(Context context) {
|
||||||
|
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(context, R.string.export_trackfileformat_default, null);
|
||||||
|
String trackFileFormatName = getString(context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
|
||||||
|
try {
|
||||||
|
return TrackFileFormat.valueOf(trackFileFormatName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isRecording(Context context) {
|
public static boolean isRecording(Context context) {
|
||||||
long recordingTrackId = getRecordingTrackId(context);
|
long recordingTrackId = getRecordingTrackId(context);
|
||||||
return isRecording(recordingTrackId);
|
return isRecording(recordingTrackId);
|
||||||
|
|||||||
@@ -20,12 +20,16 @@ import android.location.Location;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.TemporalAccessor;
|
import java.time.temporal.TemporalAccessor;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -44,6 +48,8 @@ import de.dennisguse.opentracks.R;
|
|||||||
*/
|
*/
|
||||||
public class StringUtils {
|
public class StringUtils {
|
||||||
|
|
||||||
|
private static final String TAG = StringUtils.class.getSimpleName();
|
||||||
|
|
||||||
private static final String COORDINATE_DEGREE = "\u00B0";
|
private static final String COORDINATE_DEGREE = "\u00B0";
|
||||||
|
|
||||||
//TODO Remove when upgrading to API level 26+.
|
//TODO Remove when upgrading to API level 26+.
|
||||||
@@ -280,22 +286,27 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the time, in milliseconds, from an XML date time string as defined at http://www.w3.org/TR/xmlschema-2/#dateTime
|
* Gets the time, in milliseconds, from an XML date time string (ISO8601) as defined at http://www.w3.org/TR/xmlschema-2/#dateTime
|
||||||
|
* Let's be lenient: if timezone information is not provided, UTC will be used.
|
||||||
*
|
*
|
||||||
* @param xmlDateTime the XML date time string
|
* @param xmlDateTime the XML date time string
|
||||||
*/
|
*/
|
||||||
public static long parseTime(String xmlDateTime) {
|
public static long parseTime(String xmlDateTime) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
try {
|
||||||
TemporalAccessor t = DateTimeFormatter.ISO_DATE_TIME.parse(xmlDateTime);
|
TemporalAccessor t = DateTimeFormatter.ISO_DATE_TIME.parseBest(xmlDateTime, ZonedDateTime::from, LocalDateTime::from);
|
||||||
|
if (t instanceof LocalDateTime) {
|
||||||
|
Log.w(TAG, "Date does not contain timezone information: using UTC.");
|
||||||
|
t = ((LocalDateTime) t).atZone(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
return Instant.from(t).toEpochMilli();
|
return Instant.from(t).toEpochMilli();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException("Invalid XML dateTime value: " + e);
|
Log.e(TAG, "Invalid XML dateTime value");
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Remove the following when upgrading to API level 26+.
|
//TODO Remove the following when upgrading to API level 26+.
|
||||||
//ATTENTION: The following code does not require a time zone (+01 or Z); while ISO_DATE_TIME requires this!
|
|
||||||
|
|
||||||
// Parse the date time base
|
// Parse the date time base
|
||||||
ParsePosition position = new ParsePosition(0);
|
ParsePosition position = new ParsePosition(0);
|
||||||
@@ -415,4 +426,8 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
return new Pair<>(value, unit);
|
return new Pair<>(value, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String valueInParentheses(String text) {
|
||||||
|
return "(" + text + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class UintUtils {
|
|||||||
if (a < 0 || b < 0) {
|
if (a < 0 || b < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a > UINT_MAX && b > UINT_MAX) {
|
if (a > UINT_MAX || b > UINT_MAX) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class ViewUtils {
|
||||||
|
|
||||||
|
private ViewUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses all childs of {@link View} recursively and makes links within {@link TextView}s clickable.
|
||||||
|
*/
|
||||||
|
public static void makeClickableLinks(ViewGroup view) {
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < view.getChildCount(); i++) {
|
||||||
|
final View child = view.getChildAt(i);
|
||||||
|
if (child instanceof ViewGroup) {
|
||||||
|
makeClickableLinks((ViewGroup) child);
|
||||||
|
} else if (child instanceof TextView) {
|
||||||
|
((TextView) child).setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
|
public class AggregatedStatistics {
|
||||||
|
|
||||||
|
private HashMap<String, AggregatedStatistic> aggregatedStatistics = new HashMap<>();
|
||||||
|
|
||||||
|
public void aggregate(List<Track> tracks) {
|
||||||
|
for (Track track : tracks) {
|
||||||
|
aggregate(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void aggregate(Track track) {
|
||||||
|
String category = track.getCategory();
|
||||||
|
if (aggregatedStatistics.containsKey(category)) {
|
||||||
|
aggregatedStatistics.get(category).add(track.getTrackStatistics());
|
||||||
|
} else {
|
||||||
|
aggregatedStatistics.put(category, new AggregatedStatistic(track.getTrackStatistics()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return aggregatedStatistics.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSportName(int position) {
|
||||||
|
return getKey(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AggregatedStatistic get(String category) {
|
||||||
|
return aggregatedStatistics.get(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getKey(int position) {
|
||||||
|
return (new ArrayList<>(aggregatedStatistics.keySet())).get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AggregatedStatistic getItem(int position) {
|
||||||
|
return (new ArrayList<>(aggregatedStatistics.values())).get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AggregatedStatistic {
|
||||||
|
private TrackStatistics trackStatistics;
|
||||||
|
private int countTracks;
|
||||||
|
|
||||||
|
public AggregatedStatistic(TrackStatistics trackStatistics) {
|
||||||
|
this.trackStatistics = trackStatistics;
|
||||||
|
this.countTracks = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackStatistics getTrackStatistics() {
|
||||||
|
return trackStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountTracks() {
|
||||||
|
return countTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(TrackStatistics statistics) {
|
||||||
|
trackStatistics.merge(statistics);
|
||||||
|
countTracks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
|
||||||
|
public class AggregatedStatisticsModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private MutableLiveData<AggregatedStatistics> aggregatedStats;
|
||||||
|
|
||||||
|
public AggregatedStatisticsModel(@NonNull Application application) {
|
||||||
|
super(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<AggregatedStatistics> getAggregatedStats() {
|
||||||
|
if (aggregatedStats == null) {
|
||||||
|
aggregatedStats = new MutableLiveData<>();
|
||||||
|
loadAggregatedStats();
|
||||||
|
}
|
||||||
|
return aggregatedStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAggregatedStats() {
|
||||||
|
new Thread(() -> {
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||||
|
List<Track> tracks = contentProviderUtils.getAllTracks();
|
||||||
|
|
||||||
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||||
|
aggregatedStatistics.aggregate(tracks);
|
||||||
|
|
||||||
|
aggregatedStats.postValue(aggregatedStatistics);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="100"
|
|
||||||
android:viewportHeight="100">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:pathData="M82.489,12.834C83.196,10.418 82.043,9.556 79.926,10.917L10.087,55.828c-2.117,1.361 -1.795,2.628 0.714,2.815L39.806,60.807c2.51,0.188 5.291,2.267 6.18,4.621l10.282,27.208c0.89,2.354 2.195,2.304 2.902,-0.111z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.56"
|
||||||
|
android:scaleY="0.56"
|
||||||
|
android:translateX="23.76"
|
||||||
|
android:translateY="23.76">
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M85.133,20.59C85.731,18.546 84.756,17.817 82.965,18.968L23.874,56.967c-1.791,1.152 -1.519,2.224 0.604,2.382L49.019,61.18c2.124,0.159 4.477,1.918 5.229,3.91l8.7,23.021c0.753,1.992 1.857,1.949 2.455,-0.094z"
|
||||||
|
android:strokeColor="#f69123">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startY="80.33152"
|
||||||
|
android:startX="38.773293"
|
||||||
|
android:endY="19.211866"
|
||||||
|
android:endX="84.4913"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:offset="0"
|
||||||
|
android:color="#FFFBB22F" />
|
||||||
|
<item
|
||||||
|
android:offset="1"
|
||||||
|
android:color="#FFF58820" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
|
||||||
|
</vector>
|
||||||
@@ -6,82 +6,95 @@
|
|||||||
|
|
||||||
<include layout="@layout/toolbar" />
|
<include layout="@layout/toolbar" />
|
||||||
|
|
||||||
<ImageView
|
<ScrollView
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="239dp"
|
android:layout_height="wrap_content">
|
||||||
android:contentDescription="@string/app_name"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_logo_color_24dp" />
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
style="@style/TextLarge"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:orientation="vertical"
|
||||||
android:gravity="center"
|
android:padding="20dp">
|
||||||
android:text="@string/app_name"
|
|
||||||
android:textSize="24sp" />
|
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="239dp"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_logo_color_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/about_text_description"
|
style="@style/TextLarge"
|
||||||
style="@style/TextMedium"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:gravity="center"
|
||||||
android:gravity="center"
|
android:text="@string/app_name"
|
||||||
android:padding="12dp" />
|
android:textSize="24sp" />
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<View style="@style/StatsHorizontalLine" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/about_text_version_name"
|
android:id="@+id/about_text_description"
|
||||||
style="@style/TextSmall"
|
style="@style/TextMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="6dp" />
|
android:padding="12dp" />
|
||||||
|
|
||||||
<TextView
|
<View style="@style/StatsHorizontalLine" />
|
||||||
android:id="@+id/about_text_version_code"
|
|
||||||
style="@style/TextSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/about_app_url"
|
android:id="@+id/about_text_version_name"
|
||||||
style="@style/TextSmall"
|
style="@style/TextSmall"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="web"
|
android:gravity="center"
|
||||||
android:gravity="center"
|
android:padding="6dp" />
|
||||||
android:padding="6dp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextSmall"
|
android:id="@+id/about_text_version_code"
|
||||||
android:layout_width="match_parent"
|
style="@style/TextSmall"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:autoLink="web"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="6dp"
|
android:padding="6dp" />
|
||||||
android:text="@string/about_history" />
|
|
||||||
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
<TextView
|
||||||
|
android:id="@+id/about_app_url"
|
||||||
|
style="@style/TextSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="web"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="6dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
style="@style/TextSmall"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:gravity="center"
|
android:layout_height="wrap_content"
|
||||||
android:padding="12dp"
|
android:gravity="center"
|
||||||
android:text="@string/about_libraries" />
|
android:padding="6dp"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:text="@string/about_history" />
|
||||||
|
|
||||||
<TextView
|
<View style="@style/StatsHorizontalLine" />
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
<TextView
|
||||||
android:autoLink="web"
|
android:layout_width="match_parent"
|
||||||
android:gravity="center"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/third_party_libraries" />
|
android:gravity="center"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="@string/about_libraries" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:text="@string/third_party_libraries" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/toolbar" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/aggregated_stats_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="24dp">
|
||||||
|
|
||||||
|
<!-- Guidelines -->
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_begin="8dp" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_end="8dp" />
|
||||||
|
|
||||||
|
<!-- Icon and sport name -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/aggregated_stats_sport_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/guideline1"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_activity_bike_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_type_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_sport_icon"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_sport_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_sport_icon"
|
||||||
|
tools:text="@string/activity_type_cycling"
|
||||||
|
android:layout_marginLeft="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_num_tracks"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_type_label"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_type_label"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_type_label"
|
||||||
|
tools:text="(1)"
|
||||||
|
android:layout_marginLeft="4dp" />
|
||||||
|
|
||||||
|
<!-- Distance -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_distance"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/guideline1"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_sport_icon"
|
||||||
|
tools:text="100000"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_distance_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_distance"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_distance"
|
||||||
|
tools:text="km" />
|
||||||
|
|
||||||
|
<!-- Time -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_time"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_sport_icon"
|
||||||
|
tools:text="00:00:00"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
<!-- Avg. rate -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_avg_rate"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/aggregated_stats_distance"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/guideline1"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/aggregated_stats_avg_rate_unit"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
tools:text="30" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_avg_rate_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_avg_rate"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_avg_rate"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/guideline2"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_avg_rate_label"
|
||||||
|
style="@style/StatsTinyLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_avg_rate"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/guideline1"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/guideline2"
|
||||||
|
tools:text="@string/stats_average_moving_speed" />
|
||||||
|
|
||||||
|
<!-- Max. rate -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_max_rate"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/aggregated_stats_time"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/aggregated_stats_max_rate_unit"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
tools:text="70" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_max_rate_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_max_rate"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_max_rate"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/guideline3"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aggregated_stats_max_rate_label"
|
||||||
|
style="@style/StatsTinyLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_max_rate"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/guideline3"
|
||||||
|
tools:text="@string/stats_max_speed" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/StatsScrollView">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!-- Guidelines -->
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_begin="8dp" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_end="8dp" />
|
||||||
|
|
||||||
|
<!-- Distance -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_distance"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_distance_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_distance_label"
|
||||||
|
tools:text="100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="km" />
|
||||||
|
|
||||||
|
<!-- Activity Type -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_activity_type_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/track_edit_activity_type_hint"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/stats_activity_type_icon"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/stats_distance_value" />
|
||||||
|
|
||||||
|
<!-- Total time -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_total_time_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_total_time"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_distance_value" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_total_time_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_total_time_label"
|
||||||
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Moving time -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_time_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_moving_time"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/stats_total_time_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_time_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
||||||
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_speed_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_value" />
|
||||||
|
|
||||||
|
<!-- Average speed/pace -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
|
||||||
|
tools:text="Avg. Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_average_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_average_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_average_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_average_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Speed max -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||||
|
tools:text="Max. Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_max_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_max_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_max_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Moving speed max -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||||
|
tools:text="Max. Moving Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_moving_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_moving_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_moving_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -0,0 +1,430 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/StatsScrollView">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!-- Guidelines -->
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_begin="8dp" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/guideline3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_end="8dp" />
|
||||||
|
|
||||||
|
<!-- Distance -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_distance"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_distance_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_distance_label"
|
||||||
|
tools:text="100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_distance_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="km" />
|
||||||
|
|
||||||
|
<!-- Activity Type -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_activity_type_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/track_edit_activity_type_hint"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/stats_activity_type_icon"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_distance_value"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/stats_distance_value" />
|
||||||
|
|
||||||
|
<!-- Total time -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_total_time_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_total_time"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_distance_value" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_total_time_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_total_time_label"
|
||||||
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Moving time -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_time_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/stats_moving_time"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/stats_total_time_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_time_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
||||||
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_speed_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_value" />
|
||||||
|
|
||||||
|
<!-- Speed/pace -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
|
||||||
|
tools:text="Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Average speed/pace -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
|
||||||
|
tools:text="Avg. Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_average_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_average_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_average_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_average_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_average_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Speed max -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||||
|
tools:text="Max. Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_max_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_max_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_max_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_max_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Moving speed max -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||||
|
tools:text="Max. Moving Speed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_moving_speed_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_label"
|
||||||
|
tools:text="30.00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_moving_speed_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_moving_speed_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_moving_speed_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_elevation_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_value" />
|
||||||
|
|
||||||
|
<!-- Current elevation -->
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/stats_elevation_current_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="stats_elevation_current_label,stats_elevation_current_value,stats_elevation_current_unit,stats_elevation_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_current_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
android:text="@string/stats_elevation"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_current_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_elevation_current_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_current_label"
|
||||||
|
tools:text="100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_current_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_current_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_elevation_current_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="m" />
|
||||||
|
|
||||||
|
<!-- Coordinate group -->
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/stats_coordinate_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="stats_latitude_label,stats_latitude_value,stats_longitude_label,stats_longitude_value,stats_coordinate_horizontal_line" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_coordinate_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_current_value" />
|
||||||
|
|
||||||
|
<!-- Latitude -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_latitude_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
android:text="@string/stats_latitude"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_latitude_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_latitude_label"
|
||||||
|
tools:text="0.834937848" />
|
||||||
|
|
||||||
|
<!-- Longitude -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_longitude_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
android:text="@string/stats_longitude"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_longitude_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_longitude_label"
|
||||||
|
tools:text="0.834937848" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_sensor_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_latitude_value" />
|
||||||
|
|
||||||
|
<!-- Heart rate -->
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/stats_sensor_heart_rate_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="stats_sensor_heart_rate_label,stats_sensor_heart_rate_sensor_value,stats_sensor_heart_rate_value,stats_sensor_heart_rate_unit" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_heart_rate_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/sensor_state_heart_rate"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_sensor_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_heart_rate_sensor_value"
|
||||||
|
style="@style/StatsTinyValue"
|
||||||
|
android:text="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_heart_rate_label"
|
||||||
|
tools:text="90" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_heart_rate_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_heart_rate_sensor_value"
|
||||||
|
tools:text="90" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_heart_rate_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
android:text="@string/sensor_unit_beats_per_minute"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_heart_rate_value" />
|
||||||
|
|
||||||
|
<!-- Cadence -->
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/stats_sensor_cadence_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="stats_sensor_cadence_label,stats_sensor_cadence_sensor_value,stats_sensor_cadence_value,stats_sensor_cadence_unit" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_cadence_label"
|
||||||
|
style="@style/StatsLargeLabel"
|
||||||
|
android:text="@string/sensor_state_cadence"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_sensor_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_cadence_sensor_value"
|
||||||
|
style="@style/StatsTinyValue"
|
||||||
|
android:text="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_cadence_label"
|
||||||
|
tools:text="90" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_cadence_value"
|
||||||
|
style="@style/StatsLargeValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_cadence_sensor_value"
|
||||||
|
tools:text="90" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_sensor_cadence_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
android:text="@string/sensor_unit_rounds_per_minute"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_sensor_cadence_value" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
Copyright 2010 Google Inc.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
style="@style/StatsScrollView">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- Distance and activity type -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<!-- Distance -->
|
|
||||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsLargeLabel"
|
|
||||||
android:text="@string/stats_distance" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_distance_value"
|
|
||||||
style="@style/StatsLargeValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_distance_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Activity type -->
|
|
||||||
<include
|
|
||||||
android:id="@+id/stats_activity_type"
|
|
||||||
layout="@layout/stats_activity_type" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Total time and moving time -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<!-- Total time -->
|
|
||||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsLargeLabel"
|
|
||||||
android:text="@string/stats_total_time" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_total_time_value"
|
|
||||||
style="@style/StatsLargeValue" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Moving time -->
|
|
||||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsLargeLabel"
|
|
||||||
android:text="@string/stats_moving_time" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_moving_time_value"
|
|
||||||
style="@style/StatsLargeValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
|
||||||
<View style="@style/StatsHorizontalLine" />
|
|
||||||
|
|
||||||
<!-- Speed/pace and average -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<!-- Speed/pace -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/stats_speed"
|
|
||||||
style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_speed_label"
|
|
||||||
style="@style/StatsSmallLabel" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_speed_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_speed_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Average -->
|
|
||||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_average_speed_label"
|
|
||||||
style="@style/StatsSmallLabel" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_average_speed_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_average_speed_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Speed max and moving -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<!-- Speed max -->
|
|
||||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_max_speed_label"
|
|
||||||
style="@style/StatsSmallLabel" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_max_speed_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_max_speed_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Speed moving -->
|
|
||||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_moving_speed_label"
|
|
||||||
style="@style/StatsSmallLabel" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_moving_speed_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_moving_speed_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/stats_elevation_separator"
|
|
||||||
style="@style/StatsHorizontalLine" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/stats_elevation_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
|
||||||
<!-- This is a placeholder -->
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Elevation -->
|
|
||||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallLabel"
|
|
||||||
android:text="@string/stats_elevation" />
|
|
||||||
|
|
||||||
<TableLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center">
|
|
||||||
|
|
||||||
<!-- Current elevation -->
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/StatsSmallValueContainer"
|
|
||||||
android:layout_gravity="start|bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_current_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_elevation_current_unit"
|
|
||||||
style="@style/StatsUnit" />
|
|
||||||
</LinearLayout>
|
|
||||||
</TableLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
|
||||||
<View
|
|
||||||
android:id="@+id/stats_coordinate_separator"
|
|
||||||
style="@style/StatsHorizontalLine" />
|
|
||||||
|
|
||||||
<!-- Latitude and longitude -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/stats_coordinate_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallLabel"
|
|
||||||
android:text="@string/stats_latitude" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_latitude_value"
|
|
||||||
style="@style/StatsSmallValue" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsSmallLabel"
|
|
||||||
android:text="@string/stats_longitude" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_longitude_value"
|
|
||||||
style="@style/StatsSmallValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/stats_sensor_separator"
|
|
||||||
style="@style/StatsHorizontalLine" />
|
|
||||||
|
|
||||||
<!-- Sensor data -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/stats_sensor_heart_rate_container"
|
|
||||||
style="@style/StatsLargeItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsLargeLabel"
|
|
||||||
android:text="@string/sensor_state_heart_rate" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_sensor_heart_rate_sensor_value"
|
|
||||||
style="@style/StatsTinyValue"
|
|
||||||
android:text="@string/value_unknown" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_sensor_heart_rate_value"
|
|
||||||
style="@style/StatsLargeValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsUnit"
|
|
||||||
android:text="@string/sensor_unit_beats_per_minute" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/stats_sensor_cadence_container"
|
|
||||||
style="@style/StatsLargeItemContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsLargeLabel"
|
|
||||||
android:text="@string/sensor_state_cadence" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_sensor_cadence_sensor_value"
|
|
||||||
style="@style/StatsTinyValue"
|
|
||||||
android:text="@string/value_unknown" />
|
|
||||||
|
|
||||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/stats_sensor_cadence_value"
|
|
||||||
style="@style/StatsLargeValue"
|
|
||||||
android:value="@string/value_unknown" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/StatsUnit"
|
|
||||||
android:text="@string/sensor_unit_rounds_per_minute" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
@@ -32,6 +32,4 @@ limitations under the License.
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<include layout="@layout/track_controller" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/toolbar" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/track_detail_activity_tablayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:tabIndicatorColor="@color/holo_orange_dark" />
|
||||||
|
|
||||||
|
<androidx.viewpager.widget.ViewPager
|
||||||
|
android:id="@+id/track_detail_activity_view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<include layout="@layout/track_controller" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -15,12 +15,6 @@ limitations under the License.
|
|||||||
-->
|
-->
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
|
||||||
android:id="@+id/track_detail_insert_marker"
|
|
||||||
android:icon="@drawable/ic_marker_add_24dp"
|
|
||||||
android:orderInCategory="1"
|
|
||||||
app:showAsAction="ifRoom"
|
|
||||||
android:title="@string/menu_insert_marker" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_detail_menu_show_on_map"
|
android:id="@+id/track_detail_menu_show_on_map"
|
||||||
android:icon="@drawable/ic_map_24dp"
|
android:icon="@drawable/ic_map_24dp"
|
||||||
|
|||||||
@@ -21,10 +21,17 @@ limitations under the License.
|
|||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<!-- Icon and title is overwritten by TrackListActivity -->
|
<!-- Icon and title is overwritten by TrackListActivity -->
|
||||||
|
<!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 -->
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_list_start_gps"
|
android:id="@+id/track_list_start_gps"
|
||||||
android:icon="@drawable/ic_gps_off_24dp"
|
android:icon="@drawable/ic_gps_off_24dp"
|
||||||
android:title="@string/menu_start_gps"
|
android:title="@string/menu_start_gps"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_list_aggregated_stats"
|
||||||
|
android:icon="@drawable/ic_statistics_24dp"
|
||||||
|
android:title="@string/menu_aggregated_statistics"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_list_markers"
|
android:id="@+id/track_list_markers"
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Copyright 2011 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_detail_insert_marker"
|
||||||
|
android:icon="@drawable/ic_marker_add_24dp"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_insert_marker" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_detail_menu_show_on_map"
|
||||||
|
android:icon="@drawable/ic_map_24dp"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_show_on_map" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_detail_markers"
|
||||||
|
android:icon="@drawable/ic_marker_show_24dp"
|
||||||
|
android:orderInCategory="5"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:title="@string/menu_markers" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_detail_edit"
|
||||||
|
android:icon="@drawable/ic_edit_24dp"
|
||||||
|
android:orderInCategory="9"
|
||||||
|
android:title="@string/menu_edit" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_detail_settings"
|
||||||
|
android:icon="@drawable/ic_settings_24dp"
|
||||||
|
android:orderInCategory="13"
|
||||||
|
android:title="@string/menu_settings" />
|
||||||
|
</menu>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_logo_color_108dp" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_debug_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_logo_color_108dp" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
@@ -102,14 +102,11 @@
|
|||||||
<string name="description_track_segment">جزء</string>
|
<string name="description_track_segment">جزء</string>
|
||||||
<string name="export_all_option">الكل كـ %1$s</string>
|
<string name="export_all_option">الكل كـ %1$s</string>
|
||||||
<string name="export_all_title">تصدير إلى وحدة التخزين الخارجية</string>
|
<string name="export_all_title">تصدير إلى وحدة التخزين الخارجية</string>
|
||||||
<string name="export_external_storage">وحدة التخزين الخارجية</string>
|
<string name="export_error">تم تصدير %1$d من %2$s إلى %3$s</string>
|
||||||
<string name="export_external_storage_error">تم تصدير %1$d من %2$s إلى %3$s</string>
|
<string name="export_option">كـ %1$s إلى %2$s</string>
|
||||||
<string name="export_external_storage_option">كـ %1$s إلى %2$s</string>
|
<string name="export_progress_message">جارٍ التصدير إلى %1$s…</string>
|
||||||
<string name="export_external_storage_progress_message">جارٍ التصدير إلى %1$s…</string>
|
<string name="export_success">تم تصدير %1$s إلى %2$s</string>
|
||||||
<string name="export_external_storage_success">تم تصدير %1$s إلى %2$s</string>
|
|
||||||
<string name="export_title">تصدير إلى…</string>
|
<string name="export_title">تصدير إلى…</string>
|
||||||
<string name="external_storage_not_available">لا تتوفر وحدة تخزين خارجية</string>
|
|
||||||
<string name="external_storage_not_writable">وحدة التخزين الخارجية غير قابلة للكتابة</string>
|
|
||||||
<string name="files_few">%1$d ملفات</string>
|
<string name="files_few">%1$d ملفات</string>
|
||||||
<string name="files_many">%1$d ملفًا</string>
|
<string name="files_many">%1$d ملفًا</string>
|
||||||
<string name="files_one">1 ملف</string>
|
<string name="files_one">1 ملف</string>
|
||||||
@@ -136,13 +133,9 @@
|
|||||||
<string name="generic_yes">نعم</string>
|
<string name="generic_yes">نعم</string>
|
||||||
<string name="gps_location_access">\"الدخول إلى الموقع\"</string>
|
<string name="gps_location_access">\"الدخول إلى الموقع\"</string>
|
||||||
<string name="gps_disabled">نظام تحديد المواقع العالمي (GPS) غير ممكّن. انقر هنا للانتقال إلى %1$s. اضغط على زر الرجوع للعودة.</string>
|
<string name="gps_disabled">نظام تحديد المواقع العالمي (GPS) غير ممكّن. انقر هنا للانتقال إلى %1$s. اضغط على زر الرجوع للعودة.</string>
|
||||||
<string name="gps_disabled_location_mode">حدد وضع تحديد الموقع بدقة عالية. انقر هنا للانتقال إلى %1$s. اضغط على زر الرجوع للعودة.</string>
|
|
||||||
<string name="gps_disabled_my_location">لا يمكن تحديد الموقع الحالي. تستطيع تمكين مصادر أكثر للموقع على %1$s وتأكد من أنك في مكان مفتوح. حاول مرة أخرى في وقت لاحق.</string>
|
<string name="gps_disabled_my_location">لا يمكن تحديد الموقع الحالي. تستطيع تمكين مصادر أكثر للموقع على %1$s وتأكد من أنك في مكان مفتوح. حاول مرة أخرى في وقت لاحق.</string>
|
||||||
<string name="gps_disabled_my_location_location_mode">لا يمكن تحديد الموقع الحالي. يمكنك تحديد وضع تحديد الموقع بدقة عالية على %1$s وتأكد من أنك في مكان مفتوح. حاول مرة أخرى في وقت لاحق.</string>
|
|
||||||
<string name="gps_not_found">لم يتم العثور على نظام تحديد المواقع (GPS) على الجهاز</string>
|
<string name="gps_not_found">لم يتم العثور على نظام تحديد المواقع (GPS) على الجهاز</string>
|
||||||
<string name="gps_starting">بدء نظام تحديد المواقع العالمي (GPS)</string>
|
<string name="gps_starting">بدء نظام تحديد المواقع العالمي (GPS)</string>
|
||||||
<string name="gps_starting_error">تعذر تشغيل نظام تحديد المواقع العالمي (GPS)</string>
|
|
||||||
<string name="gps_stopping">جارٍ إيقاف نظام تحديد المواقع العالمي (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="image_application">تطبيق</string>
|
<string name="image_application">تطبيق</string>
|
||||||
@@ -181,20 +174,13 @@
|
|||||||
<string name="marker_list_empty_message">ليس لديك أية علامات</string>
|
<string name="marker_list_empty_message">ليس لديك أية علامات</string>
|
||||||
<string name="menu_aggregated_statistics">الإحصاءات المجمعة</string>
|
<string name="menu_aggregated_statistics">الإحصاءات المجمعة</string>
|
||||||
<string name="menu_delete">حذف</string>
|
<string name="menu_delete">حذف</string>
|
||||||
<string name="menu_delete_all">حذف الكل</string>
|
|
||||||
<string name="menu_edit">تعديل</string>
|
<string name="menu_edit">تعديل</string>
|
||||||
<string name="menu_export">تصدير</string>
|
|
||||||
<string name="menu_export_all">تصدير جميع المسارات</string>
|
<string name="menu_export_all">تصدير جميع المسارات</string>
|
||||||
<string name="menu_feedback">تعليقات</string>
|
|
||||||
<string name="menu_help">مساعدة</string>
|
<string name="menu_help">مساعدة</string>
|
||||||
<string name="menu_help_feedback">المساعدة والتعليقات</string>
|
|
||||||
<string name="menu_help_forum">منتدى المساعدة</string>
|
|
||||||
<string name="menu_import_all">استيراد جميع المسارات</string>
|
<string name="menu_import_all">استيراد جميع المسارات</string>
|
||||||
<string name="menu_insert_marker">إدراج مُحدد</string>
|
<string name="menu_insert_marker">إدراج مُحدد</string>
|
||||||
<string name="menu_insert_photo">جارٍ إدراج الصورة</string>
|
<string name="menu_insert_photo">جارٍ إدراج الصورة</string>
|
||||||
<string name="menu_map">الخريطة</string>
|
|
||||||
<string name="menu_markers">العلامات</string>
|
<string name="menu_markers">العلامات</string>
|
||||||
<string name="menu_play">تشغيل</string>
|
|
||||||
<string name="menu_search">بحث</string>
|
<string name="menu_search">بحث</string>
|
||||||
<string name="menu_select_all">تحديد الكل</string>
|
<string name="menu_select_all">تحديد الكل</string>
|
||||||
<string name="menu_sensor_state">حالة المستشعر</string>
|
<string name="menu_sensor_state">حالة المستشعر</string>
|
||||||
@@ -211,19 +197,14 @@
|
|||||||
<string name="search_global_description">المسارات والمحددات</string>
|
<string name="search_global_description">المسارات والمحددات</string>
|
||||||
<string name="search_list_empty_message">لم يطابق بحثك أي مسارات أو علامات</string>
|
<string name="search_list_empty_message">لم يطابق بحثك أي مسارات أو علامات</string>
|
||||||
<string name="search_list_marker_track_location">في المسار ‘%1$s’.</string>
|
<string name="search_list_marker_track_location">في المسار ‘%1$s’.</string>
|
||||||
<string name="search_voice_prompt">انطق اسم مسار أو علامة</string>
|
|
||||||
<string name="sensor_state_battery">مستوى شحن البطارية</string>
|
<string name="sensor_state_battery">مستوى شحن البطارية</string>
|
||||||
<string name="sensor_state_cadence">الإيقاع</string>
|
<string name="sensor_state_cadence">الإيقاع</string>
|
||||||
<string name="sensor_state_cadence_value">%1$d دورة في الدقيقة</string>
|
<string name="sensor_state_cadence_value">%1$d دورة في الدقيقة</string>
|
||||||
<string name="sensor_state_connected">متصل</string>
|
|
||||||
<string name="sensor_state_connecting">اتصال</string>
|
|
||||||
<string name="sensor_state_disconnected">غير متصل</string>
|
<string name="sensor_state_disconnected">غير متصل</string>
|
||||||
<string name="sensor_state_heart_rate">معدل ضربات القلب</string>
|
<string name="sensor_state_heart_rate">معدل ضربات القلب</string>
|
||||||
<string name="sensor_state_heart_rate_value">%1$d نبضة في الدقيقة</string>
|
<string name="sensor_state_heart_rate_value">%1$d نبضة في الدقيقة</string>
|
||||||
<string name="sensor_state_last_sensor_time">آخر وقت للمستشعر</string>
|
|
||||||
<string name="sensor_state_power">الطاقة</string>
|
<string name="sensor_state_power">الطاقة</string>
|
||||||
<string name="sensor_state_power_value">%1$d واط</string>
|
<string name="sensor_state_power_value">%1$d واط</string>
|
||||||
<string name="sensor_state_sending">إرسال</string>
|
|
||||||
<string name="settings_not_while_recording">غير متاح أثناء تسجيل مسار</string>
|
<string name="settings_not_while_recording">غير متاح أثناء تسجيل مسار</string>
|
||||||
<string name="settings_show">إظهار</string>
|
<string name="settings_show">إظهار</string>
|
||||||
<string name="settings_advanced">خيارات متقدمة</string>
|
<string name="settings_advanced">خيارات متقدمة</string>
|
||||||
@@ -252,22 +233,13 @@
|
|||||||
<string name="settings_reset_confirm_title">إعادة التعيين الآن؟</string>
|
<string name="settings_reset_confirm_title">إعادة التعيين الآن؟</string>
|
||||||
<string name="settings_reset_done">لقد تمت إعادة جميع الإعدادات إلى القيم الافتراضية</string>
|
<string name="settings_reset_done">لقد تمت إعادة جميع الإعدادات إلى القيم الافتراضية</string>
|
||||||
<string name="settings_sensor">المستشعر</string>
|
<string name="settings_sensor">المستشعر</string>
|
||||||
<string name="settings_sensor_ant_reset_bike_cadence_sensor">إعادة ضبط إيقاع الدراجة</string>
|
|
||||||
<string name="settings_sensor_ant_reset_combined_bike_sensor">إعادة ضبط الدراجة المجمعة</string>
|
|
||||||
<string name="settings_sensor_ant_reset_heart_rate_monitor">إعادة ضبط معدل نبضات القلب</string>
|
|
||||||
<string name="settings_sensor_ant_reset_speed_distance_monitor">إعادة تعيين لوحة القدم</string>
|
|
||||||
<string name="settings_sensor_bluetooth">البلوتوث</string>
|
<string name="settings_sensor_bluetooth">البلوتوث</string>
|
||||||
<string name="settings_sensor_bluetooth_pairing">اقتران البلوتوث</string>
|
|
||||||
<string name="settings_sensor_bluetooth_pairing_summary">فتح إعدادات بلوتوث في Android لإقران الجهاز</string>
|
|
||||||
<string name="settings_sensor_bluetooth_sensor">مستشعر بلوتوث</string>
|
<string name="settings_sensor_bluetooth_sensor">مستشعر بلوتوث</string>
|
||||||
<string name="settings_sensor_connected">تم التوصيل بـ %1$s</string>
|
|
||||||
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
|
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
|
||||||
<string name="settings_stats_rate_title">المعدل المفضل</string>
|
<string name="settings_stats_rate_title">المعدل المفضل</string>
|
||||||
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
||||||
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
|
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
|
||||||
<string name="settings_stats_units_title">الوحدات المفضلة</string>
|
<string name="settings_stats_units_title">الوحدات المفضلة</string>
|
||||||
<string name="share_track_emails_hint">عناوين البريد الإلكتروني</string>
|
|
||||||
<string name="share_track_picker_title">حدد تطبيقًا</string>
|
|
||||||
<string name="share_track_share_file">مشاركة ملف مسار</string>
|
<string name="share_track_share_file">مشاركة ملف مسار</string>
|
||||||
<string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.\n\nلعرض هذا المسار، استخدام برنامج Google Earth أو غيره من البرامج تصميم الخرائط.\n\n%1$s</string>
|
<string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.\n\nلعرض هذا المسار، استخدام برنامج Google Earth أو غيره من البرامج تصميم الخرائط.\n\n%1$s</string>
|
||||||
<string name="share_track_subject">أريد مشاركة مسار معك</string>
|
<string name="share_track_subject">أريد مشاركة مسار معك</string>
|
||||||
@@ -302,9 +274,6 @@
|
|||||||
<string name="track_edit_activity_type_hint">نوع النشاط</string>
|
<string name="track_edit_activity_type_hint">نوع النشاط</string>
|
||||||
<string name="track_edit_new_track_title">مسار جديد</string>
|
<string name="track_edit_new_track_title">مسار جديد</string>
|
||||||
<string name="track_list_empty_message">بدء تسجيل مغامرتك التالية هنا</string>
|
<string name="track_list_empty_message">بدء تسجيل مغامرتك التالية هنا</string>
|
||||||
<string name="track_list_record_error">يتعذر بدء التسجيل. ليس هناك إشارة بنظام تحديد المواقع العالمي (GPS). حاول مرة أخرى.</string>
|
|
||||||
<string name="track_list_record_success">جارٍ الآن تسجيل مسار جديد</string>
|
|
||||||
<string name="track_widget_config_title">تهيئة عناصر الأداة</string>
|
|
||||||
<string name="tracks_few">%1$d مسارات</string>
|
<string name="tracks_few">%1$d مسارات</string>
|
||||||
<string name="tracks_many">%1$d مسارًا</string>
|
<string name="tracks_many">%1$d مسارًا</string>
|
||||||
<string name="tracks_one">1 مسار</string>
|
<string name="tracks_one">1 مسار</string>
|
||||||
@@ -330,10 +299,8 @@
|
|||||||
<string name="value_integer_feet">%1$d قدم</string>
|
<string name="value_integer_feet">%1$d قدم</string>
|
||||||
<string name="value_integer_feet_excellent_gps">%1$d قدم (إشارة نظام تحديد المواقع ممتازة)</string>
|
<string name="value_integer_feet_excellent_gps">%1$d قدم (إشارة نظام تحديد المواقع ممتازة)</string>
|
||||||
<string name="value_integer_feet_recommended">%1$d قدم (موصى به)</string>
|
<string name="value_integer_feet_recommended">%1$d قدم (موصى به)</string>
|
||||||
<string name="value_integer_kilobyte">%1$d كيلوبايت</string>
|
|
||||||
<string name="value_integer_kilometer">%1$d كم</string>
|
<string name="value_integer_kilometer">%1$d كم</string>
|
||||||
<string name="value_integer_kilometer_hour">%1$d كم/ساعة</string>
|
<string name="value_integer_kilometer_hour">%1$d كم/ساعة</string>
|
||||||
<string name="value_integer_megabyte">%1$d ميغابايت</string>
|
|
||||||
<string name="value_integer_meter">%1$d م</string>
|
<string name="value_integer_meter">%1$d م</string>
|
||||||
<string name="value_integer_meter_excellent_gps">%1$d م (إشارة نظام تحديد المواقع ممتازة)</string>
|
<string name="value_integer_meter_excellent_gps">%1$d م (إشارة نظام تحديد المواقع ممتازة)</string>
|
||||||
<string name="value_integer_meter_poor_gps">%1$d م (إشارة نظام تحديد المواقع سيئة)</string>
|
<string name="value_integer_meter_poor_gps">%1$d م (إشارة نظام تحديد المواقع سيئة)</string>
|
||||||
@@ -343,7 +310,6 @@
|
|||||||
<string name="value_integer_minute">%1$d دقيقة</string>
|
<string name="value_integer_minute">%1$d دقيقة</string>
|
||||||
<string name="value_integer_percent">%1$d %%</string>
|
<string name="value_integer_percent">%1$d %%</string>
|
||||||
<string name="value_integer_second">%1$d ث</string>
|
<string name="value_integer_second">%1$d ث</string>
|
||||||
<string name="value_never">مطلقًا</string>
|
|
||||||
<string name="value_none">لا شيء</string>
|
<string name="value_none">لا شيء</string>
|
||||||
<string name="value_off">إيقاف</string>
|
<string name="value_off">إيقاف</string>
|
||||||
<string name="value_smallest">الأصغر</string>
|
<string name="value_smallest">الأصغر</string>
|
||||||
|
|||||||
@@ -102,14 +102,11 @@
|
|||||||
<string name="description_track_segment">Segment</string>
|
<string name="description_track_segment">Segment</string>
|
||||||
<string name="export_all_option">All as %1$s</string>
|
<string name="export_all_option">All as %1$s</string>
|
||||||
<string name="export_all_title">Export to external storage</string>
|
<string name="export_all_title">Export to external storage</string>
|
||||||
<string name="export_external_storage">External storage</string>
|
<string name="export_error">Exported %1$d of %2$s to %3$s</string>
|
||||||
<string name="export_external_storage_error">Exported %1$d of %2$s to %3$s</string>
|
<string name="export_option">as %1$s to %2$s</string>
|
||||||
<string name="export_external_storage_option">as %1$s to %2$s</string>
|
<string name="export_progress_message">Exporting to %1$s…</string>
|
||||||
<string name="export_external_storage_progress_message">Exporting to %1$s…</string>
|
<string name="export_success">Exported %1$s to %2$s</string>
|
||||||
<string name="export_external_storage_success">Exported %1$s to %2$s</string>
|
|
||||||
<string name="export_title">Export to…</string>
|
<string name="export_title">Export to…</string>
|
||||||
<string name="external_storage_not_available">External storage not available</string>
|
|
||||||
<string name="external_storage_not_writable">External storage not writable</string>
|
|
||||||
<string name="files_few">%1$d files</string>
|
<string name="files_few">%1$d files</string>
|
||||||
<string name="files_many">%1$d files</string>
|
<string name="files_many">%1$d files</string>
|
||||||
<string name="files_one">1 file</string>
|
<string name="files_one">1 file</string>
|
||||||
@@ -136,13 +133,9 @@
|
|||||||
<string name="generic_yes">Yes</string>
|
<string name="generic_yes">Yes</string>
|
||||||
<string name="gps_location_access">\'Location access\'</string>
|
<string name="gps_location_access">\'Location access\'</string>
|
||||||
<string name="gps_disabled">GPS not enabled. Tap here to go to %1$s. Press the back button to return.</string>
|
<string name="gps_disabled">GPS not enabled. Tap here to go to %1$s. Press the back button to return.</string>
|
||||||
<string name="gps_disabled_location_mode">Select high-accuracy location mode. Tap here to go to %1$s. Press the back button to return.</string>
|
|
||||||
<string name="gps_disabled_my_location">Current location cannot be determined. Enable more location sources at %1$s and make sure the sky is visible. Try again later.</string>
|
<string name="gps_disabled_my_location">Current location cannot be determined. Enable more location sources at %1$s and make sure the sky is visible. Try again later.</string>
|
||||||
<string name="gps_disabled_my_location_location_mode">Current location cannot be determined. Select the high-accuracy location mode at %1$s and make sure the sky is visible. Try again later.</string>
|
|
||||||
<string name="gps_not_found">No GPS found on the device</string>
|
<string name="gps_not_found">No GPS found on the device</string>
|
||||||
<string name="gps_starting">Starting GPS</string>
|
<string name="gps_starting">Starting GPS</string>
|
||||||
<string name="gps_starting_error">Unable to start GPS</string>
|
|
||||||
<string name="gps_stopping">Stopping GPS</string>
|
|
||||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
||||||
<string name="gps_wait_for_signal">Waiting for GPS signal</string>
|
<string name="gps_wait_for_signal">Waiting for GPS signal</string>
|
||||||
<string name="image_application">Application</string>
|
<string name="image_application">Application</string>
|
||||||
@@ -181,20 +174,13 @@
|
|||||||
<string name="marker_list_empty_message">You don\'t have any markers</string>
|
<string name="marker_list_empty_message">You don\'t have any markers</string>
|
||||||
<string name="menu_aggregated_statistics">Aggregated stats</string>
|
<string name="menu_aggregated_statistics">Aggregated stats</string>
|
||||||
<string name="menu_delete">Delete</string>
|
<string name="menu_delete">Delete</string>
|
||||||
<string name="menu_delete_all">Delete all</string>
|
|
||||||
<string name="menu_edit">Edit</string>
|
<string name="menu_edit">Edit</string>
|
||||||
<string name="menu_export">Export</string>
|
|
||||||
<string name="menu_export_all">Export all</string>
|
<string name="menu_export_all">Export all</string>
|
||||||
<string name="menu_feedback">Feedback</string>
|
|
||||||
<string name="menu_help">Help</string>
|
<string name="menu_help">Help</string>
|
||||||
<string name="menu_help_feedback">Help & feedback</string>
|
|
||||||
<string name="menu_help_forum">Help forum</string>
|
|
||||||
<string name="menu_import_all">Import all</string>
|
<string name="menu_import_all">Import all</string>
|
||||||
<string name="menu_insert_marker">Insert marker</string>
|
<string name="menu_insert_marker">Insert marker</string>
|
||||||
<string name="menu_insert_photo">Insert photo</string>
|
<string name="menu_insert_photo">Insert photo</string>
|
||||||
<string name="menu_map">Map</string>
|
|
||||||
<string name="menu_markers">Markers</string>
|
<string name="menu_markers">Markers</string>
|
||||||
<string name="menu_play">Play</string>
|
|
||||||
<string name="menu_search">Search</string>
|
<string name="menu_search">Search</string>
|
||||||
<string name="menu_select_all">Select all</string>
|
<string name="menu_select_all">Select all</string>
|
||||||
<string name="menu_sensor_state">Sensor state</string>
|
<string name="menu_sensor_state">Sensor state</string>
|
||||||
@@ -211,19 +197,14 @@
|
|||||||
<string name="search_global_description">Tracks and markers</string>
|
<string name="search_global_description">Tracks and markers</string>
|
||||||
<string name="search_list_empty_message">No tracks or markers matched your search</string>
|
<string name="search_list_empty_message">No tracks or markers matched your search</string>
|
||||||
<string name="search_list_marker_track_location">In track ‘%1$s’.</string>
|
<string name="search_list_marker_track_location">In track ‘%1$s’.</string>
|
||||||
<string name="search_voice_prompt">Say the name of a track or marker</string>
|
|
||||||
<string name="sensor_state_battery">Battery level</string>
|
<string name="sensor_state_battery">Battery level</string>
|
||||||
<string name="sensor_state_cadence">Cadence</string>
|
<string name="sensor_state_cadence">Cadence</string>
|
||||||
<string name="sensor_state_cadence_value">%1$d rpm</string>
|
<string name="sensor_state_cadence_value">%1$d rpm</string>
|
||||||
<string name="sensor_state_connected">Connected</string>
|
|
||||||
<string name="sensor_state_connecting">Connecting</string>
|
|
||||||
<string name="sensor_state_disconnected">Disconnected</string>
|
<string name="sensor_state_disconnected">Disconnected</string>
|
||||||
<string name="sensor_state_heart_rate">Heart rate</string>
|
<string name="sensor_state_heart_rate">Heart rate</string>
|
||||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||||
<string name="sensor_state_last_sensor_time">Last sensor time</string>
|
|
||||||
<string name="sensor_state_power">Power</string>
|
<string name="sensor_state_power">Power</string>
|
||||||
<string name="sensor_state_power_value">%1$d W</string>
|
<string name="sensor_state_power_value">%1$d W</string>
|
||||||
<string name="sensor_state_sending">Sending</string>
|
|
||||||
<string name="settings_not_while_recording">Not available while recording a track</string>
|
<string name="settings_not_while_recording">Not available while recording a track</string>
|
||||||
<string name="settings_show">Show</string>
|
<string name="settings_show">Show</string>
|
||||||
<string name="settings_advanced">Advanced</string>
|
<string name="settings_advanced">Advanced</string>
|
||||||
@@ -252,22 +233,13 @@
|
|||||||
<string name="settings_reset_confirm_title">Reset now?</string>
|
<string name="settings_reset_confirm_title">Reset now?</string>
|
||||||
<string name="settings_reset_done">All settings have been reverted to default values</string>
|
<string name="settings_reset_done">All settings have been reverted to default values</string>
|
||||||
<string name="settings_sensor">Sensor</string>
|
<string name="settings_sensor">Sensor</string>
|
||||||
<string name="settings_sensor_ant_reset_bike_cadence_sensor">Reset bike cadence</string>
|
|
||||||
<string name="settings_sensor_ant_reset_combined_bike_sensor">Reset combined bike</string>
|
|
||||||
<string name="settings_sensor_ant_reset_heart_rate_monitor">Reset heart rate</string>
|
|
||||||
<string name="settings_sensor_ant_reset_speed_distance_monitor">Reset foot pod</string>
|
|
||||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||||
<string name="settings_sensor_bluetooth_pairing">Bluetooth pairing</string>
|
|
||||||
<string name="settings_sensor_bluetooth_pairing_summary">Open Android\'s Bluetooth settings to pair a sensor</string>
|
|
||||||
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
|
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
|
||||||
<string name="settings_sensor_connected">Connected to %1$s</string>
|
|
||||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||||
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
|
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||||
<string name="settings_stats_units_title">Preferred units</string>
|
<string name="settings_stats_units_title">Preferred units</string>
|
||||||
<string name="share_track_emails_hint">Email addresses</string>
|
|
||||||
<string name="share_track_picker_title">Select an application</string>
|
|
||||||
<string name="share_track_share_file">Share track file</string>
|
<string name="share_track_share_file">Share track file</string>
|
||||||
<string name="share_track_share_file_body">I think you might be interested in this track.\n\nTo view this track, use Google Earth or other
|
<string name="share_track_share_file_body">I think you might be interested in this track.\n\nTo view this track, use Google Earth or other
|
||||||
mapping software.\n\n%1$s</string>
|
mapping software.\n\n%1$s</string>
|
||||||
@@ -303,9 +275,6 @@
|
|||||||
<string name="track_edit_activity_type_hint">Activity type</string>
|
<string name="track_edit_activity_type_hint">Activity type</string>
|
||||||
<string name="track_edit_new_track_title">New Track</string>
|
<string name="track_edit_new_track_title">New Track</string>
|
||||||
<string name="track_list_empty_message">Start recording your next adventure here</string>
|
<string name="track_list_empty_message">Start recording your next adventure here</string>
|
||||||
<string name="track_list_record_error">Unable to start recording. No GPS signal. Try again.</string>
|
|
||||||
<string name="track_list_record_success">Now recording a new track</string>
|
|
||||||
<string name="track_widget_config_title">Configure Widget Items</string>
|
|
||||||
<string name="tracks_few">%1$d tracks</string>
|
<string name="tracks_few">%1$d tracks</string>
|
||||||
<string name="tracks_many">%1$d tracks</string>
|
<string name="tracks_many">%1$d tracks</string>
|
||||||
<string name="tracks_one">1 track</string>
|
<string name="tracks_one">1 track</string>
|
||||||
@@ -331,10 +300,8 @@
|
|||||||
<string name="value_integer_feet">%1$d ft</string>
|
<string name="value_integer_feet">%1$d ft</string>
|
||||||
<string name="value_integer_feet_excellent_gps">%1$d ft (excellent GPS)</string>
|
<string name="value_integer_feet_excellent_gps">%1$d ft (excellent GPS)</string>
|
||||||
<string name="value_integer_feet_recommended">%1$d ft (recommended)</string>
|
<string name="value_integer_feet_recommended">%1$d ft (recommended)</string>
|
||||||
<string name="value_integer_kilobyte">%1$d kB</string>
|
|
||||||
<string name="value_integer_kilometer">%1$d km</string>
|
<string name="value_integer_kilometer">%1$d km</string>
|
||||||
<string name="value_integer_kilometer_hour">%1$d km/h</string>
|
<string name="value_integer_kilometer_hour">%1$d km/h</string>
|
||||||
<string name="value_integer_megabyte">%1$d MB</string>
|
|
||||||
<string name="value_integer_meter">%1$d m</string>
|
<string name="value_integer_meter">%1$d m</string>
|
||||||
<string name="value_integer_meter_excellent_gps">%1$d m (excellent GPS)</string>
|
<string name="value_integer_meter_excellent_gps">%1$d m (excellent GPS)</string>
|
||||||
<string name="value_integer_meter_poor_gps">%1$d m (poor GPS)</string>
|
<string name="value_integer_meter_poor_gps">%1$d m (poor GPS)</string>
|
||||||
@@ -344,7 +311,6 @@
|
|||||||
<string name="value_integer_minute">%1$d min</string>
|
<string name="value_integer_minute">%1$d min</string>
|
||||||
<string name="value_integer_percent">%1$d %%</string>
|
<string name="value_integer_percent">%1$d %%</string>
|
||||||
<string name="value_integer_second">%1$d s</string>
|
<string name="value_integer_second">%1$d s</string>
|
||||||
<string name="value_never">Never</string>
|
|
||||||
<string name="value_none">None</string>
|
<string name="value_none">None</string>
|
||||||
<string name="value_off">Off</string>
|
<string name="value_off">Off</string>
|
||||||
<string name="value_smallest">Smallest</string>
|
<string name="value_smallest">Smallest</string>
|
||||||
|
|||||||