diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b1b9eb9
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
new file mode 100644
index 0000000..405a2b3
--- /dev/null
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -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
diff --git a/AUTHORS.md b/AUTHORS.md
index 950f3f6..39526c5 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -2,7 +2,7 @@ AUTHORS
=======
### 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
Chinese version: [yzqzss](https://github.com/yzqzss) | [weblate version history](https://hosted.weblate.org/changes/?lang=zh_HANS-CN&project=trackbook)
diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md
index ad7d1fc..db1e6c2 100644
--- a/CONTRIBUTE.md
+++ b/CONTRIBUTE.md
@@ -2,18 +2,20 @@ How to contribute to Trackbook
==============================
### 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
-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
-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.
### Suggested issues to tackle
[#19](https://github.com/y20k/trackbook/issues/19)
### 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.
-To be redeemed in Stuttgart.
+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 (to be redeemed in Stuttgart).
diff --git a/README.md b/README.md
index af99c45..dbca2c8 100644
--- a/README.md
+++ b/README.md
@@ -1,57 +1,96 @@
-README
-======
-
# Trackbook - Android Movement Recorder
-
+
**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
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).
-[](https://play.google.com/store/apps/details?id=org.y20k.trackbook)
+[](https://play.google.com/store/apps/details?id=org.y20k.trackbook)
+[](https://f-droid.org/packages/org.y20k.trackbook/)
-[](https://f-droid.org/repository/browse/?fdid=org.y20k.trackbook)
## Good To Know
### Start Recording via Quick Settings Tile
-[](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/)
+
+
+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
-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
-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.
-### 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.
+### 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.
-### 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.
+### 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 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?
-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
-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.
+## A word on privacy
+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)
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/01-map-recording-active.png)
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/02-map-context-menu.png)
+
+
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/03-track-list.png)
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/04-track.png)
+
+
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/05-settings.png)
-[](https://raw.githubusercontent.com/y20k/trackbook/master/metadata/en-US/phoneScreenshots/06-quick-settings-tile.png)
+
+
diff --git a/app/build.gradle b/app/build.gradle
index e96c1b0..91533bd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,7 +5,7 @@ apply plugin: 'androidx.navigation.safeargs.kotlin'
android {
compileSdkVersion 29
- // buildToolsVersion is optional because the plugin uses a recommended version by default
+ buildToolsVersion "29.0.3"
defaultConfig {
applicationId 'org.y20k.trackbook'
@@ -25,63 +25,46 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
- lintOptions{
- disable 'MissingTranslation'
+ lintOptions {
+ disable 'MissingTranslation', 'GoogleAppIndexingWarning'
+ }
+
+ packagingOptions {
+ exclude 'META-INF/*'
}
buildTypes {
release {
- // Enables code shrinking, obfuscation, and optimization for only
- // your project's release build type.
minifyEnabled true
-
- // Enables resource shrinking, which is performed by the
- // Android Gradle plugin.
shrinkResources true
-
- // 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'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
-
debug {
- // Comment out the below lines if you do not need to test resource shrinking
-// minifyEnabled true
-// shrinkResources true
-// proguardFiles getDefaultProguardFile(
-// 'proguard-android-optimize.txt'),
-// 'proguard-rules.pro'
+ applicationIdSuffix ".debug"
}
-
}
-
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
+// Kotlin
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.4"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
+// AndroidX
implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation "androidx.core:core-ktx:1.3.1"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
- implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
- implementation "androidx.preference:preference-ktx:1.1.1"
+ implementation 'androidx.core:core-ktx:1.3.1'
+ implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
+ 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'
- implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
+// Gson
+ implementation 'com.google.code.gson:gson:2.8.6'
-
- implementation "com.google.android.material:material:1.2.0-beta01"
- implementation "com.google.code.gson:gson:2.8.6"
-
- implementation "org.osmdroid:osmdroid-android:6.1.8"
+// OpenStreetMap
+ implementation 'org.osmdroid:osmdroid-android:6.1.8'
}
androidExtensions {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c7bfd6..9c1e41e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,10 +1,11 @@
+ package="org.y20k.trackbook">
-
+
@@ -19,27 +20,26 @@
+ android:name=".Trackbook"
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
-
-
+
+
+ android:exported="false"
+ android:foregroundServiceType="location">
@@ -50,8 +50,8 @@
@@ -60,13 +60,13 @@
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="${applicationId}.provider"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/provider_paths" />
diff --git a/app/src/main/java/org/y20k/trackbook/Keys.kt b/app/src/main/java/org/y20k/trackbook/Keys.kt
index 30a3609..cdd27c5 100644
--- a/app/src/main/java/org/y20k/trackbook/Keys.kt
+++ b/app/src/main/java/org/y20k/trackbook/Keys.kt
@@ -44,8 +44,9 @@ object Keys {
const val ARG_TRACK_ID: String = "ArgTrackId"
// 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_THEME_SELECTION: String= "prefThemeSelection"
+ 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_CURRENT_BEST_LOCATION_PROVIDER: String = "prefCurrentBestLocationProvider"
const val PREF_CURRENT_BEST_LOCATION_LATITUDE: String = "prefCurrentBestLocationLatitude"
const val PREF_CURRENT_BEST_LOCATION_LONGITUDE: String = "prefCurrentBestLocationLongitude"
@@ -77,7 +78,7 @@ object Keys {
const val DIALOG_EMPTY_PAYLOAD_INT: Int = -1
// folder names
- const val FOLDER_TEMP: String = "temp"
+ const val FOLDER_TEMP: String = "temp"
const val FOLDER_TRACKS: String = "tracks"
const val FOLDER_GPX: String = "gpx"
@@ -95,21 +96,29 @@ object Keys {
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 EMPTY_STRING_RESOURCE: Int = 0
- const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1000L // 1 second in milliseconds
- const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 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 REQUEST_CURRENT_LOCATION_INTERVAL: Long =
+ 1000L // 1 second in milliseconds
+ const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long =
+ 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 DEFAULT_LATITUDE: Double = 71.172500 // latitude Nordkapp, Norway
- const val DEFAULT_LONGITUDE: Double = 25.784444 // longitude Nordkapp, Norway
+ const val DEFAULT_LATITUDE: Double =
+ 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_ALTITUDE: Double = 0.0
const val DEFAULT_TIME: Long = 0L
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_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
// requests
diff --git a/app/src/main/java/org/y20k/trackbook/MainActivity.kt b/app/src/main/java/org/y20k/trackbook/MainActivity.kt
index 9f70971..6d898e3 100644
--- a/app/src/main/java/org/y20k/trackbook/MainActivity.kt
+++ b/app/src/main/java/org/y20k/trackbook/MainActivity.kt
@@ -69,7 +69,8 @@ class MainActivity : AppCompatActivity() {
// set up views
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(R.id.bottom_navigation_view)
bottomNavigationView.setupWithNavController(navController = navHostFragment.navController)
@@ -77,12 +78,13 @@ class MainActivity : AppCompatActivity() {
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.fragment_track -> {
- runOnUiThread( Runnable() {
- run(){
+ runOnUiThread {
+ run {
// 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 -> {
// do nothing
@@ -91,7 +93,8 @@ class MainActivity : AppCompatActivity() {
}
// register listener for changes in shared preferences
- PreferenceManager.getDefaultSharedPreferences(this as Context).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+ PreferenceManager.getDefaultSharedPreferences(this as Context)
+ .registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
}
@@ -99,20 +102,22 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
// 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
*/
- private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
- when (key) {
- Keys.PREF_THEME_SELECTION -> {
- AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection(this@MainActivity))
+ private val sharedPreferenceChangeListener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ when (key) {
+ Keys.PREF_THEME_SELECTION -> {
+ AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection(this@MainActivity))
+ }
}
}
- }
/*
* End of declaration
*/
diff --git a/app/src/main/java/org/y20k/trackbook/MapFragment.kt b/app/src/main/java/org/y20k/trackbook/MapFragment.kt
index d9f6ce0..d252872 100644
--- a/app/src/main/java/org/y20k/trackbook/MapFragment.kt
+++ b/app/src/main/java/org/y20k/trackbook/MapFragment.kt
@@ -73,9 +73,20 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
/* 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
- 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
layout.currentLocationButton.setOnClickListener {
@@ -104,11 +115,22 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
override fun onStart() {
super.onStart()
// request location permission if denied
- if (ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
- this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), Keys.REQUEST_CODE_FOREGROUND)
+ if (ContextCompat.checkSelfPermission(
+ 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
- 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 */
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
Keys.REQUEST_CODE_FOREGROUND -> {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// permission was granted - re-bind service
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.")
} else {
// permission denied - unbind service
@@ -163,7 +193,12 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
/* 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)
when (type) {
Keys.DIALOG_EMPTY_RECORDING -> {
@@ -218,11 +253,18 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
/* Saves track - shows dialog, if recording is still empty */
private fun saveTrack() {
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 {
GlobalScope.launch {
// 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()
// step 2: save track
FileHelper.saveTrackSuspended(track, saveGpxToo = true)
@@ -239,7 +281,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
/* Opens a track in TrackFragment */
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_FILE_URI, tracklistElement.trackUriString)
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
@@ -251,16 +293,17 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
/*
* Defines the listener for changes in shared preferences
*/
- private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
- when (key) {
- Keys.PREF_TRACKING_STATE -> {
- if (activity != null) {
- trackingState = PreferencesHelper.loadTrackingState(activity as Context)
- layout.updateRecordingButton(trackingState)
+ private val sharedPreferenceChangeListener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ when (key) {
+ Keys.PREF_TRACKING_STATE -> {
+ if (activity != null) {
+ trackingState = PreferencesHelper.loadTrackingState(activity as Context)
+ layout.updateRecordingButton(trackingState)
+ }
}
}
}
- }
/*
* End of declaration
*/
@@ -279,15 +322,18 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
trackingState = trackerService.trackingState
layout.updateRecordingButton(trackingState)
// 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
handler.removeCallbacks(periodicLocationRequestRunnable)
handler.postDelayed(periodicLocationRequestRunnable, 0)
}
+
override fun onServiceDisconnected(arg0: ComponentName) {
bound = false
// 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
handler.removeCallbacks(periodicLocationRequestRunnable)
}
@@ -312,7 +358,9 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlay.Mark
layout.markCurrentPosition(currentBestLocation, trackingState)
layout.overlayCurrentTrack(track, trackingState)
// 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
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
// use the handler to start runnable again after specified delay
diff --git a/app/src/main/java/org/y20k/trackbook/SettingsFragment.kt b/app/src/main/java/org/y20k/trackbook/SettingsFragment.kt
index ed33fc7..f938087 100644
--- a/app/src/main/java/org/y20k/trackbook/SettingsFragment.kt
+++ b/app/src/main/java/org/y20k/trackbook/SettingsFragment.kt
@@ -63,7 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val screen = preferenceManager.createPreferenceScreen(context)
// 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.setIcon(R.drawable.ic_gps_24dp)
preferenceGpsOnly.key = Keys.PREF_GPS_ONLY
@@ -72,26 +72,41 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceGpsOnly.setDefaultValue(false)
// set up "Use Imperial Measurements" preference
- val preferenceImperialMeasurementUnits: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
- preferenceImperialMeasurementUnits.title = getString(R.string.pref_imperial_measurement_units_title)
+ val preferenceImperialMeasurementUnits = SwitchPreferenceCompat(activity as Context)
+ preferenceImperialMeasurementUnits.title =
+ getString(R.string.pref_imperial_measurement_units_title)
preferenceImperialMeasurementUnits.setIcon(R.drawable.ic_square_foot_24px)
preferenceImperialMeasurementUnits.key = Keys.PREF_USE_IMPERIAL_UNITS
- preferenceImperialMeasurementUnits.summaryOn = getString(R.string.pref_imperial_measurement_units_summary_imperial)
- preferenceImperialMeasurementUnits.summaryOff = getString(R.string.pref_imperial_measurement_units_summary_metric)
+ preferenceImperialMeasurementUnits.summaryOn =
+ getString(R.string.pref_imperial_measurement_units_summary_imperial)
+ preferenceImperialMeasurementUnits.summaryOff =
+ getString(R.string.pref_imperial_measurement_units_summary_metric)
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
// 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.setIcon(R.drawable.ic_smartphone_24dp)
preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION
- preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme(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.summary =
+ "${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme(
+ 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 ->
if (preference is ListPreference) {
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
} else {
return@setOnPreferenceChangeListener false
@@ -99,17 +114,22 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
}
// 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.setIcon(R.drawable.ic_delete_24dp)
preferenceDeleteNonStarred.summary = getString(R.string.pref_delete_non_starred_summary)
- 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)
+ 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
+ )
return@setOnPreferenceClickListener true
}
// 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.setIcon(R.drawable.ic_timeline_24dp)
preferenceAccuracyThreshold.key = Keys.PREF_LOCATION_ACCURACY_THRESHOLD
@@ -119,32 +139,39 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceAccuracyThreshold.setDefaultValue(Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
// 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.setIcon(R.drawable.ic_undo_24dp)
preferenceResetAdvanced.summary = getString(R.string.pref_reset_advanced_summary)
- preferenceResetAdvanced.setOnPreferenceClickListener{
+ preferenceResetAdvanced.setOnPreferenceClickListener {
preferenceAccuracyThreshold.value = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
return@setOnPreferenceClickListener true
}
// set up "App Version" preference
- val preferenceAppVersion: Preference = Preference(context)
+ val preferenceAppVersion = Preference(context)
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
- preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
- R.string.app_version_name)})"
+ preferenceAppVersion.summary =
+ "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
+ R.string.app_version_name
+ )})"
preferenceAppVersion.setOnPreferenceClickListener {
// copy to clipboard
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)
- 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
}
// set up "Report Issue" preference
- val preferenceReportIssue: Preference = Preference(context)
+ val preferenceReportIssue = Preference(context)
preferenceReportIssue.title = getString(R.string.pref_report_issue_title)
preferenceReportIssue.setIcon(R.drawable.ic_bug_report_24dp)
preferenceReportIssue.summary = getString(R.string.pref_report_issue_summary)
@@ -159,20 +186,21 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
}
// 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.contains(preferenceImperialMeasurementUnits)
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.contains(preferenceDeleteNonStarred)
- val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context)
+ val preferenceCategoryAdvanced = PreferenceCategory(activity as Context)
preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title)
preferenceCategoryAdvanced.contains(preferenceAccuracyThreshold)
preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
- val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
+ val preferenceCategoryAbout = PreferenceCategory(context)
preferenceCategoryAbout.title = getString(R.string.pref_about_title)
preferenceCategoryAbout.contains(preferenceAppVersion)
preferenceCategoryAbout.contains(preferenceReportIssue)
@@ -195,7 +223,12 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* 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) {
Keys.DIALOG_DELETE_NON_STARRED -> {
when (dialogResult) {
@@ -213,12 +246,13 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* Removes track and track files for given position - used by TracklistFragment */
- fun deleteNonStarred(context: Context) {
+ private fun deleteNonStarred(context: Context) {
val backgroundJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
uiScope.launch {
var tracklist: Tracklist = FileHelper.readTracklist(context)
- val deferred: Deferred = async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
+ val deferred: Deferred =
+ async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
// wait for result and store in tracklist
tracklist = deferred.await()
backgroundJob.cancel()
diff --git a/app/src/main/java/org/y20k/trackbook/TrackFragment.kt b/app/src/main/java/org/y20k/trackbook/TrackFragment.kt
index d33305d..debd51d 100644
--- a/app/src/main/java/org/y20k/trackbook/TrackFragment.kt
+++ b/app/src/main/java/org/y20k/trackbook/TrackFragment.kt
@@ -24,7 +24,9 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
+import android.os.Build
import android.os.Bundle
+import android.os.VibrationEffect
import android.os.Vibrator
import android.view.LayoutInflater
import android.view.View
@@ -46,7 +48,8 @@ import org.y20k.trackbook.helpers.TrackHelper
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 */
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
@@ -61,19 +64,30 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get track
- val fileUriString: String = arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
- if (fileUriString.isNotBlank()) {
- track = FileHelper.readTrack(activity as Context, Uri.parse(fileUriString))
+ val fileUriString: String =
+ arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
+ track = if (fileUriString.isNotBlank()) {
+ FileHelper.readTrack(Uri.parse(fileUriString))
} else {
- track = Track()
+ Track()
}
}
/* 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
- 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
layout.shareButton.setOnClickListener {
@@ -81,18 +95,31 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
}
layout.shareButton.setOnLongClickListener {
val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
- v.vibrate(50)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ v.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE))
+ } else {
+ v.vibrate(50)
+ }
shareGpxTrack()
return@setOnLongClickListener true
}
// set up delete button
layout.deleteButton.setOnClickListener {
- val dialogMessage: String = "${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)
+ val dialogMessage =
+ "${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
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
@@ -125,8 +152,18 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
val targetUri: Uri? = data.data
if (targetUri != null) {
// copy file async (= fire & forget - no return value needed)
- GlobalScope.launch { FileHelper.saveCopyOfFileSuspended( activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri) }
- Toast.makeText(activity as Context, R.string.toast_message_save_gpx, Toast.LENGTH_LONG).show()
+ GlobalScope.launch {
+ 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 */
override fun onRenameTrackDialog(textInput: String) {
// 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
layout.track.name = textInput
layout.trackNameView.text = textInput
@@ -147,7 +190,12 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
/* 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) {
Keys.DIALOG_DELETE_TRACK -> {
when (dialogResult) {
@@ -189,7 +237,11 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
if (packageManager != null && intent.resolveActivity(packageManager) != null) {
startActivityForResult(intent, Keys.REQUEST_SAVE_GPX)
} 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 */
private fun shareGpxTrack() {
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 {
action = Intent.ACTION_SEND
data = gpxShareUri
@@ -211,7 +267,8 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
if (packageManager != null && shareIntent.resolveActivity(packageManager) != null) {
startActivity(shareIntent)
} 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()
}
}
diff --git a/app/src/main/java/org/y20k/trackbook/Trackbook.kt b/app/src/main/java/org/y20k/trackbook/Trackbook.kt
index cc9615d..af38515 100644
--- a/app/src/main/java/org/y20k/trackbook/Trackbook.kt
+++ b/app/src/main/java/org/y20k/trackbook/Trackbook.kt
@@ -27,7 +27,7 @@ import org.y20k.trackbook.helpers.PreferencesHelper
/*
* Trackbook.class
*/
-class Trackbook: Application() {
+class Trackbook : Application() {
/* Define log tag */
@@ -49,4 +49,4 @@ class Trackbook: Application() {
LogHelper.v(TAG, "Trackbook application terminated.")
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/y20k/trackbook/TrackerService.kt b/app/src/main/java/org/y20k/trackbook/TrackerService.kt
index 224810c..70ef0b5 100644
--- a/app/src/main/java/org/y20k/trackbook/TrackerService.kt
+++ b/app/src/main/java/org/y20k/trackbook/TrackerService.kt
@@ -48,7 +48,7 @@ import kotlin.coroutines.CoroutineContext
/*
* TrackerService class
*/
-class TrackerService(): Service(), CoroutineScope, SensorEventListener {
+class TrackerService : Service(), CoroutineScope, SensorEventListener {
/* Define log tag */
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 gpsProviderActive: Boolean = false
var networkProviderActive: Boolean = false
- var useImperial: Boolean = false
- var gpsOnly: Boolean = false
+ private var useImperial: Boolean = false
+ private var gpsOnly: Boolean = false
var locationAccuracyThreshold: Int = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
- var stepCountOffset: Float = 0f
+ private var stepCountOffset: Float = 0f
var resumed: Boolean = false
var track: Track = Track()
- var gpsLocationListenerRegistered: Boolean = false
- var networkLocationListenerRegistered: Boolean = false
- var bound: Boolean = false
+ private var gpsLocationListenerRegistered: Boolean = false
+ private var networkLocationListenerRegistered: Boolean = false
+ private var bound: Boolean = false
private val binder = LocalBinder()
private val handler: Handler = Handler()
private lateinit var locationManager: LocationManager
@@ -99,9 +99,10 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
networkLocationListener = createLocationListener()
trackingState = PreferencesHelper.loadTrackingState(this)
currentBestLocation = LocationHelper.getLastKnownLocation(this)
- track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
+ track = FileHelper.readTrack(FileHelper.getTempFileUri(this))
backgroundJob = Job()
- PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
}
@@ -111,16 +112,19 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
// SERVICE RESTART (via START_STICKY)
if (intent == null) {
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()
}
- // ACTION STOP
+ // ACTION STOP
} else if (Keys.ACTION_STOP == intent.action) {
stopTracking()
- // ACTION START
+ // ACTION START
} else if (Keys.ACTION_START == intent.action) {
startTracking()
- // ACTION RESUME
+ // ACTION RESUME
} else if (Keys.ACTION_RESUME == intent.action) {
resumeTracking()
}
@@ -172,7 +176,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
// remove notification
stopForeground(true)
// stop listening for changes in shared preferences
- PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
// stop receiving location updates
removeGpsLocationListener()
removeNetworkLocationListener()
@@ -189,11 +194,12 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
/* Overrides onSensorChanged from SensorEventListener */
override fun onSensorChanged(sensorEvent: SensorEvent?) {
- var steps: Float = 0f
+ var steps = 0f
if (sensorEvent != null) {
if (stepCountOffset == 0f) {
// 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
steps = sensorEvent.values[0] - stepCountOffset
@@ -206,16 +212,17 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
/* Resume tracking after stop/pause */
fun resumeTracking() {
// 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
if (track.wayPoints.size > 0) {
val lastWayPointIndex = track.wayPoints.size - 1
- track.wayPoints.get(lastWayPointIndex).isStopOver = true
+ track.wayPoints[lastWayPointIndex].isStopOver = true
}
// set resumed flag
resumed = true
// calculate length of recording break
- track.recordingPaused = track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
+ track.recordingPaused =
+ track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
// start tracking
startTracking(newTrack = false)
}
@@ -301,20 +308,27 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
currentBestLocation = location
}
}
+
override fun onProviderEnabled(provider: String) {
LogHelper.v(TAG, "onProviderEnabled $provider")
when (provider) {
- LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
- LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
+ LocationManager.GPS_PROVIDER -> gpsProviderActive =
+ LocationHelper.isGpsEnabled(locationManager)
+ LocationManager.NETWORK_PROVIDER -> networkProviderActive =
+ LocationHelper.isNetworkEnabled(locationManager)
}
}
+
override fun onProviderDisabled(provider: String) {
LogHelper.v(TAG, "onProviderDisabled $provider")
when (provider) {
- LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
- LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
+ LocationManager.GPS_PROVIDER -> gpsProviderActive =
+ LocationHelper.isGpsEnabled(locationManager)
+ LocationManager.NETWORK_PROVIDER -> networkProviderActive =
+ LocationHelper.isNetworkEnabled(locationManager)
}
}
+
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) {
// deprecated method
}
@@ -329,13 +343,25 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
// check if Network provider is available
if (gpsProviderActive) {
// 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
- locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f,gpsLocationListener)
+ locationManager.requestLocationUpdates(
+ LocationManager.GPS_PROVIDER,
+ 0,
+ 0f,
+ gpsLocationListener
+ )
gpsLocationListenerRegistered = true
LogHelper.v(TAG, "Added GPS location listener.")
} 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 {
LogHelper.w(TAG, "Unable to add GPS location listener.")
@@ -353,50 +379,83 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
// check if Network provider is available
if (networkProviderActive && !gpsOnly) {
// 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
- locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f, networkLocationListener)
+ locationManager.requestLocationUpdates(
+ LocationManager.NETWORK_PROVIDER,
+ 0,
+ 0f,
+ networkLocationListener
+ )
networkLocationListenerRegistered = true
LogHelper.v(TAG, "Added Network location listener.")
} 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 {
LogHelper.w(TAG, "Unable to add Network location listener.")
}
} 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 */
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)
gpsLocationListenerRegistered = false
LogHelper.v(TAG, "Removed GPS location listener.")
} 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 */
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)
networkLocationListenerRegistered = false
LogHelper.v(TAG, "Removed Network location listener.")
} 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 */
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) {
LogHelper.w(TAG, "Pedometer sensor not available.")
track.stepCount = -1f
@@ -406,7 +465,12 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
/* Displays / updates 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)
return notification
}
@@ -415,7 +479,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
/*
* Defines the listener for changes in shared preferences
*/
- private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
+ private val sharedPreferenceChangeListener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
when (key) {
// preference "Restrict to GPS"
Keys.PREF_GPS_ONLY -> {
@@ -431,7 +496,8 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener {
}
// preference "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 {
override fun run() {
// add waypoint to track - step count is continuously updated in onSensorChanged
- val result: Pair