forked from upstream-mirrors/OpenTracks
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 3eb89d7899 | |||
| f12ff4a625 | |||
| 71dac06ce8 | |||
| 07090cd72c | |||
| 26a20fe32d | |||
| d74ef256d7 | |||
| 0392ba5128 | |||
| 3d12ebdaa3 | |||
| b3cb5e8091 | |||
| 8f9cbcc077 | |||
| 14b6746dff | |||
| a5eca8fa64 | |||
| 498ee20e2a | |||
| 2207865a70 | |||
| 8073f12947 | |||
| cacfcbd4d6 | |||
| 08673de331 | |||
| aecd1504f3 | |||
| f3bf42083a | |||
| 8a9cd40bd5 | |||
| fee1d1a871 | |||
| 0787ae6686 | |||
| 747ef9fc56 | |||
| 5911d60818 | |||
| b89dbf5819 | |||
| 99e2c1f38d | |||
| faa1ffe53b | |||
| 8461122df0 | |||
| 2ca7aa8f1d |
@@ -1,3 +1,6 @@
|
||||
# We have flaky test results due to performance limitations in Github Actions.
|
||||
# Happens too often nowadays; See https://github.com/OpenTracksApp/OpenTracks/issues/1409
|
||||
|
||||
#https://github.com/marketplace/actions/android-emulator-runner
|
||||
name: Test
|
||||
on:
|
||||
@@ -40,8 +40,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
|
||||
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/horizontal-auto.svg" alt="Translation status" />
|
||||
<a href="https://hosted.weblate.org/engage/opentracks/">
|
||||
<img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## Screenshots
|
||||
@@ -129,6 +129,7 @@ The Public API is disabled by default to protect the user's privacy, but it can
|
||||
| [Golden Cheetah 3.5](https://www.goldencheetah.org/) | ? | [no](https://github.com/GoldenCheetah/GoldenCheetah/issues/4271) | [no](https://github.com/GoldenCheetah/GoldenCheetah/issues/4271) |
|
||||
| [GpxPod](https://apps.nextcloud.com/apps/gpxpod) | ? | ? | ? |
|
||||
| [OsmAnd](https://github.com/osmandapp/OsmAnd) | ? | [no](https://github.com/osmandapp/OsmAnd/issues/15271) | [no](https://github.com/osmandapp/OsmAnd/issues/15271) |
|
||||
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
|
||||
|
||||
## Dashboard API (incl. map)
|
||||
|
||||
@@ -149,7 +150,7 @@ The developer documentation is in [README_API.md](README_API.md).
|
||||
## Project history
|
||||
|
||||
_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.
|
||||
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).
|
||||
|
||||
+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.
|
||||
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
|
||||
Application id: de.dennisguse.opentracks.playstore
|
||||
Application id: `de.dennisguse.opentracks.playstore`
|
||||
|
||||
Requirement: `gem install fastlane`
|
||||
|
||||
@@ -35,4 +35,6 @@ Supported languages: https://support.google.com/googleplay/android-developer/tab
|
||||
|
||||
## 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)
|
||||
.
|
||||
+16
-15
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5051
|
||||
versionName "v4.1.1"
|
||||
versionCode 5151
|
||||
versionName "v4.3.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -69,6 +69,8 @@ android {
|
||||
|
||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||
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 {
|
||||
nightly {
|
||||
@@ -120,23 +122,22 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
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.core:core-splashscreen:1.0.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.8.0'
|
||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.11.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ Changes:
|
||||
Bugfix:
|
||||
- Speech announcements use MAY_DUCK (again)
|
||||
- ChartView shows markers (again)
|
||||
- Fixed landscape layout of TrackListActivity
|
||||
- Fixed landscape recordingLayout of TrackListActivity
|
||||
- Request legacy file API behavior (necessary for Android 10)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
v4.1.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Voice announcements only use on decimal for distance
|
||||
|
||||
Bugfix:
|
||||
- Crash when stopping (race condition)
|
||||
|
||||
Developer:
|
||||
- Github actions: disabled instrumentation testing (too slow)
|
||||
- Tests are done with only fake GPS data
|
||||
@@ -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,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- "Taispeáin ar léarscáil" - feidhmiúlacht trí KML in ionad KMZ
|
||||
- Ní chuimsíonn fógra gutha ainm na haipe
|
||||
- Agus é ag taifeadadh: déantar gnóthachan/caillteanas ingearchló a nuashonrú gach soicind
|
||||
- Tuairiscí an tsiopa aistrithe
|
||||
|
||||
Maoláin:
|
||||
- Aistriúcháin fógartha gutha seasta neamhbhailí
|
||||
|
||||
Forbróir:
|
||||
- ceadanna Android12 (BLUETOOTH_SCAN)
|
||||
- ceadanna Android12 (ACCESS_COARSE_LOCATION)
|
||||
- Ag dearbhú do Trackpoints (tástáil)
|
||||
- Aistriúcháin neamhúsáidte bainte
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.19.1: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Aistriúcháin nuashonraithe.
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Easpórtáil Gach Rianta fabht seasta.
|
||||
- Lean bug Track socraithe.
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.20.0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Agus tú ag taifeadadh: tá UI staitisticí inchumraithe anois
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Níor tugadh aird ar athrú ainm/catagóir an traic le linn an taifeadta (a tugadh isteach i v3.19.0)
|
||||
@@ -0,0 +1,6 @@
|
||||
v3.20.1: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Is féidir fógraí a úsáid anois de réir achair agus ama
|
||||
- Bíp fógraí mura bhfuil aon bhogearraí Téacs-go-hurlabhra suiteáilte
|
||||
- Baineadh minicíocht oiriúnaitheach suíomh GPS
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.22.0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Ní stórálfar láithreacha ach amháin má thaistilítear > an t-eatramh faid a thaifeadtar
|
||||
(feidhmiúlacht fágtha bainte den bhrath díomhaoin/neamhdhíomhaoin)
|
||||
- Is féidir Chomhéadain a chumrú chun clog a thaispeáint
|
||||
- Is féidir luas díomhaoin a chumrú anois
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Eatraimh: tuairteála nuair a stóráiltear gnóthachan ach ní caillteanas
|
||||
- Agus tú ag taifeadadh: seachain tuairteáil agus fógraí gutha á ndíchumasú
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.23.0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Cuireadh próifílí Chomhéadain leis
|
||||
- Staitisticí comhiomlánaithe le haghaidh rianta roghnaithe
|
||||
- TrackPoints díomhaoin/neamhdhomhaoin athfhorfheidhmithe de réir mar a stop an t-am bogtha ag obair (tugtha isteach in v3.22.0)
|
||||
|
||||
Ceartúcháin fhabht:
|
||||
- Braiteoirí BLE: sonraí soiléire maidir le dínascadh
|
||||
- Ní mór an t-easpórtáil uathoibríoch a cheadú go fóill
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.25.0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Tá GPS roghnach anois lena n-áirítear. Rabhadh GPS díchumasaithe
|
||||
- PublicAPI: is féidir le feidhmchláir eile sonraí taifeadta a fháil anois má thosaíonn siad ar thaifeadadh (curtha i bhfeidhm do Gadgetbridge)
|
||||
- Gníomhaíochtaí nua: snámh, snámh oscailte, agus workout
|
||||
- Baineadh feidhmiúlacht pic sciar
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Chomhéadain: luas / airde nuair a léim idir 0.0 agus an luach iarbhír
|
||||
- Is féidir rianta le emojis a onnmhairiú
|
||||
@@ -0,0 +1,5 @@
|
||||
v3.25.1: OpenTracks
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Easpórtáil uathoibríoch: tá súil againn teipeanna ciúine a chosc
|
||||
- Easpórtáil uathoibríoch: cosc tuairteála
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.26.0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Is féidir fógraí gutha a chumrú
|
||||
- Fógraí gutha faoi ráta croí
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Tuairteála mar gheall ar riocht cine
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.26.1: OpenTracks
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Níor sháraigh barra gníomhaíochta comhthéacsúla an barra uirlisí i dtéama na hoíche.
|
||||
@@ -0,0 +1,13 @@
|
||||
v4.0.1: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Is féidir fógraí gutha a chumrú anois
|
||||
- Folaíonn mód lánscáileáin anois barra nascleanúna Android freisin
|
||||
- Ba cheart go mbeadh nasc le braiteoirí BLE níos tapúla anois
|
||||
- Ceadaigh socruithe a athshocrú agus tú ag taifeadadh
|
||||
- Ceadaigh socruithe iompórtála/easpórtála a athrú agus tú ag taifeadadh
|
||||
- KML/KMZ: ná húsáid deilbhíní le luchtú ó Google
|
||||
|
||||
Ceartúchán Bug:
|
||||
- Oibríonn TrackStoppedActivity ar scáileáin níos lú anois
|
||||
- Iarratas ar cheadanna ar an rianliosta
|
||||
@@ -0,0 +1,10 @@
|
||||
v4. 0. 2: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- - Agus muirmhíle
|
||||
- Is féidir socruithe a athshocrú agus taifeadadh á dhéanamh
|
||||
|
||||
Ceartúcháin:
|
||||
- Fógra gutha slánú
|
||||
- Cairt: bhí ingearchló smoothed weirdly
|
||||
- Ní raibh easpórtáil ag seiceáil an bhfuil an comhad ann
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.0. 3: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
Sonraíocht: Max. luas mar a thuairiscigh braiteoir (GPS nó BLE)
|
||||
|
||||
Ceartúcháin:
|
||||
- Cosc ar thuairteáil mura bhfuil Painéal Na Nionstraimí OSM suiteáilte
|
||||
- Easpórtáil rian GPX: ná heaspórtáil deighleáin an amhráin folamh
|
||||
@@ -0,0 +1,13 @@
|
||||
v4. 0. 4: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Stóráil sonraí braiteoir BLE fiú gan achar (gach 10s)
|
||||
- PUBLIC API: ceadaíonn leagan ainm, catagóir, agus cur síos
|
||||
|
||||
Ceartúcháin:
|
||||
- Tuairteála nuair a ghlacadh pictiúr agus rothlach an fón
|
||||
-
|
||||
|
||||
Forbróir:
|
||||
Sprioc-SDK 33
|
||||
- Baraiméadar úsáideann eatramh de 5s
|
||||
@@ -0,0 +1,7 @@
|
||||
v4. 0. 5: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- - PlayStore release faigh lógó eile
|
||||
|
||||
Ceartúcháin:
|
||||
- Taispeánann liosta eatramh " - " mura bhfuil gnóthachan/caillteanas i láthair
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.0. 6: OpenTracks
|
||||
|
||||
Ceartúcháin:
|
||||
- Egm2008 fhritháireamh mícheart ríomh le haghaidh domhanfhad diúltach
|
||||
- - Crashed API poiblí má Bhí Rianta Oscailte sa chúlra (26 AIBREÁN+)
|
||||
@@ -0,0 +1,10 @@
|
||||
v4. 0. 7: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Gps comhartha caillte teorainn ama: 30s
|
||||
- - Android 13: cead a iarraidh chun fógraí a thaispeáint
|
||||
- Scrios rianta níos tapúla (bunachar sonraí folúis amháin más gá)
|
||||
|
||||
Ceartúcháin:
|
||||
- PublicAPI stop ordú mar thoradh ar tuairteála (a tugadh isteach i v4. 0. 6)
|
||||
- Ceadanna: ceadanna a iarraidh le linn tosaithe
|
||||
@@ -0,0 +1,11 @@
|
||||
v4. 0. 8: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Airde a thaispeántar i gcónaí i m/ft
|
||||
- Taispeáin crios ama an riain mura bhfuil sé éagsúil le crios ama na feiste
|
||||
- PublicAPI: tacaíocht deilbhín an riain a shocrú go sainráite
|
||||
|
||||
Forbróir:
|
||||
- A Tugadh Isteach Cianfhormáideoir
|
||||
- Uasghrádú GO AGP 7.3/desugar Ar Feadh Ré.aicme
|
||||
- Android12+: déan splancscáileán trí Androidx
|
||||
@@ -0,0 +1,8 @@
|
||||
v4. 1. 0: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Easpórtáil leagan KML/KMZ 2. 3 amháin (2. 2)
|
||||
- Le haghaidh crank-bhunaithe powermeters: faigh cadence
|
||||
|
||||
Ceartúcháin:
|
||||
- Workaround do tuairteála ar "iompórtáil gach rud"
|
||||
@@ -0,0 +1,7 @@
|
||||
v4.1. 1: OpenTracks
|
||||
|
||||
Athruithe:
|
||||
- Cuir scáileán réamhrá leis (taispeáin uair amháin)
|
||||
|
||||
Ceartúcháin:
|
||||
- Ná tuairteála mura bhfuil gléas GPS i láthair
|
||||
@@ -0,0 +1,22 @@
|
||||
Cairde rianaithe spóirt a bhfuil meas aige ar do phríobháideachas.
|
||||
|
||||
Tugann gníomhaíochtaí spóirt agus lasmuigh sonas, scil agus muinín.
|
||||
|
||||
Cuir luach ar do shláinte trí súil a choinneáil ar d'oiliúint.
|
||||
Taifeadann tú de réir mar a théann tú ag rith nó ag siúl, agus tugann sé ríomhaire rothar duit le scáileán níos mó le haghaidh rothaíochta.
|
||||
Marcáil láithreacha suimiúla ar do bhealach le pictiúir.
|
||||
Coinnigh staitisticí taifeadta go mion go leor le haghaidh anailíse.
|
||||
Comhroinn ach na sonraí a theastaíonn uait a bheith ag daoine eile.
|
||||
|
||||
* Fógraí gutha.
|
||||
* Tacaíonn braiteoirí Bluetooth LE: ráta croí, luas agus fad (rothaíocht), deireadh (rothaíocht), agus méadar cumhachta (rothaíocht).
|
||||
* Gnóthachan agus caillteanas airde: trí bhraiteoir barometric.
|
||||
* Airde léirithe in EGM2008 (os cionn meánleibhéal na farraige); onnmhairiú mar WGS84.
|
||||
* Easpórtáil sonraí mar rianta mar KMZ (grianghraif san áireamh), KML, nó GPX.
|
||||
* Gan rochtain ar an Idirlíon nó ceadanna breise.
|
||||
* Téama dorcha agus éadrom, ag urramú socruithe an chórais.
|
||||
* Gan fógraí.
|
||||
|
||||
Bogearraí Libre / Bogearraí Saor in Aisce / Foinse Oscailte
|
||||
rud a chiallaíonn gur féidir leat an cód foinse a úsáid, a staidéar, a athrú agus a roinnt.
|
||||
Ceadúnaithe Apache 2.0
|
||||
@@ -0,0 +1 @@
|
||||
Cairde rianaithe spóirt a bhfuil meas aige ar do phríobháideachas.
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
android.useAndroidX=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m
|
||||
|
||||
+32
-34
@@ -12,19 +12,16 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -73,6 +70,9 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
* Note: those tests are affected by {@link de.dennisguse.opentracks.sensors.sensorData.SensorData}.isRecent().
|
||||
* If the test device is too slow (like in a CI) these are likely to fail as the sensor data will be omitted from actual.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExportImportTest {
|
||||
@@ -82,9 +82,6 @@ public class ExportImportTest {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
//For csv_export_only() as we the timezone is hardcoded in the expectation.
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
@@ -157,18 +154,19 @@ public class ExportImportTest {
|
||||
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:14Z");
|
||||
mockBLESensorData(trackPointCreator, 15f, null, 66f, 3f, 50f);
|
||||
mockBLESensorData(trackPointCreator, 15f, null, 67f, 3f, 50f);
|
||||
trackPointCreator.setClock("2020-02-02T02:02:15Z");
|
||||
mockBLESensorData(trackPointCreator, null, null, 66f, 3f, 50f);
|
||||
mockBLESensorData(trackPointCreator, null, null, 68f, 3f, 50f);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:16Z");
|
||||
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 66f, 3f, 50f); // Distance will be added to next TrackPoint
|
||||
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f); // Distance will be added to next TrackPoint
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -181,7 +179,7 @@ public class ExportImportTest {
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -359,7 +357,7 @@ public class ExportImportTest {
|
||||
.setAltitudeLoss(1f)
|
||||
.setAltitudeGain(1f)
|
||||
.setSensorDistance(Distance.of(14))
|
||||
.setHeartRate(66f)
|
||||
.setHeartRate(69)
|
||||
.setPower(50f)
|
||||
.setCadence(3f)
|
||||
.setHorizontalAccuracy(Distance.of(10)),
|
||||
@@ -447,7 +445,6 @@ public class ExportImportTest {
|
||||
assertNull(trackImported);
|
||||
}
|
||||
|
||||
@Ignore(value = "TODO Fails on CI; works on API24 and API30 locally")
|
||||
@LargeTest
|
||||
@Test
|
||||
public void csv_export_only() throws TimeoutException, IOException {
|
||||
@@ -498,32 +495,33 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
|
||||
@Override
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
Mockito.when(remoteSensorManager.fill(Mockito.any())).thenAnswer(invocation -> {
|
||||
TrackPoint trackPoint = invocation.getArgument(0);
|
||||
|
||||
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
|
||||
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||
sensorDataSet.set(cyclingCadence);
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
|
||||
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||
sensorDataSet.set(cyclingCadence);
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
});
|
||||
trackPointCreator.setRemoteSensorManager(remoteSensorManager);
|
||||
trackPointCreator.onChange(new SensorDataSet());
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.junit.Test;
|
||||
|
||||
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.Speed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||
@@ -23,10 +24,10 @@ public class BluetoothUtilsTest {
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(60, heartRate);
|
||||
assertEquals(HeartRate.of(60), heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -36,10 +37,10 @@ public class BluetoothUtilsTest {
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(257, heartRate);
|
||||
assertEquals(HeartRate.of(257), heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-5
@@ -29,7 +29,6 @@ import android.os.Looper;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -57,9 +56,6 @@ public class TrackRecordingServiceTestMarker {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -128,7 +124,6 @@ public class TrackRecordingServiceTestMarker {
|
||||
public void recording_GPSfix_createsMarker() {
|
||||
// given
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.stopGPS();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:02Z");
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
-11
@@ -11,7 +11,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -53,9 +52,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -95,7 +91,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
PreferencesUtils.setString(R.string.idle_speed_key, R.string.idle_speed_default);
|
||||
|
||||
service = startService();
|
||||
service.getTrackPointCreator().stopGPS();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -117,7 +112,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
String startTime = "2020-02-02T02:02:02Z";
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// then
|
||||
@@ -155,7 +149,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
@@ -202,7 +195,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
String starTime = "2020-02-02T02:02:02Z";
|
||||
trackPointCreator.setClock(starTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
String pauseTime = "2020-02-02T02:02:03Z";
|
||||
@@ -235,7 +227,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
String stopTime = "2020-02-02T02:02:03Z";
|
||||
@@ -246,7 +237,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
String resumeTime = "2020-02-02T02:02:04Z";
|
||||
trackPointCreator.setClock(resumeTime);
|
||||
service.resumeTrack(trackId);
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// then
|
||||
@@ -268,7 +258,6 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.stopGPS();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
|
||||
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ public class TrackRecordingServiceTestStateMachine {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void cannotResume_non_existing_track() throws InterruptedException {
|
||||
public void cannotResume_non_existing_track() {
|
||||
// given
|
||||
assertFalse(service.isRecording());
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
|
||||
+4
-1
@@ -6,6 +6,8 @@ import android.location.Location;
|
||||
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@@ -27,7 +29,8 @@ public class TrackRecordingServiceTestUtils {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
|
||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
-4
@@ -8,7 +8,6 @@ import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
@@ -31,9 +30,6 @@ public class VoiceAnnouncementManagerTest {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
|
||||
+13
-13
@@ -64,7 +64,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Average moving speed 18.4 kilometers per hour.", announcement);
|
||||
assertEquals("Total distance 20.0 kilometers. 1 hour 5 minutes 10 seconds. Average moving speed 18.4 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,7 +80,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 1 second. Average moving speed 20.0 kilometers per hour.", announcement);
|
||||
assertEquals("Total distance 20.0 kilometers. 1 hour 1 second. Average moving speed 20.0 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,13 +96,13 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour. Average moving speed 20.0 kilometers per hour.", announcement);
|
||||
assertEquals("Total distance 20.0 kilometers. 1 hour. Average moving speed 20.0 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_distance_rounding_check_two() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(19990));
|
||||
stats.setTotalDistance(Distance.of(19900));
|
||||
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
@@ -112,7 +112,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 19.99 kilometers. 1 hour. Average moving speed 20.0 kilometers per hour.", announcement);
|
||||
assertEquals("Total distance 19.9 kilometers. 1 hour. Average moving speed 19.9 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,7 +134,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour.", announcement);
|
||||
assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,7 +150,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Pace 3 minutes 15 seconds per kilometer.", announcement);
|
||||
assertEquals("Total distance 20.0 kilometers. 1 hour 5 minutes 10 seconds. Pace 3 minutes 15 seconds per kilometer.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -172,7 +172,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Pace 1 minute 10 seconds per kilometer. Lap time 1 minute 10 seconds per kilometer.", announcement);
|
||||
assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Pace 1 minute 10 seconds per kilometer. Lap time 1 minute 10 seconds per kilometer.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,7 +188,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Average moving speed 11.4 miles per hour.", announcement);
|
||||
assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Average moving speed 11.4 miles per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -210,7 +210,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Average moving speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement);
|
||||
assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Average moving speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -226,7 +226,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement);
|
||||
assertEquals("Total distance 12.4 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -248,7 +248,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement);
|
||||
assertEquals("Total distance 8.8 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,7 +274,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
|
||||
assertEquals("Total distance 14.2 kilometers. 16 minutes 39 seconds. Average moving speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -49,7 +50,7 @@ public class GPSHandlerTest {
|
||||
Mockito.when(trackPointCreator.createNow())
|
||||
.thenReturn(Instant.now());
|
||||
|
||||
locationHandler.onStart(context);
|
||||
locationHandler.onStart(context, new Handler());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+18
-17
@@ -17,7 +17,8 @@ import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
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)
|
||||
public class PreferencesOpenHelperTest {
|
||||
@@ -38,10 +39,10 @@ public class PreferencesOpenHelperTest {
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then there should be one layout with old custom layout that has the new CSV value.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,10 +64,10 @@ public class PreferencesOpenHelperTest {
|
||||
+ 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.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,10 +88,10 @@ public class PreferencesOpenHelperTest {
|
||||
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.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,10 +110,10 @@ public class PreferencesOpenHelperTest {
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then custom layout should be equals to default layout.
|
||||
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
|
||||
RecordingLayout defaultRecordingLayout = RecordingLayoutIO.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||
List<RecordingLayout> customRecordingLayout = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(customLayout.size(), 1);
|
||||
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
|
||||
assertEquals(customRecordingLayout.size(), 1);
|
||||
assertEquals(defaultRecordingLayout.toCsv(), customRecordingLayout.get(0).toCsv());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import java.util.List;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
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)
|
||||
public class PreferencesUtilsTest {
|
||||
@@ -85,13 +86,13 @@ public class PreferencesUtilsTest {
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// then
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertTrue(layouts.get(0).getFields().size() > 0);
|
||||
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertTrue(recordingLayouts.get(0).getFields().size() > 0);
|
||||
assertEquals(recordingLayouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(recordingLayouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,12 +104,12 @@ public class PreferencesUtilsTest {
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
assertTrue(recordingLayout.getFields().size() > 0);
|
||||
assertEquals(recordingLayout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(recordingLayout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,28 +127,28 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getName(), "run");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 4);
|
||||
assertEquals(recordingLayout.getName(), "run");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -165,44 +166,44 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getName(), "walking");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 4);
|
||||
assertEquals(recordingLayout.getName(), "walking");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertTrue(layout.getFields().get(2).isWide());
|
||||
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||
assertTrue(recordingLayout.getFields().get(2).isWide());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCustomLayout() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Layout layoutSrc = new Layout("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));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), 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));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
RecordingLayout recordingLayoutSrc = new RecordingLayout("road cycling");
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false));
|
||||
|
||||
// when
|
||||
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();
|
||||
|
||||
// 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_speed_key) + ",0,0,0;");
|
||||
|
||||
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
||||
assertEquals(layoutSrc.getName(), layoutDst.getName());
|
||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
||||
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
RecordingLayout recordingLayoutDst = PreferencesUtils.getCustomLayout();
|
||||
assertEquals(recordingLayoutSrc.getName(), recordingLayoutDst.getName());
|
||||
assertEquals(recordingLayoutSrc.getFields().size(), recordingLayoutDst.getFields().size());
|
||||
for (int i = 0; i < recordingLayoutSrc.getFields().size(); i++) {
|
||||
assertEquals(recordingLayoutSrc.getFields().get(i).getKey(), recordingLayoutDst.getFields().get(i).getKey());
|
||||
assertEquals(recordingLayoutSrc.getFields().get(i).isVisible(), recordingLayoutDst.getFields().get(i).isVisible());
|
||||
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.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
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_speed_key) + ",0,0;";
|
||||
|
||||
List<Layout> layoutsToBeUpdated = new ArrayList<>();
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
|
||||
List<RecordingLayout> layoutsToBeUpdated = new ArrayList<>();
|
||||
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources));
|
||||
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(runningProfile, resources));
|
||||
|
||||
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
||||
|
||||
// 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(layoutsAfter.size(), 2);
|
||||
@@ -294,7 +295,7 @@ public class PreferencesUtilsTest {
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
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_average_moving_speed_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
||||
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
|
||||
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
|
||||
RecordingLayout recordingLayoutToBeUpdated = RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources);
|
||||
PreferencesUtils.updateCustomLayout(recordingLayoutToBeUpdated);
|
||||
|
||||
// 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(layoutsAfter.size(), 2);
|
||||
@@ -337,9 +338,9 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when it gets the custom layout
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then the first one was returned
|
||||
assertEquals(layout.getName(), "cycling");
|
||||
assertEquals(recordingLayout.getName(), "cycling");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> subject.addTrackPoint(it));
|
||||
data.trackPoints.forEach(subject::addTrackPoint);
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
@@ -72,7 +71,7 @@ public class EspressoAggregatedFilterTest {
|
||||
ViewInteraction checkBox = onView(
|
||||
allOf(withId(R.id.filter_dialog_check_button), withText(CATEGORY),
|
||||
withParent(allOf(withId(R.id.filter_items),
|
||||
withParent(IsInstanceOf.<View>instanceOf(android.view.ViewGroup.class)))),
|
||||
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
|
||||
isDisplayed()));
|
||||
checkBox.perform(waitFor(2000));
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CsvLayoutUtilsTest extends TestCase {
|
||||
public class CsvRecordingLayoutUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
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;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LayoutTest extends TestCase {
|
||||
public class RecordingLayoutTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
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;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "running");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getName(), "running");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals(dataFieldList.size(), 4);
|
||||
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(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(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).isPrimary());
|
||||
assertFalse(dataFieldList.get(0).isWide());
|
||||
@@ -64,13 +60,13 @@ public class LayoutTest extends TestCase {
|
||||
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,13 +75,13 @@ public class LayoutTest extends TestCase {
|
||||
String csv1 = "Layout Name;2;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,13 +90,13 @@ public class LayoutTest extends TestCase {
|
||||
String csv1 = "Layout Name;2;speed,distance,total time;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,27 +105,27 @@ public class LayoutTest extends TestCase {
|
||||
String csv1 = "Layout Name;2;speed,1,0;distance;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 1);
|
||||
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToCsv() {
|
||||
// given a layout's object
|
||||
Layout layout = new Layout("Test Layout", 2);
|
||||
layout.addField("key1", "Title 1", false, false, false);
|
||||
layout.addField("key2", "Title 2", false, true, false);
|
||||
layout.addField("key3", "Title 3", true, false, false);
|
||||
layout.addField("key4", "Title 4", true, true, false);
|
||||
layout.addField("key5", "Title 5", true, true, true);
|
||||
RecordingLayout recordingLayout = new RecordingLayout("Test Layout", 2);
|
||||
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
String csv = recordingLayout.toCsv();
|
||||
|
||||
// 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;");
|
||||
@@ -138,15 +134,15 @@ public class LayoutTest extends TestCase {
|
||||
@Test
|
||||
public void testToCsv_columnsByDefault() {
|
||||
// given a layout's object
|
||||
Layout layout = new Layout("Test Layout");
|
||||
layout.addField("key1", "Title 1", false, false, false);
|
||||
layout.addField("key2", "Title 2", false, true, false);
|
||||
layout.addField("key3", "Title 3", true, false, false);
|
||||
layout.addField("key4", "Title 4", true, true, false);
|
||||
layout.addField("key5", "Title 5", true, true, true);
|
||||
RecordingLayout recordingLayout = new RecordingLayout("Test Layout");
|
||||
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
String csv = recordingLayout.toCsv();
|
||||
|
||||
// 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;");
|
||||
@@ -12,7 +12,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StatisticsUtilsTest extends TestCase {
|
||||
@@ -44,16 +44,16 @@ public class StatisticsUtilsTest extends TestCase {
|
||||
@Test
|
||||
public void testFilterVisible() {
|
||||
// given
|
||||
Layout layout = new Layout("profile");
|
||||
layout.addField(new DataField("key1", "title1", true, true, false));
|
||||
layout.addField(new DataField("key2", "title2", false, true, false));
|
||||
layout.addField(new DataField("key3", "title3", true, true, false));
|
||||
layout.addField(new DataField("key4", "title4", false, true, false));
|
||||
layout.addField(new DataField("key5", "title5", true, true, false));
|
||||
RecordingLayout recordingLayout = new RecordingLayout("profile");
|
||||
recordingLayout.addField(new DataField("key1", true, true, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, true, false));
|
||||
recordingLayout.addField(new DataField("key4", false, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, false));
|
||||
|
||||
// when
|
||||
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
|
||||
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
|
||||
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
|
||||
// then
|
||||
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))));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
#time,trackpoint_type,latitude,longitude,altitude,accuracy_horizontal,accuracy_vertical,speed,altitude_gain,altitude_loss,sensor_distance,heartrate,cadence,power
|
||||
"2020-02-02T03:02:02+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:03+01:00","TRACKPOINT",3,3,10,10,,54,1,1,,,,
|
||||
"2020-02-02T03:02:03+01:00","TRACKPOINT",3,14,10,10,,54,1,1,,,,
|
||||
"2020-02-02T03:02:04+01:00","SENSORPOINT",,,,,,54,1,1,10,66,3,50
|
||||
"2020-02-02T03:02:05+01:00","TRACKPOINT",3,3,10,10,,18,0,0,2,66,3,50
|
||||
"2020-02-02T03:02:06+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:21+01:00","TRACKPOINT",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:22+01:00","SEGMENT_START_AUTOMATIC",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:23+01:00","TRACKPOINT",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:24+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:15+01:00","SENSORPOINT",,,,,,,,,,68,3,50
|
||||
"2020-02-02T03:02:16+01:00","SENSORPOINT",,,,,,18,,,2,69,3,50
|
||||
"2020-02-02T03:02:17+01:00","TRACKPOINT",3,14.001,10,10,,18,0,0,2,69,3,50
|
||||
"2020-02-02T03:02:18+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:03:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:03:21+01:00","TRACKPOINT",3,14.002,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3,16,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:03:23+01:00","TRACKPOINT",3,16.001,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:03:24+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
|
@@ -62,13 +62,16 @@ limitations under the License.
|
||||
|
||||
<application
|
||||
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:hasFragileUserData="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/ThemeCustom">
|
||||
android:theme="@style/ThemeCustom"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
|
||||
<activity
|
||||
android:name=".publicapi.StartRecording"
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.widget.ArrayAdapter;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackEditBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
@@ -75,7 +76,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
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.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
||||
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
|
||||
@@ -38,7 +38,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
@@ -146,9 +146,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
finish();
|
||||
return true;
|
||||
});
|
||||
viewBinding.trackRecordingFabAction.setOnClickListener((view) -> {
|
||||
Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
viewBinding.trackRecordingFabAction.setOnClickListener((view) -> Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show());
|
||||
|
||||
viewBinding.bottomAppBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24);
|
||||
setSupportActionBar(viewBinding.bottomAppBar);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
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.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
@@ -51,7 +52,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
|
||||
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.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -385,10 +385,6 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
return hasHeartRate() || hasCadence() || hasPower();
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return heartRate != null;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,8 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
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.util.StringUtils;
|
||||
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}.
|
||||
@@ -72,7 +67,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private SensorStatistics sensorStatistics;
|
||||
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
||||
|
||||
private Track.Id trackId;
|
||||
@Nullable // Lazily loaded.
|
||||
@@ -113,8 +107,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||
contentProviderUtils = new ContentProviderUtils(getContext());
|
||||
|
||||
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,7 +115,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
// sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
@@ -261,10 +253,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
if (sensorStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
||||
if (sensorDataList.size() > 0) {
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -9,16 +10,14 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.gridlayout.widget.GridLayout;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
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.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
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}.
|
||||
@@ -48,68 +48,37 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||
|
||||
private Layout layout;
|
||||
private final List<StatisticViewHolder<?>> viewHolders = new LinkedList<>();
|
||||
|
||||
private RecordingLayout recordingLayout;
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
private StatisticsAdapter statisticsAdapter;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private StatisticsDataModel viewModel;
|
||||
private LiveData<List<StatisticData>> statisticsLiveData;
|
||||
|
||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||
updateUInecessary = true;
|
||||
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)) {
|
||||
updateUInecessary = true;
|
||||
layout = PreferencesUtils.getCustomLayout();
|
||||
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
getActivity().runOnUiThread(this::updateUI);
|
||||
onLayoutChanged(PreferencesUtils.getCustomLayout());
|
||||
}
|
||||
};
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||
service.getRecordingDataObservable()
|
||||
.observe(StatisticsRecordingFragment.this, this::onRecordingDataChanged);
|
||||
};
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> service.getRecordingDataObservable()
|
||||
.observe(StatisticsRecordingFragment.this, this::onRecordingDataChanged);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
statisticsAdapter = new StatisticsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -119,10 +88,6 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
|
||||
statisticsLiveData = viewModel.getStatsData();
|
||||
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@@ -141,6 +106,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
viewHolders.clear();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@@ -148,17 +114,56 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
viewModel = null;
|
||||
|
||||
if (statisticsLiveData != null) {
|
||||
statisticsLiveData.removeObservers(getActivity());
|
||||
}
|
||||
statisticsLiveData = null;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (isResumed()) {
|
||||
viewModel.update(recordingData, layout, unitSystem);
|
||||
private void onLayoutChanged(@NonNull RecordingLayout newRecordingLayout) {
|
||||
if (newRecordingLayout.equals(recordingLayout)) {
|
||||
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(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +176,12 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||
}
|
||||
|
||||
updateUI();
|
||||
updateDataOnUI();
|
||||
}
|
||||
|
||||
private void updateDataOnUI() {
|
||||
if (isResumed()) {
|
||||
viewHolders.forEach(i -> i.onChanged(unitSystem, recordingData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ public class IntroductionActivity extends AbstractActivity {
|
||||
private static class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
private static final List<FragmentCreator> fragmentCreators = List.of(
|
||||
(FragmentCreator) WelcomeFragment::newInstance,
|
||||
(FragmentCreator) OSMDashboardFragment::newInstance
|
||||
WelcomeFragment::newInstance,
|
||||
OSMDashboardFragment::newInstance
|
||||
);
|
||||
|
||||
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import de.dennisguse.opentracks.data.models.Marker;
|
||||
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.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
|
||||
@@ -54,20 +54,23 @@ public abstract class BluetoothConnectionManager<DataType> {
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
switch (newState) {
|
||||
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:
|
||||
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
|
||||
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
|
||||
|
||||
gatt.discoverServices();
|
||||
break;
|
||||
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:
|
||||
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
||||
|
||||
//This is also triggered, if no connection was established (ca. 30s)
|
||||
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
|
||||
if (gatt.connect()) {
|
||||
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
|
||||
}
|
||||
clearData();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE_MS = Duration.ofSeconds(5);
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Context context;
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionMa
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.UUID;
|
||||
|
||||
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.Speed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||
@@ -101,7 +102,7 @@ public class BluetoothUtils {
|
||||
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
|
||||
byte[] raw = characteristic.getValue();
|
||||
if (raw.length == 0) {
|
||||
@@ -110,10 +111,10 @@ public class BluetoothUtils {
|
||||
|
||||
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
|
||||
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) {
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
|
||||
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class SensorData<T> {
|
||||
*/
|
||||
private boolean isRecent() {
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
|
||||
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.concurrent.Executors;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class TrackDeleteService extends Service {
|
||||
|
||||
|
||||
@@ -267,10 +267,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
|
||||
@Override
|
||||
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||
notificationManager.updateContent(getString(gpsStatusValue.message));
|
||||
|
||||
//TODO This check should not be necessary, but prevents a crash; somehow the shutdown is not working correctly as we should not receive a notification then.
|
||||
// It is likely a race condition as the LocationManager provides location updates without using the Handler.
|
||||
if (gpsStatusObservable != null) {
|
||||
notificationManager.updateContent(getString(gpsStatusValue.message));
|
||||
gpsStatusObservable.postValue(gpsStatusValue);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ class VoiceAnnouncementUtils {
|
||||
builder.append(context.getString(R.string.total_distance));
|
||||
// Units should always be english singular for TTS.
|
||||
// See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceInUnit, 2, unitDistanceTTS);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceInUnit, 1, unitDistanceTTS);
|
||||
// Punctuation helps introduce natural pauses in TTS
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@ package de.dennisguse.opentracks.services.handlers;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.location.LocationListenerCompat;
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -23,12 +25,16 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String TAG = GPSHandler.class.getSimpleName();
|
||||
|
||||
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
|
||||
private final TrackPointCreator trackPointCreator;
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
|
||||
private LocationManager locationManager;
|
||||
private GpsStatus gpsStatus;
|
||||
private Duration gpsInterval;
|
||||
@@ -38,8 +44,10 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
|
||||
this.trackPointCreator = trackPointCreator;
|
||||
}
|
||||
|
||||
public void onStart(@NonNull Context context) {
|
||||
public void onStart(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(this);
|
||||
gpsStatus = new GpsStatus(context, this);
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
@@ -56,10 +64,11 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
|
||||
public void onStop() {
|
||||
if (locationManager != null && context != null) {
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
locationManager.removeUpdates(this);
|
||||
LocationManagerCompat.removeUpdates(locationManager, this);
|
||||
}
|
||||
locationManager = null;
|
||||
context = null;
|
||||
handler = null;
|
||||
}
|
||||
|
||||
if (gpsStatus != null) {
|
||||
@@ -154,14 +163,20 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
|
||||
return;
|
||||
}
|
||||
|
||||
if(!LocationManagerCompat.hasProvider(locationManager, LocationManager.GPS_PROVIDER)) {
|
||||
if (!LocationManagerCompat.hasProvider(locationManager, LOCATION_PROVIDER)) {
|
||||
Log.e(TAG, "Device doesn't have GPS.");
|
||||
return;
|
||||
}
|
||||
|
||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(gpsInterval.toMillis())
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.setMaxUpdateDelayMillis(0)
|
||||
.build();
|
||||
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this);
|
||||
final Handler HANDLER = handler;
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, HANDLER::post, this);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
|
||||
gpsHandler.onStart(context);
|
||||
gpsHandler.onStart(context, handler);
|
||||
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(context, handler, this);
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
@@ -66,13 +66,6 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
return context != null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//There should be a cooler way to do this; we want to send fake locations without getting affected by real GPS data.
|
||||
@VisibleForTesting
|
||||
public void stopGPS() {
|
||||
gpsHandler.onStop();
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
if (remoteSensorManager == null || altitudeSumManager == null) {
|
||||
Log.d(TAG, "No recording running and no reset necessary.");
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.preference.DialogPreference;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
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);
|
||||
String category = PreferencesUtils.getDefaultActivity();
|
||||
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.setOnItemClickListener((parent, v, position, id) -> {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
|
||||
|
||||
@@ -73,9 +73,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private void setFilenameTemplate() {
|
||||
EditTextPreference preference = findPreference(getString(R.string.export_filename_format_key));
|
||||
preference.setOnBindEditTextListener(t -> {
|
||||
t.setHint(getString(R.string.export_filename_format_default));
|
||||
});
|
||||
preference.setOnBindEditTextListener(t -> t.setHint(getString(R.string.export_filename_format_default)));
|
||||
preference.setDialogMessage(TrackFilenameGenerator.getAllOptions());
|
||||
|
||||
preference.setOnPreferenceChangeListener((p, newValue) -> new TrackFilenameGenerator(newValue.toString()).isValid());
|
||||
|
||||
@@ -42,11 +42,14 @@ import java.util.stream.Stream;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
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.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
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.TrackIconUtils;
|
||||
|
||||
@@ -278,6 +281,12 @@ public class PreferencesUtils {
|
||||
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() {
|
||||
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);
|
||||
@@ -753,35 +762,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.
|
||||
*/
|
||||
public static Layout getCustomLayout() {
|
||||
public static RecordingLayout getCustomLayout() {
|
||||
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
||||
if (layoutSelected == null) {
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
for (String line : csvLines) {
|
||||
Layout layout = Layout.fromCsv(line, resources);
|
||||
if (layout.sameName(layoutSelected)) {
|
||||
return layout;
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources);
|
||||
if (recordingLayout.sameName(layoutSelected)) {
|
||||
return recordingLayout;
|
||||
}
|
||||
}
|
||||
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
||||
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
||||
public static void updateCustomLayouts(@NonNull List<RecordingLayout> recordingLayouts) {
|
||||
setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(recordingLayouts));
|
||||
}
|
||||
|
||||
public static void updateCustomLayout(@NonNull Layout layout) {
|
||||
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
|
||||
public static void updateCustomLayout(@NonNull RecordingLayout recordingLayout) {
|
||||
List<RecordingLayout> preferenceRecordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<RecordingLayout> layoutToBeUpdated = preferenceRecordingLayouts.stream().filter(l -> l.sameName(recordingLayout)).findFirst();
|
||||
if (layoutToBeUpdated.isPresent()) {
|
||||
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
|
||||
layoutToBeUpdated.get().replaceAllFields(recordingLayout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(recordingLayout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceRecordingLayouts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,19 +804,19 @@ public class PreferencesUtils {
|
||||
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
||||
}
|
||||
|
||||
public static List<Layout> getAllCustomLayouts() {
|
||||
List<Layout> layouts = new ArrayList<>();
|
||||
public static List<RecordingLayout> getAllCustomLayouts() {
|
||||
List<RecordingLayout> recordingLayouts = new ArrayList<>();
|
||||
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||
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() {
|
||||
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
||||
return getAllCustomLayouts().stream().map(RecordingLayout::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences() {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class SensorsSettingsFragment extends PreferenceFragmentCompat {
|
||||
if (newValue instanceof String) {
|
||||
try {
|
||||
int newValueInt = Integer.parseInt((String) newValue);
|
||||
return newValueInt > 500 && newValueInt < 4000;
|
||||
return newValueInt >= 100 && newValueInt < 4000;
|
||||
} catch (NumberFormatException e) {
|
||||
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.databinding.ActivitySettingsCustomLayoutBinding;
|
||||
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.util.ArrayAdapterFilterDisabled;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
@@ -30,8 +30,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||
private String profile;
|
||||
private Layout layoutFieldsVisible;
|
||||
private Layout layoutFieldsHidden;
|
||||
private RecordingLayout recordingLayoutFieldsVisible;
|
||||
private RecordingLayout recordingLayoutFieldsHidden;
|
||||
private int numColumns;
|
||||
|
||||
@Override
|
||||
@@ -39,12 +39,12 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Recycler view with visible stats.
|
||||
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = layout.getName();
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
||||
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = recordingLayout.getName();
|
||||
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
|
||||
|
||||
numColumns = layout.getColumnsPerRow();
|
||||
numColumns = recordingLayout.getColumnsPerRow();
|
||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||
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) {
|
||||
int fromPosition = viewHolder.getAdapterPosition();
|
||||
int toPosition = target.getAdapterPosition();
|
||||
layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||
recordingLayoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
||||
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
@@ -103,18 +103,18 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||
Layout layout = new Layout(profile, numColumns);
|
||||
layout.addFields(layoutFieldsVisible.getFields());
|
||||
layout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(layout);
|
||||
if (!recordingLayoutFieldsVisible.getFields().isEmpty() || !recordingLayoutFieldsHidden.getFields().isEmpty()) {
|
||||
RecordingLayout recordingLayout = new RecordingLayout(profile, numColumns);
|
||||
recordingLayout.addFields(recordingLayoutFieldsVisible.getFields());
|
||||
recordingLayout.addFields(recordingLayoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(recordingLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
layoutFieldsVisible = null;
|
||||
recordingLayoutFieldsVisible = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,24 +132,24 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
if (which == 0) {
|
||||
field.togglePrimary();
|
||||
} else {
|
||||
layoutFieldsVisible.removeField(field);
|
||||
recordingLayoutFieldsVisible.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsHidden.addField(field);
|
||||
recordingLayoutFieldsHidden.addField(field);
|
||||
}
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
layoutFieldsHidden.removeField(field);
|
||||
recordingLayoutFieldsHidden.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsVisible.addField(field);
|
||||
recordingLayoutFieldsVisible.addField(field);
|
||||
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||
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.R;
|
||||
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.util.RecyclerViewSwipeDeleteCallback;
|
||||
|
||||
@@ -96,7 +96,7 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final Layout item = adapter.getLayouts().get(position);
|
||||
final RecordingLayout item = adapter.getLayouts().get(position);
|
||||
|
||||
adapter.removeLayout(position);
|
||||
|
||||
@@ -150,9 +150,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout) {
|
||||
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, recordingLayout);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import java.util.Objects;
|
||||
|
||||
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 title;
|
||||
private boolean isVisible;
|
||||
private boolean isPrimary;
|
||||
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.title = title;
|
||||
this.isVisible = isVisible;
|
||||
this.isPrimary = isPrimary;
|
||||
this.isWide = isWide;
|
||||
@@ -27,7 +21,6 @@ public class DataField implements Parcelable {
|
||||
|
||||
protected DataField(Parcel in) {
|
||||
key = in.readString();
|
||||
title = in.readString();
|
||||
isVisible = in.readByte() != 0;
|
||||
isPrimary = in.readByte() != 0;
|
||||
isWide = in.readByte() != 0;
|
||||
@@ -49,10 +42,6 @@ public class DataField implements Parcelable {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
@@ -73,65 +62,6 @@ public class DataField implements Parcelable {
|
||||
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
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -140,9 +70,21 @@ public class DataField implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(key);
|
||||
parcel.writeString(title);
|
||||
parcel.writeByte((byte) (isVisible ? 1 : 0));
|
||||
parcel.writeByte((byte) (isPrimary ? 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);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-41
@@ -1,78 +1,50 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class Layout implements Parcelable {
|
||||
private static final String TAG = Layout.class.getSimpleName();
|
||||
public class RecordingLayout implements Parcelable {
|
||||
|
||||
// User-generated layout's name.
|
||||
private final String name;
|
||||
private int columnsPerRow;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public static Layout 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 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) {
|
||||
public RecordingLayout(String name) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
||||
}
|
||||
|
||||
public Layout(String name, int columnsPerRow) {
|
||||
public RecordingLayout(String name, int columnsPerRow) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
protected Layout(Parcel in) {
|
||||
protected RecordingLayout(Parcel in) {
|
||||
name = in.readString();
|
||||
columnsPerRow = in.readInt();
|
||||
in.readList(dataFields, DataField.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Layout> CREATOR = new Creator<>() {
|
||||
public static final Creator<RecordingLayout> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public Layout createFromParcel(Parcel in) {
|
||||
return new Layout(in);
|
||||
public RecordingLayout createFromParcel(Parcel in) {
|
||||
return new RecordingLayout(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout[] newArray(int size) {
|
||||
return new Layout[size];
|
||||
public RecordingLayout[] newArray(int 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) {
|
||||
dataFields.add(dataField);
|
||||
}
|
||||
@@ -111,8 +83,8 @@ public class Layout implements Parcelable {
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
public boolean sameName(Layout layout) {
|
||||
return this.name.equalsIgnoreCase(layout.getName());
|
||||
public boolean sameName(RecordingLayout recordingLayout) {
|
||||
return this.name.equalsIgnoreCase(recordingLayout.getName());
|
||||
}
|
||||
|
||||
public boolean sameName(String name) {
|
||||
@@ -126,7 +98,7 @@ public class Layout implements Parcelable {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -141,4 +113,17 @@ public class Layout implements Parcelable {
|
||||
parcel.writeInt(columnsPerRow);
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
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.concurrent.Callable;
|
||||
|
||||
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> {
|
||||
|
||||
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 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.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
this.recordingLayout = recordingLayout;
|
||||
|
||||
mapping = Mapping.create(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
||||
CustomStatsItemBinding binding = CustomStatsItemBinding.inflate(LayoutInflater.from(context), parent, false);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
DataField field = recordingLayout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
|
||||
try {
|
||||
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.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue);
|
||||
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||
viewHolder.viewBinding.statsLayout.removeAllViews(); //TODO this is not really performant
|
||||
viewHolder.viewBinding.statsLayout.addView(m.getView());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't to instantiate UI for DataField with key " + field.getKey() + " " + e.getMessage());
|
||||
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
|
||||
public int getItemCount() {
|
||||
if (layout == null) {
|
||||
if (recordingLayout == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return layout.getFields().size();
|
||||
return recordingLayout.getFields().size();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return layout.getFields().get(position).isWide();
|
||||
return recordingLayout.getFields().get(position).isWide();
|
||||
}
|
||||
|
||||
public DataField getItem(int position) {
|
||||
return layout.getFields().get(position);
|
||||
return recordingLayout.getFields().get(position);
|
||||
}
|
||||
|
||||
public void swapValues(Layout data) {
|
||||
this.layout = data;
|
||||
if (this.layout != null) {
|
||||
public void swapValues(RecordingLayout data) {
|
||||
this.recordingLayout = data;
|
||||
if (this.recordingLayout != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Layout move(int fromPosition, int toPosition) {
|
||||
layout.moveField(fromPosition, toPosition);
|
||||
public RecordingLayout move(int fromPosition, int toPosition) {
|
||||
recordingLayout.moveField(fromPosition, toPosition);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
return layout;
|
||||
return recordingLayout;
|
||||
}
|
||||
|
||||
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) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.stats_description_main);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
|
||||
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
|
||||
itemView.setOnClickListener(this);
|
||||
CustomStatsItemBinding viewBinding;
|
||||
|
||||
public ViewHolder(@NonNull CustomStatsItemBinding viewBinding) {
|
||||
super(viewBinding.getRoot());
|
||||
this.viewBinding = viewBinding;
|
||||
|
||||
viewBinding.getRoot().setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -17,14 +17,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<Layout> layoutList;
|
||||
private List<RecordingLayout> recordingLayoutList;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutProfileClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -37,37 +37,37 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
|
||||
Layout layout = layoutList.get(position);
|
||||
viewHolder.itemView.setTag(layout.getName());
|
||||
viewHolder.title.setText(layout.getName());
|
||||
RecordingLayout recordingLayout = recordingLayoutList.get(position);
|
||||
viewHolder.itemView.setTag(recordingLayout.getName());
|
||||
viewHolder.title.setText(recordingLayout.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layoutList == null) {
|
||||
if (recordingLayoutList == null) {
|
||||
return 0;
|
||||
}
|
||||
return layoutList.size();
|
||||
return recordingLayoutList.size();
|
||||
}
|
||||
|
||||
public List<Layout> getLayouts() {
|
||||
return layoutList;
|
||||
public List<RecordingLayout> getLayouts() {
|
||||
return recordingLayoutList;
|
||||
}
|
||||
|
||||
public void reloadLayouts() {
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeLayout(int position) {
|
||||
layoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
recordingLayoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void restoreItem(Layout layout, int position) {
|
||||
layoutList.add(position, layout);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
public void restoreItem(RecordingLayout recordingLayout, int position) {
|
||||
recordingLayoutList.add(position, recordingLayout);
|
||||
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutProfileClickListener {
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
OPTION_20(20),
|
||||
OPTION_50(50);
|
||||
|
||||
static IntervalOption DEFAULT = OPTION_1;
|
||||
static final IntervalOption DEFAULT = OPTION_1;
|
||||
|
||||
private final double multiplier;
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
|
||||
public class StatisticsUtils {
|
||||
|
||||
@Deprecated
|
||||
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_fastest_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_clock_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_time);
|
||||
@@ -24,9 +25,10 @@ public class StatisticsUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getName());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
@Deprecated //Add to Layout?
|
||||
public static RecordingLayout filterVisible(RecordingLayout recordingLayout, boolean visible) {
|
||||
RecordingLayout result = new RecordingLayout(recordingLayout.getName());
|
||||
result.addFields(recordingLayout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
|
||||
/**
|
||||
* Utilities for track icon.
|
||||
@@ -35,218 +35,71 @@ import de.dennisguse.opentracks.R;
|
||||
*/
|
||||
public class TrackIconUtils {
|
||||
|
||||
private static final String AIRPLANE = "AIRPLANE";
|
||||
private static final String BIKE = "BIKE";
|
||||
private static final String MOUNTAIN_BIKE = "MOUNTAIN_BIKE";
|
||||
private static final String MOTOR_BIKE = "MOTOR_BIKE";
|
||||
private static final String KAYAK = "KAYAK";
|
||||
private static final String BOAT = "BOAT";
|
||||
private static final String SAILING = "SAILING";
|
||||
private static final String DRIVE = "DRIVE";
|
||||
private static final String RUN = "RUN";
|
||||
private static final String SKI = "SKI";
|
||||
private static final String SNOW_BOARDING = "SNOW_BOARDING";
|
||||
private static final String UNKNOWN = "UNKNOWN";
|
||||
private static final String WALK = "WALK";
|
||||
private static final String ESCOOTER = "ESCOOTER";
|
||||
private static final String INLINE_SKATING = "INLINES_SKATING";
|
||||
private static final String SKATE_BOARDING = "SKATE_BOARDING";
|
||||
private static final String CLIMBING = "CLIMBING";
|
||||
private static final String SWIMMING = "SWIMMING";
|
||||
private static final String SWIMMING_OPEN = "SWIMMING_OPEN";
|
||||
private static final String WORKOUT = "WORKOUT";
|
||||
public static int getIconDrawable(String activityTypeId) {
|
||||
Optional<ActivityType> found = Arrays.stream(ActivityType.values()).filter(
|
||||
it -> it.getId().equals(activityTypeId)
|
||||
).findFirst();
|
||||
|
||||
private static final int ACTIVITY_UNKNOWN_LOGO = R.drawable.ic_logo_24dp;
|
||||
|
||||
private static final int[] AIRPLANE_LIST = new int[]{R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane};
|
||||
private static final int[] BIKE_LIST = new int[]{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};
|
||||
private static final int[] BOAT_LIST = new int[]{R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat};
|
||||
private static final int[] CLIMBING_LIST = new int[]{R.string.activity_type_climbing};
|
||||
private static final int[] DRIVE_LIST = new int[]{R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car};
|
||||
private static final int[] MOTOR_BIKE_LIST = new int[]{R.string.activity_type_motor_bike};
|
||||
private static final int[] MOUNTAIN_BIKE_LIST = new int[]{R.string.activity_type_mountain_biking};
|
||||
private static final int[] ESCOOTER_LIST = new int[]{R.string.activity_type_escooter};
|
||||
private static final int[] INLINE_SKATING_LIST = new int[]{R.string.activity_type_inline_skating};
|
||||
private static final int[] RUN_LIST = new int[]{R.string.activity_type_running, R.string.activity_type_street_running, R.string.activity_type_track_running, R.string.activity_type_trail_running};
|
||||
private static final int[] SAILING_LIST = new int[]{R.string.activity_type_sailing};
|
||||
private static final int[] SKI_LIST = new int[]{R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing};
|
||||
private static final int[] SNOW_BOARDING_LIST = new int[]{R.string.activity_type_snow_boarding};
|
||||
private static final int[] SKATE_BOARDING_LIST = new int[]{R.string.activity_type_skate_boarding};
|
||||
private static final int[] KAYAKING_LIST = new int[]{R.string.activity_type_kayaking};
|
||||
private static final int[] WALK_LIST = new int[]{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};
|
||||
private static final int[] SWIM_LIST = new int[]{R.string.activity_type_swimming, R.string.activity_type_swimming_open};
|
||||
private static final int[] WORKOUT_LIST = new int[]{R.string.activity_type_workout};
|
||||
|
||||
// List of icons whose sports associated use speed (in km/h or mi/h).
|
||||
private static final int[] SPEED_ICON = {
|
||||
// Unknown.
|
||||
R.string.activity_type_unknown,
|
||||
// All airplane categories.
|
||||
R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane,
|
||||
// All bike categories.
|
||||
R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_motor_bike, R.string.activity_type_mountain_biking, R.string.activity_type_road_biking, R.string.activity_type_track_cycling, R.string.activity_type_inline_skating,
|
||||
// All boat categories.
|
||||
R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat, R.string.activity_type_sailing, R.string.activity_type_kayaking,
|
||||
// All drive categories.
|
||||
R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car, R.string.activity_type_escooter, R.string.activity_type_skate_boarding,
|
||||
// All wintersport categories
|
||||
R.string.activity_type_skiing, R.string.activity_type_snow_boarding
|
||||
|
||||
};
|
||||
|
||||
private static final LinkedHashMap<String, Pair<Integer, Integer>> MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
//Reflects order in ChooseActivityTypeDialogFragmentActivity
|
||||
MAP.put(UNKNOWN, new Pair<>(R.string.activity_type_unknown, ACTIVITY_UNKNOWN_LOGO));
|
||||
MAP.put(RUN, new Pair<>(R.string.activity_type_running, R.drawable.ic_activity_run_24dp));
|
||||
MAP.put(WALK, new Pair<>(R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp));
|
||||
MAP.put(CLIMBING, new Pair<>(R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp));
|
||||
MAP.put(SKATE_BOARDING, new Pair<>(R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp));
|
||||
MAP.put(INLINE_SKATING, new Pair<>(R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp));
|
||||
MAP.put(SNOW_BOARDING, new Pair<>(R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp));
|
||||
MAP.put(SKI, new Pair<>(R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp));
|
||||
MAP.put(ESCOOTER, new Pair<>(R.string.activity_type_escooter, R.drawable.ic_activity_escooter_24dp));
|
||||
MAP.put(BIKE, new Pair<>(R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp));
|
||||
MAP.put(MOUNTAIN_BIKE, new Pair<>(R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp));
|
||||
MAP.put(MOTOR_BIKE, new Pair<>(R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp));
|
||||
MAP.put(DRIVE, new Pair<>(R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp));
|
||||
MAP.put(AIRPLANE, new Pair<>(R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp));
|
||||
MAP.put(KAYAK, new Pair<>(R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp));
|
||||
MAP.put(BOAT, new Pair<>(R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp));
|
||||
MAP.put(SAILING, new Pair<>(R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp));
|
||||
MAP.put(SWIMMING, new Pair<>(R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp));
|
||||
MAP.put(SWIMMING_OPEN, new Pair<>(R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp));
|
||||
MAP.put(WORKOUT, new Pair<>(R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp));
|
||||
}
|
||||
|
||||
private TrackIconUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon drawable.
|
||||
*
|
||||
* @param iconValue the icon value
|
||||
*/
|
||||
public static int getIconDrawable(String iconValue) {
|
||||
if (iconValue == null || iconValue.equals("")) {
|
||||
return ACTIVITY_UNKNOWN_LOGO;
|
||||
if (found.isEmpty()) {
|
||||
return ActivityType.UNKNOWN.getIconId();
|
||||
}
|
||||
Pair<Integer, Integer> pair = MAP.get(iconValue);
|
||||
return pair == null ? ACTIVITY_UNKNOWN_LOGO : pair.second;
|
||||
|
||||
return found.get().getIconId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon activity type.
|
||||
*
|
||||
* @param iconValue the icon value
|
||||
*/
|
||||
public static int getIconActivityType(String iconValue) {
|
||||
if (iconValue == null || iconValue.equals("")) {
|
||||
return R.string.activity_type_unknown;
|
||||
public static int getIconActivityType(String activityTypeId) {
|
||||
Optional<ActivityType> found = Arrays.stream(ActivityType.values()).filter(
|
||||
it -> it.getId().equals(activityTypeId)
|
||||
).findFirst();
|
||||
|
||||
if (found.isEmpty()) {
|
||||
return ActivityType.UNKNOWN.getFirstLocalizedStringId();
|
||||
}
|
||||
Pair<Integer, Integer> pair = MAP.get(iconValue);
|
||||
return pair == null ? R.string.activity_type_unknown : pair.first;
|
||||
|
||||
return found.get().getFirstLocalizedStringId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all icon values.
|
||||
*/
|
||||
public static List<String> getAllIconValues() {
|
||||
return new ArrayList<>(MAP.keySet());
|
||||
return Arrays.stream(ActivityType.values())
|
||||
.map(ActivityType::getId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param activityType the activity type
|
||||
* @param context the context
|
||||
* @param activityTypeId the activity type
|
||||
*/
|
||||
@NonNull
|
||||
public static String getIconValue(Context context, String activityType) {
|
||||
Resources resources = context.getResources();
|
||||
if (activityType == null || activityType.equals("")) {
|
||||
return UNKNOWN;
|
||||
public static String getIconValue(Context context, String activityTypeId) {
|
||||
Optional<ActivityType> selected = Arrays.stream(ActivityType.values())
|
||||
.filter(
|
||||
it -> Arrays.stream(it.getLocalizedStringIds())
|
||||
.anyMatch(id -> context.getString(id).equals(activityTypeId))
|
||||
)
|
||||
.findFirst();
|
||||
if (selected.isEmpty()) {
|
||||
return ActivityType.UNKNOWN.getId();
|
||||
}
|
||||
if (inList(resources, activityType, AIRPLANE_LIST)) {
|
||||
return AIRPLANE;
|
||||
}
|
||||
if (inList(resources, activityType, BIKE_LIST)) {
|
||||
return BIKE;
|
||||
}
|
||||
if (inList(resources, activityType, MOUNTAIN_BIKE_LIST)) {
|
||||
return MOUNTAIN_BIKE;
|
||||
}
|
||||
if (inList(resources, activityType, CLIMBING_LIST)) {
|
||||
return CLIMBING;
|
||||
}
|
||||
if (inList(resources, activityType, MOTOR_BIKE_LIST)) {
|
||||
return MOTOR_BIKE;
|
||||
}
|
||||
if (inList(resources, activityType, KAYAKING_LIST)) {
|
||||
return KAYAK;
|
||||
}
|
||||
if (inList(resources, activityType, BOAT_LIST)) {
|
||||
return BOAT;
|
||||
}
|
||||
if (inList(resources, activityType, SAILING_LIST)) {
|
||||
return SAILING;
|
||||
}
|
||||
if (inList(resources, activityType, DRIVE_LIST)) {
|
||||
return DRIVE;
|
||||
}
|
||||
if (inList(resources, activityType, INLINE_SKATING_LIST)) {
|
||||
return INLINE_SKATING;
|
||||
}
|
||||
if (inList(resources, activityType, ESCOOTER_LIST)) {
|
||||
return ESCOOTER;
|
||||
}
|
||||
if (inList(resources, activityType, RUN_LIST)) {
|
||||
return RUN;
|
||||
}
|
||||
if (inList(resources, activityType, SKI_LIST)) {
|
||||
return SKI;
|
||||
}
|
||||
if (inList(resources, activityType, SNOW_BOARDING_LIST)) {
|
||||
return SNOW_BOARDING;
|
||||
}
|
||||
if (inList(resources, activityType, SKATE_BOARDING_LIST)) {
|
||||
return SKATE_BOARDING;
|
||||
}
|
||||
if (inList(resources, activityType, WALK_LIST)) {
|
||||
return WALK;
|
||||
}
|
||||
if (inList(resources, activityType, SWIM_LIST)) {
|
||||
return SWIMMING;
|
||||
}
|
||||
if (inList(resources, activityType, WORKOUT_LIST)) {
|
||||
return WORKOUT;
|
||||
}
|
||||
return UNKNOWN;
|
||||
return selected.get().getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the activity type is in the list.
|
||||
*
|
||||
* @param context the context
|
||||
* @param activityType the activity type
|
||||
* @param list the list
|
||||
*/
|
||||
private static boolean inList(Resources context, String activityType, int[] list) {
|
||||
for (int i : list) {
|
||||
if (context.getString(i).equals(activityType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static boolean isSpeedIcon(Resources resources, String activityTypeId) {
|
||||
Optional<ActivityType> selected = Arrays.stream(ActivityType.values())
|
||||
.filter(
|
||||
it -> Arrays.stream(it.getLocalizedStringIds())
|
||||
.anyMatch(id -> resources.getString(id).equals(activityTypeId))
|
||||
)
|
||||
.findFirst();
|
||||
|
||||
/**
|
||||
* Returns true if category is in the SPEED_ICON array. Otherwise returns false.
|
||||
*
|
||||
* @param category the name of the category, activity type.
|
||||
*/
|
||||
public static boolean isSpeedIcon(Resources resources, String category) {
|
||||
return inList(resources, category, SPEED_ICON);
|
||||
if (selected.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selected.get().isShowSpeedPreferred();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.databinding.StatsClockItemBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public class ClockViewHolder extends StatisticViewHolder<StatsClockItemBinding> {
|
||||
|
||||
@Override
|
||||
protected StatsClockItemBinding createViewBinding(LayoutInflater inflater) {
|
||||
return StatsClockItemBinding.inflate(inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureUI(DataField dataField) {
|
||||
//TODO Unify with GenericStatisticsViewHolder?
|
||||
getBinding().statsClock.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<StatsGenericItemBinding> {
|
||||
|
||||
@Override
|
||||
protected StatsGenericItemBinding createViewBinding(LayoutInflater inflater) {
|
||||
return StatsGenericItemBinding.inflate(inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureUI(DataField dataField) {
|
||||
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
||||
}
|
||||
|
||||
public static class Distance extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext()).getDistanceParts(data.getTrackStatistics().getTotalDistance());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_distance));
|
||||
}
|
||||
}
|
||||
|
||||
public static class TotalTime extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getTrackStatistics().getTotalTime()), null);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_total_time));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MovingTime extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
String value = StringUtils.formatElapsedTime(data.getTrackStatistics().getMovingTime());
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_moving_time));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpeedOrPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
// TODO Pace wont work for now
|
||||
boolean reportSpeed = true;
|
||||
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(reportSpeed).build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
final TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
|
||||
getBinding().statsDescriptionMain.setText(sensorDataSet.getSpeed().second);
|
||||
} else {
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MaxSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_max_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageMovingPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AveragePace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FastestPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_fastest_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Altitude extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
|
||||
String altitudeReference = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? getContext().getString(latestTrackPoint.getAltitude().getLabelId()) : null;
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_altitude);
|
||||
getBinding().statsDescriptionSecondary.setText(altitudeReference);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Gain extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeGain(), unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_gain);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Loss extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeLoss(), unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_loss);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Coordinates extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
String value;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude());
|
||||
} else {
|
||||
value = getContext().getString(R.string.value_unknown);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorHeartRate extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
//TODO Loads preference every time
|
||||
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
|
||||
int textColor;
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
|
||||
sensorName = sensorDataSet.getHeartRate().second;
|
||||
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
|
||||
textColor = zones.getTextColorForZone(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
|
||||
getBinding().statsValue.setTextColor(textColor);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorCadence extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
|
||||
sensorName = sensorDataSet.getCadence().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorPower extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
|
||||
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
|
||||
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class Mapping {
|
||||
|
||||
public static Map<String, Callable<StatisticViewHolder<?>>> create(Context context) {
|
||||
HashMap<String, Callable<StatisticViewHolder<?>>> m = new HashMap<>();
|
||||
m.put(context.getString(R.string.stats_custom_layout_total_time_key), GenericStatisticsViewHolder.TotalTime::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_moving_time_key), GenericStatisticsViewHolder.MovingTime::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_speed_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_moving_speed_key), GenericStatisticsViewHolder.AverageMovingSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_speed_key), GenericStatisticsViewHolder.AverageSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_moving_pace_key), GenericStatisticsViewHolder.AverageMovingPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_pace_key), GenericStatisticsViewHolder.AveragePace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_fastest_pace_key), GenericStatisticsViewHolder.FastestPace::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_altitude_key), GenericStatisticsViewHolder.Altitude::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_gain_key), GenericStatisticsViewHolder.Gain::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), GenericStatisticsViewHolder.SensorHeartRate::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_cadence_key), GenericStatisticsViewHolder.SensorCadence::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_power_key), GenericStatisticsViewHolder.SensorPower::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public class StatisticData {
|
||||
private final DataField dataField;
|
||||
private final String value;
|
||||
private final String unit;
|
||||
private final String description;
|
||||
|
||||
public StatisticData(@NonNull DataField dataField, @NonNull Pair<String, String> valueAndUnit, String description) {
|
||||
this.dataField = dataField;
|
||||
this.value = valueAndUnit.first;
|
||||
this.unit = valueAndUnit.second;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public DataField getField() {
|
||||
return dataField;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean hasDescription() {
|
||||
return description != null;
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
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.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class StatisticDataBuilder {
|
||||
|
||||
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull Layout layout, UnitSystem unitSystem) {
|
||||
List<StatisticData> statisticDataList = layout.getFields().stream()
|
||||
.filter(DataField::isVisible)
|
||||
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), unitSystem))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, unitSystem));
|
||||
return statisticDataList;
|
||||
}
|
||||
|
||||
private static StatisticData build(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, UnitSystem unitSystem) {
|
||||
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
|
||||
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
final TrackStatistics trackStatistics = recordingData.getTrackStatistics();
|
||||
|
||||
String title = null;
|
||||
String description = null;
|
||||
Pair<String, String> valueAndUnit = null;
|
||||
boolean isWide = false;
|
||||
|
||||
final String sensorUnknown = context.getString(R.string.value_unknown);
|
||||
|
||||
SpeedFormatter speedFormatterPace = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(false).build(context);
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context);
|
||||
|
||||
|
||||
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()), null);
|
||||
title = context.getString(R.string.stats_total_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()), null);
|
||||
title = context.getString(R.string.stats_moving_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
valueAndUnit = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(context).getDistanceParts(trackStatistics.getTotalDistance());
|
||||
title = context.getString(R.string.stats_distance);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
boolean reportSpeed = fieldKey.equals("speed");
|
||||
title = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
|
||||
|
||||
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
||||
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
|
||||
description = sensorDataSet.getSpeed().second;
|
||||
} else {
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
|
||||
description = context.getString(R.string.description_speed_source_gps);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageMovingSpeed());
|
||||
title = context.getString(R.string.stats_average_moving_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageSpeed());
|
||||
title = context.getString(R.string.stats_average_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getMaxSpeed());
|
||||
title = context.getString(R.string.stats_max_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageMovingSpeed());
|
||||
title = context.getString(R.string.stats_average_moving_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageSpeed());
|
||||
title = context.getString(R.string.stats_average_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getMaxSpeed());
|
||||
title = context.getString(R.string.stats_fastest_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
|
||||
title = context.getString(R.string.stats_altitude);
|
||||
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, unitSystem);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeGain(), unitSystem);
|
||||
title = context.getString(R.string.stats_gain);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeLoss(), unitSystem);
|
||||
title = context.getString(R.string.stats_loss);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
title = context.getString(R.string.stats_coordinates);
|
||||
isWide = true;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
|
||||
} else {
|
||||
valueAndUnit = new Pair<>(context.getString(R.string.value_unknown), null);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
title = context.getString(R.string.stats_sensors_heart_rate);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().first);
|
||||
description = sensorDataSet.getHeartRate().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, null);
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
title = context.getString(R.string.stats_sensors_cadence);
|
||||
|
||||
Cadence cadence = null;
|
||||
if (sensorDataSet != null) {
|
||||
Pair<Cadence, String> cadenceData = sensorDataSet.getCadence();
|
||||
if (cadenceData != null) {
|
||||
cadence = cadenceData.first;
|
||||
description = cadenceData.second;
|
||||
}
|
||||
}
|
||||
|
||||
valueAndUnit = StringUtils.getCadenceParts(context, cadence);
|
||||
if (description == null) {
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
|
||||
title = context.getString(R.string.stats_sensors_power);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
|
||||
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, null);
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
|
||||
title = context.getString(R.string.stats_clock);
|
||||
valueAndUnit = new Pair<>(null, null);
|
||||
}
|
||||
|
||||
if (valueAndUnit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of StatisticData with sensors configured but not in statisticDataList.
|
||||
*/
|
||||
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, RecordingData recordingData, List<StatisticData> statisticDataList, UnitSystem unitSystem) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return sensorDataList;
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null) {
|
||||
sensorDataList.add(build(context, recordingData, "heart_rate", true, unitSystem));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCadence() != null) {
|
||||
sensorDataList.add(build(context, recordingData, "cadence", true, unitSystem));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
|
||||
sensorDataList.add(build(context, recordingData, "power", true, unitSystem));
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
|
||||
public static List<StatisticData> fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_max), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getMaxHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_avg), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getAvgHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.sensor_state_cadence_max), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getMaxCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_cadence_key), context.getString(R.string.sensor_state_cadence_avg), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getAvgCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.sensor_state_power_avg), true, true, false),
|
||||
StringUtils.getPowerParts(context, sensorStatistics.getAvgPower()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public abstract class StatisticViewHolder<T extends ViewBinding> {
|
||||
|
||||
private Context context;
|
||||
|
||||
private T binding;
|
||||
|
||||
public void initialize(Context context, LayoutInflater inflater) {
|
||||
this.context = context;
|
||||
this.binding = createViewBinding(inflater);
|
||||
}
|
||||
|
||||
protected abstract T createViewBinding(LayoutInflater inflater);
|
||||
|
||||
public abstract void configureUI(DataField dataField);
|
||||
|
||||
public abstract void onChanged(UnitSystem unitSystem, RecordingData data);
|
||||
|
||||
public View getView() {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
T getBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
|
||||
public class StatisticsDataModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<List<StatisticData>> statsData;
|
||||
|
||||
public StatisticsDataModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<StatisticData>> getStatsData() {
|
||||
if (statsData == null) {
|
||||
statsData = new MutableLiveData<>();
|
||||
}
|
||||
return statsData;
|
||||
}
|
||||
|
||||
public void update(RecordingData recordingData, Layout layout, UnitSystem unitSystem) {
|
||||
new Thread(() -> {
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, unitSystem);
|
||||
statsData.postValue(statisticDataList);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
||||
@@ -120,6 +120,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user