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"?>
|
||||
<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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -5,8 +5,9 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
<table>
|
||||
<tr>
|
||||
<th>Free</th>
|
||||
<th>Paid</th>
|
||||
<th>Paid (same as free)</th>
|
||||
<th>Donations</th>
|
||||
<th>Translations</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<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">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.transifex.com/opentracks/opentracks/">
|
||||
Transifex
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features:
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||
* __Heart rate:__ supports Bluetooth LE sensors
|
||||
* __Cycling (speed and cadence)__: supports Bluetooth LE sensors
|
||||
* __Bluetooth LE sensors:__
|
||||
* heart rate
|
||||
* cycling: speed and cadence
|
||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||
* __No Internet access:__ Internet is not used
|
||||
* __Voice announcements__
|
||||
* __No advertising__
|
||||
* __Privacy:__
|
||||
* _does not contain any in-app analytics_
|
||||
* _does not have Google Play Services_
|
||||
* _does not use Google Play Services_
|
||||
* _has no cloud integration_
|
||||
|
||||
__Only required permissions:__
|
||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||
|
||||
_Please note:_
|
||||
_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.
|
||||
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||
|
||||
## 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.
|
||||
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
|
||||
<div>
|
||||
<img width="20%" 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="20%" 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_1.png">
|
||||
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.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>
|
||||
|
||||
## Project history
|
||||
@@ -78,7 +93,7 @@ Rework of _OpenTracks_ included:
|
||||
* removing calorie estimation and activity estimation,
|
||||
* removing support for ANT+ and Classic Bluetooth,
|
||||
* 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## 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()
|
||||
}
|
||||
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 {
|
||||
compileSdkVersion 29
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
||||
applicationLogo: "@mipmap/ic_launcher",
|
||||
applicationName: "@string/app_name"
|
||||
]
|
||||
|
||||
versionCode 3369
|
||||
versionCode 3427
|
||||
//versionCode getVersionCode() //Useful for debugging.
|
||||
versionName "v3.7.0"
|
||||
versionName "v3.7.5"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -78,7 +83,7 @@ android {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_black_24dp",
|
||||
applicationLogo: "@mipmap/ic_launcher_debug",
|
||||
applicationName: "OpenTracks (Debug)"
|
||||
]
|
||||
}
|
||||
@@ -100,8 +105,10 @@ android {
|
||||
|
||||
dependencies {
|
||||
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 '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.espresso:espresso-core:3.2.0'
|
||||
@@ -113,5 +120,4 @@ dependencies {
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.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
|
||||
|
||||
[*] --> TrackListActivity
|
||||
TrackListActivity --> TrackDetailActivity : Start recording
|
||||
TrackListActivity --> TrackRecordingActivity : Start recording
|
||||
TrackListActivity --> SearchListActivity
|
||||
TrackListActivity --> MarkerListActivity : Show all markers
|
||||
TrackListActivity --> TrackDetailActivity : Show track
|
||||
TrackListActivity --> TrackRecordedActivity : Show track
|
||||
TrackListActivity --> TrackDeleteActivity
|
||||
|
||||
SearchListActivity --> MarkerDetailActivity : Search result
|
||||
SearchListActivity --> TrackDetailActivity : Search result
|
||||
SearchListActivity --> TrackRecordedActivity : Search result
|
||||
|
||||
TrackDetailActivity --> TrackDeleteActivity
|
||||
TrackDetailActivity --> TrackEditActivity
|
||||
TrackDetailActivity --> MarkerListActivity
|
||||
TrackDetailActivity --> MarkerEditActivity : Add marker
|
||||
TrackDetailActivity --> CameraIntent : Add marker (with photo)
|
||||
TrackDetailActivity --> ShareFileIntent
|
||||
TrackDetailActivity --> DashboardIntent : Database cursor
|
||||
TrackDetailActivity --> TrackOnMapIntent : KMZ
|
||||
TrackRecordedActivity --> TrackDeleteActivity
|
||||
TrackRecordedActivity --> TrackEditActivity
|
||||
TrackRecordedActivity --> MarkerListActivity
|
||||
TrackRecordedActivity --> MarkerEditActivity : Add marker
|
||||
TrackRecordedActivity --> CameraIntent : Add marker (with photo)
|
||||
TrackRecordedActivity --> ShareFileIntent
|
||||
TrackRecordedActivity --> DashboardIntent : Database cursor
|
||||
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
|
||||
|
||||
@@ -37,4 +48,4 @@ SettingsActivity --> HelpActivity
|
||||
SettingsActivity --> ExportActivity
|
||||
SettingsActivity --> ImportActivity
|
||||
|
||||
@enduml
|
||||
@enduml
|
||||
|
||||
@@ -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:
|
||||
* zeichnet Sport und andere Outdooraktivitäten auf
|
||||
* 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
|
||||
* 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:
|
||||
* Sport tracking: track your sport and outdoor activities
|
||||
* 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
|
||||
* No Internet access: Internet is not used
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#Sun Apr 12 18:41:12 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -175,14 +177,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# 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"
|
||||
|
||||
# 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" "$@"
|
||||
|
||||
@@ -1,100 +1,104 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@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%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
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.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@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%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
@@ -62,6 +62,7 @@ public class CustomContentProviderUtilsTest {
|
||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||
private static final String TEST_DESC = "Test Desc!";
|
||||
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 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
|
||||
public void testUpdateWaypoint() {
|
||||
@@ -641,11 +642,120 @@ public class CustomContentProviderUtilsTest {
|
||||
// Update
|
||||
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||
waypoint.setDescription(TEST_DESC_NEW);
|
||||
contentProviderUtils.updateWaypoint(waypoint);
|
||||
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||
|
||||
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)}.
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,14 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
public class ShareContentProviderTest {
|
||||
|
||||
@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};
|
||||
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 {
|
||||
|
||||
@Test
|
||||
public void compute_cadence() {
|
||||
public void compute_cadence_1() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
public void compute_cadence_sameCount() {
|
||||
// given
|
||||
@@ -77,6 +90,19 @@ public class SensorDataCyclingTest {
|
||||
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
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ExportImportTest {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -169,7 +169,7 @@ public class ExportImportTest {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 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
|
||||
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: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"
|
||||
package="de.dennisguse.opentracks"
|
||||
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
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
@@ -79,7 +86,11 @@ limitations under the License.
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".TrackDetailActivity"
|
||||
android:name=".TrackRecordedActivity"
|
||||
android:screenOrientation="userPortrait" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackRecordingActivity"
|
||||
android:screenOrientation="userPortrait" />
|
||||
|
||||
<activity android:name=".TrackEditActivity" />
|
||||
@@ -94,12 +105,30 @@ limitations under the License.
|
||||
android:value=".SearchListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".AggregatedStatisticsActivity"
|
||||
android:screenOrientation="userPortrait"
|
||||
android:label="@string/menu_aggregated_statistics"/>
|
||||
|
||||
<activity
|
||||
android:name=".io.file.exporter.ExportActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
|
||||
<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
|
||||
android:name=".content.provider.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
@@ -124,12 +153,7 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<!-- Permissions to record locations -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
|
||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.ViewUtils;
|
||||
|
||||
public class AboutActivity extends AbstractActivity {
|
||||
|
||||
@@ -27,6 +29,8 @@ public class AboutActivity extends AbstractActivity {
|
||||
|
||||
TextView textURL = findViewById(R.id.about_app_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() {
|
||||
|
||||
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
||||
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and
|
||||
* {@link SearchListActivity}:
|
||||
* <p>
|
||||
* - 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;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.util.ViewUtils;
|
||||
|
||||
public class HelpActivity extends AbstractActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.help;
|
||||
|
||||
@@ -109,12 +109,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
|
||||
@Override
|
||||
public void onDeleteMarkerDone() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -315,6 +315,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypoint.setDescription(waypointDescription.getText().toString());
|
||||
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);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MarkerListActivity.this.invalidateOptionsMenu();
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> MarkerListActivity.this.invalidateOptionsMenu());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,12 +153,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
arrayAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> arrayAdapter.notifyDataSetChanged());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -214,11 +209,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_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) {
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
@@ -346,12 +341,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
||||
|
||||
// Use the UI thread to display the results
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
arrayAdapter.clear();
|
||||
arrayAdapter.addAll(displayResults);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
arrayAdapter.clear();
|
||||
arrayAdapter.addAll(displayResults);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,12 +440,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
@Override
|
||||
public void onDeleteMarkerDone() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> handleIntent(getIntent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -463,11 +450,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
@Override
|
||||
protected void onDeleted() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> 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.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -143,14 +144,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
/*
|
||||
* After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
*/
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||
}
|
||||
});
|
||||
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
|
||||
|
||||
if (!startGps) {
|
||||
return;
|
||||
@@ -205,7 +196,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
@@ -250,9 +241,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long trackId) {
|
||||
Intent newIntent;
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -351,7 +350,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||
|
||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
@@ -397,6 +395,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
this.invalidateOptionsMenu();
|
||||
}
|
||||
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:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -480,7 +482,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, trackIds);
|
||||
IntentDashboardUtils.startDashboard(this, trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
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;
|
||||
|
||||
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.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.StatsFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
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;
|
||||
@@ -58,12 +41,11 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @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_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";
|
||||
|
||||
@@ -86,12 +68,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public void run() {
|
||||
// After binding changes (is available), update the total time in trackController.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(isRecording(), recordingTrackPaused);
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
||||
|
||||
if (recordingTrackId == -1L) {
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
@@ -115,7 +92,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
|
||||
// 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);
|
||||
trackController.update(true, false);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
@@ -126,43 +103,37 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackDetailActivity.this.invalidateOptionsMenu();
|
||||
boolean isRecording = isRecording();
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
TrackRecordingActivity.this.invalidateOptionsMenu();
|
||||
trackController.update(true, recordingTrackPaused);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem resumeMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -184,8 +155,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
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);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,12 +188,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new StatisticsRecordingFragment();
|
||||
case 1:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(true);
|
||||
default: //0
|
||||
return new StatsFragment();
|
||||
default:
|
||||
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() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
@@ -266,7 +244,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
@@ -296,10 +274,10 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
|
||||
if (trackId != -1L) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
||||
} else {
|
||||
startRecording();
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
}
|
||||
|
||||
startRecording();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -324,7 +302,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.track_detail;
|
||||
return R.layout.track_record;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -336,12 +314,10 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
|
||||
@Override
|
||||
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);
|
||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -362,13 +338,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
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:
|
||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||
IntentDashboardUtils.startDashboard(this, trackId);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
@@ -380,12 +351,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.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:
|
||||
startRecording();
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
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}.
|
||||
*/
|
||||
@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) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackId == -1L) {
|
||||
return;
|
||||
}
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
// Use the last track if markerId is not set
|
||||
if (markerId == -1L) {
|
||||
track = contentProviderUtils.getLastTrack();
|
||||
if (track != null) {
|
||||
trackId = track.getId();
|
||||
return;
|
||||
}
|
||||
track = contentProviderUtils.getLastTrack();
|
||||
if (track != null) {
|
||||
trackId = track.getId();
|
||||
return;
|
||||
}
|
||||
finish();
|
||||
}
|
||||
@@ -455,20 +388,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
* Updates the menu items.
|
||||
*/
|
||||
private void updateMenuItems(boolean isPaused) {
|
||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
||||
shareMenuItem.setVisible(!isRecording());
|
||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
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);
|
||||
insertMarkerMenuItem.setVisible(!isPaused);
|
||||
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
||||
}
|
||||
|
||||
public void chooseActivityType(String category) {
|
||||
@@ -482,10 +404,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
}
|
||||
|
||||
private boolean isRecording() {
|
||||
return trackId == recordingTrackId;
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
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);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -178,16 +175,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
|
||||
public void loadTrack(final long trackId) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (trackId == selectedTrackId) {
|
||||
Log.i(TAG, "Not reloading track " + trackId);
|
||||
return;
|
||||
}
|
||||
selectedTrackId = trackId;
|
||||
loadDataForAll();
|
||||
runInHandlerThread(() -> {
|
||||
if (trackId == selectedTrackId) {
|
||||
Log.i(TAG, "Not reloading track " + trackId);
|
||||
return;
|
||||
}
|
||||
selectedTrackId = trackId;
|
||||
loadDataForAll();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,13 +191,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @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) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -214,12 +205,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @param trackDataListener the track data listener
|
||||
*/
|
||||
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.unregisterTrackDataListener(trackDataListener);
|
||||
}
|
||||
});
|
||||
runInHandlerThread(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,15 +224,12 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -289,7 +272,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
if (isOnlyListener) {
|
||||
resetSamplingState();
|
||||
}
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.emptySet();
|
||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||
}
|
||||
|
||||
|
||||
@@ -382,17 +382,7 @@ public class ContentProviderUtils {
|
||||
|
||||
public void deleteWaypoint(Context context, long waypointId) {
|
||||
final Waypoint waypoint = getWaypoint(waypointId);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteWaypointPhoto(context, waypoint);
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Returns true if successful.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
* @param updatedWaypoint the waypoint with updated data.
|
||||
*/
|
||||
public boolean updateWaypoint(Waypoint waypoint) {
|
||||
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())});
|
||||
public boolean updateWaypoint(Context context, Waypoint updatedWaypoint) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -896,5 +910,4 @@ public class ContentProviderUtils {
|
||||
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
|
||||
static long[] parseURI(Uri uri) {
|
||||
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.");
|
||||
return new long[]{};
|
||||
}
|
||||
@@ -209,7 +209,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
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>() {
|
||||
@Override
|
||||
|
||||
@@ -64,13 +64,13 @@ public final class SensorDataCycling {
|
||||
|
||||
public void compute(Cadence previous) {
|
||||
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) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
cadence_rpm = INVALID_VALUE_FLOAT;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public final class SensorDataCycling {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "cadence=" + getCadence_rpm() + "_" + getCrankRevolutionsTime();
|
||||
return "cadence=" + getCadence_rpm() + " time=" + getCrankRevolutionsTime() + " count=" + getCrankRevolutionsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +125,7 @@ public final class SensorDataCycling {
|
||||
|
||||
public void compute(Speed previous, int wheel_circumference_mm) {
|
||||
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) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
speed_mps = INVALID_VALUE_FLOAT;
|
||||
@@ -140,7 +140,7 @@ public final class SensorDataCycling {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "speed=" + getSpeed_mps() + "_" + getWheelRevolutionsTime();
|
||||
return "speed=" + getSpeed_mps() + " time=" + getWheelRevolutionsTime() + " count=" + getWheelRevolutionsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,9 +4,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public final class SensorDataSet {
|
||||
|
||||
private SensorDataHeartRate heartRate;
|
||||
@@ -31,10 +28,6 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
public void set(SensorData data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data instanceof SensorDataHeartRate) {
|
||||
this.heartRate = (SensorDataHeartRate) data;
|
||||
return;
|
||||
@@ -48,10 +41,6 @@ public final class SensorDataSet {
|
||||
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
||||
return;
|
||||
}
|
||||
if (data instanceof SensorDataCycling.CadenceAndSpeed) {
|
||||
set(((SensorDataCycling.CadenceAndSpeed) data).getCadence());
|
||||
set(((SensorDataCycling.CadenceAndSpeed) data).getSpeed());
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.ChartView;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
@@ -90,12 +90,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
if (metricUnits != chartView.getMetricUnits()) {
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,12 +103,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
chartView.applyReportSpeed();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -214,12 +208,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
||||
pendingPoints.clear();
|
||||
chartView.reset();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
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.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,15 +80,12 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
||||
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
caller.onDeleteMarkerDone();
|
||||
new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
caller.onDeleteMarkerDone();
|
||||
}).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;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
@@ -31,15 +15,18 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
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.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.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
@@ -57,9 +44,9 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
* @author Sandor Dornbush
|
||||
* @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;
|
||||
|
||||
@@ -81,12 +68,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
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(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -109,14 +93,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
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 */
|
||||
private View heartRateContainer;
|
||||
private View sensorHorizontalLine;
|
||||
private Group heartRateGroup;
|
||||
private TextView heartRateValueView;
|
||||
private TextView heartRateSensorView;
|
||||
private View cadenceContainer;
|
||||
private Group cadenceGroup;
|
||||
private TextView cadenceValueView;
|
||||
private TextView cadenceSensorView;
|
||||
|
||||
@@ -147,16 +132,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
private TextView speedMovingLabel;
|
||||
private TextView speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
private View elevationSeparator;
|
||||
private View elevationContainer;
|
||||
private View speedContainer;
|
||||
private Group elevationGroup;
|
||||
private TextView speedLabel;
|
||||
private TextView speedValue;
|
||||
private TextView speedUnit;
|
||||
private TextView elevationValue;
|
||||
private TextView elevationUnit;
|
||||
private View coordinateSeparator;
|
||||
private View coordinateContainer;
|
||||
private Group coordinateGroup;
|
||||
private TextView latitudeValue;
|
||||
private TextView longitudeValue;
|
||||
|
||||
@@ -164,11 +146,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle 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);
|
||||
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);
|
||||
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);
|
||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||
|
||||
elevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||
elevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||
elevationGroup = view.findViewById(R.id.stats_elevation_current_group);
|
||||
|
||||
speedContainer = view.findViewById(R.id.stats_speed);
|
||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||
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);
|
||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||
|
||||
coordinateSeparator = view.findViewById(R.id.stats_coordinate_separator);
|
||||
coordinateContainer = view.findViewById(R.id.stats_coordinate_container);
|
||||
coordinateGroup = view.findViewById(R.id.stats_coordinate_group);
|
||||
|
||||
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
||||
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
||||
@@ -226,7 +207,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -235,7 +216,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -275,10 +256,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
heartRateContainer = null;
|
||||
sensorHorizontalLine = null;
|
||||
|
||||
heartRateGroup = null;
|
||||
heartRateValueView = null;
|
||||
heartRateSensorView = null;
|
||||
cadenceContainer = null;
|
||||
cadenceGroup = null;
|
||||
cadenceValueView = null;
|
||||
cadenceSensorView = null;
|
||||
|
||||
@@ -304,10 +287,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
elevationSeparator = null;
|
||||
elevationContainer = null;
|
||||
elevationGroup = null;
|
||||
|
||||
speedContainer = null;
|
||||
speedLabel = null;
|
||||
speedValue = null;
|
||||
speedUnit = null;
|
||||
@@ -315,8 +296,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
elevationValue = null;
|
||||
elevationUnit = null;
|
||||
|
||||
coordinateSeparator = null;
|
||||
coordinateContainer = null;
|
||||
coordinateGroup = null;
|
||||
|
||||
latitudeValue = null;
|
||||
longitudeValue = null;
|
||||
@@ -325,14 +305,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -356,24 +333,21 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
if (lastTrackPoint != null) {
|
||||
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
||||
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!hasFix || !hasGoodFix) {
|
||||
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.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub = ((TrackRecordingActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||
}
|
||||
|
||||
@@ -442,65 +416,79 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
SensorDataSet sensorDataSet = 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 {
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
}
|
||||
|
||||
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
setCadenceSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
setHeartRateSensorData(sensorDataSet);
|
||||
setCadenceSensorData(sensorDataSet);
|
||||
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
}
|
||||
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
||||
isVisible = View.INVISIBLE;
|
||||
if (PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
||||
isVisible = View.GONE;
|
||||
}
|
||||
heartRateContainer.setVisibility(isVisible);
|
||||
heartRateGroup.setVisibility(isVisible);
|
||||
setVisibilitySensorHorizontalLine();
|
||||
|
||||
if (isRecording) {
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
sensorName = sensorDataSet.getHeartRate().getSensorName();
|
||||
if (sensorDataSet.getHeartRate().hasHeartRate_bpm() && sensorDataSet.getHeartRate().isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getHeartRate().getHeartRate_bpm(), 0);
|
||||
}
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
SensorDataHeartRate data = sensorDataSet.getHeartRate();
|
||||
|
||||
sensorName = data.getSensorName();
|
||||
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;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
||||
isVisible = View.INVISIBLE;
|
||||
if (PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
||||
isVisible = View.GONE;
|
||||
}
|
||||
cadenceContainer.setVisibility(isVisible);
|
||||
cadenceGroup.setVisibility(isVisible);
|
||||
setVisibilitySensorHorizontalLine();
|
||||
|
||||
if (isRecording) {
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
||||
sensorName = sensorDataSet.getCyclingCadence().getSensorName();
|
||||
if (sensorDataSet.getCyclingCadence().hasCadence_rpm() && sensorDataSet.getCyclingCadence().isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getCyclingCadence().getCadence_rpm(), 0);
|
||||
}
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
||||
SensorDataCycling.Cadence data = sensorDataSet.getCyclingCadence();
|
||||
sensorName = data.getSensorName();
|
||||
|
||||
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) {
|
||||
if (isRecording) {
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
||||
if (sensorDataSet.getCyclingSpeed().hasSpeed_mps() && sensorDataSet.getCyclingSpeed().isRecent()) {
|
||||
setSpeed(sensorDataSet.getCyclingSpeed().getSpeed_mps());
|
||||
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
||||
|
||||
if (data.hasSpeed_mps() && data.isRecent()) {
|
||||
setSpeed(data.getSpeed_mps());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -574,7 +562,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
// Make elevation visible?
|
||||
{
|
||||
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() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean isRecording = isSelectedTrackRecording();
|
||||
|
||||
// Set speed/pace
|
||||
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
setSpeed(speed);
|
||||
}
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
setSpeed(speed);
|
||||
|
||||
// Set elevation
|
||||
boolean showElevation = isRecording && PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
||||
@@ -625,10 +609,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
boolean showCoordinate = PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateGroup.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
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);
|
||||
@@ -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.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporterListener;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
|
||||
|
||||
/**
|
||||
@@ -74,8 +73,8 @@ public enum TrackFileFormat {
|
||||
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,8 +100,8 @@ public enum TrackFileFormat {
|
||||
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,8 +133,8 @@ public enum TrackFileFormat {
|
||||
return MIME_KMZ;
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
@@ -163,8 +162,8 @@ public enum TrackFileFormat {
|
||||
return MIME_KMZ;
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
@@ -197,10 +196,10 @@ public enum TrackFileFormat {
|
||||
|
||||
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);
|
||||
|
||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
|
||||
|
||||
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
||||
}
|
||||
@@ -210,10 +209,10 @@ public enum TrackFileFormat {
|
||||
*/
|
||||
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);
|
||||
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.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An activity for saving tracks to the external storage.
|
||||
*
|
||||
* @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_RESULT_ID = 1;
|
||||
@@ -56,65 +55,50 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
private int processedTrackCount;
|
||||
private int totalTrackCount;
|
||||
|
||||
private Uri directoryUri;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
directoryUri = resultData.getData();
|
||||
fileTypeDialogStart();
|
||||
Uri directoryUri = resultData.getData();
|
||||
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 {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
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
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
||||
R.string.export_external_storage_progress_message,
|
||||
R.string.export_progress_message,
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
exportAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
@@ -128,11 +112,11 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
iconId = R.drawable.ic_dialog_success_24dp;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(
|
||||
R.string.export_external_storage_success, totalTracks, directoryDisplayName);
|
||||
R.string.export_success, totalTracks, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_error_24dp;
|
||||
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)
|
||||
.setIcon(iconId).setMessage(message)
|
||||
@@ -140,13 +124,13 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int arg1) {
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}).setTitle(titleId);
|
||||
return builder.create();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
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 final TrackFileFormat trackFileFormat;
|
||||
private final DocumentFile directory;
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private ExportActivity exportActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private boolean completed = false;
|
||||
|
||||
private int processedTrackCount = 0;
|
||||
private int totalTrackCount = 0;
|
||||
|
||||
@@ -66,8 +62,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
this.exportActivity = exportActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.directory = directory;
|
||||
context = exportActivity.getApplicationContext();
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
contentProviderUtils = new ContentProviderUtils(exportActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,7 +94,6 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (exportActivity != null) {
|
||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||
}
|
||||
@@ -107,7 +101,6 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
completed = true;
|
||||
if (exportActivity != null) {
|
||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||
}
|
||||
@@ -119,17 +112,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
* @param track the track
|
||||
*/
|
||||
private Boolean exportTrack(Track track) {
|
||||
TrackExporterListener trackExporterListener = new TrackExporterListener() {
|
||||
@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);
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(exportActivity, new Track[]{track});
|
||||
|
||||
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
||||
|
||||
@@ -139,8 +122,8 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
|
||||
if (trackExporter.writeTrack(context, outputStream)) {
|
||||
try (OutputStream outputStream = exportActivity.getContentResolver().openOutputStream(file.getUri())) {
|
||||
if (trackExporter.writeTrack(exportActivity, outputStream)) {
|
||||
return true;
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
@@ -167,6 +150,7 @@ class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
return false;
|
||||
}
|
||||
totalTrackCount = cursor.getCount();
|
||||
|
||||
for (int i = 0; i < totalTrackCount; i++) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
|
||||
@@ -44,7 +44,6 @@ public class FileTrackExporter implements TrackExporter {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Track[] tracks;
|
||||
private final TrackWriter trackWriter;
|
||||
private final TrackExporterListener trackExporterListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -52,13 +51,11 @@ public class FileTrackExporter implements TrackExporter {
|
||||
* @param contentProviderUtils the content provider utils
|
||||
* @param trackWriter the track writer
|
||||
* @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.tracks = tracks;
|
||||
this.trackWriter = trackWriter;
|
||||
this.trackExporterListener = trackExporterListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,9 +155,6 @@ public class FileTrackExporter implements TrackExporter {
|
||||
|
||||
// Write the current trackPoint
|
||||
trackWriter.writeTrackPoint(trackPoint);
|
||||
if (trackExporterListener != null) {
|
||||
trackExporterListener.onProgressUpdate(locationNumber, trackPointIterator.getCount());
|
||||
}
|
||||
} else {
|
||||
if (wroteSegment) {
|
||||
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 {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,26 +24,25 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An activity to import files from the external storage.
|
||||
*
|
||||
* @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_RESULT_ID = 1;
|
||||
@@ -56,8 +55,6 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
private int importedTrackCount;
|
||||
private int totalTrackCount;
|
||||
|
||||
private Uri directoryUri;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle 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.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
directoryUri = resultData.getData();
|
||||
fileTypeDialogStart();
|
||||
Uri directoryUri = resultData.getData();
|
||||
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
|
||||
directoryDisplayName = FileUtils.getPath(pickedDirectory);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(this);
|
||||
|
||||
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
|
||||
importAsyncTask.execute();
|
||||
} else {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
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
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
@@ -112,7 +94,7 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
importAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
@@ -142,13 +124,13 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
dialogInterface.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int which) {
|
||||
dialogInterface.dismiss();
|
||||
onDismissed();
|
||||
finish();
|
||||
}
|
||||
}).setTitle(titleId).create();
|
||||
default:
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
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 final TrackFileFormat trackFileFormat;
|
||||
private final DocumentFile directory;
|
||||
// TODO Can be removed, or?
|
||||
private Context context;
|
||||
|
||||
// TODO Use weak reference
|
||||
private ImportActivity importActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
private int importTrackCount;
|
||||
|
||||
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) {
|
||||
this.importActivity = importActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.directory = directory;
|
||||
context = importActivity.getApplicationContext();
|
||||
|
||||
completed = false;
|
||||
importTrackCount = 0;
|
||||
totalTrackCount = 0;
|
||||
trackId = -1L;
|
||||
lastSuccessfulTrackId = -1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,8 +73,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
List<DocumentFile> files = new ArrayList<>();
|
||||
@@ -119,7 +100,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||
}
|
||||
@@ -127,8 +107,6 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
context = null;
|
||||
completed = true;
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||
importActivity = null;
|
||||
@@ -190,9 +168,9 @@ class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
|
||||
trackId = trackImporter.importFile(inputStream);
|
||||
return trackId != -1L;
|
||||
try (InputStream inputStream = importActivity.getContentResolver().openInputStream(file.getUri())) {
|
||||
lastSuccessfulTrackId = trackImporter.importFile(inputStream);
|
||||
return lastSuccessfulTrackId != -1L;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
return false;
|
||||
|
||||
@@ -41,8 +41,8 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
@@ -150,12 +150,7 @@ public class TrackRecordingService extends Service {
|
||||
if (locationExecutorService == null || locationExecutorService.isShutdown() || locationExecutorService.isTerminated()) {
|
||||
return;
|
||||
}
|
||||
locationExecutorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onLocationChangedAsync(location);
|
||||
}
|
||||
});
|
||||
locationExecutorService.submit(() -> onLocationChangedAsync(location));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -461,12 +456,6 @@ public class TrackRecordingService extends Service {
|
||||
// Update the recording track time
|
||||
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);
|
||||
}
|
||||
@@ -482,8 +471,9 @@ public class TrackRecordingService extends Service {
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
if (track != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
if (lastTrackPoint != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
}
|
||||
insertTrackPoint(track, TrackPoint.createPause());
|
||||
}
|
||||
|
||||
@@ -748,10 +738,10 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
private void showNotification(boolean isGpsStarted) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||
.addParentStack(TrackDetailActivity.class)
|
||||
.addParentStack(TrackRecordingActivity.class)
|
||||
.addNextIntent(intent)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
|
||||
@@ -51,12 +51,14 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
|
||||
private TrackRecordingServiceInterface trackRecordingService;
|
||||
|
||||
public TrackRecordingServiceConnection() {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param callback the callback to invoke when the service binding changes
|
||||
*/
|
||||
public TrackRecordingServiceConnection(Runnable callback) {
|
||||
public TrackRecordingServiceConnection(@NonNull Runnable callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
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.SensorDataSet;
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* Note: should only be instantiated once.
|
||||
*
|
||||
* <p>
|
||||
* TODO: listen for Bluetooth enabled/disabled events.
|
||||
*
|
||||
* <p>
|
||||
* 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).
|
||||
* However, it is not clear if this is allowed.
|
||||
@@ -148,20 +149,25 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
}
|
||||
|
||||
@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.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.");
|
||||
return;
|
||||
}
|
||||
((SensorDataCycling.Cadence) sensorData).compute(sensorDataSet.getCyclingCadence());
|
||||
((SensorDataCycling.Cadence) sensorData).compute(previous);
|
||||
}
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
((SensorDataCycling.Speed) sensorData).compute(sensorDataSet.getCyclingSpeed(), PreferencesUtils.getWheelCircumference(context));
|
||||
((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(context));
|
||||
}
|
||||
|
||||
sensorDataSet.set(sensorData);
|
||||
|
||||
@@ -11,8 +11,11 @@ import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.BluetoothLeHeartRatePreference;
|
||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
|
||||
@@ -57,20 +60,10 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateReset();
|
||||
}
|
||||
});
|
||||
getActivity().runOnUiThread(() -> updateReset());
|
||||
}
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateUnits();
|
||||
}
|
||||
});
|
||||
getActivity().runOnUiThread(() -> updateUnits());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -89,6 +82,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
}
|
||||
|
||||
setExportTrackFileFormatOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,5 +167,24 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
|
||||
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();
|
||||
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
handler.post(() -> {
|
||||
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public class DialogUtils {
|
||||
* @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) {
|
||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
||||
final ProgressDialog progressDialog = new ProgressDialog(context, R.style.ThemeCustomNotTransparent);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setIcon(R.drawable.ic_dialog_info_24dp);
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
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 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() {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
uris.add(0, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||
uris.add(1, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
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.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
|
||||
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);
|
||||
ClipData clipData = ClipData.newRawUri(null, uris.get(0));
|
||||
clipData.addItem(new ClipData.Item(uris.get(1)));
|
||||
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||
intent.setClipData(clipData);
|
||||
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(Intent.createChooser(intent, null));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
@@ -35,11 +33,9 @@ import java.util.Date;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||
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.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
* Utilities for creating intents.
|
||||
@@ -92,9 +88,10 @@ public class IntentUtils {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
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);
|
||||
mime = uriAndMime.second;
|
||||
}
|
||||
@@ -152,42 +149,11 @@ public class IntentUtils {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(uri));
|
||||
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(context, context.getString(R.string.app_not_installed_show_on_map), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
context.startActivity(Intent.createChooser(intent, null));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
long recordingTrackId = getRecordingTrackId(context);
|
||||
return isRecording(recordingTrackId);
|
||||
|
||||
@@ -20,12 +20,16 @@ import android.location.Location;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Date;
|
||||
@@ -44,6 +48,8 @@ import de.dennisguse.opentracks.R;
|
||||
*/
|
||||
public class StringUtils {
|
||||
|
||||
private static final String TAG = StringUtils.class.getSimpleName();
|
||||
|
||||
private static final String COORDINATE_DEGREE = "\u00B0";
|
||||
|
||||
//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
|
||||
*/
|
||||
public static long parseTime(String xmlDateTime) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
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();
|
||||
} 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+.
|
||||
//ATTENTION: The following code does not require a time zone (+01 or Z); while ISO_DATE_TIME requires this!
|
||||
|
||||
// Parse the date time base
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
@@ -415,4 +426,8 @@ public class StringUtils {
|
||||
}
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
if (a > UINT_MAX && b > UINT_MAX) {
|
||||
if (a > UINT_MAX || b > UINT_MAX) {
|
||||
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" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="239dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_logo_color_24dp" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
style="@style/TextLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="24sp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<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
|
||||
android:id="@+id/about_text_description"
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="12dp" />
|
||||
<TextView
|
||||
style="@style/TextLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<View style="@style/StatsHorizontalLine" />
|
||||
<View style="@style/StatsHorizontalLine" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/about_text_version_name"
|
||||
style="@style/TextSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="6dp" />
|
||||
<TextView
|
||||
android:id="@+id/about_text_description"
|
||||
style="@style/TextMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="12dp" />
|
||||
|
||||
<TextView
|
||||
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" />
|
||||
<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
|
||||
android:id="@+id/about_text_version_name"
|
||||
style="@style/TextSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="6dp" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:gravity="center"
|
||||
android:padding="6dp"
|
||||
android:text="@string/about_history" />
|
||||
<TextView
|
||||
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" />
|
||||
|
||||
<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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/about_libraries" />
|
||||
<TextView
|
||||
style="@style/TextSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="6dp"
|
||||
android:linksClickable="true"
|
||||
android:text="@string/about_history" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:gravity="center"
|
||||
android:text="@string/third_party_libraries" />
|
||||
<View style="@style/StatsHorizontalLine" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
@@ -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_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<include layout="@layout/track_controller" />
|
||||
</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"
|
||||
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"
|
||||
|
||||
@@ -21,10 +21,17 @@ limitations under the License.
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom" />
|
||||
<!-- Icon and title is overwritten by TrackListActivity -->
|
||||
<!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 -->
|
||||
<item
|
||||
android:id="@+id/track_list_start_gps"
|
||||
android:icon="@drawable/ic_gps_off_24dp"
|
||||
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" />
|
||||
<item
|
||||
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="export_all_option">الكل كـ %1$s</string>
|
||||
<string name="export_all_title">تصدير إلى وحدة التخزين الخارجية</string>
|
||||
<string name="export_external_storage">وحدة التخزين الخارجية</string>
|
||||
<string name="export_external_storage_error">تم تصدير %1$d من %2$s إلى %3$s</string>
|
||||
<string name="export_external_storage_option">كـ %1$s إلى %2$s</string>
|
||||
<string name="export_external_storage_progress_message">جارٍ التصدير إلى %1$s…</string>
|
||||
<string name="export_external_storage_success">تم تصدير %1$s إلى %2$s</string>
|
||||
<string name="export_error">تم تصدير %1$d من %2$s إلى %3$s</string>
|
||||
<string name="export_option">كـ %1$s إلى %2$s</string>
|
||||
<string name="export_progress_message">جارٍ التصدير إلى %1$s…</string>
|
||||
<string name="export_success">تم تصدير %1$s إلى %2$s</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_many">%1$d ملفًا</string>
|
||||
<string name="files_one">1 ملف</string>
|
||||
@@ -136,13 +133,9 @@
|
||||
<string name="generic_yes">نعم</string>
|
||||
<string name="gps_location_access">\"الدخول إلى الموقع\"</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_location_mode">لا يمكن تحديد الموقع الحالي. يمكنك تحديد وضع تحديد الموقع بدقة عالية على %1$s وتأكد من أنك في مكان مفتوح. حاول مرة أخرى في وقت لاحق.</string>
|
||||
<string name="gps_not_found">لم يتم العثور على نظام تحديد المواقع (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_signal">في انتظار إشارة نظام تحديد المواقع العالمي (GPS)</string>
|
||||
<string name="image_application">تطبيق</string>
|
||||
@@ -181,20 +174,13 @@
|
||||
<string name="marker_list_empty_message">ليس لديك أية علامات</string>
|
||||
<string name="menu_aggregated_statistics">الإحصاءات المجمعة</string>
|
||||
<string name="menu_delete">حذف</string>
|
||||
<string name="menu_delete_all">حذف الكل</string>
|
||||
<string name="menu_edit">تعديل</string>
|
||||
<string name="menu_export">تصدير</string>
|
||||
<string name="menu_export_all">تصدير جميع المسارات</string>
|
||||
<string name="menu_feedback">تعليقات</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_insert_marker">إدراج مُحدد</string>
|
||||
<string name="menu_insert_photo">جارٍ إدراج الصورة</string>
|
||||
<string name="menu_map">الخريطة</string>
|
||||
<string name="menu_markers">العلامات</string>
|
||||
<string name="menu_play">تشغيل</string>
|
||||
<string name="menu_search">بحث</string>
|
||||
<string name="menu_select_all">تحديد الكل</string>
|
||||
<string name="menu_sensor_state">حالة المستشعر</string>
|
||||
@@ -211,19 +197,14 @@
|
||||
<string name="search_global_description">المسارات والمحددات</string>
|
||||
<string name="search_list_empty_message">لم يطابق بحثك أي مسارات أو علامات</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_cadence">الإيقاع</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_heart_rate">معدل ضربات القلب</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_value">%1$d واط</string>
|
||||
<string name="sensor_state_sending">إرسال</string>
|
||||
<string name="settings_not_while_recording">غير متاح أثناء تسجيل مسار</string>
|
||||
<string name="settings_show">إظهار</string>
|
||||
<string name="settings_advanced">خيارات متقدمة</string>
|
||||
@@ -252,22 +233,13 @@
|
||||
<string name="settings_reset_confirm_title">إعادة التعيين الآن؟</string>
|
||||
<string name="settings_reset_done">لقد تمت إعادة جميع الإعدادات إلى القيم الافتراضية</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_pairing">اقتران البلوتوث</string>
|
||||
<string name="settings_sensor_bluetooth_pairing_summary">فتح إعدادات بلوتوث في Android لإقران الجهاز</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_rate_title">المعدل المفضل</string>
|
||||
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
||||
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</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_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.\n\nلعرض هذا المسار، استخدام برنامج Google Earth أو غيره من البرامج تصميم الخرائط.\n\n%1$s</string>
|
||||
<string name="share_track_subject">أريد مشاركة مسار معك</string>
|
||||
@@ -302,9 +274,6 @@
|
||||
<string name="track_edit_activity_type_hint">نوع النشاط</string>
|
||||
<string name="track_edit_new_track_title">مسار جديد</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_many">%1$d مسارًا</string>
|
||||
<string name="tracks_one">1 مسار</string>
|
||||
@@ -330,10 +299,8 @@
|
||||
<string name="value_integer_feet">%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_kilobyte">%1$d كيلوبايت</string>
|
||||
<string name="value_integer_kilometer">%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_excellent_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_percent">%1$d %%</string>
|
||||
<string name="value_integer_second">%1$d ث</string>
|
||||
<string name="value_never">مطلقًا</string>
|
||||
<string name="value_none">لا شيء</string>
|
||||
<string name="value_off">إيقاف</string>
|
||||
<string name="value_smallest">الأصغر</string>
|
||||
|
||||
@@ -102,14 +102,11 @@
|
||||
<string name="description_track_segment">Segment</string>
|
||||
<string name="export_all_option">All as %1$s</string>
|
||||
<string name="export_all_title">Export to external storage</string>
|
||||
<string name="export_external_storage">External storage</string>
|
||||
<string name="export_external_storage_error">Exported %1$d of %2$s to %3$s</string>
|
||||
<string name="export_external_storage_option">as %1$s to %2$s</string>
|
||||
<string name="export_external_storage_progress_message">Exporting to %1$s…</string>
|
||||
<string name="export_external_storage_success">Exported %1$s to %2$s</string>
|
||||
<string name="export_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_progress_message">Exporting to %1$s…</string>
|
||||
<string name="export_success">Exported %1$s to %2$s</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_many">%1$d files</string>
|
||||
<string name="files_one">1 file</string>
|
||||
@@ -136,13 +133,9 @@
|
||||
<string name="generic_yes">Yes</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_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_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_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_signal">Waiting for GPS signal</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="menu_aggregated_statistics">Aggregated stats</string>
|
||||
<string name="menu_delete">Delete</string>
|
||||
<string name="menu_delete_all">Delete all</string>
|
||||
<string name="menu_edit">Edit</string>
|
||||
<string name="menu_export">Export</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_feedback">Help & feedback</string>
|
||||
<string name="menu_help_forum">Help forum</string>
|
||||
<string name="menu_import_all">Import all</string>
|
||||
<string name="menu_insert_marker">Insert marker</string>
|
||||
<string name="menu_insert_photo">Insert photo</string>
|
||||
<string name="menu_map">Map</string>
|
||||
<string name="menu_markers">Markers</string>
|
||||
<string name="menu_play">Play</string>
|
||||
<string name="menu_search">Search</string>
|
||||
<string name="menu_select_all">Select all</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_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_voice_prompt">Say the name of a track or marker</string>
|
||||
<string name="sensor_state_battery">Battery level</string>
|
||||
<string name="sensor_state_cadence">Cadence</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_heart_rate">Heart rate</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_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_show">Show</string>
|
||||
<string name="settings_advanced">Advanced</string>
|
||||
@@ -252,22 +233,13 @@
|
||||
<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_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_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_connected">Connected to %1$s</string>
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</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_metric">Metric (km, m)</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_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>
|
||||
@@ -303,9 +275,6 @@
|
||||
<string name="track_edit_activity_type_hint">Activity type</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_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_many">%1$d tracks</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_excellent_gps">%1$d ft (excellent GPS)</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_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_excellent_gps">%1$d m (excellent 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_percent">%1$d %%</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_off">Off</string>
|
||||
<string name="value_smallest">Smallest</string>
|
||||
|
||||