Merge pull request #76 from TacoTheDank/master
Code cleanup, updates, and typo fixes
This commit is contained in:
commit
e200aec770
99 changed files with 1667 additions and 955 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
10
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
10
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name: "Validate Gradle Wrapper"
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validation:
|
||||||
|
name: "Validation"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
|
@ -2,7 +2,7 @@ AUTHORS
|
||||||
=======
|
=======
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
Trackbook is designed, developed and maintained by: [y20k](https://github.com/y20k)
|
Trackbook is designed, developed, and maintained by: [y20k](https://github.com/y20k)
|
||||||
|
|
||||||
### Translations
|
### Translations
|
||||||
Chinese version: [yzqzss](https://github.com/yzqzss) | [weblate version history](https://hosted.weblate.org/changes/?lang=zh_HANS-CN&project=trackbook)
|
Chinese version: [yzqzss](https://github.com/yzqzss) | [weblate version history](https://hosted.weblate.org/changes/?lang=zh_HANS-CN&project=trackbook)
|
||||||
|
|
|
@ -2,18 +2,20 @@ How to contribute to Trackbook
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
### Report a bug or suggest a new feature
|
### Report a bug or suggest a new feature
|
||||||
Bugs and new features are being discussed on the GitHub [Issue Tracker](https://github.com/y20k/trackbook/issues). The issue "[Previous discussions and feature requests](https://github.com/y20k/trackbook/issues/57)" lists some of the features that were rejected - either because they did not fit conceptually or because I could not figure out how to implement them.
|
Bugs and new features are being discussed on the GitHub [Issue Tracker](https://github.com/y20k/trackbook/issues).
|
||||||
|
The issue "[Previous discussions and feature requests](https://github.com/y20k/trackbook/issues/57)" lists some of the features that were rejected - either because they did not fit conceptually or because I could not figure out how to implement them.
|
||||||
|
|
||||||
### Help with translations
|
### Help with translations
|
||||||
The translations are managed on [Weblate](https://hosted.weblate.org/projects/trackbook/strings/). Help is much appreciated.
|
The translations are managed on [Weblate](https://hosted.weblate.org/projects/trackbook/strings/).
|
||||||
|
Help is much appreciated.
|
||||||
|
|
||||||
### Submit your own solutions
|
### Submit your own solutions
|
||||||
Help is very welcome. Be it in the form of code, or artwork, or enhancements to the website, or tutorial videos, or whatever.
|
Help is very welcome, be it in the form of code, artwork, enhancements to the website, tutorial videos, or whatever.
|
||||||
**But please** suggest new features or enhancements in advance on the [Issue Tracker](https://github.com/y20k/trackbook/issues) before implementing them.
|
**But please** suggest new features or enhancements in advance on the [Issue Tracker](https://github.com/y20k/trackbook/issues) before implementing them.
|
||||||
|
|
||||||
### Suggested issues to tackle
|
### Suggested issues to tackle
|
||||||
[#19](https://github.com/y20k/trackbook/issues/19)
|
[#19](https://github.com/y20k/trackbook/issues/19)
|
||||||
|
|
||||||
### Credit for your contributions
|
### Credit for your contributions
|
||||||
Contributors - like the main translators for a certain language - are listed as co-authors of this project in [AUTHORS.md](https://github.com/y20k/trackbook/blob/master/AUTHORS.md). Bonus: If you are on this list, you are automatically eligible for a free German beverage.
|
Contributors - like the main translators for a certain language - are listed as co-authors of this project in [AUTHORS.md](https://github.com/y20k/trackbook/blob/master/AUTHORS.md).
|
||||||
To be redeemed in Stuttgart.
|
Bonus: If you are on this list, you are automatically eligible for a free German beverage (to be redeemed in Stuttgart).
|
||||||
|
|
93
README.md
93
README.md
|
@ -1,57 +1,96 @@
|
||||||
README
|
|
||||||
======
|
|
||||||
|
|
||||||
# Trackbook - Android Movement Recorder
|
# Trackbook - Android Movement Recorder
|
||||||
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png" width="192" />
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png"
|
||||||
|
width="192" />
|
||||||
|
|
||||||
**Version 2.0.x ("Echoes")**
|
**Version 2.0.x ("Echoes")**
|
||||||
|
|
||||||
Trackbook is a bare bones app for recording your movements. Trackbook is great for hiking, vacation or workout. Once started it traces your movements on a map. The map data is provided by [OpenStreetMap (OSM)](https://www.openstreetmap.org/).
|
Trackbook is a bare-bones app for recording your movements.
|
||||||
|
Trackbook is great for hiking, vacationing, or working out.
|
||||||
|
Once started, it traces your movements on a map.
|
||||||
|
The map data is provided by [OpenStreetMap (OSM)](https://www.openstreetmap.org/).
|
||||||
|
|
||||||
|
Trackbook is free software.
|
||||||
|
It is published under the [MIT open-source license](https://opensource.org/licenses/MIT).
|
||||||
|
Trackbook uses [osmdroid](https://github.com/osmdroid/osmdroid) to display the map, which is also free software published under the [Apache License](https://github.com/osmdroid/osmdroid/blob/master/LICENSE).
|
||||||
|
Want to help? Please check out the notes in [CONTRIBUTE.md](https://github.com/y20k/trackbook/blob/master/CONTRIBUTE.md) first.
|
||||||
|
|
||||||
Trackbook is free software. It is published under the [MIT open source license](https://opensource.org/licenses/MIT). Trackbook uses [osmdroid](https://github.com/osmdroid/osmdroid) to display the map, which is also free software published under the [Apache License](https://github.com/osmdroid/osmdroid/blob/master/LICENSE). Want to help? Please check out the notes in [CONTRIBUTE.md](https://github.com/y20k/trackbook/blob/master/CONTRIBUTE.md) first.
|
|
||||||
|
|
||||||
## Install Trackbook
|
## Install Trackbook
|
||||||
You can install it via Google Play and F-Droid - or you can go and grab the latest APK on [GitHub](https://github.com/y20k/trackbook/releases).
|
You can install it via Google Play and F-Droid - or you can go and grab the latest APK on [GitHub](https://github.com/y20k/trackbook/releases).
|
||||||
|
|
||||||
[<img src="https://play.google.com/intl/de_de/badges/images/generic/en_badge_web_generic.png" width="192">](https://play.google.com/store/apps/details?id=org.y20k.trackbook)
|
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
|
||||||
|
width="192">](https://play.google.com/store/apps/details?id=org.y20k.trackbook)
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
width="192">](https://f-droid.org/packages/org.y20k.trackbook/)
|
||||||
|
|
||||||
[<img src="https://cloud.githubusercontent.com/assets/9103935/14702535/45f6326a-07ab-11e6-9256-469c1dd51c22.png" width="192">](https://f-droid.org/repository/browse/?fdid=org.y20k.trackbook)
|
|
||||||
|
|
||||||
## Good To Know
|
## Good To Know
|
||||||
|
|
||||||
### Start Recording via Quick Settings Tile
|
### Start Recording via Quick Settings Tile
|
||||||
[<img src="https://user-images.githubusercontent.com/9103935/74753187-09a75f00-5270-11ea-82de-18c5b8737e2b.png" width="320">](https://user-images.githubusercontent.com/9103935/74753187-09a75f00-5270-11ea-82de-18c5b8737e2b.png)<br />
|
<img src="https://user-images.githubusercontent.com/9103935/74753187-09a75f00-5270-11ea-82de-18c5b8737e2b.png"
|
||||||
You can start a recording without opening Trackbook. Just pull down the System's Quick Settings and tap on the Start Recording tile. You need to manually add Trackbook's Recording tile to Quick Settings first. You can find information on customizing Quick Settings [here](https://support.google.com/android/answer/9083864?hl=en) and [here](https://www.xda-developers.com/get-custom-quick-settings-tiles/)
|
width="320" />
|
||||||
|
<br/>
|
||||||
|
You can start a recording without opening Trackbook.
|
||||||
|
Just pull down the System's Quick Settings and tap on the "Start Recording" tile.
|
||||||
|
You'll need to manually add Trackbook's Recording tile to Quick Settings first.
|
||||||
|
You can find information on customizing Quick Settings [here](https://support.google.com/android/answer/9083864) and [here](https://www.xda-developers.com/get-custom-quick-settings-tiles/).
|
||||||
|
|
||||||
### Save Recordings as GPX
|
### Save Recordings as GPX
|
||||||
Recordings can be exported as GPX ([GPS Exchange Format](https://en.wikipedia.org/wiki/GPS_Exchange_Format)). Tap on the save button in the lower right corner of a previously recorded track.
|
Recordings can be exported as GPX ([GPS Exchange Format](https://en.wikipedia.org/wiki/GPS_Exchange_Format)).
|
||||||
|
Tap on the save button in the lower-right corner of a previously recorded track.
|
||||||
|
|
||||||
### Copy GPX Files Manually
|
### Copy GPX Files Manually
|
||||||
Trackbook automatically generates GPX files for every recording. You can find them in the folder `/Android/data/org.y20k.trackbook/files/gpx/` on your device's storage.
|
Trackbook automatically generates GPX files for every recording.
|
||||||
|
You can find them in the folder `/Android/data/org.y20k.trackbook/files/gpx/` on your device's storage.
|
||||||
|
|
||||||
### How Does Trackbook Measure Distance?
|
### How does Trackbook measure distance?
|
||||||
Trackbook calculates the distance between markers and adds them up.
|
Trackbook calculates the distance between markers and adds them up.
|
||||||
|
|
||||||
### How Does Trackbook Measure Altitude?
|
### How does Trackbook measure altitude?
|
||||||
Many devices have altitude sensors (of varying accuracy). Trackbook compares the altitude of each new marker with the previously stored altitude. The difference is added to either the uphill or downhill elevation value.
|
Many devices have altitude sensors (of varying accuracy).
|
||||||
|
Trackbook compares the altitude of each new marker with the previously stored altitude.
|
||||||
|
The difference is added to either the uphill or downhill elevation value.
|
||||||
|
|
||||||
### What Does Accuracy Threshold Mean?
|
### What does "accuracy threshold" mean?
|
||||||
Every location fix, that Trackbook receives, is associated with an accuracy estimate. You can look up, how Android defines accuracy, in the [developer documentation](https://developer.android.com/reference/kotlin/android/location/Location.html#getaccuracy). `Accuracy Threshold` is the value, from which on location fixes are rejected. It can be adjusted in Trackbook's settings. You can increase the value, if your recordings tend to be incomplete. Trackbook will then also record less accurate location fixes.
|
Every location fix that Trackbook receives is associated with an accuracy estimate.
|
||||||
|
You can look up how Android defines accuracy in the [developer documentation](https://developer.android.com/reference/kotlin/android/location/Location.html#getaccuracy).
|
||||||
|
`Accuracy Threshold` is the value from which location fixes are rejected.
|
||||||
|
It can be adjusted in Trackbook's settings.
|
||||||
|
You can increase the value if your recordings tend to be incomplete.
|
||||||
|
Trackbook will then also record less accurate location fixes.
|
||||||
|
|
||||||
## Where are my old recordings?
|
## Where are my old recordings?
|
||||||
The F-Droid version of Trackbook features an auto-importer for old recordings. Sadly I was not able to implement the auto-importer for the Play Store version of Trackbook due to SDK requirements / restrictions. That is partly my fault and I am very sorry. There is a (quite complicated) solution to get back your old recordings. Please head over to the [Wiki](https://github.com/y20k/trackbook/wiki) to find out how.
|
The F-Droid version of Trackbook features an auto-importer for old recordings.
|
||||||
|
Sadly I was not able to implement the auto-importer for the Play Store version of Trackbook due to SDK requirements / restrictions.
|
||||||
|
That is partly my fault and I am very sorry.
|
||||||
|
There is a (quite complicated) solution to get back your old recordings.
|
||||||
|
Please head over to the [Wiki](https://github.com/y20k/trackbook/wiki) to find out how.
|
||||||
|
|
||||||
## A Word on Privacy
|
## A word on privacy
|
||||||
Trackbook begins to store location data on device as soon a user presses the record button. Those recordings are stored in the directory `/Android/data/org.y20k.trackbook/files/`. They never leave the device. There is no web-service backing Trackbook.
|
Trackbook begins to store location data on a device as soon as a user presses the record button.
|
||||||
|
Those recordings are stored in the directory `/Android/data/org.y20k.trackbook/files/`.
|
||||||
|
They never leave the device.
|
||||||
|
There is no web-service backing Trackbook.
|
||||||
|
|
||||||
Trackbook does not use Google Play Services to get its location data. It will however try to use data from the [NETWORK_PROVIDER](https://developer.android.com/reference/android/location/LocationManager#NETWORK_PROVIDER) on your device to augment the location data it received via GPS. The NETWORK_PROVIDER is a system-wide service, that Trackbook has no control over. This service will usually query an online database for the location of cell towers or Wi-Fi access points a device can see. You can prevent those kinds of requests on your device, if you set the location preferences system-wide to `Device Only`. Additionally Trackbook offers a `Restrict to GPS` setting, that deactivates the NETWORK_PROVIDER just within the app.
|
Trackbook does not use Google Play Services to get its location data.
|
||||||
|
It will, however, try to use data from the [NETWORK_PROVIDER](https://developer.android.com/reference/android/location/LocationManager#NETWORK_PROVIDER) on your device to augment the location data it received via GPS.
|
||||||
|
The NETWORK_PROVIDER is a system-wide service that Trackbook has no control over.
|
||||||
|
This service will usually query an online database for the location of cell towers or Wi-Fi access points a device can see.
|
||||||
|
You can prevent those kinds of requests on your device if you set the location preferences system-wide to `Device Only`.
|
||||||
|
Additionally, Trackbook offers a `Restrict to GPS` setting that deactivates the NETWORK_PROVIDER just within the app.
|
||||||
|
|
||||||
## Screenshots (v2.0)
|
## Screenshots (v2.0)
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/01-map-recording-active.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/01-map-recording-active.png)
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/01-map-recording-active.png"
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/02-map-context-menu.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/02-map-context-menu.png)
|
width="240" />
|
||||||
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/02-map-context-menu.png"
|
||||||
|
width="240" />
|
||||||
|
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/03-track-list.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/03-track-list.png)
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/03-track-list.png"
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/04-track.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/04-track.png)
|
width="240" />
|
||||||
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/04-track.png"
|
||||||
|
width="240" />
|
||||||
|
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/05-settings.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/05-settings.png)
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/05-settings.png"
|
||||||
[<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/06-quick-settings-tile.png" width="240">](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/06-quick-settings-tile.png)
|
width="240" />
|
||||||
|
<img src="https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/06-quick-settings-tile.png"
|
||||||
|
width="240" />
|
||||||
|
|
|
@ -5,7 +5,7 @@ apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
// buildToolsVersion is optional because the plugin uses a recommended version by default
|
buildToolsVersion "29.0.3"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'org.y20k.trackbook'
|
applicationId 'org.y20k.trackbook'
|
||||||
|
@ -26,62 +26,45 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation', 'GoogleAppIndexingWarning'
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/*'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// Enables code shrinking, obfuscation, and optimization for only
|
|
||||||
// your project's release build type.
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
|
||||||
// Enables resource shrinking, which is performed by the
|
|
||||||
// Android Gradle plugin.
|
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
// Includes the default ProGuard rules files that are packaged with
|
|
||||||
// the Android Gradle plugin. To learn more, go to the section about
|
|
||||||
// R8 configuration files.
|
|
||||||
proguardFiles getDefaultProguardFile(
|
|
||||||
'proguard-android-optimize.txt'),
|
|
||||||
'proguard-rules.pro'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
// Comment out the below lines if you do not need to test resource shrinking
|
applicationIdSuffix ".debug"
|
||||||
// minifyEnabled true
|
|
||||||
// shrinkResources true
|
|
||||||
// proguardFiles getDefaultProguardFile(
|
|
||||||
// 'proguard-android-optimize.txt'),
|
|
||||||
// 'proguard-rules.pro'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
// Kotlin
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4"
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4"
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
|
||||||
|
|
||||||
|
// AndroidX
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation "androidx.core:core-ktx:1.3.1"
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.core:core-ktx:1.3.1'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||||
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
|
implementation 'com.google.android.material:material:1.2.0-rc01'
|
||||||
|
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
|
// Gson
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
|
||||||
|
// OpenStreetMap
|
||||||
implementation "com.google.android.material:material:1.2.0-beta01"
|
implementation 'org.osmdroid:osmdroid-android:6.1.8'
|
||||||
implementation "com.google.code.gson:gson:2.8.6"
|
|
||||||
|
|
||||||
implementation "org.osmdroid:osmdroid-android:6.1.8"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.y20k.trackbook">
|
package="org.y20k.trackbook">
|
||||||
|
|
||||||
<!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES -->
|
<!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES -->
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
|
<uses-feature
|
||||||
|
android:name="android.hardware.location.gps"
|
||||||
|
android:required="true" />
|
||||||
<uses-feature android:name="android.hardware.location.network" />
|
<uses-feature android:name="android.hardware.location.network" />
|
||||||
|
|
||||||
<!-- NORMAL PERMISSIONS, automatically granted -->
|
<!-- NORMAL PERMISSIONS, automatically granted -->
|
||||||
|
@ -24,8 +25,7 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme">
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
|
||||||
|
|
||||||
<!-- MAIN ACTIVITY -->
|
<!-- MAIN ACTIVITY -->
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
|
@ -38,8 +38,8 @@
|
||||||
<!-- TRACKER SERVICE -->
|
<!-- TRACKER SERVICE -->
|
||||||
<service
|
<service
|
||||||
android:name=".TrackerService"
|
android:name=".TrackerService"
|
||||||
android:foregroundServiceType="location"
|
android:exported="false"
|
||||||
android:exported="false">
|
android:foregroundServiceType="location">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="org.y20k.trackbook.action.START" />
|
<action android:name="org.y20k.trackbook.action.START" />
|
||||||
<action android:name="org.y20k.trackbook.action.STOP" />
|
<action android:name="org.y20k.trackbook.action.STOP" />
|
||||||
|
@ -50,8 +50,8 @@
|
||||||
<!-- TRACKING TOGGLE SERVICE SYSTEM QUICK SETTINGS -->
|
<!-- TRACKING TOGGLE SERVICE SYSTEM QUICK SETTINGS -->
|
||||||
<service
|
<service
|
||||||
android:name=".TrackingToggleTileService"
|
android:name=".TrackingToggleTileService"
|
||||||
android:label="@string/quick_settings_tile_title_default"
|
|
||||||
android:icon="@drawable/ic_notification_icon_small_24dp"
|
android:icon="@drawable/ic_notification_icon_small_24dp"
|
||||||
|
android:label="@string/quick_settings_tile_title_default"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
|
|
|
@ -44,7 +44,8 @@ object Keys {
|
||||||
const val ARG_TRACK_ID: String = "ArgTrackId"
|
const val ARG_TRACK_ID: String = "ArgTrackId"
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_38" // increment to current app version code to trigger housekeeping that runs only once
|
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY =
|
||||||
|
"ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_38" // increment to current app version code to trigger housekeeping that runs only once
|
||||||
const val PREF_THEME_SELECTION: String = "prefThemeSelection"
|
const val PREF_THEME_SELECTION: String = "prefThemeSelection"
|
||||||
const val PREF_CURRENT_BEST_LOCATION_PROVIDER: String = "prefCurrentBestLocationProvider"
|
const val PREF_CURRENT_BEST_LOCATION_PROVIDER: String = "prefCurrentBestLocationProvider"
|
||||||
const val PREF_CURRENT_BEST_LOCATION_LATITUDE: String = "prefCurrentBestLocationLatitude"
|
const val PREF_CURRENT_BEST_LOCATION_LATITUDE: String = "prefCurrentBestLocationLatitude"
|
||||||
|
@ -95,21 +96,29 @@ object Keys {
|
||||||
const val DEFAULT_RFC2822_DATE: String = "Thu, 01 Jan 1970 01:00:00 +0100" // --> Date(0)
|
const val DEFAULT_RFC2822_DATE: String = "Thu, 01 Jan 1970 01:00:00 +0100" // --> Date(0)
|
||||||
const val ONE_HOUR_IN_MILLISECONDS: Int = 3600000
|
const val ONE_HOUR_IN_MILLISECONDS: Int = 3600000
|
||||||
const val EMPTY_STRING_RESOURCE: Int = 0
|
const val EMPTY_STRING_RESOURCE: Int = 0
|
||||||
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1000L // 1 second in milliseconds
|
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long =
|
||||||
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 15000L // 15 seconds in milliseconds
|
1000L // 1 second in milliseconds
|
||||||
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 120000L // 2 minutes in milliseconds
|
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long =
|
||||||
const val STOP_OVER_THRESHOLD: Long = 300000L // 5 minutes in milliseconds
|
15000L // 15 seconds in milliseconds
|
||||||
|
const val SIGNIFICANT_TIME_DIFFERENCE: Long =
|
||||||
|
120000L // 2 minutes in milliseconds
|
||||||
|
const val STOP_OVER_THRESHOLD: Long =
|
||||||
|
300000L // 5 minutes in milliseconds
|
||||||
const val IMPLAUSIBLE_TRACK_START_SPEED: Double = 250.0 // 250 km/h
|
const val IMPLAUSIBLE_TRACK_START_SPEED: Double = 250.0 // 250 km/h
|
||||||
const val DEFAULT_LATITUDE: Double = 71.172500 // latitude Nordkapp, Norway
|
const val DEFAULT_LATITUDE: Double =
|
||||||
const val DEFAULT_LONGITUDE: Double = 25.784444 // longitude Nordkapp, Norway
|
71.172500 // latitude Nordkapp, Norway
|
||||||
|
const val DEFAULT_LONGITUDE: Double =
|
||||||
|
25.784444 // longitude Nordkapp, Norway
|
||||||
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
||||||
const val DEFAULT_ALTITUDE: Double = 0.0
|
const val DEFAULT_ALTITUDE: Double = 0.0
|
||||||
const val DEFAULT_TIME: Long = 0L
|
const val DEFAULT_TIME: Long = 0L
|
||||||
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
|
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
|
||||||
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 60000000000L // one minute in nanoseconds
|
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long =
|
||||||
|
60000000000L // one minute in nanoseconds
|
||||||
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
|
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
|
||||||
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
|
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
|
||||||
const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD = 10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded
|
const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD =
|
||||||
|
10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded
|
||||||
const val REQUEST_CODE_FOREGROUND = 42
|
const val REQUEST_CODE_FOREGROUND = 42
|
||||||
|
|
||||||
// requests
|
// requests
|
||||||
|
|
|
@ -69,7 +69,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
// set up views
|
// set up views
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
navHostFragment = supportFragmentManager.findFragmentById(R.id.main_container) as NavHostFragment
|
navHostFragment =
|
||||||
|
supportFragmentManager.findFragmentById(R.id.main_container) as NavHostFragment
|
||||||
bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
|
bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
|
||||||
bottomNavigationView.setupWithNavController(navController = navHostFragment.navController)
|
bottomNavigationView.setupWithNavController(navController = navHostFragment.navController)
|
||||||
|
|
||||||
|
@ -77,12 +78,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
|
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||||
when (destination.id) {
|
when (destination.id) {
|
||||||
R.id.fragment_track -> {
|
R.id.fragment_track -> {
|
||||||
runOnUiThread( Runnable() {
|
runOnUiThread {
|
||||||
run(){
|
run {
|
||||||
// mark menu item "Tracks" as checked
|
// mark menu item "Tracks" as checked
|
||||||
bottomNavigationView.menu.findItem(R.id.tracklist_fragment).setChecked(true)
|
bottomNavigationView.menu.findItem(R.id.tracklist_fragment)
|
||||||
|
.setChecked(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -91,7 +93,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// register listener for changes in shared preferences
|
// register listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(this as Context).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this as Context)
|
||||||
|
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,14 +102,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
// unregister listener for changes in shared preferences
|
// unregister listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(this as Context).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this as Context)
|
||||||
|
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
Keys.PREF_THEME_SELECTION -> {
|
Keys.PREF_THEME_SELECTION -> {
|
||||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection(this@MainActivity))
|
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection(this@MainActivity))
|
||||||
|
|
|
@ -73,9 +73,20 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
// initialize layout
|
// initialize layout
|
||||||
layout = MapFragmentLayoutHolder(activity as Context, this as MapOverlay.MarkerListener, inflater, container, currentBestLocation, trackingState)
|
layout = MapFragmentLayoutHolder(
|
||||||
|
activity as Context,
|
||||||
|
this as MapOverlay.MarkerListener,
|
||||||
|
inflater,
|
||||||
|
container,
|
||||||
|
currentBestLocation,
|
||||||
|
trackingState
|
||||||
|
)
|
||||||
|
|
||||||
// set up buttons
|
// set up buttons
|
||||||
layout.currentLocationButton.setOnClickListener {
|
layout.currentLocationButton.setOnClickListener {
|
||||||
|
@ -104,11 +115,22 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
// request location permission if denied
|
// request location permission if denied
|
||||||
if (ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), Keys.REQUEST_CODE_FOREGROUND)
|
activity as Context,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_DENIED
|
||||||
|
) {
|
||||||
|
this.requestPermissions(
|
||||||
|
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||||
|
Keys.REQUEST_CODE_FOREGROUND
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// bind to TrackerService
|
// bind to TrackerService
|
||||||
activity?.bindService(Intent(activity, TrackerService::class.java), connection, Context.BIND_AUTO_CREATE)
|
activity?.bindService(
|
||||||
|
Intent(activity, TrackerService::class.java),
|
||||||
|
connection,
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,14 +164,22 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onRequestPermissionsResult from Fragment */
|
/* Overrides onRequestPermissionsResult from Fragment */
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
Keys.REQUEST_CODE_FOREGROUND -> {
|
Keys.REQUEST_CODE_FOREGROUND -> {
|
||||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
// permission was granted - re-bind service
|
// permission was granted - re-bind service
|
||||||
activity?.unbindService(connection)
|
activity?.unbindService(connection)
|
||||||
activity?.bindService(Intent(activity, TrackerService::class.java), connection, Context.BIND_AUTO_CREATE)
|
activity?.bindService(
|
||||||
|
Intent(activity, TrackerService::class.java),
|
||||||
|
connection,
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
)
|
||||||
LogHelper.i(TAG, "Request result: Location permission has been granted.")
|
LogHelper.i(TAG, "Request result: Location permission has been granted.")
|
||||||
} else {
|
} else {
|
||||||
// permission denied - unbind service
|
// permission denied - unbind service
|
||||||
|
@ -163,7 +193,12 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(
|
||||||
|
type: Int,
|
||||||
|
dialogResult: Boolean,
|
||||||
|
payload: Int,
|
||||||
|
payloadString: String
|
||||||
|
) {
|
||||||
super.onYesNoDialog(type, dialogResult, payload, payloadString)
|
super.onYesNoDialog(type, dialogResult, payload, payloadString)
|
||||||
when (type) {
|
when (type) {
|
||||||
Keys.DIALOG_EMPTY_RECORDING -> {
|
Keys.DIALOG_EMPTY_RECORDING -> {
|
||||||
|
@ -218,11 +253,18 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
/* Saves track - shows dialog, if recording is still empty */
|
/* Saves track - shows dialog, if recording is still empty */
|
||||||
private fun saveTrack() {
|
private fun saveTrack() {
|
||||||
if (track.wayPoints.isEmpty()) {
|
if (track.wayPoints.isEmpty()) {
|
||||||
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(activity as Context, type = Keys.DIALOG_EMPTY_RECORDING, title = R.string.dialog_error_empty_recording_title, message = R.string.dialog_error_empty_recording_message, yesButton = R.string.dialog_error_empty_recording_action_resume)
|
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(
|
||||||
|
activity as Context,
|
||||||
|
type = Keys.DIALOG_EMPTY_RECORDING,
|
||||||
|
title = R.string.dialog_error_empty_recording_title,
|
||||||
|
message = R.string.dialog_error_empty_recording_message,
|
||||||
|
yesButton = R.string.dialog_error_empty_recording_action_resume
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
// step 1: create and store filenames for json and gpx files
|
// step 1: create and store filenames for json and gpx files
|
||||||
track.trackUriString = FileHelper.getTrackFileUri(activity as Context, track).toString()
|
track.trackUriString =
|
||||||
|
FileHelper.getTrackFileUri(activity as Context, track).toString()
|
||||||
track.gpxUriString = FileHelper.getGpxFileUri(activity as Context, track).toString()
|
track.gpxUriString = FileHelper.getGpxFileUri(activity as Context, track).toString()
|
||||||
// step 2: save track
|
// step 2: save track
|
||||||
FileHelper.saveTrackSuspended(track, saveGpxToo = true)
|
FileHelper.saveTrackSuspended(track, saveGpxToo = true)
|
||||||
|
@ -239,7 +281,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
|
|
||||||
/* Opens a track in TrackFragment */
|
/* Opens a track in TrackFragment */
|
||||||
private fun openTrack(tracklistElement: TracklistElement) {
|
private fun openTrack(tracklistElement: TracklistElement) {
|
||||||
val bundle: Bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name)
|
bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name)
|
||||||
bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString)
|
bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString)
|
||||||
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
|
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
|
||||||
|
@ -251,7 +293,8 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
Keys.PREF_TRACKING_STATE -> {
|
Keys.PREF_TRACKING_STATE -> {
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
|
@ -279,15 +322,18 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
trackingState = trackerService.trackingState
|
trackingState = trackerService.trackingState
|
||||||
layout.updateRecordingButton(trackingState)
|
layout.updateRecordingButton(trackingState)
|
||||||
// register listener for changes in shared preferences
|
// register listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity as Context).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(activity as Context)
|
||||||
|
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
// start listening for location updates
|
// start listening for location updates
|
||||||
handler.removeCallbacks(periodicLocationRequestRunnable)
|
handler.removeCallbacks(periodicLocationRequestRunnable)
|
||||||
handler.postDelayed(periodicLocationRequestRunnable, 0)
|
handler.postDelayed(periodicLocationRequestRunnable, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
bound = false
|
bound = false
|
||||||
// unregister listener for changes in shared preferences
|
// unregister listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity as Context).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(activity as Context)
|
||||||
|
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
// stop receiving location updates
|
// stop receiving location updates
|
||||||
handler.removeCallbacks(periodicLocationRequestRunnable)
|
handler.removeCallbacks(periodicLocationRequestRunnable)
|
||||||
}
|
}
|
||||||
|
@ -312,7 +358,9 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
|
||||||
layout.markCurrentPosition(currentBestLocation, trackingState)
|
layout.markCurrentPosition(currentBestLocation, trackingState)
|
||||||
layout.overlayCurrentTrack(track, trackingState)
|
layout.overlayCurrentTrack(track, trackingState)
|
||||||
// center map, if it had not been dragged/zoomed before
|
// center map, if it had not been dragged/zoomed before
|
||||||
if (!layout.userInteraction) { layout.centerMap(currentBestLocation, true)}
|
if (!layout.userInteraction) {
|
||||||
|
layout.centerMap(currentBestLocation, true)
|
||||||
|
}
|
||||||
// show error snackbar if necessary
|
// show error snackbar if necessary
|
||||||
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
||||||
// use the handler to start runnable again after specified delay
|
// use the handler to start runnable again after specified delay
|
||||||
|
|
|
@ -63,7 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
val screen = preferenceManager.createPreferenceScreen(context)
|
val screen = preferenceManager.createPreferenceScreen(context)
|
||||||
|
|
||||||
// set up "Restrict to GPS" preference
|
// set up "Restrict to GPS" preference
|
||||||
val preferenceGpsOnly: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
val preferenceGpsOnly = SwitchPreferenceCompat(activity as Context)
|
||||||
preferenceGpsOnly.title = getString(R.string.pref_gps_only_title)
|
preferenceGpsOnly.title = getString(R.string.pref_gps_only_title)
|
||||||
preferenceGpsOnly.setIcon(R.drawable.ic_gps_24dp)
|
preferenceGpsOnly.setIcon(R.drawable.ic_gps_24dp)
|
||||||
preferenceGpsOnly.key = Keys.PREF_GPS_ONLY
|
preferenceGpsOnly.key = Keys.PREF_GPS_ONLY
|
||||||
|
@ -72,26 +72,41 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceGpsOnly.setDefaultValue(false)
|
preferenceGpsOnly.setDefaultValue(false)
|
||||||
|
|
||||||
// set up "Use Imperial Measurements" preference
|
// set up "Use Imperial Measurements" preference
|
||||||
val preferenceImperialMeasurementUnits: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
val preferenceImperialMeasurementUnits = SwitchPreferenceCompat(activity as Context)
|
||||||
preferenceImperialMeasurementUnits.title = getString(R.string.pref_imperial_measurement_units_title)
|
preferenceImperialMeasurementUnits.title =
|
||||||
|
getString(R.string.pref_imperial_measurement_units_title)
|
||||||
preferenceImperialMeasurementUnits.setIcon(R.drawable.ic_square_foot_24px)
|
preferenceImperialMeasurementUnits.setIcon(R.drawable.ic_square_foot_24px)
|
||||||
preferenceImperialMeasurementUnits.key = Keys.PREF_USE_IMPERIAL_UNITS
|
preferenceImperialMeasurementUnits.key = Keys.PREF_USE_IMPERIAL_UNITS
|
||||||
preferenceImperialMeasurementUnits.summaryOn = getString(R.string.pref_imperial_measurement_units_summary_imperial)
|
preferenceImperialMeasurementUnits.summaryOn =
|
||||||
preferenceImperialMeasurementUnits.summaryOff = getString(R.string.pref_imperial_measurement_units_summary_metric)
|
getString(R.string.pref_imperial_measurement_units_summary_imperial)
|
||||||
|
preferenceImperialMeasurementUnits.summaryOff =
|
||||||
|
getString(R.string.pref_imperial_measurement_units_summary_metric)
|
||||||
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
|
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
|
||||||
|
|
||||||
// set up "App Theme" preference
|
// set up "App Theme" preference
|
||||||
val preferenceThemeSelection: ListPreference = ListPreference(activity as Context)
|
val preferenceThemeSelection = ListPreference(activity as Context)
|
||||||
preferenceThemeSelection.title = getString(R.string.pref_theme_selection_title)
|
preferenceThemeSelection.title = getString(R.string.pref_theme_selection_title)
|
||||||
preferenceThemeSelection.setIcon(R.drawable.ic_smartphone_24dp)
|
preferenceThemeSelection.setIcon(R.drawable.ic_smartphone_24dp)
|
||||||
preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION
|
preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION
|
||||||
preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme(activity as Context)}"
|
preferenceThemeSelection.summary =
|
||||||
preferenceThemeSelection.entries = arrayOf(getString(R.string.pref_theme_selection_mode_device_default), getString(R.string.pref_theme_selection_mode_light), getString(R.string.pref_theme_selection_mode_dark))
|
"${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme(
|
||||||
preferenceThemeSelection.entryValues = arrayOf(Keys.STATE_THEME_FOLLOW_SYSTEM, Keys.STATE_THEME_LIGHT_MODE, Keys.STATE_THEME_DARK_MODE)
|
activity as Context
|
||||||
|
)}"
|
||||||
|
preferenceThemeSelection.entries = arrayOf(
|
||||||
|
getString(R.string.pref_theme_selection_mode_device_default),
|
||||||
|
getString(R.string.pref_theme_selection_mode_light),
|
||||||
|
getString(R.string.pref_theme_selection_mode_dark)
|
||||||
|
)
|
||||||
|
preferenceThemeSelection.entryValues = arrayOf(
|
||||||
|
Keys.STATE_THEME_FOLLOW_SYSTEM,
|
||||||
|
Keys.STATE_THEME_LIGHT_MODE,
|
||||||
|
Keys.STATE_THEME_DARK_MODE
|
||||||
|
)
|
||||||
preferenceThemeSelection.setOnPreferenceChangeListener { preference, newValue ->
|
preferenceThemeSelection.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
if (preference is ListPreference) {
|
if (preference is ListPreference) {
|
||||||
val index: Int = preference.entryValues.indexOf(newValue)
|
val index: Int = preference.entryValues.indexOf(newValue)
|
||||||
preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${preference.entries.get(index)}"
|
preferenceThemeSelection.summary =
|
||||||
|
"${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}"
|
||||||
return@setOnPreferenceChangeListener true
|
return@setOnPreferenceChangeListener true
|
||||||
} else {
|
} else {
|
||||||
return@setOnPreferenceChangeListener false
|
return@setOnPreferenceChangeListener false
|
||||||
|
@ -99,17 +114,22 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up "Delete Non-Starred" preference
|
// set up "Delete Non-Starred" preference
|
||||||
val preferenceDeleteNonStarred: Preference = Preference(activity as Context)
|
val preferenceDeleteNonStarred = Preference(activity as Context)
|
||||||
preferenceDeleteNonStarred.title = getString(R.string.pref_delete_non_starred_title)
|
preferenceDeleteNonStarred.title = getString(R.string.pref_delete_non_starred_title)
|
||||||
preferenceDeleteNonStarred.setIcon(R.drawable.ic_delete_24dp)
|
preferenceDeleteNonStarred.setIcon(R.drawable.ic_delete_24dp)
|
||||||
preferenceDeleteNonStarred.summary = getString(R.string.pref_delete_non_starred_summary)
|
preferenceDeleteNonStarred.summary = getString(R.string.pref_delete_non_starred_summary)
|
||||||
preferenceDeleteNonStarred.setOnPreferenceClickListener {
|
preferenceDeleteNonStarred.setOnPreferenceClickListener {
|
||||||
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_NON_STARRED, message = R.string.dialog_yes_no_message_delete_non_starred, yesButton = R.string.dialog_yes_no_positive_button_delete_non_starred)
|
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(
|
||||||
|
context = activity as Context,
|
||||||
|
type = Keys.DIALOG_DELETE_NON_STARRED,
|
||||||
|
message = R.string.dialog_yes_no_message_delete_non_starred,
|
||||||
|
yesButton = R.string.dialog_yes_no_positive_button_delete_non_starred
|
||||||
|
)
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up "Accuracy Threshold" preference
|
// set up "Accuracy Threshold" preference
|
||||||
val preferenceAccuracyThreshold: SeekBarPreference = SeekBarPreference(activity as Context)
|
val preferenceAccuracyThreshold = SeekBarPreference(activity as Context)
|
||||||
preferenceAccuracyThreshold.title = getString(R.string.pref_accuracy_threshold_title)
|
preferenceAccuracyThreshold.title = getString(R.string.pref_accuracy_threshold_title)
|
||||||
preferenceAccuracyThreshold.setIcon(R.drawable.ic_timeline_24dp)
|
preferenceAccuracyThreshold.setIcon(R.drawable.ic_timeline_24dp)
|
||||||
preferenceAccuracyThreshold.key = Keys.PREF_LOCATION_ACCURACY_THRESHOLD
|
preferenceAccuracyThreshold.key = Keys.PREF_LOCATION_ACCURACY_THRESHOLD
|
||||||
|
@ -119,7 +139,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceAccuracyThreshold.setDefaultValue(Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
|
preferenceAccuracyThreshold.setDefaultValue(Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
|
||||||
|
|
||||||
// set up "Reset" preference
|
// set up "Reset" preference
|
||||||
val preferenceResetAdvanced: Preference = Preference(activity as Context)
|
val preferenceResetAdvanced = Preference(activity as Context)
|
||||||
preferenceResetAdvanced.title = getString(R.string.pref_reset_advanced_title)
|
preferenceResetAdvanced.title = getString(R.string.pref_reset_advanced_title)
|
||||||
preferenceResetAdvanced.setIcon(R.drawable.ic_undo_24dp)
|
preferenceResetAdvanced.setIcon(R.drawable.ic_undo_24dp)
|
||||||
preferenceResetAdvanced.summary = getString(R.string.pref_reset_advanced_summary)
|
preferenceResetAdvanced.summary = getString(R.string.pref_reset_advanced_summary)
|
||||||
|
@ -129,22 +149,29 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up "App Version" preference
|
// set up "App Version" preference
|
||||||
val preferenceAppVersion: Preference = Preference(context)
|
val preferenceAppVersion = Preference(context)
|
||||||
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
||||||
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
|
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
|
||||||
preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
|
preferenceAppVersion.summary =
|
||||||
R.string.app_version_name)})"
|
"${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
|
||||||
|
R.string.app_version_name
|
||||||
|
)})"
|
||||||
preferenceAppVersion.setOnPreferenceClickListener {
|
preferenceAppVersion.setOnPreferenceClickListener {
|
||||||
// copy to clipboard
|
// copy to clipboard
|
||||||
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
||||||
val cm: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val cm: ClipboardManager =
|
||||||
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
cm.setPrimaryClip(clip)
|
cm.setPrimaryClip(clip)
|
||||||
Toast.makeText(activity as Context, R.string.toast_message_copied_to_clipboard, Toast.LENGTH_LONG).show()
|
Toast.makeText(
|
||||||
|
activity as Context,
|
||||||
|
R.string.toast_message_copied_to_clipboard,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up "Report Issue" preference
|
// set up "Report Issue" preference
|
||||||
val preferenceReportIssue: Preference = Preference(context)
|
val preferenceReportIssue = Preference(context)
|
||||||
preferenceReportIssue.title = getString(R.string.pref_report_issue_title)
|
preferenceReportIssue.title = getString(R.string.pref_report_issue_title)
|
||||||
preferenceReportIssue.setIcon(R.drawable.ic_bug_report_24dp)
|
preferenceReportIssue.setIcon(R.drawable.ic_bug_report_24dp)
|
||||||
preferenceReportIssue.summary = getString(R.string.pref_report_issue_summary)
|
preferenceReportIssue.summary = getString(R.string.pref_report_issue_summary)
|
||||||
|
@ -159,20 +186,21 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
}
|
}
|
||||||
|
|
||||||
// set preference categories
|
// set preference categories
|
||||||
val preferenceCategoryGeneral: PreferenceCategory = PreferenceCategory(activity as Context)
|
val preferenceCategoryGeneral = PreferenceCategory(activity as Context)
|
||||||
preferenceCategoryGeneral.title = getString(R.string.pref_general_title)
|
preferenceCategoryGeneral.title = getString(R.string.pref_general_title)
|
||||||
preferenceCategoryGeneral.contains(preferenceImperialMeasurementUnits)
|
preferenceCategoryGeneral.contains(preferenceImperialMeasurementUnits)
|
||||||
preferenceCategoryGeneral.contains(preferenceGpsOnly)
|
preferenceCategoryGeneral.contains(preferenceGpsOnly)
|
||||||
val preferenceCategoryMaintenance: PreferenceCategory = PreferenceCategory(activity as Context)
|
val preferenceCategoryMaintenance =
|
||||||
|
PreferenceCategory(activity as Context)
|
||||||
preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title)
|
preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title)
|
||||||
preferenceCategoryMaintenance.contains(preferenceDeleteNonStarred)
|
preferenceCategoryMaintenance.contains(preferenceDeleteNonStarred)
|
||||||
|
|
||||||
val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context)
|
val preferenceCategoryAdvanced = PreferenceCategory(activity as Context)
|
||||||
preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title)
|
preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title)
|
||||||
preferenceCategoryAdvanced.contains(preferenceAccuracyThreshold)
|
preferenceCategoryAdvanced.contains(preferenceAccuracyThreshold)
|
||||||
preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
|
preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
|
||||||
|
|
||||||
val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
|
val preferenceCategoryAbout = PreferenceCategory(context)
|
||||||
preferenceCategoryAbout.title = getString(R.string.pref_about_title)
|
preferenceCategoryAbout.title = getString(R.string.pref_about_title)
|
||||||
preferenceCategoryAbout.contains(preferenceAppVersion)
|
preferenceCategoryAbout.contains(preferenceAppVersion)
|
||||||
preferenceCategoryAbout.contains(preferenceReportIssue)
|
preferenceCategoryAbout.contains(preferenceReportIssue)
|
||||||
|
@ -195,7 +223,12 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(
|
||||||
|
type: Int,
|
||||||
|
dialogResult: Boolean,
|
||||||
|
payload: Int,
|
||||||
|
payloadString: String
|
||||||
|
) {
|
||||||
when (type) {
|
when (type) {
|
||||||
Keys.DIALOG_DELETE_NON_STARRED -> {
|
Keys.DIALOG_DELETE_NON_STARRED -> {
|
||||||
when (dialogResult) {
|
when (dialogResult) {
|
||||||
|
@ -213,12 +246,13 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
|
|
||||||
|
|
||||||
/* Removes track and track files for given position - used by TracklistFragment */
|
/* Removes track and track files for given position - used by TracklistFragment */
|
||||||
fun deleteNonStarred(context: Context) {
|
private fun deleteNonStarred(context: Context) {
|
||||||
val backgroundJob = Job()
|
val backgroundJob = Job()
|
||||||
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
|
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
|
||||||
uiScope.launch {
|
uiScope.launch {
|
||||||
var tracklist: Tracklist = FileHelper.readTracklist(context)
|
var tracklist: Tracklist = FileHelper.readTracklist(context)
|
||||||
val deferred: Deferred<Tracklist> = async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
|
val deferred: Deferred<Tracklist> =
|
||||||
|
async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
|
||||||
// wait for result and store in tracklist
|
// wait for result and store in tracklist
|
||||||
tracklist = deferred.await()
|
tracklist = deferred.await()
|
||||||
backgroundJob.cancel()
|
backgroundJob.cancel()
|
||||||
|
|
|
@ -24,7 +24,9 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.VibrationEffect
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -46,7 +48,8 @@ import org.y20k.trackbook.helpers.TrackHelper
|
||||||
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||||
|
|
||||||
|
|
||||||
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlay.MarkerListener {
|
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
|
||||||
|
YesNoDialog.YesNoDialogListener, MapOverlay.MarkerListener {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
|
||||||
|
@ -61,19 +64,30 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// get track
|
// get track
|
||||||
val fileUriString: String = arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
|
val fileUriString: String =
|
||||||
if (fileUriString.isNotBlank()) {
|
arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
|
||||||
track = FileHelper.readTrack(activity as Context, Uri.parse(fileUriString))
|
track = if (fileUriString.isNotBlank()) {
|
||||||
|
FileHelper.readTrack(Uri.parse(fileUriString))
|
||||||
} else {
|
} else {
|
||||||
track = Track()
|
Track()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreateView from Fragment */
|
/* Overrides onCreateView from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
// initialize layout
|
// initialize layout
|
||||||
layout = TrackFragmentLayoutHolder(activity as Context, this as MapOverlay.MarkerListener, inflater, container, track)
|
layout = TrackFragmentLayoutHolder(
|
||||||
|
activity as Context,
|
||||||
|
this as MapOverlay.MarkerListener,
|
||||||
|
inflater,
|
||||||
|
container,
|
||||||
|
track
|
||||||
|
)
|
||||||
|
|
||||||
// set up share button
|
// set up share button
|
||||||
layout.shareButton.setOnClickListener {
|
layout.shareButton.setOnClickListener {
|
||||||
|
@ -81,18 +95,31 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
}
|
}
|
||||||
layout.shareButton.setOnLongClickListener {
|
layout.shareButton.setOnLongClickListener {
|
||||||
val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
v.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||||
|
} else {
|
||||||
v.vibrate(50)
|
v.vibrate(50)
|
||||||
|
}
|
||||||
shareGpxTrack()
|
shareGpxTrack()
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
// set up delete button
|
// set up delete button
|
||||||
layout.deleteButton.setOnClickListener {
|
layout.deleteButton.setOnClickListener {
|
||||||
val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}"
|
val dialogMessage =
|
||||||
YesNoDialog(this@TrackFragment as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_TRACK, messageString = dialogMessage, yesButton = R.string.dialog_yes_no_positive_button_delete_recording)
|
"${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}"
|
||||||
|
YesNoDialog(this@TrackFragment as YesNoDialog.YesNoDialogListener).show(
|
||||||
|
context = activity as Context,
|
||||||
|
type = Keys.DIALOG_DELETE_TRACK,
|
||||||
|
messageString = dialogMessage,
|
||||||
|
yesButton = R.string.dialog_yes_no_positive_button_delete_recording
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// set up rename button
|
// set up rename button
|
||||||
layout.editButton.setOnClickListener {
|
layout.editButton.setOnClickListener {
|
||||||
RenameTrackDialog(this as RenameTrackDialog.RenameTrackListener).show(activity as Context, layout.trackNameView.text.toString())
|
RenameTrackDialog(this as RenameTrackDialog.RenameTrackListener).show(
|
||||||
|
activity as Context,
|
||||||
|
layout.trackNameView.text.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout.rootView
|
return layout.rootView
|
||||||
|
@ -125,8 +152,18 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
val targetUri: Uri? = data.data
|
val targetUri: Uri? = data.data
|
||||||
if (targetUri != null) {
|
if (targetUri != null) {
|
||||||
// copy file async (= fire & forget - no return value needed)
|
// copy file async (= fire & forget - no return value needed)
|
||||||
GlobalScope.launch { FileHelper.saveCopyOfFileSuspended( activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri) }
|
GlobalScope.launch {
|
||||||
Toast.makeText(activity as Context, R.string.toast_message_save_gpx, Toast.LENGTH_LONG).show()
|
FileHelper.saveCopyOfFileSuspended(
|
||||||
|
activity as Context,
|
||||||
|
originalFileUri = sourceUri,
|
||||||
|
targetFileUri = targetUri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Toast.makeText(
|
||||||
|
activity as Context,
|
||||||
|
R.string.toast_message_save_gpx,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +176,13 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
/* Overrides onRenameTrackDialog from RenameTrackDialog */
|
/* Overrides onRenameTrackDialog from RenameTrackDialog */
|
||||||
override fun onRenameTrackDialog(textInput: String) {
|
override fun onRenameTrackDialog(textInput: String) {
|
||||||
// rename track async (= fire & forget - no return value needed)
|
// rename track async (= fire & forget - no return value needed)
|
||||||
GlobalScope.launch { FileHelper.renameTrackSuspended(activity as Context, layout.track, textInput) }
|
GlobalScope.launch {
|
||||||
|
FileHelper.renameTrackSuspended(
|
||||||
|
activity as Context,
|
||||||
|
layout.track,
|
||||||
|
textInput
|
||||||
|
)
|
||||||
|
}
|
||||||
// update name in layout
|
// update name in layout
|
||||||
layout.track.name = textInput
|
layout.track.name = textInput
|
||||||
layout.trackNameView.text = textInput
|
layout.trackNameView.text = textInput
|
||||||
|
@ -147,7 +190,12 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(
|
||||||
|
type: Int,
|
||||||
|
dialogResult: Boolean,
|
||||||
|
payload: Int,
|
||||||
|
payloadString: String
|
||||||
|
) {
|
||||||
when (type) {
|
when (type) {
|
||||||
Keys.DIALOG_DELETE_TRACK -> {
|
Keys.DIALOG_DELETE_TRACK -> {
|
||||||
when (dialogResult) {
|
when (dialogResult) {
|
||||||
|
@ -189,7 +237,11 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
if (packageManager != null && intent.resolveActivity(packageManager) != null) {
|
if (packageManager != null && intent.resolveActivity(packageManager) != null) {
|
||||||
startActivityForResult(intent, Keys.REQUEST_SAVE_GPX)
|
startActivityForResult(intent, Keys.REQUEST_SAVE_GPX)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(activity as Context, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
|
Toast.makeText(
|
||||||
|
activity as Context,
|
||||||
|
R.string.toast_message_install_file_helper,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +249,11 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
/* Share track as GPX via share sheet */
|
/* Share track as GPX via share sheet */
|
||||||
private fun shareGpxTrack() {
|
private fun shareGpxTrack() {
|
||||||
val gpxFile = Uri.parse(layout.track.gpxUriString).toFile()
|
val gpxFile = Uri.parse(layout.track.gpxUriString).toFile()
|
||||||
val gpxShareUri = FileProvider.getUriForFile(this.activity as Context, "${requireActivity().applicationContext.packageName}.provider", gpxFile)
|
val gpxShareUri = FileProvider.getUriForFile(
|
||||||
|
this.activity as Context,
|
||||||
|
"${requireActivity().applicationContext.packageName}.provider",
|
||||||
|
gpxFile
|
||||||
|
)
|
||||||
val shareIntent: Intent = Intent.createChooser(Intent().apply {
|
val shareIntent: Intent = Intent.createChooser(Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
data = gpxShareUri
|
data = gpxShareUri
|
||||||
|
@ -211,7 +267,8 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
if (packageManager != null && shareIntent.resolveActivity(packageManager) != null) {
|
if (packageManager != null && shareIntent.resolveActivity(packageManager) != null) {
|
||||||
startActivity(shareIntent)
|
startActivity(shareIntent)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(activity, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ import kotlin.coroutines.CoroutineContext
|
||||||
/*
|
/*
|
||||||
* TrackerService class
|
* TrackerService class
|
||||||
*/
|
*/
|
||||||
class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
class TrackerService : Service(), CoroutineScope, SensorEventListener {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java)
|
||||||
|
@ -58,16 +58,16 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
var trackingState: Int = Keys.STATE_TRACKING_NOT
|
var trackingState: Int = Keys.STATE_TRACKING_NOT
|
||||||
var gpsProviderActive: Boolean = false
|
var gpsProviderActive: Boolean = false
|
||||||
var networkProviderActive: Boolean = false
|
var networkProviderActive: Boolean = false
|
||||||
var useImperial: Boolean = false
|
private var useImperial: Boolean = false
|
||||||
var gpsOnly: Boolean = false
|
private var gpsOnly: Boolean = false
|
||||||
var locationAccuracyThreshold: Int = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
|
var locationAccuracyThreshold: Int = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
|
||||||
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
||||||
var stepCountOffset: Float = 0f
|
private var stepCountOffset: Float = 0f
|
||||||
var resumed: Boolean = false
|
var resumed: Boolean = false
|
||||||
var track: Track = Track()
|
var track: Track = Track()
|
||||||
var gpsLocationListenerRegistered: Boolean = false
|
private var gpsLocationListenerRegistered: Boolean = false
|
||||||
var networkLocationListenerRegistered: Boolean = false
|
private var networkLocationListenerRegistered: Boolean = false
|
||||||
var bound: Boolean = false
|
private var bound: Boolean = false
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
private val handler: Handler = Handler()
|
private val handler: Handler = Handler()
|
||||||
private lateinit var locationManager: LocationManager
|
private lateinit var locationManager: LocationManager
|
||||||
|
@ -99,9 +99,10 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
networkLocationListener = createLocationListener()
|
networkLocationListener = createLocationListener()
|
||||||
trackingState = PreferencesHelper.loadTrackingState(this)
|
trackingState = PreferencesHelper.loadTrackingState(this)
|
||||||
currentBestLocation = LocationHelper.getLastKnownLocation(this)
|
currentBestLocation = LocationHelper.getLastKnownLocation(this)
|
||||||
track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
|
track = FileHelper.readTrack(FileHelper.getTempFileUri(this))
|
||||||
backgroundJob = Job()
|
backgroundJob = Job()
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +112,10 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
// SERVICE RESTART (via START_STICKY)
|
// SERVICE RESTART (via START_STICKY)
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
if (trackingState == Keys.STATE_TRACKING_ACTIVE) {
|
if (trackingState == Keys.STATE_TRACKING_ACTIVE) {
|
||||||
LogHelper.w(TAG, "Trackbook has been killed by the operating system. Trying to resume recording.")
|
LogHelper.w(
|
||||||
|
TAG,
|
||||||
|
"Trackbook has been killed by the operating system. Trying to resume recording."
|
||||||
|
)
|
||||||
resumeTracking()
|
resumeTracking()
|
||||||
}
|
}
|
||||||
// ACTION STOP
|
// ACTION STOP
|
||||||
|
@ -172,7 +176,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
// remove notification
|
// remove notification
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
// stop listening for changes in shared preferences
|
// stop listening for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
// stop receiving location updates
|
// stop receiving location updates
|
||||||
removeGpsLocationListener()
|
removeGpsLocationListener()
|
||||||
removeNetworkLocationListener()
|
removeNetworkLocationListener()
|
||||||
|
@ -189,11 +194,12 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
|
|
||||||
/* Overrides onSensorChanged from SensorEventListener */
|
/* Overrides onSensorChanged from SensorEventListener */
|
||||||
override fun onSensorChanged(sensorEvent: SensorEvent?) {
|
override fun onSensorChanged(sensorEvent: SensorEvent?) {
|
||||||
var steps: Float = 0f
|
var steps = 0f
|
||||||
if (sensorEvent != null) {
|
if (sensorEvent != null) {
|
||||||
if (stepCountOffset == 0f) {
|
if (stepCountOffset == 0f) {
|
||||||
// store steps previously recorded by the system
|
// store steps previously recorded by the system
|
||||||
stepCountOffset = (sensorEvent.values[0] - 1) - track.stepCount // subtract any steps recorded during this session in case the app was killed
|
stepCountOffset =
|
||||||
|
(sensorEvent.values[0] - 1) - track.stepCount // subtract any steps recorded during this session in case the app was killed
|
||||||
}
|
}
|
||||||
// calculate step count - subtract steps previously recorded
|
// calculate step count - subtract steps previously recorded
|
||||||
steps = sensorEvent.values[0] - stepCountOffset
|
steps = sensorEvent.values[0] - stepCountOffset
|
||||||
|
@ -206,16 +212,17 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
/* Resume tracking after stop/pause */
|
/* Resume tracking after stop/pause */
|
||||||
fun resumeTracking() {
|
fun resumeTracking() {
|
||||||
// load temp track - returns an empty track if not available
|
// load temp track - returns an empty track if not available
|
||||||
track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
|
track = FileHelper.readTrack(FileHelper.getTempFileUri(this))
|
||||||
// try to mark last waypoint as stopover
|
// try to mark last waypoint as stopover
|
||||||
if (track.wayPoints.size > 0) {
|
if (track.wayPoints.size > 0) {
|
||||||
val lastWayPointIndex = track.wayPoints.size - 1
|
val lastWayPointIndex = track.wayPoints.size - 1
|
||||||
track.wayPoints.get(lastWayPointIndex).isStopOver = true
|
track.wayPoints[lastWayPointIndex].isStopOver = true
|
||||||
}
|
}
|
||||||
// set resumed flag
|
// set resumed flag
|
||||||
resumed = true
|
resumed = true
|
||||||
// calculate length of recording break
|
// calculate length of recording break
|
||||||
track.recordingPaused = track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
|
track.recordingPaused =
|
||||||
|
track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
|
||||||
// start tracking
|
// start tracking
|
||||||
startTracking(newTrack = false)
|
startTracking(newTrack = false)
|
||||||
}
|
}
|
||||||
|
@ -301,20 +308,27 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
currentBestLocation = location
|
currentBestLocation = location
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProviderEnabled(provider: String) {
|
override fun onProviderEnabled(provider: String) {
|
||||||
LogHelper.v(TAG, "onProviderEnabled $provider")
|
LogHelper.v(TAG, "onProviderEnabled $provider")
|
||||||
when (provider) {
|
when (provider) {
|
||||||
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
|
LocationManager.GPS_PROVIDER -> gpsProviderActive =
|
||||||
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
|
LocationHelper.isGpsEnabled(locationManager)
|
||||||
|
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
|
||||||
|
LocationHelper.isNetworkEnabled(locationManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProviderDisabled(provider: String) {
|
override fun onProviderDisabled(provider: String) {
|
||||||
LogHelper.v(TAG, "onProviderDisabled $provider")
|
LogHelper.v(TAG, "onProviderDisabled $provider")
|
||||||
when (provider) {
|
when (provider) {
|
||||||
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
|
LocationManager.GPS_PROVIDER -> gpsProviderActive =
|
||||||
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
|
LocationHelper.isGpsEnabled(locationManager)
|
||||||
|
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
|
||||||
|
LocationHelper.isNetworkEnabled(locationManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) {
|
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) {
|
||||||
// deprecated method
|
// deprecated method
|
||||||
}
|
}
|
||||||
|
@ -329,13 +343,25 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
// check if Network provider is available
|
// check if Network provider is available
|
||||||
if (gpsProviderActive) {
|
if (gpsProviderActive) {
|
||||||
// check for location permission
|
// check for location permission
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
// adds GPS location listener
|
// adds GPS location listener
|
||||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f,gpsLocationListener)
|
locationManager.requestLocationUpdates(
|
||||||
|
LocationManager.GPS_PROVIDER,
|
||||||
|
0,
|
||||||
|
0f,
|
||||||
|
gpsLocationListener
|
||||||
|
)
|
||||||
gpsLocationListenerRegistered = true
|
gpsLocationListenerRegistered = true
|
||||||
LogHelper.v(TAG, "Added GPS location listener.")
|
LogHelper.v(TAG, "Added GPS location listener.")
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to add GPS location listener. Location permission is not granted.")
|
LogHelper.w(
|
||||||
|
TAG,
|
||||||
|
"Unable to add GPS location listener. Location permission is not granted."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to add GPS location listener.")
|
LogHelper.w(TAG, "Unable to add GPS location listener.")
|
||||||
|
@ -353,50 +379,83 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
// check if Network provider is available
|
// check if Network provider is available
|
||||||
if (networkProviderActive && !gpsOnly) {
|
if (networkProviderActive && !gpsOnly) {
|
||||||
// check for location permission
|
// check for location permission
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
// adds Network location listener
|
// adds Network location listener
|
||||||
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f, networkLocationListener)
|
locationManager.requestLocationUpdates(
|
||||||
|
LocationManager.NETWORK_PROVIDER,
|
||||||
|
0,
|
||||||
|
0f,
|
||||||
|
networkLocationListener
|
||||||
|
)
|
||||||
networkLocationListenerRegistered = true
|
networkLocationListenerRegistered = true
|
||||||
LogHelper.v(TAG, "Added Network location listener.")
|
LogHelper.v(TAG, "Added Network location listener.")
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to add Network location listener. Location permission is not granted.")
|
LogHelper.w(
|
||||||
|
TAG,
|
||||||
|
"Unable to add Network location listener. Location permission is not granted."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to add Network location listener.")
|
LogHelper.w(TAG, "Unable to add Network location listener.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogHelper.v(TAG, "Skipping registration. Network location listener has already been added.")
|
LogHelper.v(
|
||||||
|
TAG,
|
||||||
|
"Skipping registration. Network location listener has already been added."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Adds location listeners to location manager */
|
/* Adds location listeners to location manager */
|
||||||
fun removeGpsLocationListener() {
|
fun removeGpsLocationListener() {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
locationManager.removeUpdates(gpsLocationListener)
|
locationManager.removeUpdates(gpsLocationListener)
|
||||||
gpsLocationListenerRegistered = false
|
gpsLocationListenerRegistered = false
|
||||||
LogHelper.v(TAG, "Removed GPS location listener.")
|
LogHelper.v(TAG, "Removed GPS location listener.")
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to remove GPS location listener. Location permission is needed.")
|
LogHelper.w(
|
||||||
|
TAG,
|
||||||
|
"Unable to remove GPS location listener. Location permission is needed."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Adds location listeners to location manager */
|
/* Adds location listeners to location manager */
|
||||||
fun removeNetworkLocationListener() {
|
fun removeNetworkLocationListener() {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
locationManager.removeUpdates(gpsLocationListener)
|
locationManager.removeUpdates(gpsLocationListener)
|
||||||
networkLocationListenerRegistered = false
|
networkLocationListenerRegistered = false
|
||||||
LogHelper.v(TAG, "Removed Network location listener.")
|
LogHelper.v(TAG, "Removed Network location listener.")
|
||||||
} else {
|
} else {
|
||||||
LogHelper.w(TAG, "Unable to remove Network location listener. Location permission is needed.")
|
LogHelper.w(
|
||||||
|
TAG,
|
||||||
|
"Unable to remove Network location listener. Location permission is needed."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Registers a step counter listener */
|
/* Registers a step counter listener */
|
||||||
private fun startStepCounter() {
|
private fun startStepCounter() {
|
||||||
val stepCounterAvailable = sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER), SensorManager.SENSOR_DELAY_UI)
|
val stepCounterAvailable = sensorManager.registerListener(
|
||||||
|
this,
|
||||||
|
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
|
||||||
|
SensorManager.SENSOR_DELAY_UI
|
||||||
|
)
|
||||||
if (!stepCounterAvailable) {
|
if (!stepCounterAvailable) {
|
||||||
LogHelper.w(TAG, "Pedometer sensor not available.")
|
LogHelper.w(TAG, "Pedometer sensor not available.")
|
||||||
track.stepCount = -1f
|
track.stepCount = -1f
|
||||||
|
@ -406,7 +465,12 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
|
|
||||||
/* Displays / updates notification */
|
/* Displays / updates notification */
|
||||||
private fun displayNotification(): Notification {
|
private fun displayNotification(): Notification {
|
||||||
val notification: Notification = notificationHelper.createNotification(trackingState, track.length, track.duration, useImperial)
|
val notification: Notification = notificationHelper.createNotification(
|
||||||
|
trackingState,
|
||||||
|
track.length,
|
||||||
|
track.duration,
|
||||||
|
useImperial
|
||||||
|
)
|
||||||
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
|
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
|
||||||
return notification
|
return notification
|
||||||
}
|
}
|
||||||
|
@ -415,7 +479,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
// preference "Restrict to GPS"
|
// preference "Restrict to GPS"
|
||||||
Keys.PREF_GPS_ONLY -> {
|
Keys.PREF_GPS_ONLY -> {
|
||||||
|
@ -431,7 +496,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
}
|
}
|
||||||
// preference "Accuracy Threshold"
|
// preference "Accuracy Threshold"
|
||||||
Keys.PREF_LOCATION_ACCURACY_THRESHOLD -> {
|
Keys.PREF_LOCATION_ACCURACY_THRESHOLD -> {
|
||||||
locationAccuracyThreshold = PreferencesHelper.loadAccuracyThreshold(this@TrackerService)
|
locationAccuracyThreshold =
|
||||||
|
PreferencesHelper.loadAccuracyThreshold(this@TrackerService)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +523,12 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
|
||||||
private val periodicTrackUpdate: Runnable = object : Runnable {
|
private val periodicTrackUpdate: Runnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
// add waypoint to track - step count is continuously updated in onSensorChanged
|
// add waypoint to track - step count is continuously updated in onSensorChanged
|
||||||
val result: Pair<Track, Boolean> = TrackHelper.addWayPointToTrack(this@TrackerService, track, currentBestLocation, locationAccuracyThreshold, resumed)
|
val result: Pair<Track, Boolean> = TrackHelper.addWayPointToTrack(
|
||||||
|
track,
|
||||||
|
currentBestLocation,
|
||||||
|
locationAccuracyThreshold,
|
||||||
|
resumed
|
||||||
|
)
|
||||||
// get track from result
|
// get track from result
|
||||||
track = result.first
|
track = result.first
|
||||||
// check if waypoint was successfully added (= result.second)
|
// check if waypoint was successfully added (= result.second)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
/*
|
/*
|
||||||
* TrackingToggleTileService class
|
* TrackingToggleTileService class
|
||||||
*/
|
*/
|
||||||
class TrackingToggleTileService(): TileService() {
|
class TrackingToggleTileService : TileService() {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
|
||||||
|
@ -52,11 +52,6 @@ class TrackingToggleTileService(): TileService() {
|
||||||
updateTile()
|
updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onTileRemoved from TileService */
|
|
||||||
override fun onTileRemoved() {
|
|
||||||
super.onTileRemoved()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStartListening from TileService (tile becomes visible) */
|
/* Overrides onStartListening from TileService (tile becomes visible) */
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
|
@ -66,7 +61,8 @@ class TrackingToggleTileService(): TileService() {
|
||||||
// set up tile
|
// set up tile
|
||||||
updateTile()
|
updateTile()
|
||||||
// register listener for changes in shared preferences
|
// register listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService)
|
||||||
|
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,13 +80,8 @@ class TrackingToggleTileService(): TileService() {
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
super.onStopListening()
|
super.onStopListening()
|
||||||
// unregister listener for changes in shared preferences
|
// unregister listener for changes in shared preferences
|
||||||
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService)
|
||||||
}
|
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onDestroy from Service */
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +129,8 @@ class TrackingToggleTileService(): TileService() {
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
Keys.PREF_TRACKING_STATE -> {
|
Keys.PREF_TRACKING_STATE -> {
|
||||||
trackingState = PreferencesHelper.loadTrackingState(this)
|
trackingState = PreferencesHelper.loadTrackingState(this)
|
||||||
|
@ -151,6 +143,4 @@ class TrackingToggleTileService(): TileService() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -40,7 +40,8 @@ import org.y20k.trackbook.tracklist.TracklistAdapter
|
||||||
/*
|
/*
|
||||||
* TracklistFragment class
|
* TracklistFragment class
|
||||||
*/
|
*/
|
||||||
class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, YesNoDialog.YesNoDialogListener {
|
class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
|
YesNoDialog.YesNoDialogListener {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java)
|
||||||
|
@ -61,7 +62,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreateView from Fragment */
|
/* Overrides onCreateView from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
// find views
|
// find views
|
||||||
val rootView = inflater.inflate(R.layout.fragment_tracklist, container, false)
|
val rootView = inflater.inflate(R.layout.fragment_tracklist, container, false)
|
||||||
trackElementList = rootView.findViewById(R.id.track_element_list)
|
trackElementList = rootView.findViewById(R.id.track_element_list)
|
||||||
|
@ -77,8 +82,17 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
// ask user
|
// ask user
|
||||||
val adapterPosition: Int = viewHolder.adapterPosition
|
val adapterPosition: Int = viewHolder.adapterPosition
|
||||||
val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(adapterPosition)}"
|
val dialogMessage =
|
||||||
YesNoDialog(this@TracklistFragment as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_TRACK, messageString = dialogMessage, yesButton = R.string.dialog_yes_no_positive_button_delete_recording, payload = adapterPosition)
|
"${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(
|
||||||
|
adapterPosition
|
||||||
|
)}"
|
||||||
|
YesNoDialog(this@TracklistFragment as YesNoDialog.YesNoDialogListener).show(
|
||||||
|
context = activity as Context,
|
||||||
|
type = Keys.DIALOG_DELETE_TRACK,
|
||||||
|
messageString = dialogMessage,
|
||||||
|
yesButton = R.string.dialog_yes_no_positive_button_delete_recording,
|
||||||
|
payload = adapterPosition
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val itemTouchHelper = ItemTouchHelper(swipeHandler)
|
val itemTouchHelper = ItemTouchHelper(swipeHandler)
|
||||||
|
@ -93,7 +107,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
|
|
||||||
/* Overrides onTrackElementTapped from TracklistElementAdapterListener */
|
/* Overrides onTrackElementTapped from TracklistElementAdapterListener */
|
||||||
override fun onTrackElementTapped(tracklistElement: TracklistElement) {
|
override fun onTrackElementTapped(tracklistElement: TracklistElement) {
|
||||||
val bundle: Bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name)
|
bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name)
|
||||||
bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString)
|
bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString)
|
||||||
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
|
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
|
||||||
|
@ -103,7 +117,12 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(
|
||||||
|
type: Int,
|
||||||
|
dialogResult: Boolean,
|
||||||
|
payload: Int,
|
||||||
|
payloadString: String
|
||||||
|
) {
|
||||||
when (type) {
|
when (type) {
|
||||||
Keys.DIALOG_DELETE_TRACK -> {
|
Keys.DIALOG_DELETE_TRACK -> {
|
||||||
when (dialogResult) {
|
when (dialogResult) {
|
||||||
|
@ -139,11 +158,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Inner class: custom LinearLayoutManager that overrides onLayoutCompleted
|
* Inner class: custom LinearLayoutManager that overrides onLayoutCompleted
|
||||||
*/
|
*/
|
||||||
inner class CustomLinearLayoutManager(context: Context): LinearLayoutManager(context, VERTICAL, false) {
|
inner class CustomLinearLayoutManager(context: Context) :
|
||||||
|
LinearLayoutManager(context, VERTICAL, false) {
|
||||||
|
|
||||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -32,7 +32,8 @@ import java.util.*
|
||||||
*/
|
*/
|
||||||
@Keep
|
@Keep
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Track (@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
data class Track(
|
||||||
|
@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
||||||
@Expose val wayPoints: MutableList<WayPoint> = mutableListOf<WayPoint>(),
|
@Expose val wayPoints: MutableList<WayPoint> = mutableListOf<WayPoint>(),
|
||||||
@Expose var length: Float = 0f,
|
@Expose var length: Float = 0f,
|
||||||
@Expose var duration: Long = 0L,
|
@Expose var duration: Long = 0L,
|
||||||
|
@ -49,7 +50,8 @@ data class Track (@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMA
|
||||||
@Expose var latitude: Double = Keys.DEFAULT_LATITUDE,
|
@Expose var latitude: Double = Keys.DEFAULT_LATITUDE,
|
||||||
@Expose var longitude: Double = Keys.DEFAULT_LONGITUDE,
|
@Expose var longitude: Double = Keys.DEFAULT_LONGITUDE,
|
||||||
@Expose var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
@Expose var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
||||||
@Expose var name: String = String()): Parcelable {
|
@Expose var name: String = String()
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
|
||||||
/* Creates a TracklistElement */
|
/* Creates a TracklistElement */
|
||||||
|
|
|
@ -31,9 +31,11 @@ import java.util.*
|
||||||
*/
|
*/
|
||||||
@Keep
|
@Keep
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Tracklist (@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRACKLIST_FORMAT_VERSION,
|
data class Tracklist(
|
||||||
|
@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRACKLIST_FORMAT_VERSION,
|
||||||
@Expose val tracklistElements: MutableList<TracklistElement> = mutableListOf<TracklistElement>(),
|
@Expose val tracklistElements: MutableList<TracklistElement> = mutableListOf<TracklistElement>(),
|
||||||
@Expose var modificationDate: Date = Date()): Parcelable {
|
@Expose var modificationDate: Date = Date()
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
/* Return trackelement for given track id */
|
/* Return trackelement for given track id */
|
||||||
fun getTrackElement(trackId: Long): TracklistElement? {
|
fun getTrackElement(trackId: Long): TracklistElement? {
|
||||||
|
@ -47,7 +49,11 @@ data class Tracklist (@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRA
|
||||||
|
|
||||||
/* Create a deep copy */
|
/* Create a deep copy */
|
||||||
fun deepCopy(): Tracklist {
|
fun deepCopy(): Tracklist {
|
||||||
return Tracklist(tracklistFormatVersion, mutableListOf<TracklistElement>().apply { addAll(tracklistElements) }, modificationDate)
|
return Tracklist(
|
||||||
|
tracklistFormatVersion,
|
||||||
|
mutableListOf<TracklistElement>().apply { addAll(tracklistElements) },
|
||||||
|
modificationDate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -29,14 +29,16 @@ import java.util.*
|
||||||
*/
|
*/
|
||||||
@Keep
|
@Keep
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TracklistElement(@Expose var name: String,
|
data class TracklistElement(
|
||||||
|
@Expose var name: String,
|
||||||
@Expose val date: Date,
|
@Expose val date: Date,
|
||||||
@Expose val dateString: String,
|
@Expose val dateString: String,
|
||||||
@Expose val durationString: String,
|
@Expose val durationString: String,
|
||||||
@Expose val length: Float,
|
@Expose val length: Float,
|
||||||
@Expose val trackUriString: String,
|
@Expose val trackUriString: String,
|
||||||
@Expose val gpxUriString: String,
|
@Expose val gpxUriString: String,
|
||||||
@Expose var starred: Boolean = false): Parcelable {
|
@Expose var starred: Boolean = false
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
/* Returns unique ID for TracklistElement - currently the start date */
|
/* Returns unique ID for TracklistElement - currently the start date */
|
||||||
fun getTrackId(): Long {
|
fun getTrackId(): Long {
|
||||||
|
|
|
@ -29,7 +29,8 @@ import kotlinx.android.parcel.Parcelize
|
||||||
*/
|
*/
|
||||||
@Keep
|
@Keep
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class WayPoint(@Expose val provider: String,
|
data class WayPoint(
|
||||||
|
@Expose val provider: String,
|
||||||
@Expose val latitude: Double,
|
@Expose val latitude: Double,
|
||||||
@Expose val longitude: Double,
|
@Expose val longitude: Double,
|
||||||
@Expose val altitude: Double,
|
@Expose val altitude: Double,
|
||||||
|
@ -38,12 +39,13 @@ data class WayPoint(@Expose val provider: String,
|
||||||
@Expose val distanceToStartingPoint: Float = 0f,
|
@Expose val distanceToStartingPoint: Float = 0f,
|
||||||
@Expose val numberSatellites: Int = 0,
|
@Expose val numberSatellites: Int = 0,
|
||||||
@Expose var isStopOver: Boolean = false,
|
@Expose var isStopOver: Boolean = false,
|
||||||
@Expose var starred: Boolean = false): Parcelable {
|
@Expose var starred: Boolean = false
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
|
||||||
/* Converts WayPoint into Location */
|
/* Converts WayPoint into Location */
|
||||||
fun toLocation(): Location {
|
fun toLocation(): Location {
|
||||||
val location: Location = Location(provider)
|
val location = Location(provider)
|
||||||
location.latitude = latitude
|
location.latitude = latitude
|
||||||
location.longitude = longitude
|
location.longitude = longitude
|
||||||
location.altitude = altitude
|
location.altitude = altitude
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.y20k.trackbook.dialogs
|
package org.y20k.trackbook.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -38,9 +37,14 @@ object ErrorDialog {
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context, errorTitle: Int, errorMessage: Int, errorDetails: String = String()) {
|
fun show(
|
||||||
|
context: Context,
|
||||||
|
errorTitle: Int,
|
||||||
|
errorMessage: Int,
|
||||||
|
errorDetails: String = String()
|
||||||
|
) {
|
||||||
// prepare dialog builder
|
// prepare dialog builder
|
||||||
val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
|
val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
|
||||||
|
|
||||||
// set title
|
// set title
|
||||||
builder.setTitle(context.getString(errorTitle))
|
builder.setTitle(context.getString(errorTitle))
|
||||||
|
@ -81,10 +85,12 @@ object ErrorDialog {
|
||||||
errorMessageView.text = context.getString(errorMessage)
|
errorMessageView.text = context.getString(errorMessage)
|
||||||
|
|
||||||
// add okay button
|
// add okay button
|
||||||
builder.setPositiveButton(R.string.dialog_generic_button_okay, DialogInterface.OnClickListener { _, _ ->
|
builder.setPositiveButton(
|
||||||
|
R.string.dialog_generic_button_okay
|
||||||
|
) { _, _ ->
|
||||||
// listen for click on okay button
|
// listen for click on okay button
|
||||||
// do nothing
|
// do nothing
|
||||||
})
|
}
|
||||||
|
|
||||||
// display error dialog
|
// display error dialog
|
||||||
builder.show()
|
builder.show()
|
||||||
|
|
|
@ -45,7 +45,7 @@ class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) {
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context, trackName: String) {
|
fun show(context: Context, trackName: String) {
|
||||||
// prepare dialog builder
|
// prepare dialog builder
|
||||||
val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context)
|
val builder = MaterialAlertDialogBuilder(context)
|
||||||
|
|
||||||
// get input field
|
// get input field
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
|
@ -37,35 +37,45 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
|
||||||
private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName)
|
private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog - variant: message from string */
|
/* Construct and show dialog - variant: message from string */
|
||||||
fun show(context: Context,
|
fun show(
|
||||||
|
context: Context,
|
||||||
type: Int,
|
type: Int,
|
||||||
title: Int = Keys.EMPTY_STRING_RESOURCE,
|
title: Int = Keys.EMPTY_STRING_RESOURCE,
|
||||||
message: Int,
|
message: Int,
|
||||||
yesButton: Int = R.string.dialog_yes_no_positive_button_default,
|
yesButton: Int = R.string.dialog_yes_no_positive_button_default,
|
||||||
noButton: Int = R.string.dialog_generic_button_cancel,
|
noButton: Int = R.string.dialog_generic_button_cancel,
|
||||||
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
|
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
|
||||||
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) {
|
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING
|
||||||
|
) {
|
||||||
// extract string from message resource and feed into main show method
|
// extract string from message resource and feed into main show method
|
||||||
show(context, type, title, context.getString(message), yesButton, noButton, payload, payloadString)
|
show(
|
||||||
|
context,
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
context.getString(message),
|
||||||
|
yesButton,
|
||||||
|
noButton,
|
||||||
|
payload,
|
||||||
|
payloadString
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context,
|
fun show(
|
||||||
|
context: Context,
|
||||||
type: Int,
|
type: Int,
|
||||||
title: Int = Keys.EMPTY_STRING_RESOURCE,
|
title: Int = Keys.EMPTY_STRING_RESOURCE,
|
||||||
messageString: String,
|
messageString: String,
|
||||||
yesButton: Int = R.string.dialog_yes_no_positive_button_default,
|
yesButton: Int = R.string.dialog_yes_no_positive_button_default,
|
||||||
noButton: Int = R.string.dialog_generic_button_cancel,
|
noButton: Int = R.string.dialog_generic_button_cancel,
|
||||||
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
|
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
|
||||||
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) {
|
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING
|
||||||
|
) {
|
||||||
|
|
||||||
// prepare dialog builder
|
// prepare dialog builder
|
||||||
val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
|
val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
|
||||||
|
|
||||||
// set title and message
|
// set title and message
|
||||||
builder.setMessage(messageString)
|
builder.setMessage(messageString)
|
||||||
|
@ -87,7 +97,7 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle outside-click as "no"
|
// handle outside-click as "no"
|
||||||
builder.setOnCancelListener(){
|
builder.setOnCancelListener {
|
||||||
yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString)
|
yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,15 @@ package org.y20k.trackbook.extensions
|
||||||
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import java.lang.Double.doubleToRawLongBits
|
||||||
|
import java.lang.Double.longBitsToDouble
|
||||||
|
|
||||||
|
|
||||||
/* Puts a Double value in SharedPreferences */
|
/* Puts a Double value in SharedPreferences */
|
||||||
fun SharedPreferences.Editor.putDouble(key: String, double: Double) = putLong(key, java.lang.Double.doubleToRawLongBits(double))
|
fun SharedPreferences.Editor.putDouble(key: String, double: Double) =
|
||||||
|
this.putLong(key, doubleToRawLongBits(double))
|
||||||
|
|
||||||
|
|
||||||
/* gets a Double value from SharedPreferences */
|
/* gets a Double value from SharedPreferences */
|
||||||
fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default)))
|
fun SharedPreferences.getDouble(key: String, default: Double) =
|
||||||
|
longBitsToDouble(getLong(key, doubleToRawLongBits(default)))
|
||||||
|
|
|
@ -99,6 +99,4 @@ object AppThemeHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -34,22 +34,24 @@ object DateTimeHelper {
|
||||||
|
|
||||||
/* Converts milliseconds to mm:ss or hh:mm:ss */
|
/* Converts milliseconds to mm:ss or hh:mm:ss */
|
||||||
fun convertToReadableTime(context: Context, milliseconds: Long): String {
|
fun convertToReadableTime(context: Context, milliseconds: Long): String {
|
||||||
var timeString: String = String()
|
val timeString: String
|
||||||
val hours: Long = TimeUnit.MILLISECONDS.toHours(milliseconds)
|
val hours: Long = TimeUnit.MILLISECONDS.toHours(milliseconds)
|
||||||
val minutes: Long = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % TimeUnit.HOURS.toMinutes(1)
|
val minutes: Long =
|
||||||
val seconds: Long = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % TimeUnit.MINUTES.toSeconds(1)
|
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % TimeUnit.HOURS.toMinutes(1)
|
||||||
|
val seconds: Long =
|
||||||
|
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % TimeUnit.MINUTES.toSeconds(1)
|
||||||
val h: String = context.getString(R.string.abbreviation_hours)
|
val h: String = context.getString(R.string.abbreviation_hours)
|
||||||
val m: String = context.getString(R.string.abbreviation_minutes)
|
val m: String = context.getString(R.string.abbreviation_minutes)
|
||||||
val s: String = context.getString(R.string.abbreviation_seconds)
|
val s: String = context.getString(R.string.abbreviation_seconds)
|
||||||
|
|
||||||
when (milliseconds >= Keys.ONE_HOUR_IN_MILLISECONDS) {
|
timeString = when (milliseconds >= Keys.ONE_HOUR_IN_MILLISECONDS) {
|
||||||
// CASE: format hh:mm:ss
|
// CASE: format hh:mm:ss
|
||||||
true -> {
|
true -> {
|
||||||
timeString = "$hours $h $minutes $m $seconds $s"
|
"$hours $h $minutes $m $seconds $s"
|
||||||
}
|
}
|
||||||
// CASE: format mm:ss
|
// CASE: format mm:ss
|
||||||
false -> {
|
false -> {
|
||||||
timeString = "$minutes $m $seconds $s"
|
"$minutes $m $seconds $s"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return timeString
|
return timeString
|
||||||
|
@ -58,7 +60,7 @@ object DateTimeHelper {
|
||||||
|
|
||||||
/* Create sortable string for date - used for filenames */
|
/* Create sortable string for date - used for filenames */
|
||||||
fun convertToSortableDateString(date: Date): String {
|
fun convertToSortableDateString(date: Date): String {
|
||||||
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US)
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US)
|
||||||
return dateFormat.format(date)
|
return dateFormat.format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,14 +72,20 @@ object DateTimeHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Creates a readable string date and time - used in the UI */
|
/* Creates a readable string date and time - used in the UI */
|
||||||
fun convertToReadableDateAndTime(date: Date, dateStyle: Int = DateFormat.SHORT, timeStyle: Int = DateFormat.SHORT): String {
|
fun convertToReadableDateAndTime(
|
||||||
return "${DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)} ${DateFormat.getTimeInstance(timeStyle, Locale.getDefault()).format(date)}"
|
date: Date,
|
||||||
|
dateStyle: Int = DateFormat.SHORT,
|
||||||
|
timeStyle: Int = DateFormat.SHORT
|
||||||
|
): String {
|
||||||
|
return "${DateFormat.getDateInstance(dateStyle, Locale.getDefault())
|
||||||
|
.format(date)} ${DateFormat.getTimeInstance(timeStyle, Locale.getDefault())
|
||||||
|
.format(date)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Calculates time difference between two locations */
|
/* Calculates time difference between two locations */
|
||||||
fun calculateTimeDistance(previousLocation: Location?, location: Location): Long {
|
fun calculateTimeDistance(previousLocation: Location?, location: Location): Long {
|
||||||
var timeDifference: Long = 0L
|
var timeDifference = 0L
|
||||||
// two data points needed to calculate time difference
|
// two data points needed to calculate time difference
|
||||||
if (previousLocation != null) {
|
if (previousLocation != null) {
|
||||||
// get time difference
|
// get time difference
|
||||||
|
|
|
@ -35,6 +35,8 @@ import java.text.NumberFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.math.ln
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -61,14 +63,14 @@ object FileHelper {
|
||||||
/* Get file size for given Uri */
|
/* Get file size for given Uri */
|
||||||
fun getFileSize(context: Context, uri: Uri): Long {
|
fun getFileSize(context: Context, uri: Uri): Long {
|
||||||
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
||||||
if (cursor != null) {
|
return if (cursor != null) {
|
||||||
val sizeIndex: Int = cursor.getColumnIndex(OpenableColumns.SIZE)
|
val sizeIndex: Int = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||||
cursor.moveToFirst()
|
cursor.moveToFirst()
|
||||||
val size: Long = cursor.getLong(sizeIndex)
|
val size: Long = cursor.getLong(sizeIndex)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return size
|
size
|
||||||
} else {
|
} else {
|
||||||
return 0L
|
0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,14 +78,14 @@ object FileHelper {
|
||||||
/* Get file name for given Uri */
|
/* Get file name for given Uri */
|
||||||
fun getFileName(context: Context, uri: Uri): String {
|
fun getFileName(context: Context, uri: Uri): String {
|
||||||
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
|
||||||
if (cursor != null) {
|
return if (cursor != null) {
|
||||||
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
cursor.moveToFirst()
|
cursor.moveToFirst()
|
||||||
val name: String = cursor.getString(nameIndex)
|
val name: String = cursor.getString(nameIndex)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return name
|
name
|
||||||
} else {
|
} else {
|
||||||
return String()
|
String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +112,8 @@ object FileHelper {
|
||||||
fun readTracklist(context: Context): Tracklist {
|
fun readTracklist(context: Context): Tracklist {
|
||||||
LogHelper.v(TAG, "Reading Tracklist - Thread: ${Thread.currentThread().name}")
|
LogHelper.v(TAG, "Reading Tracklist - Thread: ${Thread.currentThread().name}")
|
||||||
// get JSON from text file
|
// get JSON from text file
|
||||||
val json: String = readTextFile(context, getTracklistFileUri(context))
|
val json: String = readTextFile(getTracklistFileUri(context))
|
||||||
var tracklist: Tracklist = Tracklist()
|
var tracklist = Tracklist()
|
||||||
when (json.isNotBlank()) {
|
when (json.isNotBlank()) {
|
||||||
// convert JSON and return as tracklist
|
// convert JSON and return as tracklist
|
||||||
true -> try {
|
true -> try {
|
||||||
|
@ -125,10 +127,10 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Reads track from storage using GSON */
|
/* Reads track from storage using GSON */
|
||||||
fun readTrack(context: Context, fileUri: Uri): Track {
|
fun readTrack(fileUri: Uri): Track {
|
||||||
// get JSON from text file
|
// get JSON from text file
|
||||||
val json: String = readTextFile(context, fileUri)
|
val json: String = readTextFile(fileUri)
|
||||||
var track: Track = Track()
|
var track = Track()
|
||||||
when (json.isNotEmpty()) {
|
when (json.isNotEmpty()) {
|
||||||
// convert JSON and return as track
|
// convert JSON and return as track
|
||||||
true -> try {
|
true -> try {
|
||||||
|
@ -154,16 +156,19 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Creates Uri for Gpx file of a track */
|
/* Creates Uri for Gpx file of a track */
|
||||||
fun getGpxFileUri(context: Context, track: Track): Uri = File(context.getExternalFilesDir(Keys.FOLDER_GPX), getGpxFileName(track)).toUri()
|
fun getGpxFileUri(context: Context, track: Track): Uri =
|
||||||
|
File(context.getExternalFilesDir(Keys.FOLDER_GPX), getGpxFileName(track)).toUri()
|
||||||
|
|
||||||
|
|
||||||
/* Creates file name for Gpx file of a track */
|
/* Creates file name for Gpx file of a track */
|
||||||
fun getGpxFileName(track: Track): String = DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.GPX_FILE_EXTENSION
|
fun getGpxFileName(track: Track): String =
|
||||||
|
DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.GPX_FILE_EXTENSION
|
||||||
|
|
||||||
|
|
||||||
/* Creates Uri for json track file */
|
/* Creates Uri for json track file */
|
||||||
fun getTrackFileUri(context: Context, track: Track): Uri {
|
fun getTrackFileUri(context: Context, track: Track): Uri {
|
||||||
val fileName: String = DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.TRACKBOOK_FILE_EXTENSION
|
val fileName: String =
|
||||||
|
DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.TRACKBOOK_FILE_EXTENSION
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), fileName).toUri()
|
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), fileName).toUri()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +180,11 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for saveTracklist */
|
/* Suspend function: Wrapper for saveTracklist */
|
||||||
suspend fun addTrackAndSaveTracklistSuspended(context: Context, track: Track, modificationDate: Date = track.recordingStop) {
|
suspend fun addTrackAndSaveTracklistSuspended(
|
||||||
|
context: Context,
|
||||||
|
track: Track,
|
||||||
|
modificationDate: Date = track.recordingStop
|
||||||
|
) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
val tracklist: Tracklist = readTracklist(context)
|
val tracklist: Tracklist = readTracklist(context)
|
||||||
tracklist.tracklistElements.add(track.toTracklistElement(context))
|
tracklist.tracklistElements.add(track.toTracklistElement(context))
|
||||||
|
@ -193,7 +202,11 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for saveTracklist */
|
/* Suspend function: Wrapper for saveTracklist */
|
||||||
suspend fun saveTracklistSuspended(context: Context, tracklist: Tracklist, modificationDate: Date) {
|
suspend fun saveTracklistSuspended(
|
||||||
|
context: Context,
|
||||||
|
tracklist: Tracklist,
|
||||||
|
modificationDate: Date
|
||||||
|
) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
cont.resume(saveTracklist(context, tracklist, modificationDate))
|
cont.resume(saveTracklist(context, tracklist, modificationDate))
|
||||||
}
|
}
|
||||||
|
@ -217,7 +230,11 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for deleteTrack */
|
/* Suspend function: Wrapper for deleteTrack */
|
||||||
suspend fun deleteTrackSuspended(context: Context, position: Int, tracklist: Tracklist): Tracklist {
|
suspend fun deleteTrackSuspended(
|
||||||
|
context: Context,
|
||||||
|
position: Int,
|
||||||
|
tracklist: Tracklist
|
||||||
|
): Tracklist {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
cont.resume(deleteTrack(context, position, tracklist))
|
cont.resume(deleteTrack(context, position, tracklist))
|
||||||
}
|
}
|
||||||
|
@ -247,7 +264,12 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for copyFile */
|
/* Suspend function: Wrapper for copyFile */
|
||||||
suspend fun saveCopyOfFileSuspended(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
suspend fun saveCopyOfFileSuspended(
|
||||||
|
context: Context,
|
||||||
|
originalFileUri: Uri,
|
||||||
|
targetFileUri: Uri,
|
||||||
|
deleteOriginal: Boolean = false
|
||||||
|
) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal))
|
cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal))
|
||||||
}
|
}
|
||||||
|
@ -285,7 +307,7 @@ object FileHelper {
|
||||||
tracklist.modificationDate = modificationDate
|
tracklist.modificationDate = modificationDate
|
||||||
// convert to JSON
|
// convert to JSON
|
||||||
val gson: Gson = getCustomGson()
|
val gson: Gson = getCustomGson()
|
||||||
var json: String = String()
|
var json = String()
|
||||||
try {
|
try {
|
||||||
json = gson.toJson(tracklist)
|
json = gson.toJson(tracklist)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -304,12 +326,11 @@ object FileHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Renames track */
|
/* Renames track */
|
||||||
private fun renameTrack(context: Context, track: Track, newName: String) {
|
private fun renameTrack(context: Context, track: Track, newName: String) {
|
||||||
// search track in tracklist
|
// search track in tracklist
|
||||||
val tracklist: Tracklist = readTracklist(context)
|
val tracklist: Tracklist = readTracklist(context)
|
||||||
var trackUriString: String = String()
|
var trackUriString = String()
|
||||||
tracklist.tracklistElements.forEach { tracklistElement ->
|
tracklist.tracklistElements.forEach { tracklistElement ->
|
||||||
if (tracklistElement.getTrackId() == track.getTrackId()) {
|
if (tracklistElement.getTrackId() == track.getTrackId()) {
|
||||||
// rename tracklist element
|
// rename tracklist element
|
||||||
|
@ -329,7 +350,11 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Deletes multiple tracks */
|
/* Deletes multiple tracks */
|
||||||
private fun deleteTracks(context: Context, tracklistElements: MutableList<TracklistElement>, tracklist: Tracklist): Tracklist {
|
private fun deleteTracks(
|
||||||
|
context: Context,
|
||||||
|
tracklistElements: MutableList<TracklistElement>,
|
||||||
|
tracklist: Tracklist
|
||||||
|
): Tracklist {
|
||||||
tracklistElements.forEach { tracklistElement ->
|
tracklistElements.forEach { tracklistElement ->
|
||||||
// delete track files
|
// delete track files
|
||||||
tracklistElement.trackUriString.toUri().toFile().delete()
|
tracklistElement.trackUriString.toUri().toFile().delete()
|
||||||
|
@ -348,14 +373,23 @@ object FileHelper {
|
||||||
tracklistElement.trackUriString.toUri().toFile().delete()
|
tracklistElement.trackUriString.toUri().toFile().delete()
|
||||||
tracklistElement.gpxUriString.toUri().toFile().delete()
|
tracklistElement.gpxUriString.toUri().toFile().delete()
|
||||||
// remove track element from list
|
// remove track element from list
|
||||||
tracklist.tracklistElements.removeIf { TrackHelper.getTrackId(it) == TrackHelper.getTrackId(tracklistElement) }
|
tracklist.tracklistElements.removeIf {
|
||||||
|
TrackHelper.getTrackId(it) == TrackHelper.getTrackId(
|
||||||
|
tracklistElement
|
||||||
|
)
|
||||||
|
}
|
||||||
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)
|
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)
|
||||||
return tracklist
|
return tracklist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Copies file to specified target */
|
/* Copies file to specified target */
|
||||||
private fun copyFile(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
private fun copyFile(
|
||||||
|
context: Context,
|
||||||
|
originalFileUri: Uri,
|
||||||
|
targetFileUri: Uri,
|
||||||
|
deleteOriginal: Boolean = false
|
||||||
|
) {
|
||||||
val inputStream = context.contentResolver.openInputStream(originalFileUri)
|
val inputStream = context.contentResolver.openInputStream(originalFileUri)
|
||||||
val outputStream = context.contentResolver.openOutputStream(targetFileUri)
|
val outputStream = context.contentResolver.openOutputStream(targetFileUri)
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
|
@ -370,7 +404,7 @@ object FileHelper {
|
||||||
/* Converts track to JSON */
|
/* Converts track to JSON */
|
||||||
private fun getTrackJsonString(track: Track): String {
|
private fun getTrackJsonString(track: Track): String {
|
||||||
val gson: Gson = getCustomGson()
|
val gson: Gson = getCustomGson()
|
||||||
var json: String = String()
|
var json = String()
|
||||||
try {
|
try {
|
||||||
json = gson.toJson(track)
|
json = gson.toJson(track)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -389,7 +423,6 @@ object FileHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Converts byte value into a human readable format */
|
/* Converts byte value into a human readable format */
|
||||||
// Source: https://programming.guide/java/formatting-byte-size-to-human-readable-format.html
|
// Source: https://programming.guide/java/formatting-byte-size-to-human-readable-format.html
|
||||||
fun getReadableByteCount(bytes: Long, si: Boolean = true): String {
|
fun getReadableByteCount(bytes: Long, si: Boolean = true): String {
|
||||||
|
@ -401,13 +434,13 @@ object FileHelper {
|
||||||
if (bytes < unit) return "$bytes B"
|
if (bytes < unit) return "$bytes B"
|
||||||
|
|
||||||
// calculate exp
|
// calculate exp
|
||||||
val exp: Int = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
|
val exp: Int = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
|
||||||
|
|
||||||
// determine prefix symbol
|
// determine prefix symbol
|
||||||
val prefix: String = ((if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i")
|
val prefix: String = ((if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i")
|
||||||
|
|
||||||
// calculate result and set number format
|
// calculate result and set number format
|
||||||
val result: Double = bytes / Math.pow(unit.toDouble(), exp.toDouble())
|
val result: Double = bytes / unit.toDouble().pow(exp.toDouble())
|
||||||
val numberFormat = NumberFormat.getNumberInstance()
|
val numberFormat = NumberFormat.getNumberInstance()
|
||||||
numberFormat.maximumFractionDigits = 1
|
numberFormat.maximumFractionDigits = 1
|
||||||
|
|
||||||
|
@ -416,7 +449,7 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Reads InputStream from file uri and returns it as String */
|
/* Reads InputStream from file uri and returns it as String */
|
||||||
private fun readTextFile(context: Context, fileUri: Uri): String {
|
private fun readTextFile(fileUri: Uri): String {
|
||||||
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
|
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
|
||||||
// https://developer.android.com/training/secure-file-sharing/retrieve-info
|
// https://developer.android.com/training/secure-file-sharing/retrieve-info
|
||||||
val file: File = fileUri.toFile()
|
val file: File = fileUri.toFile()
|
||||||
|
@ -426,11 +459,12 @@ object FileHelper {
|
||||||
}
|
}
|
||||||
// read until last line reached
|
// read until last line reached
|
||||||
val stream: InputStream = file.inputStream()
|
val stream: InputStream = file.inputStream()
|
||||||
val reader: BufferedReader = BufferedReader(InputStreamReader(stream))
|
val reader = BufferedReader(InputStreamReader(stream))
|
||||||
val builder: StringBuilder = StringBuilder()
|
val builder: StringBuilder = StringBuilder()
|
||||||
reader.forEachLine {
|
reader.forEachLine {
|
||||||
builder.append(it)
|
builder.append(it)
|
||||||
builder.append("\n") }
|
builder.append("\n")
|
||||||
|
}
|
||||||
stream.close()
|
stream.close()
|
||||||
return builder.toString()
|
return builder.toString()
|
||||||
}
|
}
|
||||||
|
@ -444,7 +478,12 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Writes given bitmap as image file to storage */
|
/* Writes given bitmap as image file to storage */
|
||||||
private fun writeImageFile(context: Context, bitmap: Bitmap, file: File, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, quality: Int = 75) {
|
private fun writeImageFile(
|
||||||
|
bitmap: Bitmap,
|
||||||
|
file: File,
|
||||||
|
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
|
||||||
|
quality: Int = 75
|
||||||
|
) {
|
||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
try {
|
try {
|
||||||
val out = FileOutputStream(file)
|
val out = FileOutputStream(file)
|
||||||
|
|
|
@ -89,43 +89,49 @@ object LengthUnitHelper {
|
||||||
/* Determines which unit system the device is using (metric or imperial) */
|
/* Determines which unit system the device is using (metric or imperial) */
|
||||||
fun useImperialUnits(): Boolean {
|
fun useImperialUnits(): Boolean {
|
||||||
// America (US), Liberia (LR), Myanmar(MM) use the imperial system
|
// America (US), Liberia (LR), Myanmar(MM) use the imperial system
|
||||||
val imperialSystemCountries = Arrays.asList("US", "LR", "MM")
|
val imperialSystemCountries = listOf("US", "LR", "MM")
|
||||||
val countryCode = Locale.getDefault().country
|
val countryCode = Locale.getDefault().country
|
||||||
return imperialSystemCountries.contains(countryCode)
|
return imperialSystemCountries.contains(countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
||||||
fun convertToVelocityString(trackDuration: Long, trackRecordingPause: Long, trackLength: Float, useImperialUnits: Boolean = false) : String {
|
fun convertToVelocityString(
|
||||||
var speed: String = "0"
|
trackDuration: Long,
|
||||||
|
trackRecordingPause: Long,
|
||||||
|
trackLength: Float,
|
||||||
|
useImperialUnits: Boolean = false
|
||||||
|
): String {
|
||||||
|
var speed = "0"
|
||||||
|
|
||||||
// duration minus pause in seconds
|
// duration minus pause in seconds
|
||||||
val duration: Long = (trackDuration - trackRecordingPause) / 1000L
|
val duration: Long = (trackDuration - trackRecordingPause) / 1000L
|
||||||
|
|
||||||
if (duration > 0L) {
|
if (duration > 0L) {
|
||||||
// speed in km/h / mph
|
// speed in km/h / mph
|
||||||
val velocity: Double = convertMetersPerSecond((trackLength / duration), useImperialUnits)
|
val velocity: Double =
|
||||||
|
convertMetersPerSecond((trackLength / duration), useImperialUnits)
|
||||||
// create readable speed string
|
// create readable speed string
|
||||||
var bd: BigDecimal = BigDecimal.valueOf(velocity)
|
var bd: BigDecimal = BigDecimal.valueOf(velocity)
|
||||||
bd = bd.setScale(1, RoundingMode.HALF_UP)
|
bd = bd.setScale(1, RoundingMode.HALF_UP)
|
||||||
speed = bd.toPlainString()
|
speed = bd.toPlainString()
|
||||||
}
|
}
|
||||||
|
|
||||||
when (useImperialUnits) {
|
return when (useImperialUnits) {
|
||||||
true -> return "$speed mph"
|
true -> "$speed mph"
|
||||||
false -> return "$speed km/h"
|
false -> "$speed km/h"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Coverts meters per second to either km/h or mph */
|
/* Coverts meters per second to either km/h or mph */
|
||||||
fun convertMetersPerSecond(metersPerSecond: Float, useImperial: Boolean = false): Double {
|
fun convertMetersPerSecond(metersPerSecond: Float, useImperial: Boolean = false): Double {
|
||||||
if (useImperial) {
|
return if (useImperial) {
|
||||||
// mph
|
// mph
|
||||||
return metersPerSecond * 2.2369362920544
|
metersPerSecond * 2.2369362920544
|
||||||
} else {
|
} else {
|
||||||
// km/h
|
// km/h
|
||||||
return metersPerSecond * 3.6
|
metersPerSecond * 3.6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ object LocationHelper {
|
||||||
|
|
||||||
/* Get default location */
|
/* Get default location */
|
||||||
fun getDefaultLocation(): Location {
|
fun getDefaultLocation(): Location {
|
||||||
val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
|
val defaultLocation = Location(LocationManager.NETWORK_PROVIDER)
|
||||||
defaultLocation.latitude = Keys.DEFAULT_LATITUDE
|
defaultLocation.latitude = Keys.DEFAULT_LATITUDE
|
||||||
defaultLocation.longitude = Keys.DEFAULT_LONGITUDE
|
defaultLocation.longitude = Keys.DEFAULT_LONGITUDE
|
||||||
defaultLocation.accuracy = Keys.DEFAULT_ACCURACY
|
defaultLocation.accuracy = Keys.DEFAULT_ACCURACY
|
||||||
|
@ -62,13 +62,23 @@ object LocationHelper {
|
||||||
// get last location that Trackbook has stored
|
// get last location that Trackbook has stored
|
||||||
var lastKnownLocation: Location = PreferencesHelper.loadCurrentBestLocation(context)
|
var lastKnownLocation: Location = PreferencesHelper.loadCurrentBestLocation(context)
|
||||||
// try to get the last location the system has stored - it is probably more recent
|
// try to get the last location the system has stored - it is probably more recent
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
context,
|
||||||
val lastKnownLocationGps: Location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) ?: lastKnownLocation
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
val lastKnownLocationNetwork: Location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) ?: lastKnownLocation
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
val locationManager =
|
||||||
|
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
val lastKnownLocationGps: Location =
|
||||||
|
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
|
?: lastKnownLocation
|
||||||
|
val lastKnownLocationNetwork: Location =
|
||||||
|
locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
|
||||||
|
?: lastKnownLocation
|
||||||
|
lastKnownLocation =
|
||||||
when (isBetterLocation(lastKnownLocationGps, lastKnownLocationNetwork)) {
|
when (isBetterLocation(lastKnownLocationGps, lastKnownLocationNetwork)) {
|
||||||
true -> lastKnownLocation = lastKnownLocationGps
|
true -> lastKnownLocationGps
|
||||||
false -> lastKnownLocation = lastKnownLocationNetwork
|
false -> lastKnownLocationNetwork
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lastKnownLocation
|
return lastKnownLocation
|
||||||
|
@ -118,25 +128,24 @@ object LocationHelper {
|
||||||
|
|
||||||
/* Checks if GPS location provider is available and enabled */
|
/* Checks if GPS location provider is available and enabled */
|
||||||
fun isGpsEnabled(locationManager: LocationManager): Boolean {
|
fun isGpsEnabled(locationManager: LocationManager): Boolean {
|
||||||
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
|
return if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
|
||||||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
} else {
|
} else {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if Network location provider is available and enabled */
|
/* Checks if Network location provider is available and enabled */
|
||||||
fun isNetworkEnabled(locationManager: LocationManager): Boolean {
|
fun isNetworkEnabled(locationManager: LocationManager): Boolean {
|
||||||
if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
|
return if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
|
||||||
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
} else {
|
} else {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Checks if given location is new */
|
/* Checks if given location is new */
|
||||||
fun isRecentEnough(location: Location): Boolean {
|
fun isRecentEnough(location: Location): Boolean {
|
||||||
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
|
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
|
||||||
|
@ -146,26 +155,35 @@ object LocationHelper {
|
||||||
|
|
||||||
/* Checks if given location is accurate */
|
/* Checks if given location is accurate */
|
||||||
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
|
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
|
||||||
val isAccurate: Boolean
|
return when (location.provider) {
|
||||||
when (location.provider) {
|
LocationManager.GPS_PROVIDER -> location.accuracy < locationAccuracyThreshold
|
||||||
LocationManager.GPS_PROVIDER -> isAccurate = location.accuracy < locationAccuracyThreshold
|
else -> location.accuracy < locationAccuracyThreshold + 10 // a bit more relaxed when location comes from network provider
|
||||||
else -> isAccurate = location.accuracy < locationAccuracyThreshold + 10 // a bit more relaxed when location comes from network provider
|
|
||||||
}
|
}
|
||||||
return isAccurate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if the first location of track is plausible */
|
/* Checks if the first location of track is plausible */
|
||||||
fun isFirstLocationPlausible(secondLocation: Location, track: Track): Boolean {
|
fun isFirstLocationPlausible(secondLocation: Location, track: Track): Boolean {
|
||||||
// speed in km/h
|
// speed in km/h
|
||||||
val speed: Double = calculateSpeed(firstLocation = track.wayPoints[0].toLocation(), secondLocation = secondLocation, firstTimestamp = track.recordingStart.time, secondTimestamp = GregorianCalendar.getInstance().time.time)
|
val speed: Double = calculateSpeed(
|
||||||
|
firstLocation = track.wayPoints[0].toLocation(),
|
||||||
|
secondLocation = secondLocation,
|
||||||
|
firstTimestamp = track.recordingStart.time,
|
||||||
|
secondTimestamp = GregorianCalendar.getInstance().time.time
|
||||||
|
)
|
||||||
// plausible = speed under 250 km/h
|
// plausible = speed under 250 km/h
|
||||||
return speed < Keys.IMPLAUSIBLE_TRACK_START_SPEED
|
return speed < Keys.IMPLAUSIBLE_TRACK_START_SPEED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Calculates speed */
|
/* Calculates speed */
|
||||||
private fun calculateSpeed(firstLocation: Location, secondLocation: Location, firstTimestamp: Long, secondTimestamp: Long, useImperial: Boolean = false): Double {
|
private fun calculateSpeed(
|
||||||
|
firstLocation: Location,
|
||||||
|
secondLocation: Location,
|
||||||
|
firstTimestamp: Long,
|
||||||
|
secondTimestamp: Long,
|
||||||
|
useImperial: Boolean = false
|
||||||
|
): Double {
|
||||||
// time difference in seconds
|
// time difference in seconds
|
||||||
val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L
|
val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L
|
||||||
// distance in meters
|
// distance in meters
|
||||||
|
@ -183,10 +201,10 @@ object LocationHelper {
|
||||||
val distanceThreshold: Float
|
val distanceThreshold: Float
|
||||||
val averageAccuracy: Float = (previousLocation.accuracy + location.accuracy) / 2
|
val averageAccuracy: Float = (previousLocation.accuracy + location.accuracy) / 2
|
||||||
// increase the distance threshold if one or both locations are
|
// increase the distance threshold if one or both locations are
|
||||||
if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) {
|
distanceThreshold = if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) {
|
||||||
distanceThreshold = averageAccuracy
|
averageAccuracy
|
||||||
} else {
|
} else {
|
||||||
distanceThreshold = Keys.DEFAULT_THRESHOLD_DISTANCE
|
Keys.DEFAULT_THRESHOLD_DISTANCE
|
||||||
}
|
}
|
||||||
// location is different when far enough away from previous location
|
// location is different when far enough away from previous location
|
||||||
return calculateDistance(previousLocation, location) > distanceThreshold
|
return calculateDistance(previousLocation, location) > distanceThreshold
|
||||||
|
@ -195,7 +213,7 @@ object LocationHelper {
|
||||||
|
|
||||||
/* Calculates distance in meters between two locations */
|
/* Calculates distance in meters between two locations */
|
||||||
fun calculateDistance(previousLocation: Location?, location: Location): Float {
|
fun calculateDistance(previousLocation: Location?, location: Location): Float {
|
||||||
var distance: Float = 0f
|
var distance = 0f
|
||||||
// two data points needed to calculate distance
|
// two data points needed to calculate distance
|
||||||
if (previousLocation != null) {
|
if (previousLocation != null) {
|
||||||
// add up distance
|
// add up distance
|
||||||
|
@ -206,20 +224,26 @@ object LocationHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Calculate elevation differences */
|
/* Calculate elevation differences */
|
||||||
fun calculateElevationDifferences(previousLocation: Location?, location: Location, track: Track): Pair<Double, Double> {
|
fun calculateElevationDifferences(
|
||||||
|
previousLocation: Location?,
|
||||||
|
location: Location,
|
||||||
|
track: Track
|
||||||
|
): Pair<Double, Double> {
|
||||||
// store current values
|
// store current values
|
||||||
var positiveElevation: Double = track.positiveElevation
|
var positiveElevation: Double = track.positiveElevation
|
||||||
var negativeElevation: Double = track.negativeElevation
|
var negativeElevation: Double = track.negativeElevation
|
||||||
if (previousLocation != null) {
|
if (previousLocation != null) {
|
||||||
// factor is bigger than 1 if the time stamp difference is larger than the movement recording interval
|
// factor is bigger than 1 if the time stamp difference is larger than the movement recording interval
|
||||||
val timeDifferenceFactor: Long = (location.time - previousLocation.time) / Keys.ADD_WAYPOINT_TO_TRACK_INTERVAL
|
val timeDifferenceFactor: Long =
|
||||||
|
(location.time - previousLocation.time) / Keys.ADD_WAYPOINT_TO_TRACK_INTERVAL
|
||||||
// get elevation difference and sum it up
|
// get elevation difference and sum it up
|
||||||
val altitudeDifference: Double = location.altitude - previousLocation.altitude
|
val altitudeDifference: Double = location.altitude - previousLocation.altitude
|
||||||
if (altitudeDifference > 0 && altitudeDifference < Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
|
if (altitudeDifference > 0 && altitudeDifference < Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
|
||||||
positiveElevation = track.positiveElevation + altitudeDifference // upwards movement
|
positiveElevation = track.positiveElevation + altitudeDifference // upwards movement
|
||||||
}
|
}
|
||||||
if (altitudeDifference < 0 && altitudeDifference > -Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
|
if (altitudeDifference < 0 && altitudeDifference > -Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
|
||||||
negativeElevation = track.negativeElevation + altitudeDifference // downwards movement
|
negativeElevation =
|
||||||
|
track.negativeElevation + altitudeDifference // downwards movement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(positiveElevation, negativeElevation)
|
return Pair(positiveElevation, negativeElevation)
|
||||||
|
|
|
@ -78,9 +78,9 @@ object LogHelper {
|
||||||
|
|
||||||
private fun log(tag: String, level: Int, t: Throwable?, vararg messages: Any) {
|
private fun log(tag: String, level: Int, t: Throwable?, vararg messages: Any) {
|
||||||
val message: String
|
val message: String
|
||||||
if (t == null && messages.size == 1) {
|
message = if (t == null && messages.size == 1) {
|
||||||
// handle this common case without the extra cost of creating a stringbuffer:
|
// handle this common case without the extra cost of creating a stringbuffer:
|
||||||
message = messages[0].toString()
|
messages[0].toString()
|
||||||
} else {
|
} else {
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
for (m in messages) {
|
for (m in messages) {
|
||||||
|
@ -89,7 +89,7 @@ object LogHelper {
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
sb.append("\n").append(Log.getStackTraceString(t))
|
sb.append("\n").append(Log.getStackTraceString(t))
|
||||||
}
|
}
|
||||||
message = sb.toString()
|
sb.toString()
|
||||||
}
|
}
|
||||||
Log.println(level, tag, message)
|
Log.println(level, tag, message)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ package org.y20k.trackbook.helpers
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.VibrationEffect
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -51,7 +53,11 @@ class MapOverlay (private var markerListener: MarkerListener) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates icon overlay for current position (used in MapFragment) */
|
/* Creates icon overlay for current position (used in MapFragment) */
|
||||||
fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay<OverlayItem> {
|
fun createMyLocationOverlay(
|
||||||
|
context: Context,
|
||||||
|
location: Location,
|
||||||
|
trackingState: Int
|
||||||
|
): ItemizedIconOverlay<OverlayItem> {
|
||||||
|
|
||||||
val overlayItems = ArrayList<OverlayItem>()
|
val overlayItems = ArrayList<OverlayItem>()
|
||||||
val locationIsOld = LocationHelper.isOldLocation(location)
|
val locationIsOld = LocationHelper.isOldLocation(location)
|
||||||
|
@ -61,22 +67,41 @@ class MapOverlay (private var markerListener: MarkerListener) {
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
// CASE: Tracking active
|
// CASE: Tracking active
|
||||||
Keys.STATE_TRACKING_ACTIVE -> {
|
Keys.STATE_TRACKING_ACTIVE -> {
|
||||||
when (locationIsOld) {
|
newMarker = when (locationIsOld) {
|
||||||
true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_grey_24dp)!!
|
true -> ContextCompat.getDrawable(
|
||||||
false -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_24dp)!!
|
context,
|
||||||
|
R.drawable.ic_marker_location_red_grey_24dp
|
||||||
|
)!!
|
||||||
|
false -> ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_location_red_24dp
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// CASE. Tracking is NOT active
|
// CASE. Tracking is NOT active
|
||||||
else -> {
|
else -> {
|
||||||
when (locationIsOld) {
|
newMarker = when (locationIsOld) {
|
||||||
true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_grey_24dp)!!
|
true -> ContextCompat.getDrawable(
|
||||||
false -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_24dp)!!
|
context,
|
||||||
|
R.drawable.ic_marker_location_blue_grey_24dp
|
||||||
|
)!!
|
||||||
|
false -> ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_location_blue_24dp
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add marker to list of overlay items
|
// add marker to list of overlay items
|
||||||
val overlayItem = createOverlayItem(context, location.latitude, location.longitude, location.accuracy, location.provider, location.time)
|
val overlayItem = createOverlayItem(
|
||||||
|
context,
|
||||||
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
location.accuracy,
|
||||||
|
location.provider,
|
||||||
|
location.time
|
||||||
|
)
|
||||||
overlayItem.setMarker(newMarker)
|
overlayItem.setMarker(newMarker)
|
||||||
overlayItems.add(overlayItem)
|
overlayItems.add(overlayItem)
|
||||||
|
|
||||||
|
@ -86,7 +111,11 @@ class MapOverlay (private var markerListener: MarkerListener) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates icon overlay for track */
|
/* Creates icon overlay for track */
|
||||||
fun createTrackOverlay(context: Context, track: Track, trackingState: Int): ItemizedIconOverlay<OverlayItem> {
|
fun createTrackOverlay(
|
||||||
|
context: Context,
|
||||||
|
track: Track,
|
||||||
|
trackingState: Int
|
||||||
|
): ItemizedIconOverlay<OverlayItem> {
|
||||||
|
|
||||||
val overlayItems = ArrayList<OverlayItem>()
|
val overlayItems = ArrayList<OverlayItem>()
|
||||||
val wayPoints = track.wayPoints
|
val wayPoints = track.wayPoints
|
||||||
|
@ -99,28 +128,55 @@ class MapOverlay (private var markerListener: MarkerListener) {
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
// CASE: Recording is active
|
// CASE: Recording is active
|
||||||
Keys.STATE_TRACKING_ACTIVE -> {
|
Keys.STATE_TRACKING_ACTIVE -> {
|
||||||
if (wayPoint.starred) {
|
newMarker = when {
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!
|
wayPoint.starred -> {
|
||||||
} else if (wayPoint.isStopOver) {
|
ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!!
|
}
|
||||||
} else {
|
wayPoint.isStopOver -> {
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_red_24dp)!!
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_track_location_grey_24dp
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_track_location_red_24dp
|
||||||
|
)!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// CASE: Recording is paused/stopped
|
// CASE: Recording is paused/stopped
|
||||||
else -> {
|
else -> {
|
||||||
if (wayPoint.starred) {
|
newMarker = when {
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!
|
wayPoint.starred -> {
|
||||||
} else if (wayPoint.isStopOver) {
|
ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!!
|
}
|
||||||
} else {
|
wayPoint.isStopOver -> {
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_blue_24dp)!!
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_track_location_grey_24dp
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_marker_track_location_blue_24dp
|
||||||
|
)!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create overlay item and add to list of overlay items
|
// create overlay item and add to list of overlay items
|
||||||
val overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
val overlayItem = createOverlayItem(
|
||||||
|
context,
|
||||||
|
wayPoint.latitude,
|
||||||
|
wayPoint.longitude,
|
||||||
|
wayPoint.accuracy,
|
||||||
|
wayPoint.provider,
|
||||||
|
wayPoint.time
|
||||||
|
)
|
||||||
overlayItem.setMarker(newMarker)
|
overlayItem.setMarker(newMarker)
|
||||||
overlayItems.add(overlayItem)
|
overlayItems.add(overlayItem)
|
||||||
}
|
}
|
||||||
|
@ -131,26 +187,56 @@ class MapOverlay (private var markerListener: MarkerListener) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates a marker overlay item */
|
/* Creates a marker overlay item */
|
||||||
private fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem {
|
private fun createOverlayItem(
|
||||||
val title: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}"
|
context: Context,
|
||||||
|
latitude: Double,
|
||||||
|
longitude: Double,
|
||||||
|
accuracy: Float,
|
||||||
|
provider: String,
|
||||||
|
time: Long
|
||||||
|
): OverlayItem {
|
||||||
|
val title =
|
||||||
|
"${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(
|
||||||
|
SimpleDateFormat.MEDIUM,
|
||||||
|
Locale.getDefault()
|
||||||
|
).format(time)}"
|
||||||
//val description: String = "${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
|
//val description: String = "${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
|
||||||
val description: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
|
val description =
|
||||||
val position: GeoPoint = GeoPoint(latitude, longitude)
|
"${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(
|
||||||
|
SimpleDateFormat.MEDIUM,
|
||||||
|
Locale.getDefault()
|
||||||
|
)
|
||||||
|
.format(time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat(
|
||||||
|
"#0.00"
|
||||||
|
).format(accuracy)} (${provider})"
|
||||||
|
val position = GeoPoint(latitude, longitude)
|
||||||
return OverlayItem(title, description, position)
|
return OverlayItem(title, description, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates an overlay */
|
/* Creates an overlay */
|
||||||
private fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>): ItemizedIconOverlay<OverlayItem> {
|
private fun createOverlay(
|
||||||
|
context: Context,
|
||||||
|
overlayItems: ArrayList<OverlayItem>
|
||||||
|
): ItemizedIconOverlay<OverlayItem> {
|
||||||
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
||||||
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
|
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
|
||||||
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
|
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
|
||||||
markerListener.onMarkerTapped(item.point.latitude, item.point.longitude)
|
markerListener.onMarkerTapped(item.point.latitude, item.point.longitude)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
|
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
|
||||||
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
v.vibrate(
|
||||||
|
VibrationEffect.createOneShot(
|
||||||
|
50, VibrationEffect.DEFAULT_AMPLITUDE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
v.vibrate(50)
|
v.vibrate(50)
|
||||||
|
}
|
||||||
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,17 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private val notificationManager: NotificationManager = trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
private val notificationManager: NotificationManager =
|
||||||
|
trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
|
||||||
/* Creates notification */
|
/* Creates notification */
|
||||||
fun createNotification(trackingState: Int, trackLength: Float, duration: Long, useImperial: Boolean): Notification {
|
fun createNotification(
|
||||||
|
trackingState: Int,
|
||||||
|
trackLength: Float,
|
||||||
|
duration: Long,
|
||||||
|
useImperial: Boolean
|
||||||
|
): Notification {
|
||||||
|
|
||||||
// create notification channel if necessary
|
// create notification channel if necessary
|
||||||
if (shouldCreateNotificationChannel()) {
|
if (shouldCreateNotificationChannel()) {
|
||||||
|
@ -53,7 +59,8 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build notification
|
// Build notification
|
||||||
val builder = NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING)
|
val builder =
|
||||||
|
NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING)
|
||||||
builder.setContentIntent(showActionPendingIntent)
|
builder.setContentIntent(showActionPendingIntent)
|
||||||
builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
|
builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
|
||||||
builder.setContentText(getContentString(trackerService, duration, trackLength, useImperial))
|
builder.setContentText(getContentString(trackerService, duration, trackLength, useImperial))
|
||||||
|
@ -63,13 +70,23 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
Keys.STATE_TRACKING_ACTIVE -> {
|
Keys.STATE_TRACKING_ACTIVE -> {
|
||||||
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_running))
|
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_running))
|
||||||
builder.addAction(stopAction)
|
builder.addAction(stopAction)
|
||||||
builder.setLargeIcon(AppCompatResources.getDrawable(trackerService, R.drawable.ic_notification_icon_large_tracking_active_48dp)!!.toBitmap())
|
builder.setLargeIcon(
|
||||||
|
AppCompatResources.getDrawable(
|
||||||
|
trackerService,
|
||||||
|
R.drawable.ic_notification_icon_large_tracking_active_48dp
|
||||||
|
)!!.toBitmap()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_not_running))
|
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_not_running))
|
||||||
builder.addAction(resumeAction)
|
builder.addAction(resumeAction)
|
||||||
builder.addAction(showAction)
|
builder.addAction(showAction)
|
||||||
builder.setLargeIcon(AppCompatResources.getDrawable(trackerService, R.drawable.ic_notification_icon_large_tracking_stopped_48dp)!!.toBitmap())
|
builder.setLargeIcon(
|
||||||
|
AppCompatResources.getDrawable(
|
||||||
|
trackerService,
|
||||||
|
R.drawable.ic_notification_icon_large_tracking_stopped_48dp
|
||||||
|
)!!.toBitmap()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,27 +96,42 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
|
|
||||||
|
|
||||||
/* Build context text for notification builder */
|
/* Build context text for notification builder */
|
||||||
private fun getContentString(context: Context, duration: Long, trackLength: Float, useImperial: Boolean): String {
|
private fun getContentString(
|
||||||
return "${LengthUnitHelper.convertDistanceToString(trackLength, useImperial)} • ${DateTimeHelper.convertToReadableTime(context, duration)}"
|
context: Context,
|
||||||
|
duration: Long,
|
||||||
|
trackLength: Float,
|
||||||
|
useImperial: Boolean
|
||||||
|
): String {
|
||||||
|
return "${LengthUnitHelper.convertDistanceToString(
|
||||||
|
trackLength,
|
||||||
|
useImperial
|
||||||
|
)} • ${DateTimeHelper.convertToReadableTime(context, duration)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if notification channel should be created */
|
/* Checks if notification channel should be created */
|
||||||
private fun shouldCreateNotificationChannel() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
|
private fun shouldCreateNotificationChannel() =
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
|
||||||
|
|
||||||
|
|
||||||
/* Checks if notification channel exists */
|
/* Checks if notification channel exists */
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
|
private fun nowPlayingChannelExists() =
|
||||||
|
notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
|
||||||
|
|
||||||
|
|
||||||
/* Create a notification channel */
|
/* Create a notification channel */
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
val notificationChannel = NotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING,
|
val notificationChannel = NotificationChannel(
|
||||||
|
Keys.NOTIFICATION_CHANNEL_RECORDING,
|
||||||
trackerService.getString(R.string.notification_channel_recording_name),
|
trackerService.getString(R.string.notification_channel_recording_name),
|
||||||
NotificationManager.IMPORTANCE_LOW)
|
NotificationManager.IMPORTANCE_LOW
|
||||||
.apply { description = trackerService.getString(R.string.notification_channel_recording_description) }
|
)
|
||||||
|
.apply {
|
||||||
|
description =
|
||||||
|
trackerService.getString(R.string.notification_channel_recording_description)
|
||||||
|
}
|
||||||
notificationManager.createNotificationChannel(notificationChannel)
|
notificationManager.createNotificationChannel(notificationChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,11 +139,14 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
/* Notification pending intents */
|
/* Notification pending intents */
|
||||||
private val stopActionPendingIntent = PendingIntent.getService(
|
private val stopActionPendingIntent = PendingIntent.getService(
|
||||||
trackerService, 14,
|
trackerService, 14,
|
||||||
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP),0)
|
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP), 0
|
||||||
|
)
|
||||||
private val resumeActionPendingIntent = PendingIntent.getService(
|
private val resumeActionPendingIntent = PendingIntent.getService(
|
||||||
trackerService, 16,
|
trackerService, 16,
|
||||||
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME),0)
|
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME), 0
|
||||||
private val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(trackerService).run {
|
)
|
||||||
|
private val showActionPendingIntent: PendingIntent? =
|
||||||
|
TaskStackBuilder.create(trackerService).run {
|
||||||
addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java))
|
addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java))
|
||||||
getPendingIntent(10, PendingIntent.FLAG_UPDATE_CURRENT)
|
getPendingIntent(10, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
@ -121,14 +156,17 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
private val stopAction = NotificationCompat.Action(
|
private val stopAction = NotificationCompat.Action(
|
||||||
R.drawable.ic_notification_action_stop_24dp,
|
R.drawable.ic_notification_action_stop_24dp,
|
||||||
trackerService.getString(R.string.notification_stop),
|
trackerService.getString(R.string.notification_stop),
|
||||||
stopActionPendingIntent)
|
stopActionPendingIntent
|
||||||
|
)
|
||||||
private val resumeAction = NotificationCompat.Action(
|
private val resumeAction = NotificationCompat.Action(
|
||||||
R.drawable.ic_notification_action_resume_36dp,
|
R.drawable.ic_notification_action_resume_36dp,
|
||||||
trackerService.getString(R.string.notification_resume),
|
trackerService.getString(R.string.notification_resume),
|
||||||
resumeActionPendingIntent)
|
resumeActionPendingIntent
|
||||||
|
)
|
||||||
private val showAction = NotificationCompat.Action(
|
private val showAction = NotificationCompat.Action(
|
||||||
R.drawable.ic_notification_action_show_36dp,
|
R.drawable.ic_notification_action_show_36dp,
|
||||||
trackerService.getString(R.string.notification_show),
|
trackerService.getString(R.string.notification_show),
|
||||||
showActionPendingIntent)
|
showActionPendingIntent
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
|
@ -80,7 +80,10 @@ object PreferencesHelper {
|
||||||
// get preferences
|
// get preferences
|
||||||
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
// load length unit system
|
// load length unit system
|
||||||
return settings.getBoolean(Keys.PREF_USE_IMPERIAL_UNITS, LengthUnitHelper.useImperialUnits())
|
return settings.getBoolean(
|
||||||
|
Keys.PREF_USE_IMPERIAL_UNITS,
|
||||||
|
LengthUnitHelper.useImperialUnits()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,7 +100,10 @@ object PreferencesHelper {
|
||||||
// get preferences
|
// get preferences
|
||||||
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
// load tracking state
|
// load tracking state
|
||||||
return settings.getInt(Keys.PREF_LOCATION_ACCURACY_THRESHOLD, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
|
return settings.getInt(
|
||||||
|
Keys.PREF_LOCATION_ACCURACY_THRESHOLD,
|
||||||
|
Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +111,23 @@ object PreferencesHelper {
|
||||||
fun loadCurrentBestLocation(context: Context): Location {
|
fun loadCurrentBestLocation(context: Context): Location {
|
||||||
// get preferences
|
// get preferences
|
||||||
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
val settings = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val provider: String = settings.getString(Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER, LocationManager.NETWORK_PROVIDER) ?: LocationManager.NETWORK_PROVIDER
|
val provider: String = settings.getString(
|
||||||
|
Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER,
|
||||||
|
LocationManager.NETWORK_PROVIDER
|
||||||
|
) ?: LocationManager.NETWORK_PROVIDER
|
||||||
// create location
|
// create location
|
||||||
val currentBestLocation: Location = Location(provider)
|
val currentBestLocation = Location(provider)
|
||||||
// load location attributes
|
// load location attributes
|
||||||
currentBestLocation.latitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE)
|
currentBestLocation.latitude =
|
||||||
currentBestLocation.longitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE)
|
settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE)
|
||||||
currentBestLocation.accuracy = settings.getFloat(Keys.PREF_CURRENT_BEST_LOCATION_ACCURACY, Keys.DEFAULT_ACCURACY)
|
currentBestLocation.longitude =
|
||||||
currentBestLocation.altitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_ALTITUDE, Keys.DEFAULT_ALTITUDE)
|
settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE)
|
||||||
currentBestLocation.time = settings.getLong(Keys.PREF_CURRENT_BEST_LOCATION_TIME, Keys.DEFAULT_TIME)
|
currentBestLocation.accuracy =
|
||||||
|
settings.getFloat(Keys.PREF_CURRENT_BEST_LOCATION_ACCURACY, Keys.DEFAULT_ACCURACY)
|
||||||
|
currentBestLocation.altitude =
|
||||||
|
settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_ALTITUDE, Keys.DEFAULT_ALTITUDE)
|
||||||
|
currentBestLocation.time =
|
||||||
|
settings.getLong(Keys.PREF_CURRENT_BEST_LOCATION_TIME, Keys.DEFAULT_TIME)
|
||||||
return currentBestLocation
|
return currentBestLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +149,9 @@ object PreferencesHelper {
|
||||||
|
|
||||||
/* Load currently selected app theme */
|
/* Load currently selected app theme */
|
||||||
fun loadThemeSelection(context: Context): String {
|
fun loadThemeSelection(context: Context): String {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(Keys.PREF_THEME_SELECTION, Keys.STATE_THEME_FOLLOW_SYSTEM) ?: Keys.STATE_THEME_FOLLOW_SYSTEM
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getString(Keys.PREF_THEME_SELECTION, Keys.STATE_THEME_FOLLOW_SYSTEM)
|
||||||
|
?: Keys.STATE_THEME_FOLLOW_SYSTEM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,13 @@ object TrackHelper {
|
||||||
tracklistElement.date.time
|
tracklistElement.date.time
|
||||||
|
|
||||||
|
|
||||||
/* Adds given locatiom as waypoint to track */
|
/* Adds given location as waypoint to track */
|
||||||
fun addWayPointToTrack(context: Context, track: Track, location: Location, locationAccuracyThreshold: Int, resumed: Boolean): Pair<Track, Boolean> {
|
fun addWayPointToTrack(
|
||||||
|
track: Track,
|
||||||
|
location: Location,
|
||||||
|
locationAccuracyThreshold: Int,
|
||||||
|
resumed: Boolean
|
||||||
|
): Pair<Track, Boolean> {
|
||||||
// get previous location
|
// get previous location
|
||||||
val previousLocation: Location?
|
val previousLocation: Location?
|
||||||
var numberOfWayPoints: Int = track.wayPoints.size
|
var numberOfWayPoints: Int = track.wayPoints.size
|
||||||
|
@ -58,7 +63,11 @@ object TrackHelper {
|
||||||
previousLocation = null
|
previousLocation = null
|
||||||
}
|
}
|
||||||
// CASE: Second location - check if first location was plausible & remove implausible location
|
// CASE: Second location - check if first location was plausible & remove implausible location
|
||||||
else if (numberOfWayPoints == 1 && !LocationHelper.isFirstLocationPlausible(location, track)) {
|
else if (numberOfWayPoints == 1 && !LocationHelper.isFirstLocationPlausible(
|
||||||
|
location,
|
||||||
|
track
|
||||||
|
)
|
||||||
|
) {
|
||||||
previousLocation = null
|
previousLocation = null
|
||||||
numberOfWayPoints = 0
|
numberOfWayPoints = 0
|
||||||
track.wayPoints.removeAt(0)
|
track.wayPoints.removeAt(0)
|
||||||
|
@ -95,7 +104,8 @@ object TrackHelper {
|
||||||
if (shouldBeAdded) {
|
if (shouldBeAdded) {
|
||||||
// update distance (do not update if resumed -> we do not want to add values calculated during a recording pause)
|
// update distance (do not update if resumed -> we do not want to add values calculated during a recording pause)
|
||||||
if (!resumed) {
|
if (!resumed) {
|
||||||
track.length = track.length + LocationHelper.calculateDistance(previousLocation, location)
|
track.length =
|
||||||
|
track.length + LocationHelper.calculateDistance(previousLocation, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.altitude != 0.0) {
|
if (location.altitude != 0.0) {
|
||||||
|
@ -105,12 +115,23 @@ object TrackHelper {
|
||||||
track.minAltitude = location.altitude
|
track.minAltitude = location.altitude
|
||||||
} else {
|
} else {
|
||||||
// calculate elevation values (upwards / downwards movements)
|
// calculate elevation values (upwards / downwards movements)
|
||||||
val elevationDifferences: Pair<Double, Double> = LocationHelper.calculateElevationDifferences(previousLocation, location, track)
|
val elevationDifferences: Pair<Double, Double> =
|
||||||
|
LocationHelper.calculateElevationDifferences(
|
||||||
|
previousLocation,
|
||||||
|
location,
|
||||||
|
track
|
||||||
|
)
|
||||||
// check if any differences were calculated
|
// check if any differences were calculated
|
||||||
if (elevationDifferences != Pair(track.positiveElevation, track.negativeElevation)) {
|
if (elevationDifferences != Pair(
|
||||||
|
track.positiveElevation,
|
||||||
|
track.negativeElevation
|
||||||
|
)
|
||||||
|
) {
|
||||||
// update altitude values
|
// update altitude values
|
||||||
if (location.altitude > track.maxAltitude) track.maxAltitude = location.altitude
|
if (location.altitude > track.maxAltitude) track.maxAltitude =
|
||||||
if (location.altitude < track.minAltitude) track.minAltitude = location.altitude
|
location.altitude
|
||||||
|
if (location.altitude < track.minAltitude) track.minAltitude =
|
||||||
|
location.altitude
|
||||||
// update elevation values (do not update if resumed -> we do not want to add values calculated during a recording pause)
|
// update elevation values (do not update if resumed -> we do not want to add values calculated during a recording pause)
|
||||||
if (!resumed) {
|
if (!resumed) {
|
||||||
track.positiveElevation = elevationDifferences.first
|
track.positiveElevation = elevationDifferences.first
|
||||||
|
@ -122,16 +143,17 @@ object TrackHelper {
|
||||||
|
|
||||||
// toggle stop over status, if necessary
|
// toggle stop over status, if necessary
|
||||||
if (track.wayPoints.size < 0) {
|
if (track.wayPoints.size < 0) {
|
||||||
track.wayPoints[track.wayPoints.size - 1].isStopOver = LocationHelper.isStopOver(previousLocation, location)
|
track.wayPoints[track.wayPoints.size - 1].isStopOver =
|
||||||
|
LocationHelper.isStopOver(previousLocation, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// save number of satellites
|
// save number of satellites
|
||||||
val numberOfSatellites: Int
|
val numberOfSatellites: Int
|
||||||
val extras = location.extras
|
val extras = location.extras
|
||||||
if (extras != null && extras.containsKey("satellites")) {
|
numberOfSatellites = if (extras != null && extras.containsKey("satellites")) {
|
||||||
numberOfSatellites = extras.getInt("satellites", 0)
|
extras.getInt("satellites", 0)
|
||||||
} else {
|
} else {
|
||||||
numberOfSatellites = 0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// add current location as point to center on for later display
|
// add current location as point to center on for later display
|
||||||
|
@ -139,7 +161,18 @@ object TrackHelper {
|
||||||
track.longitude = location.longitude
|
track.longitude = location.longitude
|
||||||
|
|
||||||
// add location as new waypoint
|
// add location as new waypoint
|
||||||
track.wayPoints.add(WayPoint(provider = location.provider, latitude = location.latitude, longitude = location.longitude, altitude = location.altitude, accuracy = location.accuracy, time = location.time, distanceToStartingPoint = track.length, numberSatellites = numberOfSatellites))
|
track.wayPoints.add(
|
||||||
|
WayPoint(
|
||||||
|
provider = location.provider,
|
||||||
|
latitude = location.latitude,
|
||||||
|
longitude = location.longitude,
|
||||||
|
altitude = location.altitude,
|
||||||
|
accuracy = location.accuracy,
|
||||||
|
time = location.time,
|
||||||
|
distanceToStartingPoint = track.length,
|
||||||
|
numberSatellites = numberOfSatellites
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(track, shouldBeAdded)
|
return Pair(track, shouldBeAdded)
|
||||||
|
@ -153,10 +186,9 @@ object TrackHelper {
|
||||||
|
|
||||||
/* Creates GPX string for given track */
|
/* Creates GPX string for given track */
|
||||||
fun createGpxString(track: Track): String {
|
fun createGpxString(track: Track): String {
|
||||||
var gpxString: String
|
|
||||||
|
|
||||||
// add header
|
// add header
|
||||||
gpxString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
|
var gpxString: String = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
|
||||||
"<gpx version=\"1.1\" creator=\"Trackbook App (Android)\"\n" +
|
"<gpx version=\"1.1\" creator=\"Trackbook App (Android)\"\n" +
|
||||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||||
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
|
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
|
||||||
|
@ -227,8 +259,16 @@ object TrackHelper {
|
||||||
if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
|
if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
|
||||||
waypoint.starred = !waypoint.starred
|
waypoint.starred = !waypoint.starred
|
||||||
when (waypoint.starred) {
|
when (waypoint.starred) {
|
||||||
true -> Toast.makeText(context, R.string.toast_message_poi_added, Toast.LENGTH_LONG).show()
|
true -> Toast.makeText(
|
||||||
false -> Toast.makeText(context, R.string.toast_message_poi_removed, Toast.LENGTH_LONG).show()
|
context,
|
||||||
|
R.string.toast_message_poi_added,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
false -> Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.toast_message_poi_removed,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,14 @@ object UiHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Sets layout margins for given view in DP */
|
/* Sets layout margins for given view in DP */
|
||||||
fun setViewMargins(context: Context, view: View, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
|
private fun setViewMargins(
|
||||||
|
context: Context,
|
||||||
|
view: View,
|
||||||
|
left: Int = 0,
|
||||||
|
right: Int = 0,
|
||||||
|
top: Int = 0,
|
||||||
|
bottom: Int = 0
|
||||||
|
) {
|
||||||
val scalingFactor: Float = context.resources.displayMetrics.density
|
val scalingFactor: Float = context.resources.displayMetrics.density
|
||||||
val l: Int = (left * scalingFactor).toInt()
|
val l: Int = (left * scalingFactor).toInt()
|
||||||
val r: Int = (right * scalingFactor).toInt()
|
val r: Int = (right * scalingFactor).toInt()
|
||||||
|
@ -56,7 +63,16 @@ object UiHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Sets layout margins for given view in percent */
|
/* Sets layout margins for given view in percent */
|
||||||
fun setViewMarginsPercentage(context: Context, view: View, height: Int, width: Int, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
|
fun setViewMarginsPercentage(
|
||||||
|
context: Context,
|
||||||
|
view: View,
|
||||||
|
height: Int,
|
||||||
|
width: Int,
|
||||||
|
left: Int = 0,
|
||||||
|
right: Int = 0,
|
||||||
|
top: Int = 0,
|
||||||
|
bottom: Int = 0
|
||||||
|
) {
|
||||||
val l: Int = ((width / 100.0f) * left).toInt()
|
val l: Int = ((width / 100.0f) * left).toInt()
|
||||||
val r: Int = ((width / 100.0f) * right).toInt()
|
val r: Int = ((width / 100.0f) * right).toInt()
|
||||||
val t: Int = ((height / 100.0f) * top).toInt()
|
val t: Int = ((height / 100.0f) * top).toInt()
|
||||||
|
@ -69,28 +85,58 @@ object UiHelper {
|
||||||
* Inner class: Callback that detects a left swipe
|
* Inner class: Callback that detects a left swipe
|
||||||
* Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt
|
* Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt
|
||||||
*/
|
*/
|
||||||
abstract class SwipeToDeleteCallback(context: Context): ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
abstract class SwipeToDeleteCallback(context: Context) :
|
||||||
|
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||||
|
|
||||||
private val deleteIcon = ContextCompat.getDrawable(context, R.drawable.ic_remove_circle_24dp)
|
private val deleteIcon =
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_remove_circle_24dp)
|
||||||
private val intrinsicWidth: Int = deleteIcon?.intrinsicWidth ?: 0
|
private val intrinsicWidth: Int = deleteIcon?.intrinsicWidth ?: 0
|
||||||
private val intrinsicHeight: Int = deleteIcon?.intrinsicHeight ?: 0
|
private val intrinsicHeight: Int = deleteIcon?.intrinsicHeight ?: 0
|
||||||
private val background: ColorDrawable = ColorDrawable()
|
private val background: ColorDrawable = ColorDrawable()
|
||||||
private val backgroundColor = context.resources.getColor(R.color.list_card_delete_background, null)
|
private val backgroundColor =
|
||||||
private val clearPaint: Paint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
|
context.resources.getColor(R.color.list_card_delete_background, null)
|
||||||
|
private val clearPaint: Paint =
|
||||||
|
Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
// do nothing
|
// do nothing
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
|
override fun onChildDraw(
|
||||||
|
c: Canvas,
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float,
|
||||||
|
dY: Float,
|
||||||
|
actionState: Int,
|
||||||
|
isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
val itemView = viewHolder.itemView
|
val itemView = viewHolder.itemView
|
||||||
val itemHeight = itemView.bottom - itemView.top
|
val itemHeight = itemView.bottom - itemView.top
|
||||||
val isCanceled = dX == 0f && !isCurrentlyActive
|
val isCanceled = dX == 0f && !isCurrentlyActive
|
||||||
|
|
||||||
if (isCanceled) {
|
if (isCanceled) {
|
||||||
clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat())
|
clearCanvas(
|
||||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
c,
|
||||||
|
itemView.right + dX,
|
||||||
|
itemView.top.toFloat(),
|
||||||
|
itemView.right.toFloat(),
|
||||||
|
itemView.bottom.toFloat()
|
||||||
|
)
|
||||||
|
super.onChildDraw(
|
||||||
|
c,
|
||||||
|
recyclerView,
|
||||||
|
viewHolder,
|
||||||
|
dX,
|
||||||
|
dY,
|
||||||
|
actionState,
|
||||||
|
isCurrentlyActive
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ import java.util.*
|
||||||
/*
|
/*
|
||||||
* TracklistAdapter class
|
* TracklistAdapter class
|
||||||
*/
|
*/
|
||||||
class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class TracklistAdapter(private val fragment: Fragment) :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java)
|
||||||
|
@ -111,7 +112,8 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
val backgroundJob = Job()
|
val backgroundJob = Job()
|
||||||
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
|
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
|
||||||
uiScope.launch {
|
uiScope.launch {
|
||||||
val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, position, tracklist) }
|
val deferred: Deferred<Tracklist> =
|
||||||
|
async { FileHelper.deleteTrackSuspended(context, position, tracklist) }
|
||||||
// wait for result and store in tracklist
|
// wait for result and store in tracklist
|
||||||
tracklist = deferred.await()
|
tracklist = deferred.await()
|
||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
|
@ -143,7 +145,11 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
FileHelper.saveTracklistSuspended(context, tracklist, GregorianCalendar.getInstance().time)
|
FileHelper.saveTracklistSuspended(
|
||||||
|
context,
|
||||||
|
tracklist,
|
||||||
|
GregorianCalendar.getInstance().time
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +158,17 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
private fun createTrackDataString(position: Int): String {
|
private fun createTrackDataString(position: Int): String {
|
||||||
val tracklistElement: TracklistElement = tracklist.tracklistElements[position]
|
val tracklistElement: TracklistElement = tracklist.tracklistElements[position]
|
||||||
val trackDataString: String
|
val trackDataString: String
|
||||||
when (tracklistElement.name == tracklistElement.dateString) {
|
trackDataString = when (tracklistElement.name == tracklistElement.dateString) {
|
||||||
// CASE: no individual name set - exclude date
|
// CASE: no individual name set - exclude date
|
||||||
true -> trackDataString = "${LengthUnitHelper.convertDistanceToString(tracklistElement.length, useImperial)} • ${tracklistElement.durationString}"
|
true -> "${LengthUnitHelper.convertDistanceToString(
|
||||||
|
tracklistElement.length,
|
||||||
|
useImperial
|
||||||
|
)} • ${tracklistElement.durationString}"
|
||||||
// CASE: no individual name set - include date
|
// CASE: no individual name set - include date
|
||||||
false -> trackDataString = "${tracklistElement.dateString} • ${LengthUnitHelper.convertDistanceToString(tracklistElement.length, useImperial)} • ${tracklistElement.durationString}"
|
false -> "${tracklistElement.dateString} • ${LengthUnitHelper.convertDistanceToString(
|
||||||
|
tracklistElement.length,
|
||||||
|
useImperial
|
||||||
|
)} • ${tracklistElement.durationString}"
|
||||||
}
|
}
|
||||||
return trackDataString
|
return trackDataString
|
||||||
}
|
}
|
||||||
|
@ -165,7 +177,8 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
/*
|
/*
|
||||||
* Inner class: DiffUtil.Callback that determines changes in data - improves list performance
|
* Inner class: DiffUtil.Callback that determines changes in data - improves list performance
|
||||||
*/
|
*/
|
||||||
private inner class DiffCallback(val oldList: Tracklist, val newList: Tracklist): DiffUtil.Callback() {
|
private inner class DiffCallback(val oldList: Tracklist, val newList: Tracklist) :
|
||||||
|
DiffUtil.Callback() {
|
||||||
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
val oldItem = oldList.tracklistElements[oldItemPosition]
|
val oldItem = oldList.tracklistElements[oldItemPosition]
|
||||||
|
@ -195,7 +208,8 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
/*
|
/*
|
||||||
* Inner class: ViewHolder for a track element
|
* Inner class: ViewHolder for a track element
|
||||||
*/
|
*/
|
||||||
private inner class TrackElementViewHolder (trackElementLayout: View): RecyclerView.ViewHolder(trackElementLayout) {
|
private inner class TrackElementViewHolder(trackElementLayout: View) :
|
||||||
|
RecyclerView.ViewHolder(trackElementLayout) {
|
||||||
val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element)
|
val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element)
|
||||||
val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name)
|
val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name)
|
||||||
val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data)
|
val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data)
|
||||||
|
|
|
@ -51,18 +51,25 @@ import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
/*
|
/*
|
||||||
* MapFragmentLayoutHolder class
|
* MapFragmentLayoutHolder class
|
||||||
*/
|
*/
|
||||||
data class MapFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlay.MarkerListener, private var inflater: LayoutInflater, private var container: ViewGroup?, private val startLocation: Location, private val trackingState: Int) {
|
data class MapFragmentLayoutHolder(
|
||||||
|
private var context: Context,
|
||||||
|
private var markerListener: MapOverlay.MarkerListener,
|
||||||
|
private var inflater: LayoutInflater,
|
||||||
|
private var container: ViewGroup?,
|
||||||
|
private val startLocation: Location,
|
||||||
|
private val trackingState: Int
|
||||||
|
) {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(MapFragmentLayoutHolder::class.java)
|
private val TAG: String = LogHelper.makeLogTag(MapFragmentLayoutHolder::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
val rootView: View
|
val rootView: View = inflater.inflate(R.layout.fragment_map, container, false)
|
||||||
val mapView: MapView
|
private val mapView: MapView
|
||||||
val currentLocationButton: FloatingActionButton
|
val currentLocationButton: FloatingActionButton
|
||||||
val recordingButton: FloatingActionButton
|
val recordingButton: FloatingActionButton
|
||||||
val recordingButtonSubMenu: Group
|
private val recordingButtonSubMenu: Group
|
||||||
val saveButton: FloatingActionButton
|
val saveButton: FloatingActionButton
|
||||||
val clearButton: FloatingActionButton
|
val clearButton: FloatingActionButton
|
||||||
val resumeButton: FloatingActionButton
|
val resumeButton: FloatingActionButton
|
||||||
|
@ -77,7 +84,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
/* Init block */
|
/* Init block */
|
||||||
init {
|
init {
|
||||||
// find views
|
// find views
|
||||||
rootView = inflater.inflate(R.layout.fragment_map, container, false)
|
|
||||||
mapView = rootView.findViewById(R.id.map)
|
mapView = rootView.findViewById(R.id.map)
|
||||||
currentLocationButton = rootView.findViewById(R.id.fab_location_button)
|
currentLocationButton = rootView.findViewById(R.id.fab_location_button)
|
||||||
recordingButton = rootView.findViewById(R.id.fab_main_button)
|
recordingButton = rootView.findViewById(R.id.fab_main_button)
|
||||||
|
@ -102,14 +108,19 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
}
|
}
|
||||||
|
|
||||||
// add compass to map
|
// add compass to map
|
||||||
val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
val compassOverlay =
|
||||||
|
CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
||||||
compassOverlay.enableCompass()
|
compassOverlay.enableCompass()
|
||||||
compassOverlay.setCompassCenter(36f, 60f)
|
compassOverlay.setCompassCenter(36f, 60f)
|
||||||
|
|
||||||
mapView.overlays.add(compassOverlay)
|
mapView.overlays.add(compassOverlay)
|
||||||
|
|
||||||
// add my location overlay
|
// add my location overlay
|
||||||
currentPositionOverlay = MapOverlay(markerListener).createMyLocationOverlay(context, startLocation, trackingState)
|
currentPositionOverlay = MapOverlay(markerListener).createMyLocationOverlay(
|
||||||
|
context,
|
||||||
|
startLocation,
|
||||||
|
trackingState
|
||||||
|
)
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
mapView.overlays.add(currentPositionOverlay)
|
||||||
centerMap(startLocation)
|
centerMap(startLocation)
|
||||||
|
|
||||||
|
@ -127,7 +138,7 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
/* Listen for user interaction */
|
/* Listen for user interaction */
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun addInteractionListener() {
|
private fun addInteractionListener() {
|
||||||
mapView.setOnTouchListener { v, event ->
|
mapView.setOnTouchListener { _, _ ->
|
||||||
userInteraction = true
|
userInteraction = true
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -148,7 +159,7 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
/* Save current best location and state of map to shared preferences */
|
/* Save current best location and state of map to shared preferences */
|
||||||
fun saveState(currentBestLocation: Location) {
|
fun saveState(currentBestLocation: Location) {
|
||||||
PreferencesHelper.saveCurrentBestLocation(context, currentBestLocation)
|
PreferencesHelper.saveCurrentBestLocation(context, currentBestLocation)
|
||||||
PreferencesHelper.saveZoomLevel(context, mapView.getZoomLevelDouble())
|
PreferencesHelper.saveZoomLevel(context, mapView.zoomLevelDouble)
|
||||||
// reset user interaction state
|
// reset user interaction state
|
||||||
userInteraction = false
|
userInteraction = false
|
||||||
}
|
}
|
||||||
|
@ -157,7 +168,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
/* Mark current position on map */
|
/* Mark current position on map */
|
||||||
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_NOT) {
|
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_NOT) {
|
||||||
mapView.overlays.remove(currentPositionOverlay)
|
mapView.overlays.remove(currentPositionOverlay)
|
||||||
currentPositionOverlay = MapOverlay(markerListener).createMyLocationOverlay(context, location, trackingState)
|
currentPositionOverlay =
|
||||||
|
MapOverlay(markerListener).createMyLocationOverlay(context, location, trackingState)
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
mapView.overlays.add(currentPositionOverlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +180,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
mapView.overlays.remove(currentTrackOverlay)
|
mapView.overlays.remove(currentTrackOverlay)
|
||||||
}
|
}
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.wayPoints.isNotEmpty()) {
|
||||||
currentTrackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, trackingState)
|
currentTrackOverlay =
|
||||||
|
MapOverlay(markerListener).createTrackOverlay(context, track, trackingState)
|
||||||
mapView.overlays.add(currentTrackOverlay)
|
mapView.overlays.add(currentTrackOverlay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,10 +214,13 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Toggles content and visibility of the location error snackbar */
|
/* Toggles content and visibility of the location error snackbar */
|
||||||
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_DENIED
|
||||||
|
) {
|
||||||
// CASE: Location permission not granted
|
// CASE: Location permission not granted
|
||||||
locationErrorBar.setText(R.string.snackbar_message_location_permission_denied)
|
locationErrorBar.setText(R.string.snackbar_message_location_permission_denied)
|
||||||
locationErrorBar.show()
|
locationErrorBar.show()
|
||||||
|
|
|
@ -50,14 +50,20 @@ import kotlin.math.roundToInt
|
||||||
/*
|
/*
|
||||||
* TrackFragmentLayoutHolder class
|
* TrackFragmentLayoutHolder class
|
||||||
*/
|
*/
|
||||||
data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlay.MarkerListener, private var inflater: LayoutInflater, private var container: ViewGroup?, var track: Track) {
|
data class TrackFragmentLayoutHolder(
|
||||||
|
private var context: Context,
|
||||||
|
private var markerListener: MapOverlay.MarkerListener,
|
||||||
|
private var inflater: LayoutInflater,
|
||||||
|
private var container: ViewGroup?,
|
||||||
|
var track: Track
|
||||||
|
) {
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
val rootView: View
|
val rootView: View = inflater.inflate(R.layout.fragment_track, container, false)
|
||||||
val shareButton: ImageButton
|
val shareButton: ImageButton
|
||||||
val deleteButton: ImageButton
|
val deleteButton: ImageButton
|
||||||
val editButton: ImageButton
|
val editButton: ImageButton
|
||||||
|
@ -90,7 +96,6 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
/* Init block */
|
/* Init block */
|
||||||
init {
|
init {
|
||||||
// find views
|
// find views
|
||||||
rootView = inflater.inflate(R.layout.fragment_track, container, false)
|
|
||||||
mapView = rootView.findViewById(R.id.map)
|
mapView = rootView.findViewById(R.id.map)
|
||||||
shareButton = rootView.findViewById(R.id.save_button)
|
shareButton = rootView.findViewById(R.id.save_button)
|
||||||
deleteButton = rootView.findViewById(R.id.delete_button)
|
deleteButton = rootView.findViewById(R.id.delete_button)
|
||||||
|
@ -134,13 +139,15 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
}
|
}
|
||||||
|
|
||||||
// add compass to map
|
// add compass to map
|
||||||
val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
val compassOverlay =
|
||||||
|
CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
||||||
compassOverlay.enableCompass()
|
compassOverlay.enableCompass()
|
||||||
compassOverlay.setCompassCenter(36f, 60f)
|
compassOverlay.setCompassCenter(36f, 60f)
|
||||||
mapView.overlays.add(compassOverlay)
|
mapView.overlays.add(compassOverlay)
|
||||||
|
|
||||||
// create map overlay
|
// create map overlay
|
||||||
trackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
|
trackOverlay =
|
||||||
|
MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.wayPoints.isNotEmpty()) {
|
||||||
mapView.overlays.add(trackOverlay)
|
mapView.overlays.add(trackOverlay)
|
||||||
}
|
}
|
||||||
|
@ -168,7 +175,11 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
mapView.overlays.remove(trackOverlay)
|
mapView.overlays.remove(trackOverlay)
|
||||||
}
|
}
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.wayPoints.isNotEmpty()) {
|
||||||
trackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
|
trackOverlay = MapOverlay(markerListener).createTrackOverlay(
|
||||||
|
context,
|
||||||
|
track,
|
||||||
|
Keys.STATE_TRACKING_NOT
|
||||||
|
)
|
||||||
mapView.overlays.add(trackOverlay)
|
mapView.overlays.add(trackOverlay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,9 +201,9 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
private fun setupStatisticsViews() {
|
private fun setupStatisticsViews() {
|
||||||
|
|
||||||
// get step count string
|
// get step count string
|
||||||
val steps: String
|
val steps: String =
|
||||||
if (track.stepCount == -1f) steps = context.getString(R.string.statistics_sheet_p_steps_no_pedometer)
|
if (track.stepCount == -1f) context.getString(R.string.statistics_sheet_p_steps_no_pedometer)
|
||||||
else steps = track.stepCount.roundToInt().toString()
|
else track.stepCount.roundToInt().toString()
|
||||||
|
|
||||||
// populate views
|
// populate views
|
||||||
trackNameView.text = track.name
|
trackNameView.text = track.name
|
||||||
|
@ -200,19 +211,29 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
stepsView.text = steps
|
stepsView.text = steps
|
||||||
waypointsView.text = track.wayPoints.size.toString()
|
waypointsView.text = track.wayPoints.size.toString()
|
||||||
durationView.text = DateTimeHelper.convertToReadableTime(context, track.duration)
|
durationView.text = DateTimeHelper.convertToReadableTime(context, track.duration)
|
||||||
velocityView.text = LengthUnitHelper.convertToVelocityString(track.duration, track.recordingPaused, track.length, useImperialUnits)
|
velocityView.text = LengthUnitHelper.convertToVelocityString(
|
||||||
|
track.duration,
|
||||||
|
track.recordingPaused,
|
||||||
|
track.length,
|
||||||
|
useImperialUnits
|
||||||
|
)
|
||||||
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
|
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
|
||||||
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
|
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
|
||||||
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits)
|
maxAltitudeView.text =
|
||||||
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits)
|
LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits)
|
||||||
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits)
|
minAltitudeView.text =
|
||||||
negativeElevationView.text = LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits)
|
LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits)
|
||||||
|
positiveElevationView.text =
|
||||||
|
LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits)
|
||||||
|
negativeElevationView.text =
|
||||||
|
LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits)
|
||||||
|
|
||||||
// show / hide recording pause
|
// show / hide recording pause
|
||||||
if (track.recordingPaused != 0L) {
|
if (track.recordingPaused != 0L) {
|
||||||
recordingPausedLabelView.visibility = View.VISIBLE
|
recordingPausedLabelView.visibility = View.VISIBLE
|
||||||
recordingPausedView.visibility = View.VISIBLE
|
recordingPausedView.visibility = View.VISIBLE
|
||||||
recordingPausedView.text = DateTimeHelper.convertToReadableTime(context, track.recordingPaused)
|
recordingPausedView.text =
|
||||||
|
DateTimeHelper.convertToReadableTime(context, track.recordingPaused)
|
||||||
} else {
|
} else {
|
||||||
recordingPausedLabelView.visibility = View.GONE
|
recordingPausedLabelView.visibility = View.GONE
|
||||||
recordingPausedView.visibility = View.GONE
|
recordingPausedView.visibility = View.GONE
|
||||||
|
@ -221,7 +242,8 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
// inform user about possible accuracy issues with altitude measurements
|
// inform user about possible accuracy issues with altitude measurements
|
||||||
elevationDataViews.referencedIds.forEach { id ->
|
elevationDataViews.referencedIds.forEach { id ->
|
||||||
(rootView.findViewById(id) as View).setOnClickListener {
|
(rootView.findViewById(id) as View).setOnClickListener {
|
||||||
Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make track name on statistics sheet clickable
|
// make track name on statistics sheet clickable
|
||||||
|
@ -234,7 +256,8 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
/* Shows/hides the statistics sheet */
|
/* Shows/hides the statistics sheet */
|
||||||
private fun toggleStatisticsSheetVisibility() {
|
private fun toggleStatisticsSheetVisibility() {
|
||||||
when (statisticsSheetBehavior.state) {
|
when (statisticsSheetBehavior.state) {
|
||||||
BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state =
|
||||||
|
BottomSheetBehavior.STATE_COLLAPSED
|
||||||
else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,26 +269,31 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
when (newState) {
|
when (newState) {
|
||||||
BottomSheetBehavior.STATE_EXPANDED -> {
|
BottomSheetBehavior.STATE_EXPANDED -> {
|
||||||
statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_expanded)
|
statisticsSheet.background =
|
||||||
|
context.getDrawable(R.drawable.shape_statistics_background_expanded)
|
||||||
trackManagementViews.visibility = View.VISIBLE
|
trackManagementViews.visibility = View.VISIBLE
|
||||||
shareButton.visibility = View.GONE
|
shareButton.visibility = View.GONE
|
||||||
// bottomSheet.setPadding(0,24,0,0)
|
// bottomSheet.setPadding(0,24,0,0)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_collapsed)
|
statisticsSheet.background =
|
||||||
|
context.getDrawable(R.drawable.shape_statistics_background_collapsed)
|
||||||
trackManagementViews.visibility = View.GONE
|
trackManagementViews.visibility = View.GONE
|
||||||
shareButton.visibility = View.VISIBLE
|
shareButton.visibility = View.VISIBLE
|
||||||
// bottomSheet.setPadding(0,0,0,0)
|
// bottomSheet.setPadding(0,0,0,0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||||
if (slideOffset < 0.125f) {
|
if (slideOffset < 0.125f) {
|
||||||
statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_collapsed)
|
statisticsSheet.background =
|
||||||
|
context.getDrawable(R.drawable.shape_statistics_background_collapsed)
|
||||||
trackManagementViews.visibility = View.GONE
|
trackManagementViews.visibility = View.GONE
|
||||||
shareButton.visibility = View.VISIBLE
|
shareButton.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_expanded)
|
statisticsSheet.background =
|
||||||
|
context.getDrawable(R.drawable.shape_statistics_background_expanded)
|
||||||
trackManagementViews.visibility = View.VISIBLE
|
trackManagementViews.visibility = View.VISIBLE
|
||||||
shareButton.visibility = View.GONE
|
shareButton.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
|
android:fillColor="@color/trackbook_white"
|
||||||
android:fillColor="@color/trackbook_white" />
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
|
android:fillColor="#DC2B00"
|
||||||
android:fillColor="#DC2B00"/>
|
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
|
android:fillColor="@color/trackbook_white"
|
||||||
android:fillColor="@color/trackbook_white" />
|
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.33"
|
||||||
android:fillColor="@color/trackbook_blue"
|
android:fillColor="@color/trackbook_blue"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.33"
|
||||||
android:fillColor="@color/trackbook_blue"
|
android:fillColor="@color/trackbook_blue"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.33"
|
||||||
android:fillColor="@color/trackbook_red"
|
android:fillColor="@color/trackbook_red"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.33"
|
||||||
android:fillColor="@color/trackbook_red"
|
android:fillColor="@color/trackbook_red"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_blue"
|
android:fillColor="@color/trackbook_blue"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_grey_light"
|
android:fillColor="@color/trackbook_grey_light"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportHeight="96.0"
|
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:viewportHeight="96.0">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_red"
|
android:fillColor="@color/trackbook_red"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="96.0"
|
android:width="24dp"
|
||||||
android:viewportWidth="96.0"
|
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:width="24dp">
|
android:viewportWidth="96.0"
|
||||||
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_black"
|
android:fillColor="@color/trackbook_black"
|
||||||
android:pathData="M44,12.99L20.69,8.74L5.12,5.89C2.29,5.38 0,7.44 0,10.49v67.38c0,3.06 2.29,5.96 5.12,6.47l15.57,2.85l22.19,4.05L44,91.46V12.99z" />
|
android:pathData="M44,12.99L20.69,8.74L5.12,5.89C2.29,5.38 0,7.44 0,10.49v67.38c0,3.06 2.29,5.96 5.12,6.47l15.57,2.85l22.19,4.05L44,91.46V12.99z" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="36dp"
|
||||||
android:height="36dp"
|
android:height="36dp"
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:width="36dp">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_black"
|
android:fillColor="@color/trackbook_black"
|
||||||
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" />
|
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="36dp"
|
||||||
android:height="36dp"
|
android:height="36dp"
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:width="36dp">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_black"
|
android:fillColor="@color/trackbook_black"
|
||||||
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
|
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="36dp"
|
||||||
android:height="36dp"
|
android:height="36dp"
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:width="36dp">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_black"
|
android:fillColor="@color/trackbook_black"
|
||||||
android:pathData="M6,6h12v12H6z" />
|
android:pathData="M6,6h12v12H6z" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
android:height="48dp"
|
android:height="48dp"
|
||||||
android:viewportHeight="192.0"
|
|
||||||
android:viewportWidth="192.0"
|
android:viewportWidth="192.0"
|
||||||
android:width="48dp" >
|
android:viewportHeight="192.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_blue"
|
android:fillColor="@color/trackbook_blue"
|
||||||
android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" />
|
android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
android:height="48dp"
|
android:height="48dp"
|
||||||
android:viewportHeight="192.0"
|
|
||||||
android:viewportWidth="192.0"
|
android:viewportWidth="192.0"
|
||||||
android:width="48dp" >
|
android:viewportHeight="192.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_blue"
|
android:fillColor="@color/trackbook_blue"
|
||||||
android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" />
|
android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="96.0"
|
android:width="24dp"
|
||||||
android:viewportWidth="96.0"
|
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:width="24dp">
|
android:viewportWidth="96.0"
|
||||||
|
android:viewportHeight="96.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/trackbook_white"
|
android:fillColor="@color/trackbook_white"
|
||||||
android:pathData="M44,12.99L20.69,8.74L5.12,5.89C2.29,5.38 0,7.44 0,10.49v67.38c0,3.06 2.29,5.96 5.12,6.47l15.57,2.85l22.19,4.05L44,91.46V12.99z" />
|
android:pathData="M44,12.99L20.69,8.74L5.12,5.89C2.29,5.38 0,7.44 0,10.49v67.38c0,3.06 2.29,5.96 5.12,6.47l15.57,2.85l22.19,4.05L44,91.46V12.99z" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:state_checked="false" android:color="@color/bottom_navigation_element" />
|
<item android:color="@color/bottom_navigation_element" android:state_checked="false" />
|
||||||
<item android:state_checked="true" android:color="@color/bottom_navigation_element_selected" />
|
<item android:color="@color/bottom_navigation_element_selected" android:state_checked="true" />
|
||||||
</selector>
|
</selector>
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:top="0dp"
|
android:bottom="-2dp"
|
||||||
android:left="-2dp"
|
android:left="-2dp"
|
||||||
android:right="-2dp"
|
android:right="-2dp"
|
||||||
android:bottom="-2dp">
|
android:top="0dp">
|
||||||
|
|
||||||
<shape
|
<shape android:shape="rectangle">
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/statistic_sheet_background_collapsed" />
|
<solid android:color="@color/statistic_sheet_background_collapsed" />
|
||||||
<corners android:topLeftRadius="20dp" android:topRightRadius="20dp" />
|
<corners
|
||||||
<stroke android:width="1dp" android:color="@color/statistic_sheet_background_border"/>
|
android:topLeftRadius="20dp"
|
||||||
|
android:topRightRadius="20dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/statistic_sheet_background_border" />
|
||||||
|
|
||||||
</shape>
|
</shape>
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:top="0dp"
|
android:bottom="-2dp"
|
||||||
android:left="-2dp"
|
android:left="-2dp"
|
||||||
android:right="-2dp"
|
android:right="-2dp"
|
||||||
android:bottom="-2dp">
|
android:top="0dp">
|
||||||
|
|
||||||
<shape
|
<shape android:shape="rectangle">
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/statistic_sheet_background_expanded" />
|
<solid android:color="@color/statistic_sheet_background_expanded" />
|
||||||
<corners android:topLeftRadius="20dp" android:topRightRadius="20dp" />
|
<corners
|
||||||
<stroke android:width="1dp" android:color="@color/statistic_sheet_background_border"/>
|
android:topLeftRadius="20dp"
|
||||||
|
android:topRightRadius="20dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/statistic_sheet_background_border" />
|
||||||
|
|
||||||
</shape>
|
</shape>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/map_fragment"
|
android:id="@+id/map_fragment"
|
||||||
|
@ -14,8 +13,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/descr_map_current_track"
|
android:contentDescription="@string/descr_map_current_track"
|
||||||
android:visibility="visible">
|
android:visibility="visible" />
|
||||||
</org.osmdroid.views.MapView>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/track_list_onboarding"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:id="@+id/track_list_onboarding">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -49,8 +49,8 @@
|
||||||
tools:layout="@layout/fragment_track">
|
tools:layout="@layout/fragment_track">
|
||||||
<argument
|
<argument
|
||||||
android:name="delete_track_id"
|
android:name="delete_track_id"
|
||||||
app:argType="long"
|
android:defaultValue="-1L"
|
||||||
android:defaultValue="-1L" />
|
app:argType="long" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources />
|
||||||
|
|
|
@ -31,17 +31,21 @@
|
||||||
|
|
||||||
|
|
||||||
<!-- COLOR NAMES -->
|
<!-- COLOR NAMES -->
|
||||||
<color name="trackbook_grey">#FF595959</color> <!-- derived from recommended dark theme surface color -->
|
|
||||||
|
<!-- derived from recommended dark theme surface color -->
|
||||||
|
<color name="trackbook_grey">#FF595959</color>
|
||||||
<color name="trackbook_grey_light">#FF7D7D7D</color>
|
<color name="trackbook_grey_light">#FF7D7D7D</color>
|
||||||
<color name="trackbook_grey_lighter">#FFDADADA</color>
|
<color name="trackbook_grey_lighter">#FFDADADA</color>
|
||||||
<color name="trackbook_grey_very_light">#FFF2F2F2</color>
|
<color name="trackbook_grey_very_light">#FFF2F2F2</color>
|
||||||
<color name="trackbook_grey_dark">#FF414141</color>
|
<color name="trackbook_grey_dark">#FF414141</color>
|
||||||
<color name="trackbook_grey_darker">#FF2D2D2D</color>
|
<color name="trackbook_grey_darker">#FF2D2D2D</color>
|
||||||
|
|
||||||
<color name="trackbook_red">#DC3D33</color> <!-- Slightly muted variant of -> Material Design 2: Red 600 -->
|
<!-- Slightly muted variant of -> Material Design 2: Red 600 -->
|
||||||
|
<color name="trackbook_red">#DC3D33</color>
|
||||||
<color name="trackbook_red_dark">#FFCA2D23</color>
|
<color name="trackbook_red_dark">#FFCA2D23</color>
|
||||||
|
|
||||||
<color name="trackbook_black">#FF121212</color> <!-- Material Design recommended dark theme surface color -->
|
<!-- Material Design recommended dark theme surface color -->
|
||||||
|
<color name="trackbook_black">#FF121212</color>
|
||||||
<color name="trackbook_white">#FFFFFFFF</color>
|
<color name="trackbook_white">#FFFFFFFF</color>
|
||||||
<color name="trackbook_transparent">#00ffffff</color>
|
<color name="trackbook_transparent">#00ffffff</color>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
<item name="custom_match_parent" type="dimen">-1</item> <!-- CUSTOM MATCH_PARENT - see https://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html#MATCH_PARENT -->
|
|
||||||
|
<!-- CUSTOM MATCH_PARENT - see https://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html#MATCH_PARENT -->
|
||||||
|
<item name="custom_match_parent" type="dimen">-1</item>
|
||||||
|
|
||||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
<dimen name="bottom_sheet_width">@dimen/custom_match_parent</dimen>
|
<dimen name="bottom_sheet_width">@dimen/custom_match_parent</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -20,9 +20,11 @@
|
||||||
<item name="buttonBarNegativeButtonStyle">@style/NegativeButtonStyle</item>
|
<item name="buttonBarNegativeButtonStyle">@style/NegativeButtonStyle</item>
|
||||||
<item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
|
<item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="NegativeButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
<style name="NegativeButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||||
<item name="android:textColor">@color/text_default</item>
|
<item name="android:textColor">@color/text_default</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="PositiveButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
<style name="PositiveButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||||
<item name="android:textColor">@color/text_default</item>
|
<item name="android:textColor">@color/text_default</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths>
|
<paths>
|
||||||
<external-path name="external_files" path="."/>
|
<external-path
|
||||||
|
name="external_files"
|
||||||
|
path="." />
|
||||||
</paths>
|
</paths>
|
10
build.gradle
10
build.gradle
|
@ -1,16 +1,19 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.72'
|
ext {
|
||||||
|
kotlin_version = '1.3.72'
|
||||||
|
navigation_version = '2.3.0'
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
|
@ -20,7 +23,6 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,5 @@
|
||||||
#Sat May 30 22:16:24 CEST 2020
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
|
||||||
|
|
53
gradlew
vendored
53
gradlew
vendored
|
@ -1,5 +1,21 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
|
@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
@ -66,6 +82,7 @@ esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
@ -109,10 +126,11 @@ if $darwin; then
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
@ -138,19 +156,19 @@ if $cygwin ; then
|
||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -159,14 +177,9 @@ save () {
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|
22
gradlew.bat
vendored
22
gradlew.bat
vendored
|
@ -1,3 +1,19 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
|
@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
@ -65,6 +84,7 @@ set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
# F-Droid Assets
|
# F-Droid Assets
|
||||||
This folder contains the assets used on [Trackbook's F-Droid store page](https://f-droid.org/repository/browse/?fdid=org.y20k.trackbook). More about adding assets -> [All About Descriptions, Graphics, and Screenshots](https://f-droid.org/en/docs/All_About_Descriptions_Graphics_and_Screenshots/).
|
This folder contains the assets used on [Trackbook's F-Droid store page](https://f-droid.org/packages/org.y20k.trackbook/).
|
||||||
|
More about adding assets -> [All About Descriptions, Graphics, and Screenshots](https://f-droid.org/docs/All_About_Descriptions_Graphics_and_Screenshots/).
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
Trackbook is a bare bones app for recording your movements. Trackbook is great for hiking, vacation or workout. Once started it traces your movements on a map. The map data is provided by OpenStreetMap (OSM).
|
Trackbook is a bare-bones app for recording your movements. Trackbook is great for hiking, vacationing, or working out. Once started, it traces your movements on a map. The map data is provided by OpenStreetMap (OSM).
|
||||||
|
|
||||||
Trackbook is free software. It is published under the MIT open source license. Trackbook uses osmdroid to display the map, which is also free software published under the Apache License.
|
Trackbook is free software. It is published under the MIT open-source license. Trackbook uses osmdroid to display the map, which is also free software published under the Apache License.
|
||||||
|
|
||||||
GOOD TO KNOW
|
GOOD TO KNOW
|
||||||
------------
|
------------
|
||||||
|
|
||||||
START RECORDING VIA QUICK SETTINGS TILE
|
START RECORDING VIA QUICK SETTINGS TILE
|
||||||
You can start a recording without opening Trackbook. Just pull down the System's Quick Settings and tap on the Start Recording tile. You need to manually add Trackbook's Recording tile to Quick Settings first. Information on customizing Quick Settings:
|
You can start a recording without opening Trackbook. Just pull down the System's Quick Settings and tap on the Start Recording tile. You'll need to manually add Trackbook's Recording tile to Quick Settings first. Information on customizing Quick Settings:
|
||||||
https://support.google.com/android/answer/9083864?hl=en
|
https://support.google.com/android/answer/9083864
|
||||||
https://www.xda-developers.com/get-custom-quick-settings-tiles/
|
https://www.xda-developers.com/get-custom-quick-settings-tiles/
|
||||||
|
|
||||||
SAVE RECORDINGS AS GPX
|
SAVE RECORDINGS AS GPX
|
||||||
Recordings can be exported as GPX (GPS Exchange Format). Tap on the save button in the lower right corner of a previously recorded track.
|
Recordings can be exported as GPX (GPS Exchange Format). Tap on the save button in the lower-right corner of a previously recorded track.
|
||||||
|
|
||||||
COPY GPX FILES MANUALLY
|
COPY GPX FILES MANUALLY
|
||||||
Trackbook automatically generates GPX files for every recording. You can find them in the folder /Android/data/org.y20k.trackbook/files/gpx/ on your device's storage.
|
Trackbook automatically generates GPX files for every recording. You can find them in the folder /Android/data/org.y20k.trackbook/files/gpx/ on your device's storage.
|
||||||
|
@ -23,7 +23,7 @@ HOW DOES TRACKBOOK MEASURE ALTITUDE?
|
||||||
Many devices have altitude sensors (of varying accuracy). Trackbook compares the altitude of each new marker with the previously stored altitude. The difference is added to either the uphill or downhill elevation value.
|
Many devices have altitude sensors (of varying accuracy). Trackbook compares the altitude of each new marker with the previously stored altitude. The difference is added to either the uphill or downhill elevation value.
|
||||||
|
|
||||||
WHAT DOES ACCURACY THRESHOLD MEAN?
|
WHAT DOES ACCURACY THRESHOLD MEAN?
|
||||||
Every location fix, that Trackbook receives, is associated with an accuracy estimate. You can look up, how Android defines accuracy, in the developer documentation. "Accuracy Threshold" is the value, from which on location fixes are rejected. It can be adjusted in Trackbook's settings. You can increase the value, if your recordings tend to be incomplete. Trackbook will then also record less accurate location fixes.
|
Every location fix that Trackbook receives is associated with an accuracy estimate. You can look up how Android defines accuracy in the developer documentation. "Accuracy Threshold" is the value from which location fixes are rejected. It can be adjusted in Trackbook's settings. You can increase the value if your recordings tend to be incomplete. Trackbook will then also record less accurate location fixes.
|
||||||
|
|
||||||
WHERE ARE MY OLD RECORDINGS?
|
WHERE ARE MY OLD RECORDINGS?
|
||||||
----------------------------
|
----------------------------
|
||||||
|
@ -31,6 +31,6 @@ The F-Droid version of Trackbook features an auto-importer for old recordings. S
|
||||||
|
|
||||||
A WORD ON PRIVACY
|
A WORD ON PRIVACY
|
||||||
-----------------
|
-----------------
|
||||||
Trackbook begins to store location data on device as soon a user presses the record button. Those recordings are stored in the directory /Android/data/org.y20k.trackbook/files/. They never leave the device. There is no web-service backing Trackbook.
|
Trackbook begins to store location data on a device as soon as a user presses the record button. Those recordings are stored in the directory /Android/data/org.y20k.trackbook/files/. They never leave the device. There is no web-service backing Trackbook.
|
||||||
|
|
||||||
Trackbook does not use Google Play Services to get its location data. It will however try to use data from the NETWORK_PROVIDER on your device to augment the location data it received via GPS. The NETWORK_PROVIDER is a system-wide service, that Trackbook has no control over. This service will usually query an online database for the location of cell towers or Wi-Fi access points a device can see. You can prevent those kinds of requests on your device, if you set the location preferences system-wide to "Device Only". Additionally Trackbook offers a "Restrict to GPS" setting, that deactivates the NETWORK_PROVIDER just within the app.
|
Trackbook does not use Google Play Services to get its location data. It will, however, try to use data from the NETWORK_PROVIDER on your device to augment the location data it received via GPS. The NETWORK_PROVIDER is a system-wide service that Trackbook has no control over. This service will usually query an online database for the location of cell towers or Wi-Fi access points a device can see. You can prevent those kinds of requests on your device if you set the location preferences system-wide to "Device Only". Additionally, Trackbook offers a "Restrict to GPS" setting that deactivates the NETWORK_PROVIDER just within the app.
|
||||||
|
|
Loading…
Reference in a new issue