forked from upstream-mirrors/OpenTracks
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 241a52ee7c | |||
| c66e285bf5 | |||
| 8c1ddc077d | |||
| 61c18af639 | |||
| b83df3ac4b | |||
| 53f1c4b7f3 | |||
| 6290bdbab0 | |||
| bd0d85cd37 | |||
| ba08417236 | |||
| 63281a3ca8 | |||
| fae0cf8454 | |||
| efddb31391 | |||
| 3a8e547e10 | |||
| fe09d18197 | |||
| 93bcf8d494 | |||
| 819f454bd8 | |||
| 7d58014b15 | |||
| 9dac95569c | |||
| 96c98ad275 | |||
| 7dcc0ec8bc | |||
| 09ecc03be5 | |||
| d7a5ffb938 | |||
| 0605dbb0ab | |||
| bf03de6be7 | |||
| d65ce361f7 | |||
| 3f600f6be7 | |||
| 9aab3c216c | |||
| 7935185812 | |||
| 4c1b1be96c | |||
| 5762830655 | |||
| 6d61206782 | |||
| b9dedef292 | |||
| f3caecd049 | |||
| e16780277f | |||
| 13484685a3 | |||
| 7b745d9837 | |||
| 52742064f6 | |||
| 2edf5582aa | |||
| 808eb92bf2 | |||
| c0b84c3d72 | |||
| e275d3e5c0 | |||
| b4ebad2c3c | |||
| 1ab76e1317 | |||
| 3d1104dd0b | |||
| 2df45d31de | |||
| 5436981e0d | |||
| fd2b1bec7d | |||
| bb1eaa5b56 | |||
| dae8eaa97a | |||
| 565b1ce94d | |||
| 543d5e18bf | |||
| 01368df6b1 | |||
| c666d55211 | |||
| 8cc4fd1d4f | |||
| c241a35861 | |||
| 674d231b7a | |||
| bebee59eb2 | |||
| 24f843b0db | |||
| f0259d13dd | |||
| 9dff965a52 | |||
| b4e6d10fbe | |||
| 1a9d88277f | |||
| 4ea956ca0e | |||
| 8060c2e1e3 | |||
| 8c96b65e59 | |||
| a407e94d3c | |||
| 310c790324 | |||
| 12b3d31f5c | |||
| d43dca61c1 | |||
| 20c14a03b2 | |||
| a6601f4eea | |||
| 220c7b1443 | |||
| 33bb63976d | |||
| 8db8f68d69 | |||
| 99b417217e | |||
| ac80386b8a | |||
| b96441a964 | |||
| 207f9563df | |||
| 7b84bf448b | |||
| 41a62fd412 | |||
| 1087dbf387 | |||
| e16d1d53c6 | |||
| 00c9ba6595 | |||
| 7c1995c67f | |||
| d2e0ae8b11 | |||
| f0aa49537d | |||
| 14ace294f3 | |||
| b53ef455a6 | |||
| 3dc6c6607d | |||
| dd91e05fb6 | |||
| 19fe5275b9 | |||
| 1aeb9ca332 | |||
| 520c240a19 | |||
| 48aaca13d0 | |||
| 88a31f4f39 | |||
| 5ce9e6606b | |||
| 4ea70b15e7 | |||
| d3cf2957cc | |||
| dbac595754 | |||
| d70e7b8788 | |||
| f6835852b6 | |||
| d5864726f6 | |||
| 3cc3271726 | |||
| 8aef9c8d28 | |||
| bcca00d110 | |||
| aa929161b4 | |||
| 70cb9cbf51 | |||
| fef3b97661 | |||
| 945f366195 | |||
| 8c0a851d87 |
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Assemble APK
|
- name: Assemble APK
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ jobs:
|
|||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
|
|
||||||
- run: ./gradlew lint
|
- run: ./gradlew lint
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||||
|
|
||||||
[](https://github.com/humanetech-community/awesome-humane-tech)
|
[](https://delightful.club/delightful-humane-design/#health)
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -40,8 +40,10 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
|
Translations are hosted on <a href="https://hosted.weblate.org/engage/opentracks/">
|
||||||
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/horizontal-auto.svg" alt="Translation status" />
|
hosted.weblate.org</a>.
|
||||||
|
<a href="https://hosted.weblate.org/engage/opentracks/">
|
||||||
|
<img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
@@ -150,7 +152,7 @@ The developer documentation is in [README_API.md](README_API.md).
|
|||||||
## Project history
|
## Project history
|
||||||
|
|
||||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||||
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
_My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
||||||
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
||||||
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
||||||
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
||||||
|
|||||||
+5
-3
@@ -17,10 +17,10 @@ Application id: de.dennisguse.opentracks
|
|||||||
It if it properly tagged F-Droid takes care of building the app and publishing.
|
It if it properly tagged F-Droid takes care of building the app and publishing.
|
||||||
Usually, it takes two days before the update is published (build, signed, and published).
|
Usually, it takes two days before the update is published (build, signed, and published).
|
||||||
|
|
||||||
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
|
The recent build activity can be found [here](https://monitor.f-droid.org/builds/build)
|
||||||
|
|
||||||
## Google Play Store
|
## Google Play Store
|
||||||
Application id: de.dennisguse.opentracks.playstore
|
Application id: `de.dennisguse.opentracks.playstore`
|
||||||
|
|
||||||
Requirement: `gem install fastlane`
|
Requirement: `gem install fastlane`
|
||||||
|
|
||||||
@@ -35,4 +35,6 @@ Supported languages: https://support.google.com/googleplay/android-developer/tab
|
|||||||
|
|
||||||
## Translations (localization)
|
## Translations (localization)
|
||||||
|
|
||||||
Translations of the OpenTracks are handled in Weblate: [https://weblate.bubu1.eu/projects/OpenTracks/](https://weblate.bubu1.eu/projects/OpenTracks/).
|
Translations of the OpenTracks are handled in
|
||||||
|
Weblate: [https://hosted.weblate.org/projects/opentracks/](https://hosted.weblate.org/projects/opentracks)
|
||||||
|
.
|
||||||
+27
-18
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
classpath 'com.android.tools.build:gradle:8.1.0-beta01'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,20 +47,25 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
|
buildConfig true
|
||||||
|
}
|
||||||
|
|
||||||
|
androidResources {
|
||||||
|
generateLocaleConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 5080
|
versionCode 5189
|
||||||
versionName "v4.1.2"
|
versionName "v4.3.2"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -69,6 +74,8 @@ android {
|
|||||||
|
|
||||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
|
resConfigs "ar", "bg", "ca", "cs", "da", "de", "el", "en", "es", "et", "eu", "fa", "fi", "fr", "ga", "gl", "hi", "hr", "hu", "in", "it", "iw", "ja", "ko", "lt", "lv", "mo", "ms", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh"
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
nightly {
|
nightly {
|
||||||
@@ -106,11 +113,11 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.configureEach { variant ->
|
||||||
variant.resValue "string", "applicationId", variant.applicationId
|
variant.resValue "string", "applicationId", variant.applicationId
|
||||||
|
|
||||||
if (variant.buildType.name == 'nightly') {
|
if (variant.buildType.name == 'nightly') {
|
||||||
variant.outputs.all {
|
variant.outputs.configureEach {
|
||||||
setVersionCodeOverride(getVersionCode())
|
setVersionCodeOverride(getVersionCode())
|
||||||
setVersionNameOverride(getVersionName())
|
setVersionNameOverride(getVersionName())
|
||||||
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
||||||
@@ -120,21 +127,23 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.7.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
implementation 'androidx.mediarouter:mediarouter:1.3.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:4.8.1'
|
androidTestImplementation 'org.mockito:mockito-android:5.3.0'
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ Changes:
|
|||||||
Bugfix:
|
Bugfix:
|
||||||
- Speech announcements use MAY_DUCK (again)
|
- Speech announcements use MAY_DUCK (again)
|
||||||
- ChartView shows markers (again)
|
- ChartView shows markers (again)
|
||||||
- Fixed landscape layout of TrackListActivity
|
- Fixed landscape recordingLayout of TrackListActivity
|
||||||
- Request legacy file API behavior (necessary for Android 10)
|
- Request legacy file API behavior (necessary for Android 10)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v4.2.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Show heartrate zone information
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Migrated to hosted.weblate.org
|
||||||
|
- Recording UI uses Views directly
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v4.2.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Speed sensor: acceptable wheel diameter is 100mm-4000mm
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Reconnect to a sensor if connection is lost
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
v4.2.2: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Track icon wrongly shown in dialog
|
||||||
|
- Textcolor for heartrate zone 1 was ignoring theme
|
||||||
|
- Not all activityTypes were shown in dropdownlist
|
||||||
|
- Crash due to color not found
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
v4.3.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Allow backups (incl. encrypted to the Google)
|
||||||
|
- Android 13: allow app-specific language settings
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v4.3.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Setting to skip voice announcements on device speaker
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- GPS disabled warning was falsely shown
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
v4.3.2: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- KML header was misformed (#1522)
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Use Java 17 (multi-line text, switch)
|
||||||
|
- API33: locale config is generated
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||||
|
|
||||||
|
運動與戶外活動賦予幸福、技能與自信。
|
||||||
|
|
||||||
|
由保持追蹤您的訓練來重視您的健康。
|
||||||
|
紀錄您的跑步或健行,並且在您騎自行車時給您一台具有大螢幕的自行車電腦。
|
||||||
|
用照片標記您一路上有趣的地點。
|
||||||
|
保持紀錄非常詳細的統計資料以供分析。
|
||||||
|
僅分享資料給您想要分享的其他人。
|
||||||
|
|
||||||
|
* 語音播報。
|
||||||
|
* 支援藍芽 LE 感測器:心律、速度與距離 (騎自行車)、踏頻 (騎自行車)、功率計 (騎自行車)。
|
||||||
|
* 海拔上升與下降:通過氣壓感測器。
|
||||||
|
* EGM2008 海拔顯示 (平均海平面以上);匯出為 WGS84。
|
||||||
|
* 匯出蹤跡資料為 KMZ (包含照片)、KML、GPX。
|
||||||
|
* 沒有網路存取或額外的權限需求。
|
||||||
|
* 深色與淺色主題,取決於系統設定。
|
||||||
|
* 沒有廣告。
|
||||||
|
|
||||||
|
此為 自由軟體 / 免費軟體 / 開放原始碼
|
||||||
|
這意味您可以使用、學習、修改與分享此專案的原始碼。
|
||||||
|
授權條款:Apache 2.0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m
|
org.gradle.jvmargs=-Xmx1024m
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -205,6 +205,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
Vendored
+6
-4
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
+12
-10
@@ -27,21 +27,23 @@ public class KmlTrackExporterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void writeCloseSegment_only_write_sensordata_if_present() {
|
public void writeCloseSegment_only_write_sensordata_if_present() {
|
||||||
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
|
String expected = """
|
||||||
"<coord/>\n" +
|
<when>1970-01-01T00:00:00Z</when>
|
||||||
"<when>1970-01-01T01:00:00+01:00</when>\n" +
|
<coord/>
|
||||||
"<coord/>\n" +
|
<when>1970-01-01T01:00:00+01:00</when>
|
||||||
"<ExtendedData>\n" +
|
<coord/>
|
||||||
"<SchemaData schemaUrl=\"#schema\">\n" +
|
<ExtendedData>
|
||||||
"</SchemaData>\n" +
|
<SchemaData schemaUrl="#schema">
|
||||||
"</ExtendedData>\n" +
|
</SchemaData>
|
||||||
"</Track>\n";
|
</ExtendedData>
|
||||||
|
</Track>
|
||||||
|
""";
|
||||||
|
|
||||||
// given
|
// given
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, null);
|
||||||
kmlTrackWriter.prepare(outputStream);
|
kmlTrackWriter.prepare(outputStream);
|
||||||
|
|
||||||
kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint);
|
kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint);
|
||||||
|
|||||||
+14
-13
@@ -12,7 +12,6 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -166,7 +165,8 @@ public class ExportImportTest {
|
|||||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||||
|
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||||
@@ -179,7 +179,7 @@ public class ExportImportTest {
|
|||||||
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
@@ -204,7 +204,7 @@ public class ExportImportTest {
|
|||||||
// given
|
// given
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, contentProviderUtils);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -287,7 +287,7 @@ public class ExportImportTest {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, contentProviderUtils);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||||
@@ -310,7 +310,7 @@ public class ExportImportTest {
|
|||||||
// given
|
// given
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -428,7 +428,7 @@ public class ExportImportTest {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -453,7 +453,7 @@ public class ExportImportTest {
|
|||||||
// given
|
// given
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context, contentProviderUtils);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -495,9 +495,10 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
|
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||||
@Override
|
Mockito.when(remoteSensorManager.fill(Mockito.any())).thenAnswer(invocation -> {
|
||||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
TrackPoint trackPoint = invocation.getArgument(0);
|
||||||
|
|
||||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||||
@@ -519,13 +520,13 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
sensorDataSet.fillTrackPoint(trackPoint);
|
sensorDataSet.fillTrackPoint(trackPoint);
|
||||||
return sensorDataSet;
|
return sensorDataSet;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
trackPointCreator.getSensorManager().setBluetoothSensorManager(remoteSensorManager);
|
||||||
trackPointCreator.onChange(new SensorDataSet());
|
trackPointCreator.onChange(new SensorDataSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||||
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
|
AltitudeSumManager altitudeSumManager = trackPointCreator.getSensorManager().getAltitudeSumManager();
|
||||||
altitudeSumManager.setAltitudeGain_m(altitudeGain);
|
altitudeSumManager.setAltitudeGain_m(altitudeGain);
|
||||||
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
|
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -231,7 +231,7 @@ public class GPXTrackImporterTest {
|
|||||||
importTrackId = importer.importFile(inputStream).get(0);
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
|
||||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||||
trackExporter.writeTrack(importedTrack, outputStream);
|
trackExporter.writeTrack(importedTrack, outputStream);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
@@ -23,10 +24,10 @@ public class BluetoothUtilsTest {
|
|||||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(60, heartRate);
|
assertEquals(HeartRate.of(60), heartRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -36,10 +37,10 @@ public class BluetoothUtilsTest {
|
|||||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(257, heartRate);
|
assertEquals(HeartRate.of(257), heartRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+20
-17
@@ -45,6 +45,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests insert location.
|
* Tests insert location.
|
||||||
|
* Note: on API23, the TrackRecordingService may not be stopped properly before the next test.
|
||||||
|
* So, if something fails, subsequent tests may be affected.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackRecordingServiceTestRecording {
|
public class TrackRecordingServiceTestRecording {
|
||||||
@@ -112,7 +114,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
String startTime = "2020-02-02T02:02:02Z";
|
String startTime = "2020-02-02T02:02:02Z";
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
|
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
|
||||||
@@ -149,7 +151,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
|
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String pauseTime = "2020-02-02T02:02:03Z";
|
String pauseTime = "2020-02-02T02:02:03Z";
|
||||||
@@ -195,7 +197,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
String starTime = "2020-02-02T02:02:02Z";
|
String starTime = "2020-02-02T02:02:02Z";
|
||||||
trackPointCreator.setClock(starTime);
|
trackPointCreator.setClock(starTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
String pauseTime = "2020-02-02T02:02:03Z";
|
String pauseTime = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(pauseTime);
|
trackPointCreator.setClock(pauseTime);
|
||||||
@@ -227,7 +229,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
|
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
String stopTime = "2020-02-02T02:02:03Z";
|
String stopTime = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(stopTime);
|
trackPointCreator.setClock(stopTime);
|
||||||
@@ -237,7 +239,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
String resumeTime = "2020-02-02T02:02:04Z";
|
String resumeTime = "2020-02-02T02:02:04Z";
|
||||||
trackPointCreator.setClock(resumeTime);
|
trackPointCreator.setClock(resumeTime);
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
new TrackPointAssert().assertEquals(List.of(
|
new TrackPointAssert().assertEquals(List.of(
|
||||||
@@ -258,12 +260,13 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
|
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
|
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String sensor1 = "2020-02-02T02:02:03Z";
|
String sensor1 = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(sensor1);
|
trackPointCreator.setClock(sensor1);
|
||||||
|
|
||||||
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
|
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
|
||||||
|
|
||||||
String sensor3 = "2020-02-02T02:02:13Z";
|
String sensor3 = "2020-02-02T02:02:13Z";
|
||||||
@@ -297,7 +300,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -371,7 +374,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -436,7 +439,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -500,7 +503,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -564,7 +567,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -652,7 +655,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -702,7 +705,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -748,7 +751,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
@@ -812,8 +815,8 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
|
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String sensor1 = "2020-02-02T02:02:03Z";
|
String sensor1 = "2020-02-02T02:02:03Z";
|
||||||
|
|||||||
-11
@@ -1,15 +1,12 @@
|
|||||||
package de.dennisguse.opentracks.services;
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import androidx.test.rule.ServiceTestRule;
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
@@ -22,14 +19,6 @@ public class TrackRecordingServiceTestUtils {
|
|||||||
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
|
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
PreferencesUtils.clear();
|
PreferencesUtils.clear();
|
||||||
|
|
||||||
// Reset service (if some previous test failed)
|
|
||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
|
||||||
.getService();
|
|
||||||
|
|
||||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
|
|
||||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
|
||||||
service.endCurrentTrack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.data.models.Distance;
|
|||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class GPSHandlerTest {
|
public class GPSManagerTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public class GPSHandlerTest {
|
|||||||
private TrackPointCreator trackPointCreator;
|
private TrackPointCreator trackPointCreator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private GPSHandler locationHandler;
|
private GPSManager locationHandler;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void preSetUp() {
|
public static void preSetUp() {
|
||||||
@@ -50,7 +50,7 @@ public class GPSHandlerTest {
|
|||||||
Mockito.when(trackPointCreator.createNow())
|
Mockito.when(trackPointCreator.createNow())
|
||||||
.thenReturn(Instant.now());
|
.thenReturn(Instant.now());
|
||||||
|
|
||||||
locationHandler.onStart(context, new Handler());
|
locationHandler.start(context, new Handler());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+1
-1
@@ -25,7 +25,7 @@ public class TrackPointCreatorTest {
|
|||||||
private TrackPointCreator.Callback server;
|
private TrackPointCreator.Callback server;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private GPSHandler locationHandler;
|
private GPSManager locationHandler;
|
||||||
|
|
||||||
private TrackPointCreator subject;
|
private TrackPointCreator subject;
|
||||||
|
|
||||||
|
|||||||
+18
-17
@@ -17,7 +17,8 @@ import org.junit.runner.RunWith;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PreferencesOpenHelperTest {
|
public class PreferencesOpenHelperTest {
|
||||||
@@ -38,10 +39,10 @@ public class PreferencesOpenHelperTest {
|
|||||||
PreferencesOpenHelper.newInstance(1).check();
|
PreferencesOpenHelper.newInstance(1).check();
|
||||||
|
|
||||||
// then there should be one layout with old custom layout that has the new CSV value.
|
// then there should be one layout with old custom layout that has the new CSV value.
|
||||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||||
assertNotNull(layouts);
|
assertNotNull(recordingLayouts);
|
||||||
assertEquals(layouts.size(), 1);
|
assertEquals(recordingLayouts.size(), 1);
|
||||||
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
assertEquals(recordingLayouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -63,10 +64,10 @@ public class PreferencesOpenHelperTest {
|
|||||||
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
|
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
|
||||||
|
|
||||||
// then there should be one layout with old custom layout that has the new CSV value.
|
// then there should be one layout with old custom layout that has the new CSV value.
|
||||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||||
assertNotNull(layouts);
|
assertNotNull(recordingLayouts);
|
||||||
assertEquals(layouts.size(), 1);
|
assertEquals(recordingLayouts.size(), 1);
|
||||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -87,10 +88,10 @@ public class PreferencesOpenHelperTest {
|
|||||||
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||||
|
|
||||||
// then there should be one layout with old custom layout that has the new CSV value.
|
// then there should be one layout with old custom layout that has the new CSV value.
|
||||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||||
assertNotNull(layouts);
|
assertNotNull(recordingLayouts);
|
||||||
assertEquals(layouts.size(), 1);
|
assertEquals(recordingLayouts.size(), 1);
|
||||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -109,10 +110,10 @@ public class PreferencesOpenHelperTest {
|
|||||||
PreferencesOpenHelper.newInstance(1).check();
|
PreferencesOpenHelper.newInstance(1).check();
|
||||||
|
|
||||||
// then custom layout should be equals to default layout.
|
// then custom layout should be equals to default layout.
|
||||||
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
RecordingLayout defaultRecordingLayout = RecordingLayoutIO.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||||
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> customRecordingLayout = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
assertEquals(customLayout.size(), 1);
|
assertEquals(customRecordingLayout.size(), 1);
|
||||||
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
|
assertEquals(defaultRecordingLayout.toCsv(), customRecordingLayout.get(0).toCsv());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import java.util.List;
|
|||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PreferencesUtilsTest {
|
public class PreferencesUtilsTest {
|
||||||
@@ -85,13 +86,13 @@ public class PreferencesUtilsTest {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(layouts.size(), 1);
|
assertEquals(recordingLayouts.size(), 1);
|
||||||
assertTrue(layouts.get(0).getFields().size() > 0);
|
assertTrue(recordingLayouts.get(0).getFields().size() > 0);
|
||||||
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
assertEquals(recordingLayouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||||
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
assertTrue(recordingLayouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,12 +104,12 @@ public class PreferencesUtilsTest {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Layout layout = PreferencesUtils.getCustomLayout();
|
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(layout.getFields().size() > 0);
|
assertTrue(recordingLayout.getFields().size() > 0);
|
||||||
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
assertEquals(recordingLayout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
assertTrue(recordingLayout.getFields().stream().anyMatch(DataField::isVisible));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -126,28 +127,28 @@ public class PreferencesUtilsTest {
|
|||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Layout layout = PreferencesUtils.getCustomLayout();
|
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(layout.getFields().size(), 4);
|
assertEquals(recordingLayout.getFields().size(), 4);
|
||||||
assertEquals(layout.getName(), "run");
|
assertEquals(recordingLayout.getName(), "run");
|
||||||
assertEquals(layout.getColumnsPerRow(), 2);
|
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||||
assertTrue(layout.getFields().get(0).isVisible());
|
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||||
assertTrue(layout.getFields().get(0).isPrimary());
|
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||||
assertTrue(layout.getFields().get(1).isVisible());
|
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||||
assertFalse(layout.getFields().get(1).isPrimary());
|
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||||
assertFalse(layout.getFields().get(2).isVisible());
|
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||||
assertTrue(layout.getFields().get(2).isPrimary());
|
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||||
assertFalse(layout.getFields().get(3).isVisible());
|
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||||
assertFalse(layout.getFields().get(3).isPrimary());
|
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -165,44 +166,44 @@ public class PreferencesUtilsTest {
|
|||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Layout layout = PreferencesUtils.getCustomLayout();
|
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(layout.getFields().size(), 4);
|
assertEquals(recordingLayout.getFields().size(), 4);
|
||||||
assertEquals(layout.getName(), "walking");
|
assertEquals(recordingLayout.getName(), "walking");
|
||||||
assertEquals(layout.getColumnsPerRow(), 2);
|
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||||
assertTrue(layout.getFields().get(0).isVisible());
|
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||||
assertTrue(layout.getFields().get(0).isPrimary());
|
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||||
assertTrue(layout.getFields().get(1).isVisible());
|
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||||
assertFalse(layout.getFields().get(1).isPrimary());
|
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||||
assertFalse(layout.getFields().get(2).isVisible());
|
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||||
assertTrue(layout.getFields().get(2).isPrimary());
|
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||||
assertTrue(layout.getFields().get(2).isWide());
|
assertTrue(recordingLayout.getFields().get(2).isWide());
|
||||||
|
|
||||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||||
assertFalse(layout.getFields().get(3).isVisible());
|
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||||
assertFalse(layout.getFields().get(3).isPrimary());
|
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetCustomLayout() {
|
public void testSetCustomLayout() {
|
||||||
// given
|
// given
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
Layout layoutSrc = new Layout("road cycling");
|
RecordingLayout recordingLayoutSrc = new RecordingLayout("road cycling");
|
||||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
|
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false));
|
||||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
|
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false));
|
||||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
|
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false));
|
||||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
|
editor.putString(context.getString(R.string.stats_custom_layouts_key), recordingLayoutSrc.toCsv());
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -215,13 +216,13 @@ public class PreferencesUtilsTest {
|
|||||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
|
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
|
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
|
||||||
|
|
||||||
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
RecordingLayout recordingLayoutDst = PreferencesUtils.getCustomLayout();
|
||||||
assertEquals(layoutSrc.getName(), layoutDst.getName());
|
assertEquals(recordingLayoutSrc.getName(), recordingLayoutDst.getName());
|
||||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
assertEquals(recordingLayoutSrc.getFields().size(), recordingLayoutDst.getFields().size());
|
||||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
for (int i = 0; i < recordingLayoutSrc.getFields().size(); i++) {
|
||||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
assertEquals(recordingLayoutSrc.getFields().get(i).getKey(), recordingLayoutDst.getFields().get(i).getKey());
|
||||||
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
|
assertEquals(recordingLayoutSrc.getFields().get(i).isVisible(), recordingLayoutDst.getFields().get(i).isVisible());
|
||||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
assertEquals(recordingLayoutSrc.getFields().get(i).isPrimary(), recordingLayoutDst.getFields().get(i).isPrimary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +248,7 @@ public class PreferencesUtilsTest {
|
|||||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
// when cyling profile is updated
|
// when cyling profile is updated
|
||||||
String cyclingProfileUpdated = "cycling;2;"
|
String cyclingProfileUpdated = "cycling;2;"
|
||||||
@@ -256,14 +257,14 @@ public class PreferencesUtilsTest {
|
|||||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
||||||
|
|
||||||
List<Layout> layoutsToBeUpdated = new ArrayList<>();
|
List<RecordingLayout> layoutsToBeUpdated = new ArrayList<>();
|
||||||
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
|
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources));
|
||||||
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
|
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(runningProfile, resources));
|
||||||
|
|
||||||
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
||||||
|
|
||||||
// then only updated profile is modified in the custom layouts
|
// then only updated profile is modified in the custom layouts
|
||||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
assertEquals(layoutsBefore.size(), 2);
|
assertEquals(layoutsBefore.size(), 2);
|
||||||
assertEquals(layoutsAfter.size(), 2);
|
assertEquals(layoutsAfter.size(), 2);
|
||||||
@@ -294,7 +295,7 @@ public class PreferencesUtilsTest {
|
|||||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
// when cyling profile is updated
|
// when cyling profile is updated
|
||||||
String cyclingProfileUpdated = "cycling;2;"
|
String cyclingProfileUpdated = "cycling;2;"
|
||||||
@@ -302,11 +303,11 @@ public class PreferencesUtilsTest {
|
|||||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
|
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
||||||
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
|
RecordingLayout recordingLayoutToBeUpdated = RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources);
|
||||||
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
|
PreferencesUtils.updateCustomLayout(recordingLayoutToBeUpdated);
|
||||||
|
|
||||||
// then only updated profile is modified in the custom layouts
|
// then only updated profile is modified in the custom layouts
|
||||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||||
|
|
||||||
assertEquals(layoutsBefore.size(), 2);
|
assertEquals(layoutsBefore.size(), 2);
|
||||||
assertEquals(layoutsAfter.size(), 2);
|
assertEquals(layoutsAfter.size(), 2);
|
||||||
@@ -337,9 +338,9 @@ public class PreferencesUtilsTest {
|
|||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
// when it gets the custom layout
|
// when it gets the custom layout
|
||||||
Layout layout = PreferencesUtils.getCustomLayout();
|
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
// then the first one was returned
|
// then the first one was returned
|
||||||
assertEquals(layout.getName(), "cycling");
|
assertEquals(recordingLayout.getName(), "cycling");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class CsvLayoutUtilsTest extends TestCase {
|
public class CsvRecordingLayoutUtilsTest extends TestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCsvLineParts() {
|
public void testGetCsvLineParts() {
|
||||||
-76
@@ -1,76 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
|
||||||
|
|
||||||
|
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
|
||||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.test.espresso.NoMatchingViewException;
|
|
||||||
import androidx.test.espresso.ViewAssertion;
|
|
||||||
import androidx.test.espresso.ViewInteraction;
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers;
|
|
||||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.TrackListActivity;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
@LargeTest
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class EspressoCustomLayoutTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void customLayoutTest() {
|
|
||||||
// TrackListActivity: start recording
|
|
||||||
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.track_list_fab_action));
|
|
||||||
trackControllerRecordButton.perform(click());
|
|
||||||
|
|
||||||
// Get custom layout preferences and check all data fields are showed.
|
|
||||||
Layout layout = PreferencesUtils.getCustomLayout();
|
|
||||||
|
|
||||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
|
||||||
|
|
||||||
// stop recording
|
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
|
||||||
trackControllerStopButton.perform(longClick());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
|
||||||
private final int expectedCount;
|
|
||||||
|
|
||||||
public RecyclerViewItemCountAssertion(int expectedCount) {
|
|
||||||
this.expectedCount = expectedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void check(View view, NoMatchingViewException noViewFoundException) {
|
|
||||||
if (noViewFoundException != null) {
|
|
||||||
throw noViewFoundException;
|
|
||||||
}
|
|
||||||
|
|
||||||
RecyclerView recyclerView = (RecyclerView) view;
|
|
||||||
RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
|
|
||||||
assertThat(adapter.getItemCount(), is(expectedCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+39
-43
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.R;
|
|||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class LayoutTest extends TestCase {
|
public class RecordingLayoutTest extends TestCase {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||||
@@ -28,21 +28,17 @@ public class LayoutTest extends TestCase {
|
|||||||
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
|
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
|
||||||
|
|
||||||
// when create a layout from CSV line
|
// when create a layout from CSV line
|
||||||
Layout layout = Layout.fromCsv(csv, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv, resources);
|
||||||
List<DataField> dataFieldList = layout.getFields();
|
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||||
|
|
||||||
// then layout and data fields are built correctly
|
// then layout and data fields are built correctly
|
||||||
assertEquals(layout.getName(), "running");
|
assertEquals(recordingLayout.getName(), "running");
|
||||||
assertEquals(layout.getColumnsPerRow(), 2);
|
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||||
assertEquals(dataFieldList.size(), 4);
|
assertEquals(dataFieldList.size(), 4);
|
||||||
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||||
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||||
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
|
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
|
||||||
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
|
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
|
||||||
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
|
|
||||||
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
|
|
||||||
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
|
|
||||||
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
|
|
||||||
assertTrue(dataFieldList.get(0).isVisible());
|
assertTrue(dataFieldList.get(0).isVisible());
|
||||||
assertTrue(dataFieldList.get(0).isPrimary());
|
assertTrue(dataFieldList.get(0).isPrimary());
|
||||||
assertFalse(dataFieldList.get(0).isWide());
|
assertFalse(dataFieldList.get(0).isWide());
|
||||||
@@ -64,13 +60,13 @@ public class LayoutTest extends TestCase {
|
|||||||
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
|
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
|
||||||
|
|
||||||
// when create a layout from CSV line
|
// when create a layout from CSV line
|
||||||
Layout layout = Layout.fromCsv(csv1, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||||
List<DataField> dataFieldList = layout.getFields();
|
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||||
|
|
||||||
// then layout and data fields are built correctly
|
// then layout and data fields are built correctly
|
||||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||||
assertEquals(layout.getFields().size(), 0);
|
assertEquals(recordingLayout.getFields().size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -79,13 +75,13 @@ public class LayoutTest extends TestCase {
|
|||||||
String csv1 = "Layout Name;2;";
|
String csv1 = "Layout Name;2;";
|
||||||
|
|
||||||
// when create a layout from CSV line
|
// when create a layout from CSV line
|
||||||
Layout layout = Layout.fromCsv(csv1, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||||
List<DataField> dataFieldList = layout.getFields();
|
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||||
|
|
||||||
// then layout and data fields are built correctly
|
// then layout and data fields are built correctly
|
||||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||||
assertEquals(layout.getFields().size(), 0);
|
assertEquals(recordingLayout.getFields().size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -94,13 +90,13 @@ public class LayoutTest extends TestCase {
|
|||||||
String csv1 = "Layout Name;2;speed,distance,total time;";
|
String csv1 = "Layout Name;2;speed,distance,total time;";
|
||||||
|
|
||||||
// when create a layout from CSV line
|
// when create a layout from CSV line
|
||||||
Layout layout = Layout.fromCsv(csv1, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||||
List<DataField> dataFieldList = layout.getFields();
|
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||||
|
|
||||||
// then layout and data fields are built correctly
|
// then layout and data fields are built correctly
|
||||||
assertEquals(layout.getName(), "Layout Name");
|
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||||
assertEquals(layout.getColumnsPerRow(), 2);
|
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||||
assertEquals(layout.getFields().size(), 0);
|
assertEquals(recordingLayout.getFields().size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -109,27 +105,27 @@ public class LayoutTest extends TestCase {
|
|||||||
String csv1 = "Layout Name;2;speed,1,0;distance;";
|
String csv1 = "Layout Name;2;speed,1,0;distance;";
|
||||||
|
|
||||||
// when create a layout from CSV line
|
// when create a layout from CSV line
|
||||||
Layout layout = Layout.fromCsv(csv1, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||||
List<DataField> dataFieldList = layout.getFields();
|
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||||
|
|
||||||
// then layout and data fields are built correctly
|
// then layout and data fields are built correctly
|
||||||
assertEquals(layout.getName(), "Layout Name");
|
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||||
assertEquals(layout.getColumnsPerRow(), 2);
|
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||||
assertEquals(layout.getFields().size(), 1);
|
assertEquals(recordingLayout.getFields().size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToCsv() {
|
public void testToCsv() {
|
||||||
// given a layout's object
|
// given a layout's object
|
||||||
Layout layout = new Layout("Test Layout", 2);
|
RecordingLayout recordingLayout = new RecordingLayout("Test Layout", 2);
|
||||||
layout.addField("key1", "Title 1", false, false, false);
|
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||||
layout.addField("key2", "Title 2", false, true, false);
|
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||||
layout.addField("key3", "Title 3", true, false, false);
|
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||||
layout.addField("key4", "Title 4", true, true, false);
|
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||||
layout.addField("key5", "Title 5", true, true, true);
|
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||||
|
|
||||||
// when converts it to CSV
|
// when converts it to CSV
|
||||||
String csv = layout.toCsv();
|
String csv = recordingLayout.toCsv();
|
||||||
|
|
||||||
// then csv is well built
|
// then csv is well built
|
||||||
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
||||||
@@ -138,15 +134,15 @@ public class LayoutTest extends TestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testToCsv_columnsByDefault() {
|
public void testToCsv_columnsByDefault() {
|
||||||
// given a layout's object
|
// given a layout's object
|
||||||
Layout layout = new Layout("Test Layout");
|
RecordingLayout recordingLayout = new RecordingLayout("Test Layout");
|
||||||
layout.addField("key1", "Title 1", false, false, false);
|
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||||
layout.addField("key2", "Title 2", false, true, false);
|
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||||
layout.addField("key3", "Title 3", true, false, false);
|
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||||
layout.addField("key4", "Title 4", true, true, false);
|
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||||
layout.addField("key5", "Title 5", true, true, true);
|
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||||
|
|
||||||
// when converts it to CSV
|
// when converts it to CSV
|
||||||
String csv = layout.toCsv();
|
String csv = recordingLayout.toCsv();
|
||||||
|
|
||||||
// then csv is well built
|
// then csv is well built
|
||||||
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
||||||
@@ -34,23 +34,24 @@ public class EGM2008UtilsTest {
|
|||||||
int expectedLength = 18671444;
|
int expectedLength = 18671444;
|
||||||
int expectedHeaderLength = 404;
|
int expectedHeaderLength = 404;
|
||||||
|
|
||||||
String expectedHeader = "P5\n" +
|
String expectedHeader = """
|
||||||
"# Geoid file in PGM format for the GeographicLib::Geoid class\n" +
|
P5
|
||||||
"# Description WGS84 EGM2008, 5-minute grid\n" +
|
# Geoid file in PGM format for the GeographicLib::Geoid class
|
||||||
"# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008\n" +
|
# Description WGS84 EGM2008, 5-minute grid
|
||||||
"# DateTime 2009-08-29 18:45:00\n" +
|
# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008
|
||||||
"# MaxBilinearError 0.478\n" +
|
# DateTime 2009-08-29 18:45:00
|
||||||
"# RMSBilinearError 0.012\n" +
|
# MaxBilinearError 0.478
|
||||||
"# MaxCubicError 0.294\n" +
|
# RMSBilinearError 0.012
|
||||||
"# RMSCubicError 0.005\n" +
|
# MaxCubicError 0.294
|
||||||
"# Offset -108\n" +
|
# RMSCubicError 0.005
|
||||||
"# Scale 0.003\n" +
|
# Offset -108
|
||||||
"# Origin 90N 0E\n" +
|
# Scale 0.003
|
||||||
"# AREA_OR_POINT Point\n" +
|
# Origin 90N 0E
|
||||||
"# Vertical_Datum WGS84\n" +
|
# AREA_OR_POINT Point
|
||||||
"4320 2161\n" +
|
# Vertical_Datum WGS84
|
||||||
"65535" +
|
4320 2161
|
||||||
"\n";
|
65535
|
||||||
|
""";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
|
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import org.junit.runner.RunWith;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class StatisticsUtilsTest extends TestCase {
|
public class StatisticsUtilsTest extends TestCase {
|
||||||
@@ -44,16 +44,16 @@ public class StatisticsUtilsTest extends TestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testFilterVisible() {
|
public void testFilterVisible() {
|
||||||
// given
|
// given
|
||||||
Layout layout = new Layout("profile");
|
RecordingLayout recordingLayout = new RecordingLayout("profile");
|
||||||
layout.addField(new DataField("key1", "title1", true, true, false));
|
recordingLayout.addField(new DataField("key1", true, true, false));
|
||||||
layout.addField(new DataField("key2", "title2", false, true, false));
|
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||||
layout.addField(new DataField("key3", "title3", true, true, false));
|
recordingLayout.addField(new DataField("key3", true, true, false));
|
||||||
layout.addField(new DataField("key4", "title4", false, true, false));
|
recordingLayout.addField(new DataField("key4", false, true, false));
|
||||||
layout.addField(new DataField("key5", "title5", true, true, false));
|
recordingLayout.addField(new DataField("key5", true, true, false));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
|
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||||
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
|
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(resultTrue.getFields().size(), 3);
|
assertEquals(resultTrue.getFields().size(), 3);
|
||||||
|
|||||||
-199
@@ -1,199 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
|
||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
|
||||||
import de.dennisguse.opentracks.services.RecordingData;
|
|
||||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
|
||||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class StatisticDataBuilderTest extends TestCase {
|
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Track trackMock;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private TrackStatistics trackStatisticsMock;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RecordingData recordingDataMock;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private SensorStatistics sensorStatisticsMock;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromRecordingData() {
|
|
||||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
|
||||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
|
||||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
|
||||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
|
||||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
|
||||||
|
|
||||||
// given
|
|
||||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 3);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromRecordingData_allFields() {
|
|
||||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
|
||||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
|
||||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
|
||||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
|
||||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
|
||||||
|
|
||||||
// given
|
|
||||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false);
|
|
||||||
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 18);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromSensorStatistics_onlyHeartRate() {
|
|
||||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
|
|
||||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
|
|
||||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
|
||||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 2);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromSensorStatistics_onlyCadence() {
|
|
||||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
|
||||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
|
|
||||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
|
|
||||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 2);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromSensorStatistics_onlyPower() {
|
|
||||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
|
||||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
|
||||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFromSensorStatistics() {
|
|
||||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
|
|
||||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
|
|
||||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
|
|
||||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
|
|
||||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
|
||||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
|
|
||||||
|
|
||||||
// when
|
|
||||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(statisticDataList.size(), 5);
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
|
||||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -62,7 +62,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="@xml/backup_rules_30_lower"
|
||||||
|
android:dataExtractionRules="@xml/backup_rules_31_higher"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:hasFragileUserData="false"
|
android:hasFragileUserData="false"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.widget.ArrayAdapter;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.databinding.TrackEditBinding;
|
import de.dennisguse.opentracks.databinding.TrackEditBinding;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
@@ -75,7 +76,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
|
|
||||||
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
||||||
|
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
|
||||||
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
||||||
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
||||||
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
|||||||
@@ -311,9 +311,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||||
|
|
||||||
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
|
searchView.findViewById(androidx.appcompat.R.id.search_edit_frame).setPadding(0, 0, 48, 0);
|
||||||
|
|
||||||
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
|
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(androidx.appcompat.R.id.search_src_text);
|
||||||
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
|
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||||
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
|
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||||
|
|
||||||
|
|||||||
@@ -274,18 +274,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment createFragment(int position) {
|
public Fragment createFragment(int position) {
|
||||||
switch (position) {
|
return switch (position) {
|
||||||
case 0:
|
case 0 -> StatisticsRecordedFragment.newInstance(trackId);
|
||||||
return StatisticsRecordedFragment.newInstance(trackId);
|
case 1 -> IntervalsFragment.newInstance(trackId, true);
|
||||||
case 1:
|
case 2 -> ChartFragment.newInstance(false);
|
||||||
return IntervalsFragment.newInstance(trackId, true);
|
case 3 -> ChartFragment.newInstance(true);
|
||||||
case 2:
|
default ->
|
||||||
return ChartFragment.newInstance(false);
|
|
||||||
case 3:
|
|
||||||
return ChartFragment.newInstance(true);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -294,18 +290,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
return switch (position) {
|
||||||
case 0:
|
case 0 -> getString(R.string.track_detail_stats_tab);
|
||||||
return getString(R.string.track_detail_stats_tab);
|
case 1 -> getString(R.string.track_detail_intervals_tab);
|
||||||
case 1:
|
case 2 -> getString(R.string.settings_chart_by_time);
|
||||||
return getString(R.string.track_detail_intervals_tab);
|
case 3 -> getString(R.string.settings_chart_by_distance);
|
||||||
case 2:
|
default ->
|
||||||
return getString(R.string.settings_chart_by_time);
|
|
||||||
case 3:
|
|
||||||
return getString(R.string.settings_chart_by_distance);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -333,18 +333,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment createFragment(int position) {
|
public Fragment createFragment(int position) {
|
||||||
switch (position) {
|
return switch (position) {
|
||||||
case 0:
|
case 0 -> StatisticsRecordingFragment.newInstance();
|
||||||
return StatisticsRecordingFragment.newInstance();
|
case 1 -> IntervalsFragment.newInstance(trackId, false);
|
||||||
case 1:
|
case 2 -> ChartFragment.newInstance(false);
|
||||||
return IntervalsFragment.newInstance(trackId, false);
|
case 3 -> ChartFragment.newInstance(true);
|
||||||
case 2:
|
default ->
|
||||||
return ChartFragment.newInstance(false);
|
|
||||||
case 3:
|
|
||||||
return ChartFragment.newInstance(true);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -353,18 +349,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
return switch (position) {
|
||||||
case 0:
|
case 0 -> getString(R.string.track_detail_stats_tab);
|
||||||
return getString(R.string.track_detail_stats_tab);
|
case 1 -> getString(R.string.track_detail_intervals_tab);
|
||||||
case 1:
|
case 2 -> getString(R.string.settings_chart_by_time);
|
||||||
return getString(R.string.track_detail_intervals_tab);
|
case 3 -> getString(R.string.settings_chart_by_distance);
|
||||||
case 2:
|
default ->
|
||||||
return getString(R.string.settings_chart_by_time);
|
|
||||||
case 3:
|
|
||||||
return getString(R.string.settings_chart_by_distance);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,13 +373,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
||||||
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
|
boolean snackbarShowing = snackbar != null && snackbar.isShown();
|
||||||
|
if (gpsStatusValue.isGpsStarted()) {
|
||||||
|
if (snackbarShowing) {
|
||||||
snackbar.dismiss();
|
snackbar.dismiss();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (gpsStatusValue != GpsStatusValue.GPS_DISABLED) {
|
|
||||||
|
if (snackbarShowing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
snackbar = Snackbar
|
snackbar = Snackbar
|
||||||
.make(viewBinding.trackRecordingCoordinatorLayout,
|
.make(viewBinding.trackRecordingCoordinatorLayout,
|
||||||
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.view.View;
|
|||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
@@ -51,7 +52,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
|||||||
|
|
||||||
viewBinding.trackEditActivityType.setText(track.getCategory());
|
viewBinding.trackEditActivityType.setText(track.getCategory());
|
||||||
|
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
|
||||||
viewBinding.trackEditActivityType.setAdapter(adapter);
|
viewBinding.trackEditActivityType.setAdapter(adapter);
|
||||||
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
||||||
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.adapters;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextClock;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CustomLayoutFieldType;
|
|
||||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
|
||||||
|
|
||||||
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|
||||||
|
|
||||||
private List<StatisticData> statisticDataList;
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
public StatisticsAdapter(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
this.statisticDataList = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View view;
|
|
||||||
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
|
|
||||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false);
|
|
||||||
return new StatisticsAdapter.ViewClockHolder(view);
|
|
||||||
} else {
|
|
||||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false);
|
|
||||||
return new StatisticsAdapter.ViewHolder(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
|
||||||
StatisticData statisticData = statisticDataList.get(position);
|
|
||||||
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
|
|
||||||
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
|
|
||||||
viewHolder.setData(statisticData);
|
|
||||||
} else {
|
|
||||||
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
|
|
||||||
viewHolder.setData(statisticData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
if (statisticDataList == null) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return statisticDataList.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
if (statisticDataList.get(position).getField().getType(context) == CustomLayoutFieldType.CLOCK) {
|
|
||||||
return CustomLayoutFieldType.CLOCK.value();
|
|
||||||
} else {
|
|
||||||
return CustomLayoutFieldType.GENERIC.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isItemWide(int position) {
|
|
||||||
return statisticDataList.get(position).getField().isWide();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<StatisticData> swapData(List<StatisticData> data) {
|
|
||||||
if (statisticDataList == data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
statisticDataList = data;
|
|
||||||
|
|
||||||
if (data != null) {
|
|
||||||
this.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class WithRecordedLayout extends StatisticsAdapter {
|
|
||||||
public WithRecordedLayout(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false);
|
|
||||||
return new StatisticsAdapter.ViewRecordedHolder(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
final TextView value;
|
|
||||||
final TextView unit;
|
|
||||||
final TextView descMain;
|
|
||||||
final TextView descSecondary;
|
|
||||||
|
|
||||||
public ViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
value = itemView.findViewById(R.id.stats_value);
|
|
||||||
unit = itemView.findViewById(R.id.stats_unit);
|
|
||||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
|
||||||
descSecondary = itemView.findViewById(R.id.stats_description_secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(StatisticData statisticData) {
|
|
||||||
if (statisticData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
|
||||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
|
||||||
this.unit.setText(statisticData.getUnit());
|
|
||||||
this.descMain.setText(statisticData.getField().getTitle());
|
|
||||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
|
||||||
if (statisticData.hasDescription()) {
|
|
||||||
this.descSecondary.setVisibility(View.VISIBLE);
|
|
||||||
this.descSecondary.setText(statisticData.getDescription());
|
|
||||||
} else {
|
|
||||||
this.descSecondary.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewClockHolder extends RecyclerView.ViewHolder {
|
|
||||||
final TextClock value;
|
|
||||||
final TextView descMain;
|
|
||||||
|
|
||||||
public ViewClockHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
value = itemView.findViewById(R.id.stats_clock);
|
|
||||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(StatisticData statisticData) {
|
|
||||||
if (statisticData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
|
||||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewRecordedHolder extends ViewHolder {
|
|
||||||
|
|
||||||
public ViewRecordedHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(StatisticData statisticData) {
|
|
||||||
if (statisticData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
|
||||||
this.unit.setText(statisticData.getUnit());
|
|
||||||
this.descMain.setText(statisticData.getField().getTitle());
|
|
||||||
if (statisticData.hasDescription()) {
|
|
||||||
this.descSecondary.setVisibility(View.VISIBLE);
|
|
||||||
this.descSecondary.setText(statisticData.getDescription());
|
|
||||||
} else {
|
|
||||||
this.descSecondary.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -210,16 +210,11 @@ abstract class ChartValueSeries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int getTitleId(UnitSystem unitSystem) {
|
int getTitleId(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> metricTitleId;
|
||||||
return metricTitleId;
|
case IMPERIAL -> imperialTitleId;
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL -> nauticalTitleId;
|
||||||
return imperialTitleId;
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
return nauticalTitleId;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Paint getTitlePaint() {
|
Paint getTitlePaint() {
|
||||||
|
|||||||
@@ -733,16 +733,11 @@ public class ChartView extends View {
|
|||||||
private String getXAxisLabel() {
|
private String getXAxisLabel() {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> context.getString(R.string.unit_kilometer);
|
||||||
return context.getString(R.string.unit_kilometer);
|
case IMPERIAL -> context.getString(R.string.unit_mile);
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL -> context.getString(R.string.unit_nautical_mile);
|
||||||
return context.getString(R.string.unit_mile);
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
return context.getString(R.string.unit_nautical_mile);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return context.getString(R.string.description_time);
|
return context.getString(R.string.description_time);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,20 +133,12 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
|
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
|
||||||
String table;
|
String table = switch (getUrlType(url)) {
|
||||||
switch (getUrlType(url)) {
|
case TRACKPOINTS -> TrackPointsColumns.TABLE_NAME;
|
||||||
case TRACKPOINTS:
|
case TRACKS -> TracksColumns.TABLE_NAME;
|
||||||
table = TrackPointsColumns.TABLE_NAME;
|
case MARKERS -> MarkerColumns.TABLE_NAME;
|
||||||
break;
|
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||||
case TRACKS:
|
};
|
||||||
table = TracksColumns.TABLE_NAME;
|
|
||||||
break;
|
|
||||||
case MARKERS:
|
|
||||||
table = MarkerColumns.TABLE_NAME;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown URL " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, "Deleting from table " + table);
|
Log.w(TAG, "Deleting from table " + table);
|
||||||
int totalChangesBefore = getTotalChanges();
|
int totalChangesBefore = getTotalChanges();
|
||||||
@@ -186,24 +178,15 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType(@NonNull Uri url) {
|
public String getType(@NonNull Uri url) {
|
||||||
switch (getUrlType(url)) {
|
return switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS:
|
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||||
return TrackPointsColumns.CONTENT_TYPE;
|
case TRACKPOINTS_BY_ID, TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||||
case TRACKPOINTS_BY_ID:
|
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||||
case TRACKPOINTS_BY_TRACKID:
|
case TRACKS_BY_ID -> TracksColumns.CONTENT_ITEMTYPE;
|
||||||
return TrackPointsColumns.CONTENT_ITEMTYPE;
|
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||||
case TRACKS:
|
case MARKERS_BY_ID, MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||||
return TracksColumns.CONTENT_TYPE;
|
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||||
case TRACKS_BY_ID:
|
};
|
||||||
return TracksColumns.CONTENT_ITEMTYPE;
|
|
||||||
case MARKERS:
|
|
||||||
return MarkerColumns.CONTENT_TYPE;
|
|
||||||
case MARKERS_BY_ID:
|
|
||||||
case MARKERS_BY_TRACKID:
|
|
||||||
return MarkerColumns.CONTENT_ITEMTYPE;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown URL " + url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -251,47 +234,47 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
String sortOrder = null;
|
String sortOrder = null;
|
||||||
switch (getUrlType(url)) {
|
switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS:
|
case TRACKPOINTS -> {
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
|
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||||
break;
|
}
|
||||||
case TRACKPOINTS_BY_ID:
|
case TRACKPOINTS_BY_ID -> {
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
|
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
break;
|
}
|
||||||
case TRACKPOINTS_BY_TRACKID:
|
case TRACKPOINTS_BY_TRACKID -> {
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
break;
|
}
|
||||||
case TRACKS:
|
case TRACKS -> {
|
||||||
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
||||||
} else {
|
} else {
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
}
|
}
|
||||||
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
|
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
|
||||||
break;
|
}
|
||||||
case TRACKS_BY_ID:
|
case TRACKS_BY_ID -> {
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
break;
|
}
|
||||||
case TRACKS_SENSOR_STATS:
|
case TRACKS_SENSOR_STATS -> {
|
||||||
long trackId = ContentUris.parseId(url);
|
long trackId = ContentUris.parseId(url);
|
||||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||||
case MARKERS:
|
}
|
||||||
|
case MARKERS -> {
|
||||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||||
break;
|
}
|
||||||
case MARKERS_BY_ID:
|
case MARKERS_BY_ID -> {
|
||||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
|
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
break;
|
}
|
||||||
case MARKERS_BY_TRACKID:
|
case MARKERS_BY_TRACKID -> {
|
||||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||||
throw new IllegalArgumentException("Unknown url " + url);
|
|
||||||
}
|
}
|
||||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
||||||
@@ -304,41 +287,40 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
String table;
|
String table;
|
||||||
String whereClause;
|
String whereClause;
|
||||||
switch (getUrlType(url)) {
|
switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS:
|
case TRACKPOINTS -> {
|
||||||
table = TrackPointsColumns.TABLE_NAME;
|
table = TrackPointsColumns.TABLE_NAME;
|
||||||
whereClause = where;
|
whereClause = where;
|
||||||
break;
|
}
|
||||||
case TRACKPOINTS_BY_ID:
|
case TRACKPOINTS_BY_ID -> {
|
||||||
table = TrackPointsColumns.TABLE_NAME;
|
table = TrackPointsColumns.TABLE_NAME;
|
||||||
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
|
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TRACKS:
|
case TRACKS -> {
|
||||||
table = TracksColumns.TABLE_NAME;
|
table = TracksColumns.TABLE_NAME;
|
||||||
whereClause = where;
|
whereClause = where;
|
||||||
break;
|
}
|
||||||
case TRACKS_BY_ID:
|
case TRACKS_BY_ID -> {
|
||||||
table = TracksColumns.TABLE_NAME;
|
table = TracksColumns.TABLE_NAME;
|
||||||
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
|
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case MARKERS:
|
case MARKERS -> {
|
||||||
table = MarkerColumns.TABLE_NAME;
|
table = MarkerColumns.TABLE_NAME;
|
||||||
whereClause = where;
|
whereClause = where;
|
||||||
break;
|
}
|
||||||
case MARKERS_BY_ID:
|
case MARKERS_BY_ID -> {
|
||||||
table = MarkerColumns.TABLE_NAME;
|
table = MarkerColumns.TABLE_NAME;
|
||||||
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
|
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
|
||||||
if (!TextUtils.isEmpty(where)) {
|
if (!TextUtils.isEmpty(where)) {
|
||||||
whereClause += " AND (" + where + ")";
|
whereClause += " AND (" + where + ")";
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||||
throw new IllegalArgumentException("Unknown url " + url);
|
|
||||||
}
|
}
|
||||||
int count;
|
int count;
|
||||||
try {
|
try {
|
||||||
@@ -371,16 +353,12 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
* @param contentValues the content values
|
* @param contentValues the content values
|
||||||
*/
|
*/
|
||||||
private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) {
|
private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) {
|
||||||
switch (urlType) {
|
return switch (urlType) {
|
||||||
case TRACKPOINTS:
|
case TRACKPOINTS -> insertTrackPoint(url, contentValues);
|
||||||
return insertTrackPoint(url, contentValues);
|
case TRACKS -> insertTrack(url, contentValues);
|
||||||
case TRACKS:
|
case MARKERS -> insertMarker(url, contentValues);
|
||||||
return insertTrack(url, contentValues);
|
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||||
case MARKERS:
|
};
|
||||||
return insertMarker(url, contentValues);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown url " + url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Uri insertTrackPoint(Uri url, ContentValues values) {
|
private Uri insertTrackPoint(Uri url, ContentValues values) {
|
||||||
|
|||||||
@@ -61,42 +61,18 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
|
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
|
||||||
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
|
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
|
||||||
switch (toVersion) {
|
switch (toVersion) {
|
||||||
case 24:
|
case 24 -> upgradeFrom23to24(db);
|
||||||
upgradeFrom23to24(db);
|
case 25 -> upgradeFrom24to25(db);
|
||||||
break;
|
case 26 -> upgradeFrom25to26(db);
|
||||||
case 25:
|
case 27 -> upgradeFrom26to27(db);
|
||||||
upgradeFrom24to25(db);
|
case 28 -> upgradeFrom27to28(db);
|
||||||
break;
|
case 29 -> upgradeFrom28to29(db);
|
||||||
case 26:
|
case 30 -> upgradeFrom29to30(db);
|
||||||
upgradeFrom25to26(db);
|
case 31 -> upgradeFrom30to31(db);
|
||||||
break;
|
case 32 -> upgradeFrom31to32(db);
|
||||||
case 27:
|
case 33 -> upgradeFrom32to33(db);
|
||||||
upgradeFrom26to27(db);
|
case 34 -> upgradeFrom33to34(db);
|
||||||
break;
|
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
case 28:
|
|
||||||
upgradeFrom27to28(db);
|
|
||||||
break;
|
|
||||||
case 29:
|
|
||||||
upgradeFrom28to29(db);
|
|
||||||
break;
|
|
||||||
case 30:
|
|
||||||
upgradeFrom29to30(db);
|
|
||||||
break;
|
|
||||||
case 31:
|
|
||||||
upgradeFrom30to31(db);
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
upgradeFrom31to32(db);
|
|
||||||
break;
|
|
||||||
case 33:
|
|
||||||
upgradeFrom32to33(db);
|
|
||||||
break;
|
|
||||||
case 34:
|
|
||||||
upgradeFrom33to34(db);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,41 +82,18 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
|
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
|
||||||
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
|
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
|
||||||
switch (toVersion) {
|
switch (toVersion) {
|
||||||
case 23:
|
case 23 -> downgradeFrom24to23(db);
|
||||||
downgradeFrom24to23(db);
|
case 24 -> downgradeFrom25to24(db);
|
||||||
break;
|
case 25 -> downgradeFrom26to25(db);
|
||||||
case 24:
|
case 26 -> downgradeFrom27to26(db);
|
||||||
downgradeFrom25to24(db);
|
case 27 -> downgradeFrom28to27(db);
|
||||||
break;
|
case 28 -> downgradeFrom29to28(db);
|
||||||
case 25:
|
case 29 -> downgradeFrom30to29(db);
|
||||||
downgradeFrom26to25(db);
|
case 30 -> downgradeFrom31to30(db);
|
||||||
break;
|
case 31 -> downgradeFrom32to31(db);
|
||||||
case 26:
|
case 32 -> downgradeFrom33to32(db);
|
||||||
downgradeFrom27to26(db);
|
case 33 -> downgradeFrom34to33(db);
|
||||||
break;
|
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
case 27:
|
|
||||||
downgradeFrom28to27(db);
|
|
||||||
break;
|
|
||||||
case 28:
|
|
||||||
downgradeFrom29to28(db);
|
|
||||||
break;
|
|
||||||
case 29:
|
|
||||||
downgradeFrom30to29(db);
|
|
||||||
break;
|
|
||||||
case 30:
|
|
||||||
downgradeFrom31to30(db);
|
|
||||||
break;
|
|
||||||
case 31:
|
|
||||||
downgradeFrom32to31(db);
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
downgradeFrom33to32(db);
|
|
||||||
break;
|
|
||||||
case 33:
|
|
||||||
downgradeFrom34to33(db);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,24 +129,18 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
||||||
switch (uriMatcher.match(uri)) {
|
return switch (uriMatcher.match(uri)) {
|
||||||
case URI_GPX:
|
case URI_GPX -> TrackFileFormat.GPX;
|
||||||
return TrackFileFormat.GPX;
|
case URI_KML_WITH_TRACKDETAIL_SENSORDATA ->
|
||||||
|
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||||
case URI_KML_WITH_TRACKDETAIL_SENSORDATA:
|
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA ->
|
||||||
return TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||||
|
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
|
||||||
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA:
|
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||||
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
case URI_CSV -> TrackFileFormat.CSV;
|
||||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
|
default ->
|
||||||
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
|
||||||
|
|
||||||
case URI_CSV:
|
|
||||||
return TrackFileFormat.CSV;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -170,14 +164,14 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
for (String col : projection) {
|
for (String col : projection) {
|
||||||
switch (col) {
|
switch (col) {
|
||||||
case OpenableColumns.DISPLAY_NAME:
|
case OpenableColumns.DISPLAY_NAME -> {
|
||||||
cols[i] = OpenableColumns.DISPLAY_NAME;
|
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||||
values[i++] = uri.getLastPathSegment();
|
values[i++] = uri.getLastPathSegment();
|
||||||
break;
|
}
|
||||||
case OpenableColumns.SIZE:
|
case OpenableColumns.SIZE -> {
|
||||||
cols[i] = OpenableColumns.SIZE;
|
cols[i] = OpenableColumns.SIZE;
|
||||||
values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream.
|
values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream.
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +208,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final TrackExporter trackExporter = getTrackFileFormat(uri).createTrackExporter(getContext());
|
final TrackExporter trackExporter = getTrackFileFormat(uri).createTrackExporter(getContext(), new ContentProviderUtils(getContext()));
|
||||||
|
|
||||||
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
|
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
|
||||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package de.dennisguse.opentracks.data.models;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
|
public enum ActivityType {
|
||||||
|
AIRPLANE("AIRPLANE", R.drawable.ic_activity_flight_24dp, true, R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane),
|
||||||
|
BIKE("BIKE", R.drawable.ic_activity_bike_24dp, true, R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_road_biking, R.string.activity_type_track_cycling),
|
||||||
|
MOUNTAIN_BIKE("MOUNTAIN_BIKE", R.drawable.ic_activity_mtb_24dp, true, R.string.activity_type_mountain_biking),
|
||||||
|
MOTOR_BIKE("MOTOR_BIKE", R.drawable.ic_activity_motorbike_24dp, true, R.string.activity_type_motor_bike),
|
||||||
|
KAYAK("KAYAK", R.drawable.ic_activity_kayaking_24dp, true, R.string.activity_type_kayaking),
|
||||||
|
BOAT("BOAT", R.drawable.ic_activity_boat_24dp, true, R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat),
|
||||||
|
SAILING("SAILING", R.drawable.ic_activity_sailing_24dp, true, R.string.activity_type_sailing),
|
||||||
|
DRIVE("DRIVE", R.drawable.ic_activity_drive_24dp, true, R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car),
|
||||||
|
RUN("RUN", R.drawable.ic_activity_run_24dp, false, R.string.activity_type_running, R.string.activity_type_street_running, R.string.activity_type_track_running, R.string.activity_type_trail_running),
|
||||||
|
SKI("SKI", R.drawable.ic_activity_skiing_24dp, true, R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing),
|
||||||
|
SNOW_BOARDING("SNOW_BOARDING", R.drawable.ic_activity_snowboarding_24dp, true, R.string.activity_type_snow_boarding),
|
||||||
|
UNKNOWN("UNKNOWN", R.drawable.ic_logo_24dp, true, R.string.activity_type_unknown),
|
||||||
|
WALK("WALK", R.drawable.ic_activity_walk_24dp, false, R.string.activity_type_hiking, R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking, R.string.activity_type_trail_hiking, R.string.activity_type_walking),
|
||||||
|
ESCOOTER("ESCOOTER", R.drawable.ic_activity_escooter_24dp, true, R.string.activity_type_escooter),
|
||||||
|
INLINE_SKATING("INLINES_SKATING", R.drawable.ic_activity_inline_skating_24dp, true, R.string.activity_type_inline_skating),
|
||||||
|
SKATE_BOARDING("SKATE_BOARDING", R.drawable.ic_activity_skateboarding_24dp, true, R.string.activity_type_skate_boarding),
|
||||||
|
CLIMBING("CLIMBING", R.drawable.ic_activity_climbing_24dp, false, R.string.activity_type_climbing),
|
||||||
|
SWIMMING("SWIMMING", R.drawable.ic_activity_swimming_24dp, false, R.string.activity_type_swimming),
|
||||||
|
SWIMMING_OPEN("SWIMMING_OPEN", R.drawable.ic_activity_swimming_open_24dp, false, R.string.activity_type_swimming_open),
|
||||||
|
WORKOUT("WORKOUT", R.drawable.ic_activity_workout_24dp, false, R.string.activity_type_workout);
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final int iconId;
|
||||||
|
final boolean showSpeedPreferred;
|
||||||
|
final int[] localizedStringIds;
|
||||||
|
|
||||||
|
//isSpeed?
|
||||||
|
|
||||||
|
ActivityType(String id, int iconId, boolean showSpeedPreferred, int... localizedStringIds) {
|
||||||
|
this.id = id;
|
||||||
|
this.iconId = iconId;
|
||||||
|
this.showSpeedPreferred = showSpeedPreferred;
|
||||||
|
this.localizedStringIds = localizedStringIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIconId() {
|
||||||
|
return iconId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowSpeedPreferred() {
|
||||||
|
return showSpeedPreferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getLocalizedStringIds() {
|
||||||
|
return localizedStringIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFirstLocalizedStringId() {
|
||||||
|
return localizedStringIds[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getLocalizedStrings(Context context) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (ActivityType activityType : values()) {
|
||||||
|
List<String> strings = Arrays.stream(activityType.localizedStringIds)
|
||||||
|
.mapToObj(context::getString)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
result.addAll(strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,16 +58,11 @@ public class Distance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Distance one(UnitSystem unitSystem) {
|
public static Distance one(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> Distance.ofKilometer(1);
|
||||||
return Distance.ofKilometer(1);
|
case IMPERIAL -> Distance.ofMile(1);
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL -> Distance.ofNauticalMile(1);
|
||||||
return Distance.ofMile(1);
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
return Distance.ofNauticalMile(1);
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final double distance_m;
|
private final double distance_m;
|
||||||
@@ -133,28 +128,18 @@ public class Distance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double toKM_Miles(UnitSystem unitSystem) {
|
public double toKM_Miles(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> toKM();
|
||||||
return toKM();
|
case IMPERIAL -> toMI();
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL -> toNauticalMiles();
|
||||||
return toMI();
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
return toNauticalMiles();
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double toM_FT(UnitSystem unitSystem) {
|
public double toM_FT(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> toM();
|
||||||
return toM();
|
case NAUTICAL_IMPERIAL, IMPERIAL -> toFT();
|
||||||
case NAUTICAL_IMPERIAL:
|
};
|
||||||
case IMPERIAL:
|
|
||||||
return toFT();
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -46,38 +46,36 @@ public class DistanceFormatter {
|
|||||||
public Pair<String, String> getDistanceParts(Distance distance) {
|
public Pair<String, String> getDistanceParts(Distance distance) {
|
||||||
if (distance.isInvalid()) {
|
if (distance.isInvalid()) {
|
||||||
String valueUnknown = resources.getString(R.string.value_unknown);
|
String valueUnknown = resources.getString(R.string.value_unknown);
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
||||||
return new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
case IMPERIAL, NAUTICAL_IMPERIAL ->
|
||||||
case IMPERIAL:
|
new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
|
||||||
case NAUTICAL_IMPERIAL:
|
};
|
||||||
return new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
if (distance.greaterThan(Distance.ofKilometer(threshold))) {
|
if (distance.greaterThan(Distance.ofKilometer(threshold))) {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
|
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
|
||||||
} else {
|
} else {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
|
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
|
||||||
}
|
}
|
||||||
case IMPERIAL:
|
}
|
||||||
|
case IMPERIAL -> {
|
||||||
if (distance.greaterThan(Distance.ofMile(threshold))) {
|
if (distance.greaterThan(Distance.ofMile(threshold))) {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
|
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
|
||||||
} else {
|
} else {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||||
}
|
}
|
||||||
case NAUTICAL_IMPERIAL:
|
}
|
||||||
|
case NAUTICAL_IMPERIAL -> {
|
||||||
if (distance.greaterThan(Distance.ofNauticalMile(threshold))) {
|
if (distance.greaterThan(Distance.ofNauticalMile(threshold))) {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
||||||
} else {
|
} else {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
throw new RuntimeException("Not implemented");
|
default -> throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.dennisguse.opentracks.data.models;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.ui.util.ThemeUtils;
|
||||||
|
|
||||||
|
public class HeartRateZones {
|
||||||
|
|
||||||
|
private final HeartRate max;
|
||||||
|
|
||||||
|
public HeartRateZones(@NonNull HeartRate max) {
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTextColorForZone(Context context, HeartRate current) {
|
||||||
|
if (current != null) {
|
||||||
|
if (current.getBPM() >= max.getBPM() * 0.9) {
|
||||||
|
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_5);
|
||||||
|
}
|
||||||
|
if (current.getBPM() >= max.getBPM() * 0.8) {
|
||||||
|
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_4);
|
||||||
|
}
|
||||||
|
if (current.getBPM() >= max.getBPM() * 0.7) {
|
||||||
|
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_3);
|
||||||
|
}
|
||||||
|
if (current.getBPM() >= max.getBPM() * 0.6) {
|
||||||
|
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone 1
|
||||||
|
return ThemeUtils.getTextColorPrimary(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,16 +115,11 @@ public class Speed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double to(UnitSystem unitSystem) {
|
public double to(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
return switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> toKMH();
|
||||||
return toKMH();
|
case IMPERIAL -> toMPH();
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL -> toKnots();
|
||||||
return toMPH();
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
return toKnots();
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -39,20 +39,14 @@ public class SpeedFormatter {
|
|||||||
* @return the formatted speed (or null) and it's unit as {@link Pair}
|
* @return the formatted speed (or null) and it's unit as {@link Pair}
|
||||||
*/
|
*/
|
||||||
public Pair<String, String> getSpeedParts(Speed speed) {
|
public Pair<String, String> getSpeedParts(Speed speed) {
|
||||||
int unitId;
|
int unitId = switch (unitSystem) {
|
||||||
switch (unitSystem) {
|
case METRIC ->
|
||||||
case METRIC:
|
reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||||
unitId = reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
case IMPERIAL ->
|
||||||
break;
|
reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||||
case IMPERIAL:
|
case NAUTICAL_IMPERIAL ->
|
||||||
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
||||||
break;
|
};
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
unitId = reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
String unitString = resources.getString(unitId);
|
String unitString = resources.getString(unitId);
|
||||||
|
|
||||||
|
|||||||
@@ -385,10 +385,6 @@ public class TrackPoint {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSensorData() {
|
|
||||||
return hasHeartRate() || hasCadence() || hasPower();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasHeartRate() {
|
public boolean hasHeartRate() {
|
||||||
return heartRate != null;
|
return heartRate != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,8 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||||
@@ -47,8 +44,6 @@ import de.dennisguse.opentracks.stats.SensorStatistics;
|
|||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
|
||||||
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
||||||
@@ -72,7 +67,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SensorStatistics sensorStatistics;
|
private SensorStatistics sensorStatistics;
|
||||||
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
|
||||||
|
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
@Nullable // Lazily loaded.
|
@Nullable // Lazily loaded.
|
||||||
@@ -113,8 +107,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
|
|
||||||
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||||
contentProviderUtils = new ContentProviderUtils(getContext());
|
contentProviderUtils = new ContentProviderUtils(getContext());
|
||||||
|
|
||||||
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,7 +115,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
|
|
||||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
// sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||||
|
|
||||||
return viewBinding.getRoot();
|
return viewBinding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -261,10 +253,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
if (sensorStatistics == null) {
|
if (sensorStatistics == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//TODO
|
||||||
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
|
||||||
if (sensorDataList.size() > 0) {
|
|
||||||
sensorsAdapter.swapData(sensorDataList);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.fragments;
|
|||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -9,16 +10,14 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.gridlayout.widget.GridLayout;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||||
import de.dennisguse.opentracks.services.RecordingData;
|
import de.dennisguse.opentracks.services.RecordingData;
|
||||||
@@ -26,9 +25,10 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
|
|||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
import de.dennisguse.opentracks.viewmodels.Mapping;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||||
@@ -48,32 +48,22 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
|
|
||||||
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||||
|
|
||||||
private Layout layout;
|
private final List<StatisticViewHolder<?>> viewHolders = new LinkedList<>();
|
||||||
|
|
||||||
|
private RecordingLayout recordingLayout;
|
||||||
|
|
||||||
private StatisticsRecordingBinding viewBinding;
|
private StatisticsRecordingBinding viewBinding;
|
||||||
private StatisticsAdapter statisticsAdapter;
|
|
||||||
private GridLayoutManager gridLayoutManager;
|
|
||||||
private StatisticsDataModel viewModel;
|
|
||||||
private LiveData<List<StatisticData>> statisticsLiveData;
|
|
||||||
|
|
||||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
boolean updateUInecessary = false;
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
updateUInecessary = true;
|
|
||||||
unitSystem = PreferencesUtils.getUnitSystem();
|
unitSystem = PreferencesUtils.getUnitSystem();
|
||||||
|
updateDataOnUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
|
||||||
updateUInecessary = true;
|
onLayoutChanged(PreferencesUtils.getCustomLayout());
|
||||||
layout = PreferencesUtils.getCustomLayout();
|
|
||||||
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key != null && updateUInecessary && isResumed()) {
|
|
||||||
getActivity().runOnUiThread(this::updateUI);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,30 +74,11 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
|
|
||||||
statisticsAdapter = new StatisticsAdapter(getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
|
||||||
layout = PreferencesUtils.getCustomLayout();
|
|
||||||
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
|
|
||||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
|
||||||
@Override
|
|
||||||
public int getSpanSize(int position) {
|
|
||||||
if (statisticsAdapter.isItemWide(position)) {
|
|
||||||
return layout.getColumnsPerRow();
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
recyclerView.setLayoutManager(gridLayoutManager);
|
|
||||||
recyclerView.setAdapter(statisticsAdapter);
|
|
||||||
|
|
||||||
return viewBinding.getRoot();
|
return viewBinding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,10 +88,6 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
|
|
||||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
|
|
||||||
statisticsLiveData = viewModel.getStatsData();
|
|
||||||
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
|
|
||||||
|
|
||||||
trackRecordingServiceConnection.startConnection(getContext());
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +106,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
viewHolders.clear();
|
||||||
viewBinding = null;
|
viewBinding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,17 +114,56 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
trackRecordingServiceConnection = null;
|
trackRecordingServiceConnection = null;
|
||||||
viewModel = null;
|
|
||||||
|
|
||||||
if (statisticsLiveData != null) {
|
|
||||||
statisticsLiveData.removeObservers(getActivity());
|
|
||||||
}
|
|
||||||
statisticsLiveData = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private void onLayoutChanged(@NonNull RecordingLayout newRecordingLayout) {
|
||||||
if (isResumed()) {
|
if (newRecordingLayout.equals(recordingLayout)) {
|
||||||
viewModel.update(recordingData, layout, unitSystem);
|
return;
|
||||||
|
}
|
||||||
|
recordingLayout = newRecordingLayout;
|
||||||
|
|
||||||
|
viewBinding.statsLayout.setColumnCount(recordingLayout.getColumnsPerRow());
|
||||||
|
|
||||||
|
viewBinding.statsLayout.removeAllViews(); //Let's start from scratch
|
||||||
|
viewHolders.clear();
|
||||||
|
|
||||||
|
Map<String, Callable<StatisticViewHolder<?>>> m = Mapping.create(getContext());
|
||||||
|
|
||||||
|
int rowIndex = 0;
|
||||||
|
int columnIndex = 0;
|
||||||
|
for (int i = 0; i < recordingLayout.getFields().size(); i++) {
|
||||||
|
DataField dataField = recordingLayout.getFields().get(i);
|
||||||
|
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
|
||||||
|
param.setGravity(Gravity.FILL_HORIZONTAL);
|
||||||
|
param.width = 0;
|
||||||
|
|
||||||
|
if (dataField.isWide()) {
|
||||||
|
rowIndex++;
|
||||||
|
param.columnSpec = GridLayout.spec(0, recordingLayout.getColumnsPerRow(), 1);
|
||||||
|
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
|
||||||
|
columnIndex = 0;
|
||||||
|
rowIndex++;
|
||||||
|
} else {
|
||||||
|
if (columnIndex >= recordingLayout.getColumnsPerRow()) {
|
||||||
|
columnIndex = 0;
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
param.columnSpec = GridLayout.spec(columnIndex, 1, 1);
|
||||||
|
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
|
||||||
|
|
||||||
|
columnIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StatisticViewHolder<?> viewHolder = m.get(dataField.getKey()).call();
|
||||||
|
viewHolder.initialize(getContext(), getLayoutInflater());
|
||||||
|
viewHolder.configureUI(dataField);
|
||||||
|
viewHolders.add(viewHolder);
|
||||||
|
|
||||||
|
viewBinding.statsLayout.addView(viewHolder.getView(), param);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not add " + dataField.getKey(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +176,12 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUI();
|
updateDataOnUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDataOnUI() {
|
||||||
|
if (isResumed()) {
|
||||||
|
viewHolders.forEach(i -> i.onChanged(unitSystem, recordingData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package de.dennisguse.opentracks.io.file;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -25,8 +27,8 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") {
|
KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
return new KMLTrackExporter(context, false);
|
return new KMLTrackExporter(context, contentProviderUtils, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,9 +47,9 @@ public enum TrackFileFormat {
|
|||||||
private static final boolean exportPhotos = false;
|
private static final boolean exportPhotos = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos);
|
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||||
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos);
|
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,9 +72,9 @@ public enum TrackFileFormat {
|
|||||||
private static final boolean exportPhotos = true;
|
private static final boolean exportPhotos = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos);
|
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||||
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos);
|
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,8 +95,8 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
GPX("GPX") {
|
GPX("GPX") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
return new GPXTrackExporter(new ContentProviderUtils(context), context.getString(R.string.app_name));
|
return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,8 +111,8 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
CSV("CSV") {
|
CSV("CSV") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||||
return new CSVTrackExporter(new ContentProviderUtils(context));
|
return new CSVTrackExporter(contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -161,7 +163,7 @@ public enum TrackFileFormat {
|
|||||||
*
|
*
|
||||||
* @param context the context
|
* @param context the context
|
||||||
*/
|
*/
|
||||||
public abstract TrackExporter createTrackExporter(Context context);
|
public abstract TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the file extension for each format.
|
* Returns the file extension for each format.
|
||||||
|
|||||||
@@ -93,23 +93,9 @@ public class CSVTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||||
List<Column> columns = Collections.unmodifiableList(Arrays.asList(
|
List<Column> columns = Collections.unmodifiableList(Arrays.asList(new Column("time", null), new Column("trackpoint_type", t -> quote(t.getType().name())), new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""), new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""), new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""), new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""), new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
||||||
new Column("time", null),
|
|
||||||
new Column("trackpoint_type", t -> quote(t.getType().name())),
|
|
||||||
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
|
||||||
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
|
|
||||||
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
|
|
||||||
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
|
|
||||||
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
|
||||||
|
|
||||||
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""),
|
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""), new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""), new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""), new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""), new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""), new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""), new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : "")));
|
||||||
new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""),
|
|
||||||
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""),
|
|
||||||
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
|
|
||||||
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
|
|
||||||
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
|
|
||||||
new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : "")
|
|
||||||
));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
prepare(outputStream);
|
prepare(outputStream);
|
||||||
@@ -144,14 +130,9 @@ public class CSVTrackExporter implements TrackExporter {
|
|||||||
TrackPoint trackPoint = trackPointIterator.next();
|
TrackPoint trackPoint = trackPointIterator.next();
|
||||||
|
|
||||||
switch (trackPoint.getType()) {
|
switch (trackPoint.getType()) {
|
||||||
case SEGMENT_START_MANUAL:
|
case SEGMENT_START_MANUAL, SEGMENT_END_MANUAL, SEGMENT_START_AUTOMATIC, SENSORPOINT, TRACKPOINT ->
|
||||||
case SEGMENT_END_MANUAL:
|
|
||||||
case SEGMENT_START_AUTOMATIC:
|
|
||||||
case SENSORPOINT:
|
|
||||||
case TRACKPOINT:
|
|
||||||
writeTrackPoint(columns, trackPoint);
|
writeTrackPoint(columns, trackPoint);
|
||||||
break;
|
default ->
|
||||||
default:
|
|
||||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,18 +152,14 @@ public class CSVTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
public void writeHeader(List<Column> columns) {
|
public void writeHeader(List<Column> columns) {
|
||||||
if (printWriter != null) {
|
if (printWriter != null) {
|
||||||
String columnNames = columns.stream().map(c -> c.columnName)
|
String columnNames = columns.stream().map(c -> c.columnName).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||||
.reduce((s, s2) -> s + "," + s2)
|
|
||||||
.orElseThrow(() -> new RuntimeException("No columns defined"));
|
|
||||||
printWriter.println("#" + columnNames);
|
printWriter.println("#" + columnNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeTrackPoint(List<Column> columns, TrackPoint trackPoint) {
|
public void writeTrackPoint(List<Column> columns, TrackPoint trackPoint) {
|
||||||
if (printWriter != null) {
|
if (printWriter != null) {
|
||||||
String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint))
|
String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint)).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||||
.reduce((s, s2) -> s + "," + s2)
|
|
||||||
.orElseThrow(() -> new RuntimeException("No columns defined"));
|
|
||||||
printWriter.println(columnNames);
|
printWriter.println(columnNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-8
@@ -30,14 +30,9 @@ public class ExportServiceResultReceiver extends ResultReceiver {
|
|||||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||||
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||||
switch (resultCode) {
|
switch (resultCode) {
|
||||||
case RESULT_CODE_SUCCESS:
|
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(trackId);
|
||||||
receiver.onExportSuccess(trackId);
|
case RESULT_CODE_ERROR -> receiver.onExportError(trackId);
|
||||||
break;
|
default -> throw new RuntimeException("Unknown resultCode.");
|
||||||
case RESULT_CODE_ERROR:
|
|
||||||
receiver.onExportError(trackId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unknown resultCode.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,36 +145,31 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (trackPoint.getType()) {
|
switch (trackPoint.getType()) {
|
||||||
case SEGMENT_START_MANUAL:
|
case SEGMENT_START_MANUAL ->
|
||||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
|
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
|
||||||
break;
|
case SEGMENT_END_MANUAL -> {
|
||||||
case SEGMENT_END_MANUAL:
|
|
||||||
if (wroteSegment) writeCloseSegment();
|
if (wroteSegment) writeCloseSegment();
|
||||||
wroteSegment = false;
|
wroteSegment = false;
|
||||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported.");
|
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported.");
|
||||||
break;
|
}
|
||||||
case SEGMENT_START_AUTOMATIC:
|
case SEGMENT_START_AUTOMATIC -> {
|
||||||
if (wroteSegment) writeCloseSegment();
|
if (wroteSegment) writeCloseSegment();
|
||||||
writeOpenSegment();
|
writeOpenSegment();
|
||||||
wroteSegment = true;
|
wroteSegment = true;
|
||||||
|
|
||||||
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
||||||
sensorPoints.clear();
|
sensorPoints.clear();
|
||||||
break;
|
}
|
||||||
case SENSORPOINT:
|
case SENSORPOINT -> sensorPoints.add(trackPoint);
|
||||||
sensorPoints.add(trackPoint);
|
case TRACKPOINT -> {
|
||||||
break;
|
|
||||||
case TRACKPOINT:
|
|
||||||
if (!wroteSegment) {
|
if (!wroteSegment) {
|
||||||
// Might happen for older data (pre v3.15.0)
|
// Might happen for older data (pre v3.15.0)
|
||||||
writeOpenSegment();
|
writeOpenSegment();
|
||||||
wroteSegment = true;
|
wroteSegment = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
||||||
sensorPoints.clear();
|
sensorPoints.clear();
|
||||||
break;
|
}
|
||||||
default:
|
default ->
|
||||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
private final List<Float> accuracyHorizontal = new ArrayList<>();
|
private final List<Float> accuracyHorizontal = new ArrayList<>();
|
||||||
private final List<Float> accuracyVertical = new ArrayList<>();
|
private final List<Float> accuracyVertical = new ArrayList<>();
|
||||||
|
|
||||||
public KMLTrackExporter(Context context, boolean exportPhotos) {
|
public KMLTrackExporter(Context context, ContentProviderUtils contentProviderUtils, boolean exportPhotos) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.exportPhotos = exportPhotos;
|
this.exportPhotos = exportPhotos;
|
||||||
this.contentProviderUtils = new ContentProviderUtils(context);
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||||
@@ -169,29 +169,27 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (trackPoint.getType()) {
|
switch (trackPoint.getType()) {
|
||||||
case SEGMENT_START_MANUAL:
|
case SEGMENT_START_MANUAL, SEGMENT_START_AUTOMATIC -> {
|
||||||
case SEGMENT_START_AUTOMATIC:
|
|
||||||
if (wroteSegment) writeCloseSegment();
|
if (wroteSegment) writeCloseSegment();
|
||||||
writeOpenSegment();
|
writeOpenSegment();
|
||||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||||
wroteSegment = true;
|
wroteSegment = true;
|
||||||
break;
|
}
|
||||||
case SEGMENT_END_MANUAL:
|
case SEGMENT_END_MANUAL -> {
|
||||||
if (!wroteSegment) writeOpenSegment();
|
if (!wroteSegment) writeOpenSegment();
|
||||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||||
writeCloseSegment();
|
writeCloseSegment();
|
||||||
wroteSegment = false;
|
wroteSegment = false;
|
||||||
break;
|
}
|
||||||
case SENSORPOINT:
|
case SENSORPOINT, TRACKPOINT -> {
|
||||||
case TRACKPOINT:
|
|
||||||
if (!wroteSegment) {
|
if (!wroteSegment) {
|
||||||
// Might happen for older data (pre v3.15.0)
|
// Might happen for older data (pre v3.15.0)
|
||||||
writeOpenSegment();
|
writeOpenSegment();
|
||||||
wroteSegment = true;
|
wroteSegment = true;
|
||||||
}
|
}
|
||||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||||
break;
|
}
|
||||||
default:
|
default ->
|
||||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,15 +224,19 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
private void writeHeader(Track[] tracks) {
|
private void writeHeader(Track[] tracks) {
|
||||||
if (printWriter != null) {
|
if (printWriter != null) {
|
||||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
printWriter.println(
|
||||||
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.3\"");
|
"""
|
||||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">");
|
""");
|
||||||
//TODO ADD xsi:schemaLocation for atom
|
printWriter.println(
|
||||||
printWriter.println("xsi:schemaLocation=" +
|
"""
|
||||||
"\"http://www.opengis.net/kml/2.3 http://schemas.opengis.net/kml/2.3/ogckml23.xsd"
|
<kml xmlns="http://www.opengis.net/kml/2.3"
|
||||||
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||||
|
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.opengis.net/kml/2.3 http://schemas.opengis.net/kml/2.3/ogckml23.xsd
|
||||||
|
http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
|
||||||
|
"""); //TODO ADD xsi:schemaLocation for atom
|
||||||
printWriter.println("<Document>");
|
printWriter.println("<Document>");
|
||||||
printWriter.println("<open>1</open>");
|
printWriter.println("<open>1</open>");
|
||||||
printWriter.println("<visibility>1</visibility>");
|
printWriter.println("<visibility>1</visibility>");
|
||||||
|
|||||||
@@ -156,103 +156,94 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String tag) {
|
public void endElement(String uri, String localName, String tag) {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TAG_GPX:
|
case TAG_GPX -> onFileEnd();
|
||||||
onFileEnd();
|
case TAG_MARKER -> onMarkerEnd();
|
||||||
break;
|
case TAG_TRACK -> {
|
||||||
case TAG_MARKER:
|
|
||||||
onMarkerEnd();
|
|
||||||
break;
|
|
||||||
case TAG_TRACK:
|
|
||||||
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
|
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
|
||||||
zoneOffset = null;
|
zoneOffset = null;
|
||||||
break;
|
}
|
||||||
case TAG_TRACK_SEGMENT:
|
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||||
onTrackSegmentEnd();
|
case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
|
||||||
break;
|
case TAG_NAME -> {
|
||||||
case TAG_TRACK_POINT:
|
|
||||||
currentSegment.add(createTrackPoint());
|
|
||||||
break;
|
|
||||||
case TAG_NAME:
|
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
name = content.trim();
|
name = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_DESCRIPTION:
|
case TAG_DESCRIPTION -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
description = content.trim();
|
description = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_TYPE:
|
case TAG_TYPE -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
category = content.trim();
|
category = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_TIME:
|
case TAG_TIME -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
time = content.trim();
|
time = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_ALTITUDE:
|
case TAG_ALTITUDE -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
altitude = content.trim();
|
altitude = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_COMMENT:
|
case TAG_COMMENT -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
markerType = content.trim();
|
markerType = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_SPEED:
|
case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
|
||||||
case TAG_EXTENSION_SPEED_COMPAT:
|
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
speed = content.trim();
|
speed = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_HEARTRATE:
|
case TAG_EXTENSION_HEARTRATE -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
heartrate = content.trim();
|
heartrate = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_CADENCE:
|
case TAG_EXTENSION_CADENCE -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
cadence = content.trim();
|
cadence = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_POWER:
|
case TAG_EXTENSION_POWER -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
power = content.trim();
|
power = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_ID:
|
case TAG_ID -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
uuid = content.trim();
|
uuid = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_GAIN:
|
case TAG_EXTENSION_GAIN -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
gain = content.trim();
|
gain = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_LOSS:
|
case TAG_EXTENSION_LOSS -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
loss = content.trim();
|
loss = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_DISTANCE:
|
case TAG_EXTENSION_DISTANCE -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
sensorDistance = content.trim();
|
sensorDistance = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL:
|
case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
accuracyHorizontal = content.trim();
|
accuracyHorizontal = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_EXTENSION_ACCURACY_VERTICAL:
|
case TAG_EXTENSION_ACCURACY_VERTICAL -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
accuracyVertical = content.trim();
|
accuracyVertical = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content = "";
|
content = "";
|
||||||
|
|||||||
@@ -79,18 +79,16 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
|||||||
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
||||||
|
|
||||||
switch (resultCode) {
|
switch (resultCode) {
|
||||||
case ImportServiceResultReceiver.RESULT_CODE_ERROR:
|
case ImportServiceResultReceiver.RESULT_CODE_ERROR -> {
|
||||||
summary.errorCount++;
|
summary.errorCount++;
|
||||||
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
||||||
break;
|
}
|
||||||
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
|
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED -> {
|
||||||
summary.importedTrackIds.addAll(trackIds);
|
summary.importedTrackIds.addAll(trackIds);
|
||||||
summary.successCount++;
|
summary.successCount++;
|
||||||
break;
|
}
|
||||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
|
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS -> summary.existsCount++;
|
||||||
summary.existsCount++;
|
default ->
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
|
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,27 +136,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TAG_PLACEMARK:
|
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||||
case TAG_PHOTO_OVERLAY:
|
|
||||||
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, category, and description.
|
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, category, and description.
|
||||||
onMarkerStart();
|
onMarkerStart();
|
||||||
break;
|
case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> trackImporter.newTrack();
|
||||||
case TAG_MULTI_TRACK:
|
case TAG_TRACK, TAG_KML22_TRACK -> {
|
||||||
case TAG_KML22_MULTI_TRACK:
|
|
||||||
trackImporter.newTrack();
|
|
||||||
break;
|
|
||||||
case TAG_TRACK:
|
|
||||||
case TAG_KML22_TRACK:
|
|
||||||
if (trackImporter == null) {
|
if (trackImporter == null) {
|
||||||
throw new SAXException("Missing " + TAG_MULTI_TRACK);
|
throw new SAXException("Missing " + TAG_MULTI_TRACK);
|
||||||
}
|
}
|
||||||
onTrackSegmentStart();
|
onTrackSegmentStart();
|
||||||
break;
|
}
|
||||||
case TAG_DATA_CATEGORY:
|
case TAG_DATA_CATEGORY, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA ->
|
||||||
case TAG_SIMPLE_ARRAY_DATA:
|
|
||||||
case TAG_KML22_SIMPLE_ARRAY_DATA:
|
|
||||||
dataType = attributes.getValue(ATTRIBUTE_NAME);
|
dataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,32 +159,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TAG_KML:
|
case TAG_KML -> onFileEnd();
|
||||||
onFileEnd();
|
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||||
break;
|
|
||||||
case TAG_PLACEMARK:
|
|
||||||
case TAG_PHOTO_OVERLAY:
|
|
||||||
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
||||||
onMarkerEnd();
|
onMarkerEnd();
|
||||||
break;
|
case TAG_COORDINATES -> onMarkerLocationEnd();
|
||||||
case TAG_COORDINATES:
|
case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> {
|
||||||
onMarkerLocationEnd();
|
|
||||||
break;
|
|
||||||
case TAG_MULTI_TRACK:
|
|
||||||
case TAG_KML22_MULTI_TRACK:
|
|
||||||
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
|
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
|
||||||
zoneOffset = null;
|
zoneOffset = null;
|
||||||
break;
|
}
|
||||||
case TAG_TRACK:
|
case TAG_TRACK, TAG_KML22_TRACK -> onTrackSegmentEnd();
|
||||||
case TAG_KML22_TRACK:
|
case TAG_COORD, TAG_KML22_COORD -> onCoordEnded();
|
||||||
onTrackSegmentEnd();
|
case TAG_VALUE, TAG_KML22_VALUE -> {
|
||||||
break;
|
|
||||||
case TAG_COORD:
|
|
||||||
case TAG_KML22_COORD:
|
|
||||||
onCoordEnded();
|
|
||||||
break;
|
|
||||||
case TAG_VALUE:
|
|
||||||
case TAG_KML22_VALUE:
|
|
||||||
if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) {
|
if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
category = content.trim();
|
category = content.trim();
|
||||||
@@ -201,28 +178,28 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
} else {
|
} else {
|
||||||
onExtendedDataValueEnd();
|
onExtendedDataValueEnd();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_NAME:
|
case TAG_NAME -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
name = content.trim();
|
name = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_UUID:
|
case TAG_UUID -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
uuid = content.trim();
|
uuid = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_DESCRIPTION:
|
case TAG_DESCRIPTION -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
description = content.trim();
|
description = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_ICON:
|
case TAG_ICON -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
icon = content.trim();
|
icon = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_WHEN:
|
case TAG_WHEN -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
try {
|
try {
|
||||||
OffsetDateTime time = StringUtils.parseTime(content.trim());
|
OffsetDateTime time = StringUtils.parseTime(content.trim());
|
||||||
@@ -234,18 +211,17 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
case TAG_STYLE_URL -> {
|
||||||
case TAG_STYLE_URL:
|
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
markerType = content.trim();
|
markerType = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TAG_HREF:
|
case TAG_HREF -> {
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
photoUrl = content.trim();
|
photoUrl = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset element content
|
// Reset element content
|
||||||
@@ -436,34 +412,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED -> sensorSpeedList.add(value);
|
||||||
sensorSpeedList.add(value);
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE -> sensorDistanceList.add(value);
|
||||||
break;
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER -> sensorPowerList.add(value);
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE -> sensorHeartRateList.add(value);
|
||||||
sensorDistanceList.add(value);
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value);
|
||||||
break;
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value);
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value);
|
||||||
sensorPowerList.add(value);
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL ->
|
||||||
break;
|
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE:
|
|
||||||
sensorHeartRateList.add(value);
|
|
||||||
break;
|
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE:
|
|
||||||
sensorCadenceList.add(value);
|
|
||||||
break;
|
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN:
|
|
||||||
altitudeGainList.add(value);
|
|
||||||
break;
|
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS:
|
|
||||||
altitudeLossList.add(value);
|
|
||||||
break;
|
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL:
|
|
||||||
accuracyHorizontal.add(value);
|
accuracyHorizontal.add(value);
|
||||||
break;
|
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL ->
|
||||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL:
|
|
||||||
accuracyVertical.add(value);
|
accuracyVertical.add(value);
|
||||||
break;
|
default ->
|
||||||
default:
|
|
||||||
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
|
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-19
@@ -40,9 +40,9 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
|||||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||||
* Also parses the transferred data into {@link SensorDataObserver}.
|
* Also parses the transferred data into {@link SensorDataObserver}.
|
||||||
*/
|
*/
|
||||||
public abstract class BluetoothConnectionManager<DataType> {
|
public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||||
|
|
||||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
|
||||||
|
|
||||||
private final SensorDataObserver observer;
|
private final SensorDataObserver observer;
|
||||||
|
|
||||||
@@ -53,23 +53,22 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
@Override
|
@Override
|
||||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
case BluetoothProfile.STATE_CONNECTING:
|
case BluetoothProfile.STATE_CONNECTING ->
|
||||||
Log.d(TAG, "Connecting to sensor: " + gatt.getDevice());
|
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
|
||||||
break;
|
case BluetoothProfile.STATE_CONNECTED -> {
|
||||||
case BluetoothProfile.STATE_CONNECTED:
|
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
|
||||||
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
|
|
||||||
|
|
||||||
gatt.discoverServices();
|
gatt.discoverServices();
|
||||||
break;
|
}
|
||||||
case BluetoothProfile.STATE_DISCONNECTING:
|
case BluetoothProfile.STATE_DISCONNECTING ->
|
||||||
Log.d(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
||||||
break;
|
case BluetoothProfile.STATE_DISCONNECTED -> {
|
||||||
|
//This is also triggered, if no connection was established (ca. 30s)
|
||||||
case BluetoothProfile.STATE_DISCONNECTED:
|
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
|
||||||
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
if (gatt.connect()) {
|
||||||
|
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
|
||||||
|
}
|
||||||
clearData();
|
clearData();
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +132,12 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
AbstractBluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
||||||
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
|
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
||||||
this.serviceMeasurementUUIDs = serviceUUUID;
|
this.serviceMeasurementUUIDs = serviceUUUID;
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
|||||||
/**
|
/**
|
||||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||||
*/
|
*/
|
||||||
public class AltitudeSumManager implements SensorEventListener {
|
public class AltitudeSumManager implements SensorConnector, SensorEventListener {
|
||||||
|
|
||||||
private static final String TAG = AltitudeSumManager.class.getSimpleName();
|
private static final String TAG = AltitudeSumManager.class.getSimpleName();
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -8,11 +8,11 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
|||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
|
|
||||||
public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnectionManager<Cadence> {
|
public class BluetoothConnectionManagerCyclingCadence extends AbstractBluetoothConnectionManager<Cadence> {
|
||||||
|
|
||||||
private static final String TAG = BluetoothRemoteSensorManagerCyclingCadence.class.getSimpleName();
|
private static final String TAG = BluetoothConnectionManagerCyclingCadence.class.getSimpleName();
|
||||||
|
|
||||||
BluetoothRemoteSensorManagerCyclingCadence(SensorDataObserver observer) {
|
BluetoothConnectionManagerCyclingCadence(SensorDataObserver observer) {
|
||||||
super(BluetoothUtils.CYCLING_CADENCE, observer);
|
super(BluetoothUtils.CYCLING_CADENCE, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
+2
-2
@@ -5,9 +5,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
|||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||||
|
|
||||||
public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
|
public class BluetoothConnectionManagerCyclingDistanceSpeed extends AbstractBluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
|
||||||
|
|
||||||
BluetoothRemoteSensorManagerCyclingDistanceSpeed(SensorDataObserver observer) {
|
BluetoothConnectionManagerCyclingDistanceSpeed(SensorDataObserver observer) {
|
||||||
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
|
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
+2
-2
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
|
|||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
|
|
||||||
public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectionManager<Power> {
|
public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothConnectionManager<Power> {
|
||||||
|
|
||||||
BluetoothRemoteSensorManagerCyclingPower(@NonNull SensorDataObserver observer) {
|
BluetoothConnectionManagerCyclingPower(@NonNull SensorDataObserver observer) {
|
||||||
super(BluetoothUtils.CYCLING_POWER, observer);
|
super(BluetoothUtils.CYCLING_POWER, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-4
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
|
|||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
||||||
|
|
||||||
public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionManager<HeartRate> {
|
public class BluetoothConnectionManagerHeartRate extends AbstractBluetoothConnectionManager<HeartRate> {
|
||||||
|
|
||||||
BluetoothRemoteSensorManagerHeartRate(@NonNull SensorDataObserver observer) {
|
BluetoothConnectionManagerHeartRate(@NonNull SensorDataObserver observer) {
|
||||||
super(BluetoothUtils.HEARTRATE, observer);
|
super(BluetoothUtils.HEARTRATE, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionMa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||||
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||||
|
|
||||||
return heartRate != null ? new SensorDataHeartRate(address, sensorName, HeartRate.of(heartRate)) : null;
|
return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -6,9 +6,9 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
|
|
||||||
public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
|
public class BluetoothConnectionRunningSpeedAndCadence extends AbstractBluetoothConnectionManager<SensorDataRunning.Data> {
|
||||||
|
|
||||||
BluetoothRemoteSensorManagerRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
BluetoothConnectionRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
||||||
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
|
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ import de.dennisguse.opentracks.util.PermissionRequester;
|
|||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.SensorDataObserver {
|
public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBluetoothConnectionManager.SensorDataObserver {
|
||||||
|
|
||||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||||
|
|
||||||
@@ -65,15 +65,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
|
|
||||||
private Distance preferenceWheelCircumference;
|
private Distance preferenceWheelCircumference;
|
||||||
|
|
||||||
private final BluetoothRemoteSensorManagerHeartRate heartRate = new BluetoothRemoteSensorManagerHeartRate(this);
|
private final BluetoothConnectionManagerHeartRate heartRate = new BluetoothConnectionManagerHeartRate(this);
|
||||||
private final BluetoothRemoteSensorManagerCyclingCadence cyclingCadence = new BluetoothRemoteSensorManagerCyclingCadence(this);
|
private final BluetoothConnectionManagerCyclingCadence cyclingCadence = new BluetoothConnectionManagerCyclingCadence(this);
|
||||||
private final BluetoothRemoteSensorManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothRemoteSensorManagerCyclingDistanceSpeed(this);
|
private final BluetoothConnectionManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManagerCyclingDistanceSpeed(this);
|
||||||
private final BluetoothRemoteSensorManagerCyclingPower cyclingPower = new BluetoothRemoteSensorManagerCyclingPower(this);
|
private final BluetoothConnectionManagerCyclingPower cyclingPower = new BluetoothConnectionManagerCyclingPower(this);
|
||||||
private final BluetoothRemoteSensorManagerRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothRemoteSensorManagerRunningSpeedAndCadence(this);
|
private final BluetoothConnectionRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionRunningSpeedAndCadence(this);
|
||||||
|
|
||||||
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
|
|
||||||
private final SensorDataSetChangeObserver observer;
|
private final SensorManager.SensorDataSetChangeObserver observer;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -114,21 +114,23 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorDataSetChangeObserver observer) {
|
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorManager.SensorDataSetChangeObserver observer) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
@Override
|
||||||
|
public void start(Context context, Handler handler) {
|
||||||
started = true;
|
started = true;
|
||||||
|
|
||||||
//Registering triggers connection startup
|
//Registering triggers connection startup
|
||||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void stop() {
|
@Override
|
||||||
|
public synchronized void stop(Context context) {
|
||||||
heartRate.disconnect();
|
heartRate.disconnect();
|
||||||
cyclingCadence.disconnect();
|
cyclingCadence.disconnect();
|
||||||
cyclingSpeed.disconnect();
|
cyclingSpeed.disconnect();
|
||||||
@@ -145,7 +147,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
|
private synchronized void connect(AbstractBluetoothConnectionManager<?> connectionManager, String address) {
|
||||||
if (!isEnabled()) {
|
if (!isEnabled()) {
|
||||||
Log.w(TAG, "Bluetooth not enabled.");
|
Log.w(TAG, "Bluetooth not enabled.");
|
||||||
return;
|
return;
|
||||||
@@ -230,8 +232,4 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
public Handler getHandler() {
|
public Handler getHandler() {
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SensorDataSetChangeObserver {
|
|
||||||
void onChange(SensorDataSet sensorDataSet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||||
@@ -101,7 +102,7 @@ public class BluetoothUtils {
|
|||||||
return BluetoothUtils.getAdapter(context) != null;
|
return BluetoothUtils.getAdapter(context) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
public static HeartRate parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||||
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
|
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
|
||||||
byte[] raw = characteristic.getValue();
|
byte[] raw = characteristic.getValue();
|
||||||
if (raw.length == 0) {
|
if (raw.length == 0) {
|
||||||
@@ -110,10 +111,10 @@ public class BluetoothUtils {
|
|||||||
|
|
||||||
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
|
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
|
||||||
if (formatUINT16 && raw.length >= 3) {
|
if (formatUINT16 && raw.length >= 3) {
|
||||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
|
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1));
|
||||||
}
|
}
|
||||||
if (!formatUINT16 && raw.length >= 2) {
|
if (!formatUINT16 && raw.length >= 2) {
|
||||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
|
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
public interface SensorConnector {
|
||||||
|
|
||||||
|
void start(Context context, Handler handler);
|
||||||
|
|
||||||
|
void stop(Context context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
|
|
||||||
|
public class SensorManager {
|
||||||
|
|
||||||
|
private static final String TAG = SensorManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private BluetoothRemoteSensorManager bluetoothSensorManager;
|
||||||
|
|
||||||
|
private AltitudeSumManager altitudeSumManager;
|
||||||
|
|
||||||
|
public SensorManager(Context context, Handler handler, SensorDataSetChangeObserver observer) {
|
||||||
|
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, observer);
|
||||||
|
altitudeSumManager = new AltitudeSumManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Context context, Handler handler) {
|
||||||
|
bluetoothSensorManager.start(context, handler);
|
||||||
|
altitudeSumManager.start(context, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(Context context) {
|
||||||
|
if (bluetoothSensorManager != null) {
|
||||||
|
bluetoothSensorManager.stop(context);
|
||||||
|
bluetoothSensorManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altitudeSumManager != null) {
|
||||||
|
altitudeSumManager.stop(context);
|
||||||
|
altitudeSumManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataSet fill(TrackPoint trackPoint) {
|
||||||
|
altitudeSumManager.fill(trackPoint);
|
||||||
|
return bluetoothSensorManager.fill(trackPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
if (bluetoothSensorManager == null || altitudeSumManager == null) {
|
||||||
|
Log.d(TAG, "No recording running and no reset necessary.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bluetoothSensorManager.reset();
|
||||||
|
altitudeSumManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@VisibleForTesting
|
||||||
|
public BluetoothRemoteSensorManager getBluetoothSensorManager() {
|
||||||
|
return bluetoothSensorManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setBluetoothSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||||
|
this.bluetoothSensorManager = remoteSensorManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@VisibleForTesting
|
||||||
|
public AltitudeSumManager getAltitudeSumManager() {
|
||||||
|
return altitudeSumManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
|
||||||
|
this.altitudeSumManager = altitudeSumManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public interface SensorDataSetChangeObserver {
|
||||||
|
void onChange(SensorDataSet sensorDataSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -32,16 +31,6 @@ public abstract class SensorData<T> {
|
|||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public String getSensorAddress() {
|
|
||||||
return sensorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getSensorName() {
|
|
||||||
return sensorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSensorNameOrAddress() {
|
public String getSensorNameOrAddress() {
|
||||||
return sensorName != null ? sensorName : sensorAddress;
|
return sensorName != null ? sensorName : sensorAddress;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -76,9 +76,8 @@ public class SensorDataCyclingCadence extends SensorData<Cadence> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (!(obj instanceof SensorDataCyclingCadence)) return false;
|
if (!(obj instanceof SensorDataCyclingCadence comp)) return false;
|
||||||
|
|
||||||
SensorDataCyclingCadence comp = (SensorDataCyclingCadence) obj;
|
|
||||||
if (hasData() && comp.hasData() == hasData()) {
|
if (hasData() && comp.hasData() == hasData()) {
|
||||||
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-2
@@ -93,9 +93,8 @@ public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCycling
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (!(obj instanceof SensorDataCyclingDistanceSpeed)) return false;
|
if (!(obj instanceof SensorDataCyclingDistanceSpeed comp)) return false;
|
||||||
|
|
||||||
SensorDataCyclingDistanceSpeed comp = (SensorDataCyclingDistanceSpeed) obj;
|
|
||||||
if (!(hasData() && comp.hasData())) {
|
if (!(hasData() && comp.hasData())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
egm2008CorrectionManager = new EGM2008CorrectionManager();
|
egm2008CorrectionManager = new EGM2008CorrectionManager();
|
||||||
trackRecordingManager = new TrackRecordingManager(this);
|
trackRecordingManager = new TrackRecordingManager(this);
|
||||||
trackRecordingManager.start();
|
trackRecordingManager.start();
|
||||||
trackPointCreator = new TrackPointCreator(this);
|
trackPointCreator = new TrackPointCreator(this, this, handler);
|
||||||
|
|
||||||
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
||||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
@@ -177,7 +177,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void resumeTrack(Track.Id trackId) {
|
public void resumeTrack(Track.Id trackId) {
|
||||||
trackPointCreator.reset();
|
|
||||||
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
|
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
|
||||||
Log.w(TAG, "Cannot resume a non-existing track.");
|
Log.w(TAG, "Cannot resume a non-existing track.");
|
||||||
return;
|
return;
|
||||||
@@ -328,7 +327,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
TrackPoint trackPoint = data.second.first;
|
TrackPoint trackPoint = data.second.first;
|
||||||
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
||||||
|
|
||||||
localVoiceAnnouncementManager.update(data.first);
|
localVoiceAnnouncementManager.update(this, data.first);
|
||||||
|
|
||||||
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-11
@@ -28,6 +28,7 @@ import android.util.Log;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -67,17 +68,8 @@ public class VoiceAnnouncement {
|
|||||||
public void onAudioFocusChange(int focusChange) {
|
public void onAudioFocusChange(int focusChange) {
|
||||||
Log.d(TAG, "Audio focus changed to " + focusChange);
|
Log.d(TAG, "Audio focus changed to " + focusChange);
|
||||||
|
|
||||||
boolean stop = false;
|
boolean stop = List.of(AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
|
||||||
switch (focusChange) {
|
.contains(focusChange);
|
||||||
case AudioManager.AUDIOFOCUS_GAIN:
|
|
||||||
stop = false;
|
|
||||||
break;
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
||||||
stop = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop && tts != null && tts.isSpeaking()) {
|
if (stop && tts != null && tts.isSpeaking()) {
|
||||||
tts.stop();
|
tts.stop();
|
||||||
|
|||||||
+11
-1
@@ -15,12 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package de.dennisguse.opentracks.services.announcement;
|
package de.dennisguse.opentracks.services.announcement;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.mediarouter.media.MediaRouter;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
@@ -73,12 +75,20 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
|||||||
updateNextTaskDistance();
|
updateNextTaskDistance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(@NonNull Track track) {
|
public void update(@NonNull Context context, @NonNull Track track) {
|
||||||
if (voiceAnnouncement == null) {
|
if (voiceAnnouncement == null) {
|
||||||
Log.e(TAG, "Cannot update when in status shutdown.");
|
Log.e(TAG, "Cannot update when in status shutdown.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
|
||||||
|
&& MediaRouter.getInstance(context)
|
||||||
|
.getSelectedRoute()
|
||||||
|
.isDeviceSpeaker()) {
|
||||||
|
Log.i(TAG, "No voice announcement on device speaker.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
boolean announce = false;
|
boolean announce = false;
|
||||||
this.trackStatistics = track.getTrackStatistics();
|
this.trackStatistics = track.getTrackStatistics();
|
||||||
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
|
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
|
||||||
|
|||||||
+7
-8
@@ -43,29 +43,28 @@ class VoiceAnnouncementUtils {
|
|||||||
String unitDistanceTTS;
|
String unitDistanceTTS;
|
||||||
String unitSpeedTTS;
|
String unitSpeedTTS;
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
perUnitStringId = R.string.voice_per_kilometer;
|
perUnitStringId = R.string.voice_per_kilometer;
|
||||||
distanceId = R.plurals.voiceDistanceKilometers;
|
distanceId = R.plurals.voiceDistanceKilometers;
|
||||||
speedId = R.plurals.voiceSpeedKilometersPerHour;
|
speedId = R.plurals.voiceSpeedKilometersPerHour;
|
||||||
unitDistanceTTS = "kilometer";
|
unitDistanceTTS = "kilometer";
|
||||||
unitSpeedTTS = "kilometer per hour";
|
unitSpeedTTS = "kilometer per hour";
|
||||||
break;
|
}
|
||||||
case IMPERIAL:
|
case IMPERIAL -> {
|
||||||
perUnitStringId = R.string.voice_per_mile;
|
perUnitStringId = R.string.voice_per_mile;
|
||||||
distanceId = R.plurals.voiceDistanceMiles;
|
distanceId = R.plurals.voiceDistanceMiles;
|
||||||
speedId = R.plurals.voiceSpeedMilesPerHour;
|
speedId = R.plurals.voiceSpeedMilesPerHour;
|
||||||
unitDistanceTTS = "mile";
|
unitDistanceTTS = "mile";
|
||||||
unitSpeedTTS = "mile per hour";
|
unitSpeedTTS = "mile per hour";
|
||||||
break;
|
}
|
||||||
case NAUTICAL_IMPERIAL:
|
case NAUTICAL_IMPERIAL -> {
|
||||||
perUnitStringId = R.string.voice_per_nautical_mile;
|
perUnitStringId = R.string.voice_per_nautical_mile;
|
||||||
distanceId = R.plurals.voiceDistanceNauticalMiles;
|
distanceId = R.plurals.voiceDistanceNauticalMiles;
|
||||||
speedId = R.plurals.voiceSpeedMKnots;
|
speedId = R.plurals.voiceSpeedMKnots;
|
||||||
unitDistanceTTS = "nautical mile";
|
unitDistanceTTS = "nautical mile";
|
||||||
unitSpeedTTS = "knots";
|
unitSpeedTTS = "knots";
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new RuntimeException("Not implemented");
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
|
double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
|
||||||
|
|||||||
+6
-5
@@ -20,14 +20,15 @@ import java.time.Instant;
|
|||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.sensors.SensorConnector;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
private final String TAG = GPSHandler.class.getSimpleName();
|
private final String TAG = GPSManager.class.getSimpleName();
|
||||||
|
|
||||||
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||||
|
|
||||||
@@ -40,11 +41,11 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
|
|||||||
private Duration gpsInterval;
|
private Duration gpsInterval;
|
||||||
private Distance thresholdHorizontalAccuracy;
|
private Distance thresholdHorizontalAccuracy;
|
||||||
|
|
||||||
public GPSHandler(TrackPointCreator trackPointCreator) {
|
public GPSManager(TrackPointCreator trackPointCreator) {
|
||||||
this.trackPointCreator = trackPointCreator;
|
this.trackPointCreator = trackPointCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStart(@NonNull Context context, @NonNull Handler handler) {
|
public void start(@NonNull Context context, @NonNull Handler handler) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
|
|||||||
|
|
||||||
@SuppressWarnings({"MissingPermission"})
|
@SuppressWarnings({"MissingPermission"})
|
||||||
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
|
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
|
||||||
public void onStop() {
|
public void stop(Context context) {
|
||||||
if (locationManager != null && context != null) {
|
if (locationManager != null && context != null) {
|
||||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||||
LocationManagerCompat.removeUpdates(locationManager, this);
|
LocationManagerCompat.removeUpdates(locationManager, this);
|
||||||
@@ -16,14 +16,13 @@ import java.time.ZoneId;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
|
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
|
||||||
*/
|
*/
|
||||||
public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDataSetChangeObserver {
|
public class TrackPointCreator implements SensorManager.SensorDataSetChangeObserver {
|
||||||
|
|
||||||
private static final String TAG = TrackPointCreator.class.getSimpleName();
|
private static final String TAG = TrackPointCreator.class.getSimpleName();
|
||||||
|
|
||||||
@@ -34,45 +33,34 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
|||||||
@NonNull
|
@NonNull
|
||||||
private Clock clock = new MonotonicClock();
|
private Clock clock = new MonotonicClock();
|
||||||
|
|
||||||
private final GPSHandler gpsHandler;
|
private final GPSManager gpsManager;
|
||||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
private SensorManager sensorManager;
|
||||||
private AltitudeSumManager altitudeSumManager;
|
|
||||||
|
|
||||||
public TrackPointCreator(Callback service) {
|
public TrackPointCreator(Callback service, Context context, Handler handler) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.gpsHandler = new GPSHandler(this);
|
this.gpsManager = new GPSManager(this);
|
||||||
|
this.sensorManager = new SensorManager(context, handler, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
TrackPointCreator(GPSHandler gpsHandler, Callback service) {
|
TrackPointCreator(GPSManager gpsManager, Callback service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.gpsHandler = gpsHandler;
|
this.gpsManager = gpsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
|
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
gpsHandler.onStart(context, handler);
|
gpsManager.start(context, handler);
|
||||||
|
sensorManager.start(context, handler);
|
||||||
remoteSensorManager = new BluetoothRemoteSensorManager(context, handler, this);
|
|
||||||
altitudeSumManager = new AltitudeSumManager();
|
|
||||||
|
|
||||||
remoteSensorManager.start();
|
|
||||||
altitudeSumManager.start(context, handler);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStarted() {
|
private boolean isStarted() {
|
||||||
return context != null;
|
return context != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reset() {
|
private synchronized void reset() {
|
||||||
if (remoteSensorManager == null || altitudeSumManager == null) {
|
sensorManager.reset();
|
||||||
Log.d(TAG, "No recording running and no reset necessary.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
remoteSensorManager.reset();
|
|
||||||
altitudeSumManager.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SensorDataSet addSensorData(TrackPoint trackPoint) {
|
private SensorDataSet addSensorData(TrackPoint trackPoint) {
|
||||||
@@ -80,31 +68,12 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
|||||||
Log.w(TAG, "Not started, should not be called.");
|
Log.w(TAG, "Not started, should not be called.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
SensorDataSet sensorDataSet = null;
|
|
||||||
BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager;
|
|
||||||
if (localRemoteSensorManager != null) {
|
|
||||||
sensorDataSet = localRemoteSensorManager.fill(trackPoint);
|
|
||||||
}
|
|
||||||
AltitudeSumManager localAltitudeSumManager = altitudeSumManager;
|
|
||||||
if (localAltitudeSumManager != null) {
|
|
||||||
localAltitudeSumManager.fill(trackPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sensorDataSet;
|
return sensorManager.fill(trackPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void stop() {
|
public synchronized void stop() {
|
||||||
gpsHandler.onStop();
|
gpsManager.stop(context);
|
||||||
|
|
||||||
if (remoteSensorManager != null) {
|
|
||||||
remoteSensorManager.stop();
|
|
||||||
remoteSensorManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (altitudeSumManager != null) {
|
|
||||||
altitudeSumManager.stop(context);
|
|
||||||
altitudeSumManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context = null;
|
this.context = null;
|
||||||
}
|
}
|
||||||
@@ -125,7 +94,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
|||||||
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
|
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||||
addSensorData(trackPoint);
|
addSensorData(trackPoint);
|
||||||
|
|
||||||
boolean stored = service.newTrackPoint(trackPoint, gpsHandler.getThresholdHorizontalAccuracy());
|
boolean stored = service.newTrackPoint(trackPoint, gpsManager.getThresholdHorizontalAccuracy());
|
||||||
if (stored) {
|
if (stored) {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
@@ -169,26 +138,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public AltitudeSumManager getAltitudeSumManager() {
|
public SensorManager getSensorManager() {
|
||||||
return altitudeSumManager;
|
return sensorManager;
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
|
|
||||||
this.altitudeSumManager = altitudeSumManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@VisibleForTesting
|
|
||||||
public BluetoothRemoteSensorManager getRemoteSensorManager() {
|
|
||||||
return remoteSensorManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
|
||||||
this.remoteSensorManager = remoteSensorManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -202,8 +153,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public GPSHandler getGpsHandler() {
|
public GPSManager getGpsHandler() {
|
||||||
return gpsHandler;
|
return gpsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
|
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.preference.DialogPreference;
|
|||||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ public class ActivityTypePreference extends DialogPreference {
|
|||||||
textView = view.findViewById(R.id.activity_type_preference_text_view);
|
textView = view.findViewById(R.id.activity_type_preference_text_view);
|
||||||
String category = PreferencesUtils.getDefaultActivity();
|
String category = PreferencesUtils.getDefaultActivity();
|
||||||
textView.setText(category);
|
textView.setText(category);
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(context));
|
||||||
textView.setAdapter(adapter);
|
textView.setAdapter(adapter);
|
||||||
textView.setOnItemClickListener((parent, v, position, id) -> {
|
textView.setOnItemClickListener((parent, v, position, id) -> {
|
||||||
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
|
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
|
||||||
|
|||||||
@@ -68,18 +68,10 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement
|
|||||||
|
|
||||||
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
||||||
|
|
||||||
int entriesId;
|
int entriesId = switch (unitSystem) {
|
||||||
switch (unitSystem) {
|
case METRIC -> R.array.stats_rate_metric_options;
|
||||||
case METRIC:
|
case IMPERIAL, NAUTICAL_IMPERIAL -> R.array.stats_rate_imperial_options;
|
||||||
entriesId = R.array.stats_rate_metric_options;
|
};
|
||||||
break;
|
|
||||||
case IMPERIAL:
|
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
entriesId = R.array.stats_rate_imperial_options;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] entries = getResources().getStringArray(entriesId);
|
String[] entries = getResources().getStringArray(entriesId);
|
||||||
statsRatePreferences.setEntries(entries);
|
statsRatePreferences.setEntries(entries);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
|
|||||||
DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri);
|
DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri);
|
||||||
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
||||||
if (directory == null) {
|
if (directory == null) {
|
||||||
return getString(R.string.not_set);
|
return getString(androidx.preference.R.string.not_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
return directoryUri + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable));
|
return directoryUri + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable));
|
||||||
|
|||||||
@@ -32,14 +32,9 @@ class PreferencesOpenHelper {
|
|||||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
||||||
for (int i = 1; i <= version; i++) {
|
for (int i = 1; i <= version; i++) {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 1:
|
case 1 -> upgradeFrom0to1();
|
||||||
upgradeFrom0to1();
|
case 2 -> upgradeFrom1to2();
|
||||||
break;
|
default -> throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||||
case 2:
|
|
||||||
upgradeFrom1to2();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Not implemented: upgrade to " + version);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,14 @@ import java.util.stream.Stream;
|
|||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
@@ -278,6 +281,12 @@ public class PreferencesUtils {
|
|||||||
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HeartRateZones getHeartRateZones() {
|
||||||
|
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_heart_rate_max_default));
|
||||||
|
int value = getInt(R.string.settings_sensor_heart_rate_max_key, DEFAULT);
|
||||||
|
return new HeartRateZones(HeartRate.of(value));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean shouldShowStatsOnLockscreen() {
|
public static boolean shouldShowStatsOnLockscreen() {
|
||||||
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = resources.getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
|
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = resources.getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
|
||||||
return getBoolean(R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
return getBoolean(R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
||||||
@@ -293,6 +302,11 @@ public class PreferencesUtils {
|
|||||||
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldVoiceAnnouncementOnDeviceSpeaker() {
|
||||||
|
final boolean DEFAULT = resources.getBoolean(R.bool.voice_on_device_speaker_default);
|
||||||
|
return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
public static Duration getVoiceAnnouncementFrequency() {
|
public static Duration getVoiceAnnouncementFrequency() {
|
||||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
|
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
|
||||||
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
|
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
|
||||||
@@ -434,23 +448,21 @@ public class PreferencesUtils {
|
|||||||
|
|
||||||
String displayValue = formatter.formatDistance(distance);
|
String displayValue = formatter.formatDistance(distance);
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
if (value == recordingDistanceIntervalDefault) {
|
if (value == recordingDistanceIntervalDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case IMPERIAL:
|
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
if (value == recordingDistanceIntervalDefault) {
|
if (value == recordingDistanceIntervalDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new RuntimeException("Not implemented");
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,23 +492,21 @@ public class PreferencesUtils {
|
|||||||
|
|
||||||
String displayValue = formatter.formatDistance(distance);
|
String displayValue = formatter.formatDistance(distance);
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
if (value == maxRecordingDistanceDefault) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case IMPERIAL:
|
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
if (value == maxRecordingDistanceDefault) {
|
if (value == maxRecordingDistanceDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new RuntimeException("Not implemented");
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +565,7 @@ public class PreferencesUtils {
|
|||||||
|
|
||||||
String displayValue = formatter.formatDistance(distance);
|
String displayValue = formatter.formatDistance(distance);
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
if (value == recordingGPSAccuracyDefault) {
|
if (value == recordingGPSAccuracyDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||||
} else if (value == recordingGPSAccuracyExcellent) {
|
} else if (value == recordingGPSAccuracyExcellent) {
|
||||||
@@ -565,9 +575,8 @@ public class PreferencesUtils {
|
|||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case IMPERIAL:
|
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||||
case NAUTICAL_IMPERIAL:
|
|
||||||
if (value == recordingGPSAccuracyDefault) {
|
if (value == recordingGPSAccuracyDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||||
} else if (value == recordingGPSAccuracyExcellent) {
|
} else if (value == recordingGPSAccuracyExcellent) {
|
||||||
@@ -575,9 +584,8 @@ public class PreferencesUtils {
|
|||||||
} else {
|
} else {
|
||||||
entries[i] = displayValue;
|
entries[i] = displayValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new RuntimeException("Not implemented");
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,32 +611,30 @@ public class PreferencesUtils {
|
|||||||
float value = Float.parseFloat(entryValues[i]);
|
float value = Float.parseFloat(entryValues[i]);
|
||||||
|
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC -> {
|
||||||
if (value == idleSpeedDefault) {
|
if (value == idleSpeedDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value);
|
entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = resources.getString(R.string.value_float_kilometer_hour, value);
|
entries[i] = resources.getString(R.string.value_float_kilometer_hour, value);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case IMPERIAL:
|
case IMPERIAL -> {
|
||||||
double valueMPH = Speed.ofKMH(value).toMPH();
|
double valueMPH = Speed.ofKMH(value).toMPH();
|
||||||
|
|
||||||
if (value == idleSpeedDefault) {
|
if (value == idleSpeedDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH);
|
entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH);
|
entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case NAUTICAL_IMPERIAL:
|
case NAUTICAL_IMPERIAL -> {
|
||||||
double valueKnots = Speed.ofKMH(value).toKnots();
|
double valueKnots = Speed.ofKMH(value).toKnots();
|
||||||
if (value == idleSpeedDefault) {
|
if (value == idleSpeedDefault) {
|
||||||
entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots);
|
entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots);
|
||||||
} else {
|
} else {
|
||||||
entries[i] = resources.getString(R.string.value_float_knots, valueKnots);
|
entries[i] = resources.getString(R.string.value_float_knots, valueKnots);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> throw new RuntimeException("Not implemented");
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,35 +759,35 @@ public class PreferencesUtils {
|
|||||||
/**
|
/**
|
||||||
* @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore.
|
* @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore.
|
||||||
*/
|
*/
|
||||||
public static Layout getCustomLayout() {
|
public static RecordingLayout getCustomLayout() {
|
||||||
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||||
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||||
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
||||||
if (layoutSelected == null) {
|
if (layoutSelected == null) {
|
||||||
return Layout.fromCsv(csvLines[0], resources);
|
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String line : csvLines) {
|
for (String line : csvLines) {
|
||||||
Layout layout = Layout.fromCsv(line, resources);
|
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources);
|
||||||
if (layout.sameName(layoutSelected)) {
|
if (recordingLayout.sameName(layoutSelected)) {
|
||||||
return layout;
|
return recordingLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Layout.fromCsv(csvLines[0], resources);
|
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
public static void updateCustomLayouts(@NonNull List<RecordingLayout> recordingLayouts) {
|
||||||
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(recordingLayouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateCustomLayout(@NonNull Layout layout) {
|
public static void updateCustomLayout(@NonNull RecordingLayout recordingLayout) {
|
||||||
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
|
List<RecordingLayout> preferenceRecordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||||
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
|
Optional<RecordingLayout> layoutToBeUpdated = preferenceRecordingLayouts.stream().filter(l -> l.sameName(recordingLayout)).findFirst();
|
||||||
if (layoutToBeUpdated.isPresent()) {
|
if (layoutToBeUpdated.isPresent()) {
|
||||||
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
|
layoutToBeUpdated.get().replaceAllFields(recordingLayout.getFields());
|
||||||
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
|
layoutToBeUpdated.get().setColumnsPerRow(recordingLayout.getColumnsPerRow());
|
||||||
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
|
PreferencesUtils.updateCustomLayouts(preferenceRecordingLayouts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,19 +801,19 @@ public class PreferencesUtils {
|
|||||||
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Layout> getAllCustomLayouts() {
|
public static List<RecordingLayout> getAllCustomLayouts() {
|
||||||
List<Layout> layouts = new ArrayList<>();
|
List<RecordingLayout> recordingLayouts = new ArrayList<>();
|
||||||
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||||
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
|
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||||
for (String line : csvLines) {
|
for (String line : csvLines) {
|
||||||
layouts.add(Layout.fromCsv(line, resources));
|
recordingLayouts.add(RecordingLayoutIO.fromCsv(line, resources));
|
||||||
}
|
}
|
||||||
|
|
||||||
return layouts;
|
return recordingLayouts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getAllCustomLayoutNames() {
|
public static List<String> getAllCustomLayoutNames() {
|
||||||
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
return getAllCustomLayouts().stream().map(RecordingLayout::getName).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resetCustomLayoutPreferences() {
|
public static void resetCustomLayoutPreferences() {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class SensorsSettingsFragment extends PreferenceFragmentCompat {
|
|||||||
if (newValue instanceof String) {
|
if (newValue instanceof String) {
|
||||||
try {
|
try {
|
||||||
int newValueInt = Integer.parseInt((String) newValue);
|
int newValueInt = Integer.parseInt((String) newValue);
|
||||||
return newValueInt > 500 && newValueInt < 4000;
|
return newValueInt >= 100 && newValueInt < 4000;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Entered string is no number.");
|
Log.w(TAG, "Entered string is no number.");
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.AbstractActivity;
|
|||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
|
||||||
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
|
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
|
||||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||||
@@ -30,8 +30,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||||
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||||
private String profile;
|
private String profile;
|
||||||
private Layout layoutFieldsVisible;
|
private RecordingLayout recordingLayoutFieldsVisible;
|
||||||
private Layout layoutFieldsHidden;
|
private RecordingLayout recordingLayoutFieldsHidden;
|
||||||
private int numColumns;
|
private int numColumns;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,12 +39,12 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Recycler view with visible stats.
|
// Recycler view with visible stats.
|
||||||
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||||
profile = layout.getName();
|
profile = recordingLayout.getName();
|
||||||
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
|
||||||
|
|
||||||
numColumns = layout.getColumnsPerRow();
|
numColumns = recordingLayout.getColumnsPerRow();
|
||||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||||
@@ -65,7 +65,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
int fromPosition = viewHolder.getAdapterPosition();
|
int fromPosition = viewHolder.getAdapterPosition();
|
||||||
int toPosition = target.getAdapterPosition();
|
int toPosition = target.getAdapterPosition();
|
||||||
layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
recordingLayoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
|
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
|
||||||
|
|
||||||
// Recycler view with not visible stats.
|
// Recycler view with not visible stats.
|
||||||
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
|
||||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||||
@@ -103,18 +103,18 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
if (!recordingLayoutFieldsVisible.getFields().isEmpty() || !recordingLayoutFieldsHidden.getFields().isEmpty()) {
|
||||||
Layout layout = new Layout(profile, numColumns);
|
RecordingLayout recordingLayout = new RecordingLayout(profile, numColumns);
|
||||||
layout.addFields(layoutFieldsVisible.getFields());
|
recordingLayout.addFields(recordingLayoutFieldsVisible.getFields());
|
||||||
layout.addFields(layoutFieldsHidden.getFields());
|
recordingLayout.addFields(recordingLayoutFieldsHidden.getFields());
|
||||||
PreferencesUtils.updateCustomLayout(layout);
|
PreferencesUtils.updateCustomLayout(recordingLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
layoutFieldsVisible = null;
|
recordingLayoutFieldsVisible = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,24 +132,24 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
|||||||
if (which == 0) {
|
if (which == 0) {
|
||||||
field.togglePrimary();
|
field.togglePrimary();
|
||||||
} else {
|
} else {
|
||||||
layoutFieldsVisible.removeField(field);
|
recordingLayoutFieldsVisible.removeField(field);
|
||||||
field.toggleVisibility();
|
field.toggleVisibility();
|
||||||
layoutFieldsHidden.addField(field);
|
recordingLayoutFieldsHidden.addField(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
|
||||||
})
|
})
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
layoutFieldsHidden.removeField(field);
|
recordingLayoutFieldsHidden.removeField(field);
|
||||||
field.toggleVisibility();
|
field.toggleVisibility();
|
||||||
layoutFieldsVisible.addField(field);
|
recordingLayoutFieldsVisible.addField(field);
|
||||||
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||||
|
|
||||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -23,7 +23,7 @@ import com.google.android.material.textfield.TextInputLayout;
|
|||||||
import de.dennisguse.opentracks.AbstractActivity;
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
|
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter;
|
||||||
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
|||||||
@Override
|
@Override
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||||
final int position = viewHolder.getAdapterPosition();
|
final int position = viewHolder.getAdapterPosition();
|
||||||
final Layout item = adapter.getLayouts().get(position);
|
final RecordingLayout item = adapter.getLayouts().get(position);
|
||||||
|
|
||||||
adapter.removeLayout(position);
|
adapter.removeLayout(position);
|
||||||
|
|
||||||
@@ -150,9 +150,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
|
public void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout) {
|
||||||
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
||||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
|
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, recordingLayout);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (!(o instanceof Device)) return false;
|
if (!(o instanceof Device device)) return false;
|
||||||
Device device = (Device) o;
|
|
||||||
return address.equals(device.address);
|
return address.equals(device.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DataField implements Parcelable {
|
public class DataField implements Parcelable {
|
||||||
public static final String YES_VALUE = "1";
|
|
||||||
public static final String NOT_VALUE = "0";
|
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
private final String title;
|
|
||||||
private boolean isVisible;
|
private boolean isVisible;
|
||||||
private boolean isPrimary;
|
private boolean isPrimary;
|
||||||
private final boolean isWide;
|
private final boolean isWide;
|
||||||
|
|
||||||
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
|
public DataField(String key, boolean isVisible, boolean isPrimary, boolean isWide) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.title = title;
|
|
||||||
this.isVisible = isVisible;
|
this.isVisible = isVisible;
|
||||||
this.isPrimary = isPrimary;
|
this.isPrimary = isPrimary;
|
||||||
this.isWide = isWide;
|
this.isWide = isWide;
|
||||||
@@ -27,7 +21,6 @@ public class DataField implements Parcelable {
|
|||||||
|
|
||||||
protected DataField(Parcel in) {
|
protected DataField(Parcel in) {
|
||||||
key = in.readString();
|
key = in.readString();
|
||||||
title = in.readString();
|
|
||||||
isVisible = in.readByte() != 0;
|
isVisible = in.readByte() != 0;
|
||||||
isPrimary = in.readByte() != 0;
|
isPrimary = in.readByte() != 0;
|
||||||
isWide = in.readByte() != 0;
|
isWide = in.readByte() != 0;
|
||||||
@@ -49,10 +42,6 @@ public class DataField implements Parcelable {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVisible() {
|
public boolean isVisible() {
|
||||||
return isVisible;
|
return isVisible;
|
||||||
}
|
}
|
||||||
@@ -73,65 +62,6 @@ public class DataField implements Parcelable {
|
|||||||
return isWide;
|
return isWide;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomLayoutFieldType getType(Context context) {
|
|
||||||
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
|
|
||||||
return CustomLayoutFieldType.CLOCK;
|
|
||||||
} else {
|
|
||||||
return CustomLayoutFieldType.GENERIC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toCsv() {
|
|
||||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
|
||||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
|
||||||
String wide = this.isWide ? YES_VALUE : NOT_VALUE;
|
|
||||||
return key + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTitleByKey(Resources resources, String key) {
|
|
||||||
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
|
|
||||||
return resources.getString(R.string.stats_total_time);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
|
|
||||||
return resources.getString(R.string.stats_moving_time);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
|
|
||||||
return resources.getString(R.string.stats_distance);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
|
|
||||||
return resources.getString(R.string.stats_speed);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
|
|
||||||
return resources.getString(R.string.stats_pace);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
|
||||||
return resources.getString(R.string.stats_average_moving_speed);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
|
|
||||||
return resources.getString(R.string.stats_average_speed);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
|
|
||||||
return resources.getString(R.string.stats_max_speed);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
|
||||||
return resources.getString(R.string.stats_average_moving_pace);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
|
|
||||||
return resources.getString(R.string.stats_average_pace);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
|
||||||
return resources.getString(R.string.stats_fastest_pace);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
|
|
||||||
return resources.getString(R.string.stats_altitude);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
|
|
||||||
return resources.getString(R.string.stats_gain);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
|
|
||||||
return resources.getString(R.string.stats_loss);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
|
|
||||||
return resources.getString(R.string.stats_coordinates);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
|
||||||
return resources.getString(R.string.stats_sensors_heart_rate);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
|
|
||||||
return resources.getString(R.string.stats_sensors_cadence);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
|
|
||||||
return resources.getString(R.string.stats_sensors_power);
|
|
||||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
|
|
||||||
return resources.getString(R.string.stats_clock);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -140,9 +70,31 @@ public class DataField implements Parcelable {
|
|||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel parcel, int i) {
|
public void writeToParcel(Parcel parcel, int i) {
|
||||||
parcel.writeString(key);
|
parcel.writeString(key);
|
||||||
parcel.writeString(title);
|
|
||||||
parcel.writeByte((byte) (isVisible ? 1 : 0));
|
parcel.writeByte((byte) (isVisible ? 1 : 0));
|
||||||
parcel.writeByte((byte) (isPrimary ? 1 : 0));
|
parcel.writeByte((byte) (isPrimary ? 1 : 0));
|
||||||
parcel.writeByte((byte) (isWide ? 1 : 0));
|
parcel.writeByte((byte) (isWide ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DataField dataField = (DataField) o;
|
||||||
|
return isVisible == dataField.isVisible && isPrimary == dataField.isPrimary && isWide == dataField.isWide && Objects.equals(key, dataField.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(key, isVisible, isPrimary, isWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DataField{" +
|
||||||
|
"key='" + key + '\'' +
|
||||||
|
", isVisible=" + isVisible +
|
||||||
|
", isPrimary=" + isPrimary +
|
||||||
|
", isWide=" + isWide +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-41
@@ -1,78 +1,50 @@
|
|||||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
public class Layout implements Parcelable {
|
public class RecordingLayout implements Parcelable {
|
||||||
private static final String TAG = Layout.class.getSimpleName();
|
|
||||||
|
|
||||||
// User-generated layout's name.
|
// User-generated layout's name.
|
||||||
private final String name;
|
private final String name;
|
||||||
private int columnsPerRow;
|
private int columnsPerRow;
|
||||||
private final List<DataField> dataFields = new ArrayList<>();
|
private final List<DataField> dataFields = new ArrayList<>();
|
||||||
|
|
||||||
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
public RecordingLayout(String name) {
|
||||||
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
|
|
||||||
if (csvParts == null) {
|
|
||||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
|
||||||
return new Layout(PreferencesUtils.getDefaultLayoutName());
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
|
|
||||||
for (int i = 2; i < csvParts.size(); i++) {
|
|
||||||
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
|
|
||||||
if (fieldParts == null) {
|
|
||||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
|
||||||
}
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Layout(String name) {
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layout(String name, int columnsPerRow) {
|
public RecordingLayout(String name, int columnsPerRow) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.columnsPerRow = columnsPerRow;
|
this.columnsPerRow = columnsPerRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Layout(Parcel in) {
|
protected RecordingLayout(Parcel in) {
|
||||||
name = in.readString();
|
name = in.readString();
|
||||||
columnsPerRow = in.readInt();
|
columnsPerRow = in.readInt();
|
||||||
in.readList(dataFields, DataField.class.getClassLoader());
|
in.readList(dataFields, DataField.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<Layout> CREATOR = new Creator<>() {
|
public static final Creator<RecordingLayout> CREATOR = new Creator<>() {
|
||||||
@Override
|
@Override
|
||||||
public Layout createFromParcel(Parcel in) {
|
public RecordingLayout createFromParcel(Parcel in) {
|
||||||
return new Layout(in);
|
return new RecordingLayout(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Layout[] newArray(int size) {
|
public RecordingLayout[] newArray(int size) {
|
||||||
return new Layout[size];
|
return new RecordingLayout[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
|
||||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addField(DataField dataField) {
|
public void addField(DataField dataField) {
|
||||||
dataFields.add(dataField);
|
dataFields.add(dataField);
|
||||||
}
|
}
|
||||||
@@ -111,8 +83,8 @@ public class Layout implements Parcelable {
|
|||||||
this.columnsPerRow = columnsPerRow;
|
this.columnsPerRow = columnsPerRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sameName(Layout layout) {
|
public boolean sameName(RecordingLayout recordingLayout) {
|
||||||
return this.name.equalsIgnoreCase(layout.getName());
|
return this.name.equalsIgnoreCase(recordingLayout.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sameName(String name) {
|
public boolean sameName(String name) {
|
||||||
@@ -126,7 +98,7 @@ public class Layout implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
|
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
|
||||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
+ fields.stream().map(RecordingLayoutIO::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,4 +113,17 @@ public class Layout implements Parcelable {
|
|||||||
parcel.writeInt(columnsPerRow);
|
parcel.writeInt(columnsPerRow);
|
||||||
parcel.writeList(dataFields);
|
parcel.writeList(dataFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
RecordingLayout recordingLayout = (RecordingLayout) o;
|
||||||
|
return columnsPerRow == recordingLayout.columnsPerRow && Objects.equals(name, recordingLayout.name) && Objects.equals(dataFields, recordingLayout.dataFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, columnsPerRow, dataFields);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
|
public class RecordingLayoutIO {
|
||||||
|
|
||||||
|
private static final String TAG = RecordingLayout.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String YES_VALUE = "1";
|
||||||
|
private static final String NOT_VALUE = "0";
|
||||||
|
|
||||||
|
public static RecordingLayout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||||
|
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
|
||||||
|
if (csvParts == null) {
|
||||||
|
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||||
|
return new RecordingLayout(PreferencesUtils.getDefaultLayoutName());
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordingLayout recordingLayout = new RecordingLayout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
|
||||||
|
for (int i = 2; i < csvParts.size(); i++) {
|
||||||
|
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
|
||||||
|
if (fieldParts == null) {
|
||||||
|
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||||
|
return recordingLayout;
|
||||||
|
}
|
||||||
|
recordingLayout.addField(fromCSV(fieldParts, resources));
|
||||||
|
}
|
||||||
|
return recordingLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toCSV(List<RecordingLayout> recordingLayouts) {
|
||||||
|
return recordingLayouts.stream().map(RecordingLayout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataField fromCSV(String[] fieldParts, @NonNull Resources resources) {
|
||||||
|
return new DataField(
|
||||||
|
fieldParts[0],
|
||||||
|
fieldParts[1].equals(YES_VALUE),
|
||||||
|
fieldParts[2].equals(YES_VALUE),
|
||||||
|
fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toCsv(DataField datafield) {
|
||||||
|
String visible = datafield.isVisible() ? YES_VALUE : NOT_VALUE;
|
||||||
|
String primary = datafield.isPrimary() ? YES_VALUE : NOT_VALUE;
|
||||||
|
String wide = datafield.isWide() ? YES_VALUE : NOT_VALUE;
|
||||||
|
return datafield.getKey() + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
-40
@@ -1,106 +1,151 @@
|
|||||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
import de.dennisguse.opentracks.data.models.Altitude;
|
||||||
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.databinding.CustomStatsItemBinding;
|
||||||
|
import de.dennisguse.opentracks.services.RecordingData;
|
||||||
|
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.Mapping;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
|
||||||
|
|
||||||
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
private Layout layout;
|
private static final String TAG = SettingsCustomLayoutEditAdapter.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final RecordingData demoData;
|
||||||
|
|
||||||
|
static {
|
||||||
|
TrackStatistics trackStatistics = new TrackStatistics();
|
||||||
|
trackStatistics.setStartTime(Instant.ofEpochMilli(0));
|
||||||
|
trackStatistics.setMovingTime(Duration.ofMinutes(0));
|
||||||
|
trackStatistics.setTotalTime(Duration.ofMinutes(0));
|
||||||
|
|
||||||
|
trackStatistics.setTotalDistance(Distance.of(0));
|
||||||
|
|
||||||
|
trackStatistics.setTotalAltitudeGain(0f);
|
||||||
|
trackStatistics.setTotalAltitudeLoss(0f);
|
||||||
|
Track track = new Track(ZoneOffset.UTC);
|
||||||
|
track.setTrackStatistics(trackStatistics);
|
||||||
|
|
||||||
|
TrackPoint lastTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
lastTrackPoint.setLatitude(0);
|
||||||
|
lastTrackPoint.setLongitude(0);
|
||||||
|
lastTrackPoint.setAltitude(Altitude.EGM2008.of(0));
|
||||||
|
lastTrackPoint.setSpeed(Speed.of(0));
|
||||||
|
|
||||||
|
demoData = new RecordingData(track, lastTrackPoint, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecordingLayout recordingLayout;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||||
|
private final Map<String, Callable<StatisticViewHolder<?>>> mapping;
|
||||||
|
|
||||||
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
|
||||||
|
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, RecordingLayout recordingLayout) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.itemClickListener = itemClickListener;
|
this.itemClickListener = itemClickListener;
|
||||||
this.layout = layout;
|
this.recordingLayout = recordingLayout;
|
||||||
|
|
||||||
|
mapping = Mapping.create(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
CustomStatsItemBinding binding = CustomStatsItemBinding.inflate(LayoutInflater.from(context), parent, false);
|
||||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
return new SettingsCustomLayoutEditAdapter.ViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
||||||
DataField field = layout.getFields().get(position);
|
DataField field = recordingLayout.getFields().get(position);
|
||||||
viewHolder.itemView.setTag(field.getKey());
|
viewHolder.itemView.setTag(field.getKey());
|
||||||
viewHolder.title.setText(field.getTitle());
|
try {
|
||||||
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
|
StatisticViewHolder<?> m = mapping.get(field.getKey()).call();
|
||||||
|
m.initialize(context, LayoutInflater.from(context));
|
||||||
|
m.configureUI(field);
|
||||||
|
m.onChanged(UnitSystem.METRIC, demoData);
|
||||||
|
|
||||||
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader);
|
viewHolder.viewBinding.statsLayout.removeAllViews(); //TODO this is not really performant
|
||||||
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue);
|
viewHolder.viewBinding.statsLayout.addView(m.getView());
|
||||||
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
} catch (Exception e) {
|
||||||
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
Log.e(TAG, "Couldn't to instantiate UI for DataField with key " + field.getKey() + " " + e.getMessage());
|
||||||
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
viewHolder.viewBinding.statsIconShowStatus.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||||
|
viewHolder.viewBinding.statsIconShowStatus.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||||
|
viewHolder.viewBinding.statsIvDragIndicator.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
if (layout == null) {
|
if (recordingLayout == null) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return layout.getFields().size();
|
return recordingLayout.getFields().size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isItemWide(int position) {
|
public boolean isItemWide(int position) {
|
||||||
return layout.getFields().get(position).isWide();
|
return recordingLayout.getFields().get(position).isWide();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataField getItem(int position) {
|
public DataField getItem(int position) {
|
||||||
return layout.getFields().get(position);
|
return recordingLayout.getFields().get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapValues(Layout data) {
|
public void swapValues(RecordingLayout data) {
|
||||||
this.layout = data;
|
this.recordingLayout = data;
|
||||||
if (this.layout != null) {
|
if (this.recordingLayout != null) {
|
||||||
this.notifyDataSetChanged();
|
this.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layout move(int fromPosition, int toPosition) {
|
public RecordingLayout move(int fromPosition, int toPosition) {
|
||||||
layout.moveField(fromPosition, toPosition);
|
recordingLayout.moveField(fromPosition, toPosition);
|
||||||
notifyItemMoved(fromPosition, toPosition);
|
notifyItemMoved(fromPosition, toPosition);
|
||||||
return layout;
|
return recordingLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
final TextView title;
|
|
||||||
final TextView value;
|
|
||||||
final TextView unit;
|
|
||||||
final ImageView statusIcon;
|
|
||||||
final ImageView moveIcon;
|
|
||||||
|
|
||||||
public ViewHolder(@NonNull View itemView) {
|
CustomStatsItemBinding viewBinding;
|
||||||
super(itemView);
|
|
||||||
title = itemView.findViewById(R.id.stats_description_main);
|
public ViewHolder(@NonNull CustomStatsItemBinding viewBinding) {
|
||||||
value = itemView.findViewById(R.id.stats_value);
|
super(viewBinding.getRoot());
|
||||||
unit = itemView.findViewById(R.id.stats_unit);
|
this.viewBinding = viewBinding;
|
||||||
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
|
|
||||||
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
|
viewBinding.getRoot().setOnClickListener(this);
|
||||||
itemView.setOnClickListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
String statTitle = (String) view.getTag();
|
String statTitle = (String) view.getTag();
|
||||||
Optional<DataField> optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
Optional<DataField> optionalField = recordingLayout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
||||||
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
|
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -17,14 +17,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|||||||
|
|
||||||
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
private List<Layout> layoutList;
|
private List<RecordingLayout> recordingLayoutList;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final SettingsCustomLayoutProfileClickListener itemClickListener;
|
private final SettingsCustomLayoutProfileClickListener itemClickListener;
|
||||||
|
|
||||||
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.itemClickListener = itemClickListener;
|
this.itemClickListener = itemClickListener;
|
||||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -37,37 +37,37 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
|
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
|
||||||
Layout layout = layoutList.get(position);
|
RecordingLayout recordingLayout = recordingLayoutList.get(position);
|
||||||
viewHolder.itemView.setTag(layout.getName());
|
viewHolder.itemView.setTag(recordingLayout.getName());
|
||||||
viewHolder.title.setText(layout.getName());
|
viewHolder.title.setText(recordingLayout.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
if (layoutList == null) {
|
if (recordingLayoutList == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return layoutList.size();
|
return recordingLayoutList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Layout> getLayouts() {
|
public List<RecordingLayout> getLayouts() {
|
||||||
return layoutList;
|
return recordingLayoutList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadLayouts() {
|
public void reloadLayouts() {
|
||||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeLayout(int position) {
|
public void removeLayout(int position) {
|
||||||
layoutList.remove(position);
|
recordingLayoutList.remove(position);
|
||||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void restoreItem(Layout layout, int position) {
|
public void restoreItem(RecordingLayout recordingLayout, int position) {
|
||||||
layoutList.add(position, layout);
|
recordingLayoutList.add(position, recordingLayout);
|
||||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +83,12 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
String profile = (String) view.getTag();
|
String profile = (String) view.getTag();
|
||||||
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
|
Optional<RecordingLayout> optionalLayout = recordingLayoutList.stream().filter(layout -> layout.sameName(new RecordingLayout(profile))).findFirst();
|
||||||
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
|
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SettingsCustomLayoutProfileClickListener {
|
public interface SettingsCustomLayoutProfileClickListener {
|
||||||
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
|
void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import androidx.loader.app.LoaderManager;
|
|||||||
import androidx.loader.content.CursorLoader;
|
import androidx.loader.content.CursorLoader;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.AbstractActivity;
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
@@ -309,11 +311,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
public void setSearch(String searchQuery) {
|
public void setSearch(String searchQuery) {
|
||||||
this.searchQuery = searchQuery;
|
this.searchQuery = searchQuery;
|
||||||
restart();
|
restart();
|
||||||
if (searchQuery != null) {
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.menu_markers)));
|
||||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(searchQuery);
|
|
||||||
} else {
|
|
||||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_markers));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void restart() {
|
public void restart() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user