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 = TrackHelper.addWayPointToTrack(this@TrackerService, track, currentBestLocation, locationAccuracyThreshold, resumed) + val result: Pair = TrackHelper.addWayPointToTrack( + track, + currentBestLocation, + locationAccuracyThreshold, + resumed + ) // get track from result track = result.first // check if waypoint was successfully added (= result.second) @@ -478,4 +549,4 @@ class TrackerService(): Service(), CoroutineScope, SensorEventListener { */ -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/TrackingToggleTileService.kt b/app/src/main/java/org/y20k/trackbook/TrackingToggleTileService.kt index 3234150..cca22aa 100644 --- a/app/src/main/java/org/y20k/trackbook/TrackingToggleTileService.kt +++ b/app/src/main/java/org/y20k/trackbook/TrackingToggleTileService.kt @@ -31,7 +31,7 @@ import org.y20k.trackbook.helpers.PreferencesHelper /* * TrackingToggleTileService class */ -class TrackingToggleTileService(): TileService() { +class TrackingToggleTileService : TileService() { /* Define log tag */ private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java) @@ -52,11 +52,6 @@ class TrackingToggleTileService(): TileService() { updateTile() } - /* Overrides onTileRemoved from TileService */ - override fun onTileRemoved() { - super.onTileRemoved() - } - /* Overrides onStartListening from TileService (tile becomes visible) */ override fun onStartListening() { @@ -66,7 +61,8 @@ class TrackingToggleTileService(): TileService() { // set up tile updateTile() // 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() { super.onStopListening() // unregister listener for changes in shared preferences - PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener) - } - - - /* Overrides onDestroy from Service */ - override fun onDestroy() { - super.onDestroy() + PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService) + .unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener) } @@ -138,19 +129,18 @@ class TrackingToggleTileService(): TileService() { /* * Defines the listener for changes in shared preferences */ - private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> - when (key) { - Keys.PREF_TRACKING_STATE -> { - trackingState = PreferencesHelper.loadTrackingState(this) - updateTile() + private val sharedPreferenceChangeListener = + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + when (key) { + Keys.PREF_TRACKING_STATE -> { + trackingState = PreferencesHelper.loadTrackingState(this) + updateTile() + } } } - } /* * End of declaration */ - - -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/TracklistFragment.kt b/app/src/main/java/org/y20k/trackbook/TracklistFragment.kt index b673e8e..ed53b1d 100644 --- a/app/src/main/java/org/y20k/trackbook/TracklistFragment.kt +++ b/app/src/main/java/org/y20k/trackbook/TracklistFragment.kt @@ -40,7 +40,8 @@ import org.y20k.trackbook.tracklist.TracklistAdapter /* * TracklistFragment class */ -class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, YesNoDialog.YesNoDialogListener { +class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, + YesNoDialog.YesNoDialogListener { /* Define log tag */ private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java) @@ -61,7 +62,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, /* 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 val rootView = inflater.inflate(R.layout.fragment_tracklist, container, false) 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) { // ask user val adapterPosition: Int = viewHolder.adapterPosition - val dialogMessage: String = "${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 dialogMessage = + "${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) @@ -93,7 +107,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, /* Overrides onTrackElementTapped from TracklistElementAdapterListener */ 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_FILE_URI, tracklistElement.trackUriString) bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString) @@ -103,13 +117,18 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, /* 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) { // user tapped remove track true -> { - toggleOnboardingLayout(tracklistAdapter.itemCount -1) + toggleOnboardingLayout(tracklistAdapter.itemCount - 1) tracklistAdapter.removeTrack(activity as Context, payload) } // user tapped cancel @@ -139,11 +158,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, } - /* * 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 { return true @@ -157,7 +176,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, if (deleteTrackId != -1L) { val position: Int = tracklistAdapter.findPosition(deleteTrackId) tracklistAdapter.removeTrack(this@TracklistFragment.activity as Context, position) - toggleOnboardingLayout(tracklistAdapter.itemCount -1) + toggleOnboardingLayout(tracklistAdapter.itemCount - 1) } } diff --git a/app/src/main/java/org/y20k/trackbook/core/Track.kt b/app/src/main/java/org/y20k/trackbook/core/Track.kt index 361911b..2096ac1 100644 --- a/app/src/main/java/org/y20k/trackbook/core/Track.kt +++ b/app/src/main/java/org/y20k/trackbook/core/Track.kt @@ -32,24 +32,26 @@ import java.util.* */ @Keep @Parcelize -data class Track (@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION, - @Expose val wayPoints: MutableList = mutableListOf(), - @Expose var length: Float = 0f, - @Expose var duration: Long = 0L, - @Expose var recordingPaused: Long = 0L, - @Expose var stepCount: Float = 0f, - @Expose var recordingStart: Date = GregorianCalendar.getInstance().time, - @Expose var recordingStop: Date = recordingStart, - @Expose var maxAltitude: Double = 0.0, - @Expose var minAltitude: Double = 0.0, - @Expose var positiveElevation: Double = 0.0, - @Expose var negativeElevation: Double = 0.0, - @Expose var trackUriString: String = String(), - @Expose var gpxUriString: String = String(), - @Expose var latitude: Double = Keys.DEFAULT_LATITUDE, - @Expose var longitude: Double = Keys.DEFAULT_LONGITUDE, - @Expose var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL, - @Expose var name: String = String()): Parcelable { +data class Track( + @Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION, + @Expose val wayPoints: MutableList = mutableListOf(), + @Expose var length: Float = 0f, + @Expose var duration: Long = 0L, + @Expose var recordingPaused: Long = 0L, + @Expose var stepCount: Float = 0f, + @Expose var recordingStart: Date = GregorianCalendar.getInstance().time, + @Expose var recordingStop: Date = recordingStart, + @Expose var maxAltitude: Double = 0.0, + @Expose var minAltitude: Double = 0.0, + @Expose var positiveElevation: Double = 0.0, + @Expose var negativeElevation: Double = 0.0, + @Expose var trackUriString: String = String(), + @Expose var gpxUriString: String = String(), + @Expose var latitude: Double = Keys.DEFAULT_LATITUDE, + @Expose var longitude: Double = Keys.DEFAULT_LONGITUDE, + @Expose var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL, + @Expose var name: String = String() +) : Parcelable { /* Creates a TracklistElement */ @@ -75,4 +77,4 @@ data class Track (@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMA } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/core/Tracklist.kt b/app/src/main/java/org/y20k/trackbook/core/Tracklist.kt index 8944ed8..780231b 100644 --- a/app/src/main/java/org/y20k/trackbook/core/Tracklist.kt +++ b/app/src/main/java/org/y20k/trackbook/core/Tracklist.kt @@ -31,9 +31,11 @@ import java.util.* */ @Keep @Parcelize -data class Tracklist (@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRACKLIST_FORMAT_VERSION, - @Expose val tracklistElements: MutableList = mutableListOf(), - @Expose var modificationDate: Date = Date()): Parcelable { +data class Tracklist( + @Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRACKLIST_FORMAT_VERSION, + @Expose val tracklistElements: MutableList = mutableListOf(), + @Expose var modificationDate: Date = Date() +) : Parcelable { /* Return trackelement for given track id */ fun getTrackElement(trackId: Long): TracklistElement? { @@ -47,7 +49,11 @@ data class Tracklist (@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRA /* Create a deep copy */ fun deepCopy(): Tracklist { - return Tracklist(tracklistFormatVersion, mutableListOf().apply { addAll(tracklistElements) }, modificationDate) + return Tracklist( + tracklistFormatVersion, + mutableListOf().apply { addAll(tracklistElements) }, + modificationDate + ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/core/TracklistElement.kt b/app/src/main/java/org/y20k/trackbook/core/TracklistElement.kt index baa7e5a..bab7cd7 100644 --- a/app/src/main/java/org/y20k/trackbook/core/TracklistElement.kt +++ b/app/src/main/java/org/y20k/trackbook/core/TracklistElement.kt @@ -29,18 +29,20 @@ import java.util.* */ @Keep @Parcelize -data class TracklistElement(@Expose var name: String, - @Expose val date: Date, - @Expose val dateString: String, - @Expose val durationString: String, - @Expose val length: Float, - @Expose val trackUriString: String, - @Expose val gpxUriString: String, - @Expose var starred: Boolean = false): Parcelable { +data class TracklistElement( + @Expose var name: String, + @Expose val date: Date, + @Expose val dateString: String, + @Expose val durationString: String, + @Expose val length: Float, + @Expose val trackUriString: String, + @Expose val gpxUriString: String, + @Expose var starred: Boolean = false +) : Parcelable { /* Returns unique ID for TracklistElement - currently the start date */ fun getTrackId(): Long { return date.time } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/core/WayPoint.kt b/app/src/main/java/org/y20k/trackbook/core/WayPoint.kt index d3bf6b0..48aca0e 100644 --- a/app/src/main/java/org/y20k/trackbook/core/WayPoint.kt +++ b/app/src/main/java/org/y20k/trackbook/core/WayPoint.kt @@ -29,21 +29,23 @@ import kotlinx.android.parcel.Parcelize */ @Keep @Parcelize -data class WayPoint(@Expose val provider: String, - @Expose val latitude: Double, - @Expose val longitude: Double, - @Expose val altitude: Double, - @Expose val accuracy: Float, - @Expose val time: Long, - @Expose val distanceToStartingPoint: Float = 0f, - @Expose val numberSatellites: Int = 0, - @Expose var isStopOver: Boolean = false, - @Expose var starred: Boolean = false): Parcelable { +data class WayPoint( + @Expose val provider: String, + @Expose val latitude: Double, + @Expose val longitude: Double, + @Expose val altitude: Double, + @Expose val accuracy: Float, + @Expose val time: Long, + @Expose val distanceToStartingPoint: Float = 0f, + @Expose val numberSatellites: Int = 0, + @Expose var isStopOver: Boolean = false, + @Expose var starred: Boolean = false +) : Parcelable { /* Converts WayPoint into Location */ fun toLocation(): Location { - val location: Location = Location(provider) + val location = Location(provider) location.latitude = latitude location.longitude = longitude location.altitude = altitude @@ -52,4 +54,4 @@ data class WayPoint(@Expose val provider: String, return location } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/dialogs/ErrorDialog.kt b/app/src/main/java/org/y20k/trackbook/dialogs/ErrorDialog.kt index 48864c9..dbc6c06 100644 --- a/app/src/main/java/org/y20k/trackbook/dialogs/ErrorDialog.kt +++ b/app/src/main/java/org/y20k/trackbook/dialogs/ErrorDialog.kt @@ -18,7 +18,6 @@ package org.y20k.trackbook.dialogs import android.content.Context -import android.content.DialogInterface import android.text.method.ScrollingMovementMethod import android.view.LayoutInflater import android.view.View @@ -38,9 +37,14 @@ object ErrorDialog { /* 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 - val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) + val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) // set title builder.setTitle(context.getString(errorTitle)) @@ -81,12 +85,14 @@ object ErrorDialog { errorMessageView.text = context.getString(errorMessage) // 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 // do nothing - }) + } // display error dialog builder.show() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/dialogs/RenameTrackDialog.kt b/app/src/main/java/org/y20k/trackbook/dialogs/RenameTrackDialog.kt index 0e700a5..36b1225 100644 --- a/app/src/main/java/org/y20k/trackbook/dialogs/RenameTrackDialog.kt +++ b/app/src/main/java/org/y20k/trackbook/dialogs/RenameTrackDialog.kt @@ -30,7 +30,7 @@ import org.y20k.trackbook.helpers.LogHelper /* * RenameTrackDialog class */ -class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) { +class RenameTrackDialog(private var renameTrackListener: RenameTrackListener) { /* Interface used to communicate back to activity */ interface RenameTrackListener { @@ -45,7 +45,7 @@ class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) { /* Construct and show dialog */ fun show(context: Context, trackName: String) { // prepare dialog builder - val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context) + val builder = MaterialAlertDialogBuilder(context) // get input field val inflater = LayoutInflater.from(context) @@ -78,4 +78,4 @@ class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) { builder.show() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/dialogs/YesNoDialog.kt b/app/src/main/java/org/y20k/trackbook/dialogs/YesNoDialog.kt index 83c391e..867acf3 100644 --- a/app/src/main/java/org/y20k/trackbook/dialogs/YesNoDialog.kt +++ b/app/src/main/java/org/y20k/trackbook/dialogs/YesNoDialog.kt @@ -24,7 +24,7 @@ import org.y20k.trackbook.helpers.LogHelper /* * YesNoDialog class */ -class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) { +class YesNoDialog(private var yesNoDialogListener: YesNoDialogListener) { /* Interface used to communicate back to activity */ interface YesNoDialogListener { @@ -37,35 +37,45 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) { private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName) - - - /* Construct and show dialog - variant: message from string */ - fun show(context: Context, - type: Int, - title: Int = Keys.EMPTY_STRING_RESOURCE, - message: Int, - yesButton: Int = R.string.dialog_yes_no_positive_button_default, - noButton: Int = R.string.dialog_generic_button_cancel, - payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, - payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) { + fun show( + context: Context, + type: Int, + title: Int = Keys.EMPTY_STRING_RESOURCE, + message: Int, + yesButton: Int = R.string.dialog_yes_no_positive_button_default, + noButton: Int = R.string.dialog_generic_button_cancel, + payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, + payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING + ) { // 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 */ - fun show(context: Context, - type: Int, - title: Int = Keys.EMPTY_STRING_RESOURCE, - messageString: String, - yesButton: Int = R.string.dialog_yes_no_positive_button_default, - noButton: Int = R.string.dialog_generic_button_cancel, - payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, - payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) { + fun show( + context: Context, + type: Int, + title: Int = Keys.EMPTY_STRING_RESOURCE, + messageString: String, + yesButton: Int = R.string.dialog_yes_no_positive_button_default, + noButton: Int = R.string.dialog_generic_button_cancel, + payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, + payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING + ) { // prepare dialog builder - val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) + val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) // set title and message builder.setMessage(messageString) @@ -87,11 +97,11 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) { } // handle outside-click as "no" - builder.setOnCancelListener(){ + builder.setOnCancelListener { yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString) } // display dialog builder.show() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/extensions/SharedPreferencesExt.kt b/app/src/main/java/org/y20k/trackbook/extensions/SharedPreferencesExt.kt index b4836eb..ee6f3ad 100644 --- a/app/src/main/java/org/y20k/trackbook/extensions/SharedPreferencesExt.kt +++ b/app/src/main/java/org/y20k/trackbook/extensions/SharedPreferencesExt.kt @@ -19,11 +19,15 @@ package org.y20k.trackbook.extensions import android.content.SharedPreferences +import java.lang.Double.doubleToRawLongBits +import java.lang.Double.longBitsToDouble /* 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 */ -fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default))) \ No newline at end of file +fun SharedPreferences.getDouble(key: String, default: Double) = + longBitsToDouble(getLong(key, doubleToRawLongBits(default))) diff --git a/app/src/main/java/org/y20k/trackbook/helpers/AppThemeHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/AppThemeHelper.kt index 9500951..160e856 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/AppThemeHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/AppThemeHelper.kt @@ -99,6 +99,4 @@ object AppThemeHelper { } - - -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/DateTimeHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/DateTimeHelper.kt index 3cadcae..144e5d4 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/DateTimeHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/DateTimeHelper.kt @@ -34,22 +34,24 @@ object DateTimeHelper { /* Converts milliseconds to mm:ss or hh:mm:ss */ fun convertToReadableTime(context: Context, milliseconds: Long): String { - var timeString: String = String() + val timeString: String val hours: Long = TimeUnit.MILLISECONDS.toHours(milliseconds) - val minutes: Long = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % TimeUnit.HOURS.toMinutes(1) - val seconds: Long = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % TimeUnit.MINUTES.toSeconds(1) + val minutes: Long = + 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 m: String = context.getString(R.string.abbreviation_minutes) 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 true -> { - timeString = "$hours $h $minutes $m $seconds $s" + "$hours $h $minutes $m $seconds $s" } // CASE: format mm:ss false -> { - timeString = "$minutes $m $seconds $s" + "$minutes $m $seconds $s" } } return timeString @@ -58,7 +60,7 @@ object DateTimeHelper { /* Create sortable string for date - used for filenames */ 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) } @@ -70,14 +72,20 @@ object DateTimeHelper { /* Creates a readable string date and time - used in the UI */ - fun convertToReadableDateAndTime(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)}" + fun convertToReadableDateAndTime( + 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 */ - fun calculateTimeDistance(previousLocation: Location?, location: Location): Long { - var timeDifference: Long = 0L + fun calculateTimeDistance(previousLocation: Location?, location: Location): Long { + var timeDifference = 0L // two data points needed to calculate time difference if (previousLocation != null) { // get time difference @@ -87,4 +95,4 @@ object DateTimeHelper { } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/FileHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/FileHelper.kt index 647ff05..7111788 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/FileHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/FileHelper.kt @@ -35,6 +35,8 @@ import java.text.NumberFormat import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlin.math.ln +import kotlin.math.pow /* @@ -48,10 +50,10 @@ object FileHelper { /* Return an InputStream for given Uri */ fun getTextFileStream(context: Context, uri: Uri): InputStream? { - var stream : InputStream? = null + var stream: InputStream? = null try { stream = context.contentResolver.openInputStream(uri) - } catch (e : Exception) { + } catch (e: Exception) { e.printStackTrace() } return stream @@ -61,14 +63,14 @@ object FileHelper { /* Get file size for given Uri */ fun getFileSize(context: Context, uri: Uri): Long { 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) cursor.moveToFirst() val size: Long = cursor.getLong(sizeIndex) cursor.close() - return size + size } else { - return 0L + 0L } } @@ -76,14 +78,14 @@ object FileHelper { /* Get file name for given Uri */ fun getFileName(context: Context, uri: Uri): String { 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) cursor.moveToFirst() val name: String = cursor.getString(nameIndex) cursor.close() - return name + name } else { - return String() + String() } } @@ -110,8 +112,8 @@ object FileHelper { fun readTracklist(context: Context): Tracklist { LogHelper.v(TAG, "Reading Tracklist - Thread: ${Thread.currentThread().name}") // get JSON from text file - val json: String = readTextFile(context, getTracklistFileUri(context)) - var tracklist: Tracklist = Tracklist() + val json: String = readTextFile(getTracklistFileUri(context)) + var tracklist = Tracklist() when (json.isNotBlank()) { // convert JSON and return as tracklist true -> try { @@ -125,10 +127,10 @@ object FileHelper { /* Reads track from storage using GSON */ - fun readTrack(context: Context, fileUri: Uri): Track { + fun readTrack(fileUri: Uri): Track { // get JSON from text file - val json: String = readTextFile(context, fileUri) - var track: Track = Track() + val json: String = readTextFile(fileUri) + var track = Track() when (json.isNotEmpty()) { // convert JSON and return as track true -> try { @@ -154,16 +156,19 @@ object FileHelper { /* 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 */ - 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 */ 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() } @@ -175,7 +180,11 @@ object FileHelper { /* 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 -> val tracklist: Tracklist = readTracklist(context) tracklist.tracklistElements.add(track.toTracklistElement(context)) @@ -193,7 +202,11 @@ object FileHelper { /* 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 -> cont.resume(saveTracklist(context, tracklist, modificationDate)) } @@ -217,7 +230,11 @@ object FileHelper { /* 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 -> cont.resume(deleteTrack(context, position, tracklist)) } @@ -240,14 +257,19 @@ object FileHelper { /* Suspend function: Wrapper for readTracklist */ suspend fun readTracklistSuspended(context: Context): Tracklist { - return suspendCoroutine {cont -> + return suspendCoroutine { cont -> cont.resume(readTracklist(context)) } } /* 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 -> cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal)) } @@ -285,7 +307,7 @@ object FileHelper { tracklist.modificationDate = modificationDate // convert to JSON val gson: Gson = getCustomGson() - var json: String = String() + var json = String() try { json = gson.toJson(tracklist) } catch (e: Exception) { @@ -304,12 +326,11 @@ object FileHelper { } - /* Renames track */ private fun renameTrack(context: Context, track: Track, newName: String) { // search track in tracklist val tracklist: Tracklist = readTracklist(context) - var trackUriString: String = String() + var trackUriString = String() tracklist.tracklistElements.forEach { tracklistElement -> if (tracklistElement.getTrackId() == track.getTrackId()) { // rename tracklist element @@ -329,13 +350,17 @@ object FileHelper { /* Deletes multiple tracks */ - private fun deleteTracks(context: Context, tracklistElements: MutableList, tracklist: Tracklist): Tracklist { + private fun deleteTracks( + context: Context, + tracklistElements: MutableList, + tracklist: Tracklist + ): Tracklist { tracklistElements.forEach { tracklistElement -> // delete track files tracklistElement.trackUriString.toUri().toFile().delete() tracklistElement.gpxUriString.toUri().toFile().delete() } - tracklist.tracklistElements.removeAll{ tracklistElements.contains(it) } + tracklist.tracklistElements.removeAll { tracklistElements.contains(it) } saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) return tracklist } @@ -348,14 +373,23 @@ object FileHelper { tracklistElement.trackUriString.toUri().toFile().delete() tracklistElement.gpxUriString.toUri().toFile().delete() // 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) return tracklist } /* 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 outputStream = context.contentResolver.openOutputStream(targetFileUri) if (outputStream != null) { @@ -370,7 +404,7 @@ object FileHelper { /* Converts track to JSON */ private fun getTrackJsonString(track: Track): String { val gson: Gson = getCustomGson() - var json: String = String() + var json = String() try { json = gson.toJson(track) } catch (e: Exception) { @@ -389,7 +423,6 @@ object FileHelper { } - /* Converts byte value into a human readable format */ // Source: https://programming.guide/java/formatting-byte-size-to-human-readable-format.html fun getReadableByteCount(bytes: Long, si: Boolean = true): String { @@ -401,13 +434,13 @@ object FileHelper { if (bytes < unit) return "$bytes B" // 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 val prefix: String = ((if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i") // 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() numberFormat.maximumFractionDigits = 1 @@ -416,7 +449,7 @@ object FileHelper { /* 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 // https://developer.android.com/training/secure-file-sharing/retrieve-info val file: File = fileUri.toFile() @@ -426,11 +459,12 @@ object FileHelper { } // read until last line reached val stream: InputStream = file.inputStream() - val reader: BufferedReader = BufferedReader(InputStreamReader(stream)) + val reader = BufferedReader(InputStreamReader(stream)) val builder: StringBuilder = StringBuilder() reader.forEachLine { builder.append(it) - builder.append("\n") } + builder.append("\n") + } stream.close() return builder.toString() } @@ -444,8 +478,13 @@ object FileHelper { /* 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) { - if (file.exists()) file.delete () + private fun writeImageFile( + bitmap: Bitmap, + file: File, + format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, + quality: Int = 75 + ) { + if (file.exists()) file.delete() try { val out = FileOutputStream(file) bitmap.compress(format, quality, out) @@ -456,4 +495,4 @@ object FileHelper { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/LengthUnitHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/LengthUnitHelper.kt index d2f6e4c..25249a0 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/LengthUnitHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/LengthUnitHelper.kt @@ -89,45 +89,51 @@ object LengthUnitHelper { /* Determines which unit system the device is using (metric or imperial) */ fun useImperialUnits(): Boolean { // 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 return imperialSystemCountries.contains(countryCode) } /* 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 { - var speed: String = "0" + fun convertToVelocityString( + trackDuration: Long, + trackRecordingPause: Long, + trackLength: Float, + useImperialUnits: Boolean = false + ): String { + var speed = "0" // duration minus pause in seconds val duration: Long = (trackDuration - trackRecordingPause) / 1000L if (duration > 0L) { // speed in km/h / mph - val velocity: Double = convertMetersPerSecond((trackLength / duration), useImperialUnits) + val velocity: Double = + convertMetersPerSecond((trackLength / duration), useImperialUnits) // create readable speed string var bd: BigDecimal = BigDecimal.valueOf(velocity) bd = bd.setScale(1, RoundingMode.HALF_UP) speed = bd.toPlainString() } - when (useImperialUnits) { - true -> return "$speed mph" - false -> return "$speed km/h" + return when (useImperialUnits) { + true -> "$speed mph" + false -> "$speed km/h" } } /* Coverts meters per second to either km/h or mph */ fun convertMetersPerSecond(metersPerSecond: Float, useImperial: Boolean = false): Double { - if (useImperial) { + return if (useImperial) { // mph - return metersPerSecond * 2.2369362920544 + metersPerSecond * 2.2369362920544 } else { // km/h - return metersPerSecond * 3.6 + metersPerSecond * 3.6 } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.kt index 3a6584a..a810940 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.kt @@ -40,7 +40,7 @@ object LocationHelper { /* Get default location */ fun getDefaultLocation(): Location { - val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER) + val defaultLocation = Location(LocationManager.NETWORK_PROVIDER) defaultLocation.latitude = Keys.DEFAULT_LATITUDE defaultLocation.longitude = Keys.DEFAULT_LONGITUDE defaultLocation.accuracy = Keys.DEFAULT_ACCURACY @@ -62,14 +62,24 @@ object LocationHelper { // get last location that Trackbook has stored var lastKnownLocation: Location = PreferencesHelper.loadCurrentBestLocation(context) // 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) { - 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 - when (isBetterLocation(lastKnownLocationGps, lastKnownLocationNetwork)) { - true -> lastKnownLocation = lastKnownLocationGps - false -> lastKnownLocation = lastKnownLocationNetwork - } + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == 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)) { + true -> lastKnownLocationGps + false -> lastKnownLocationNetwork + } } return lastKnownLocation } @@ -87,7 +97,7 @@ object LocationHelper { // check whether the new location fix is newer or older val timeDelta: Long = location.time - currentBestLocation.time val isSignificantlyNewer: Boolean = timeDelta > Keys.SIGNIFICANT_TIME_DIFFERENCE - val isSignificantlyOlder:Boolean = timeDelta < -Keys.SIGNIFICANT_TIME_DIFFERENCE + val isSignificantlyOlder: Boolean = timeDelta < -Keys.SIGNIFICANT_TIME_DIFFERENCE when { // if it's been more than two minutes since the current location, use the new location because the user has likely moved @@ -118,25 +128,24 @@ object LocationHelper { /* Checks if GPS location provider is available and enabled */ fun isGpsEnabled(locationManager: LocationManager): Boolean { - if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) { - return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + return if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) { + locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) } else { - return false + false } } /* Checks if Network location provider is available and enabled */ fun isNetworkEnabled(locationManager: LocationManager): Boolean { - if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) { - return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + return if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) { + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) } else { - return false + false } } - /* Checks if given location is new */ fun isRecentEnough(location: Location): Boolean { val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos @@ -146,26 +155,35 @@ object LocationHelper { /* Checks if given location is accurate */ fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean { - val isAccurate: Boolean - when (location.provider) { - LocationManager.GPS_PROVIDER -> isAccurate = location.accuracy < locationAccuracyThreshold - else -> isAccurate = location.accuracy < locationAccuracyThreshold + 10 // a bit more relaxed when location comes from network provider + return when (location.provider) { + LocationManager.GPS_PROVIDER -> location.accuracy < locationAccuracyThreshold + else -> 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 */ fun isFirstLocationPlausible(secondLocation: Location, track: Track): Boolean { // 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 return speed < Keys.IMPLAUSIBLE_TRACK_START_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 val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L // distance in meters @@ -183,10 +201,10 @@ object LocationHelper { val distanceThreshold: Float val averageAccuracy: Float = (previousLocation.accuracy + location.accuracy) / 2 // increase the distance threshold if one or both locations are - if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) { - distanceThreshold = averageAccuracy + distanceThreshold = if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) { + averageAccuracy } else { - distanceThreshold = Keys.DEFAULT_THRESHOLD_DISTANCE + Keys.DEFAULT_THRESHOLD_DISTANCE } // location is different when far enough away from previous location return calculateDistance(previousLocation, location) > distanceThreshold @@ -194,8 +212,8 @@ object LocationHelper { /* Calculates distance in meters between two locations */ - fun calculateDistance(previousLocation: Location?, location: Location): Float { - var distance: Float = 0f + fun calculateDistance(previousLocation: Location?, location: Location): Float { + var distance = 0f // two data points needed to calculate distance if (previousLocation != null) { // add up distance @@ -206,20 +224,26 @@ object LocationHelper { /* Calculate elevation differences */ - fun calculateElevationDifferences(previousLocation: Location?, location: Location, track: Track): Pair { + fun calculateElevationDifferences( + previousLocation: Location?, + location: Location, + track: Track + ): Pair { // store current values var positiveElevation: Double = track.positiveElevation var negativeElevation: Double = track.negativeElevation if (previousLocation != null) { // 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 val altitudeDifference: Double = location.altitude - previousLocation.altitude if (altitudeDifference > 0 && altitudeDifference < Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) { positiveElevation = track.positiveElevation + altitudeDifference // upwards movement } 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) @@ -234,4 +258,4 @@ object LocationHelper { } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.kt index 5ca9427..837ee90 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.kt @@ -78,9 +78,9 @@ object LogHelper { private fun log(tag: String, level: Int, t: Throwable?, vararg messages: Any) { 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: - message = messages[0].toString() + messages[0].toString() } else { val sb = StringBuilder() for (m in messages) { @@ -89,7 +89,7 @@ object LogHelper { if (t != null) { sb.append("\n").append(Log.getStackTraceString(t)) } - message = sb.toString() + sb.toString() } Log.println(level, tag, message) @@ -112,4 +112,4 @@ object LogHelper { // Log.println(level, tag, message) // } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/MapOverlay.kt b/app/src/main/java/org/y20k/trackbook/helpers/MapOverlay.kt index 8fcb595..e5e4e42 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/MapOverlay.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/MapOverlay.kt @@ -21,6 +21,8 @@ package org.y20k.trackbook.helpers import android.content.Context import android.graphics.drawable.Drawable import android.location.Location +import android.os.Build +import android.os.VibrationEffect import android.os.Vibrator import android.widget.Toast import androidx.core.content.ContextCompat @@ -38,7 +40,7 @@ import java.util.* /* * MapHelper class */ -class MapOverlay (private var markerListener: MarkerListener) { +class MapOverlay(private var markerListener: MarkerListener) { /* Interface used to communicate back to activity/fragment */ interface MarkerListener { @@ -51,7 +53,11 @@ class MapOverlay (private var markerListener: MarkerListener) { /* Creates icon overlay for current position (used in MapFragment) */ - fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay { + fun createMyLocationOverlay( + context: Context, + location: Location, + trackingState: Int + ): ItemizedIconOverlay { val overlayItems = ArrayList() val locationIsOld = LocationHelper.isOldLocation(location) @@ -61,22 +67,41 @@ class MapOverlay (private var markerListener: MarkerListener) { when (trackingState) { // CASE: Tracking active Keys.STATE_TRACKING_ACTIVE -> { - when (locationIsOld) { - true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_grey_24dp)!! - false -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_24dp)!! + newMarker = when (locationIsOld) { + true -> ContextCompat.getDrawable( + 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 else -> { - when (locationIsOld) { - true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_grey_24dp)!! - false -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_24dp)!! + newMarker = when (locationIsOld) { + true -> ContextCompat.getDrawable( + 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 - 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) overlayItems.add(overlayItem) @@ -86,7 +111,11 @@ class MapOverlay (private var markerListener: MarkerListener) { /* Creates icon overlay for track */ - fun createTrackOverlay(context: Context, track: Track, trackingState: Int): ItemizedIconOverlay { + fun createTrackOverlay( + context: Context, + track: Track, + trackingState: Int + ): ItemizedIconOverlay { val overlayItems = ArrayList() val wayPoints = track.wayPoints @@ -99,28 +128,55 @@ class MapOverlay (private var markerListener: MarkerListener) { when (trackingState) { // CASE: Recording is active Keys.STATE_TRACKING_ACTIVE -> { - if (wayPoint.starred) { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!! - } else if (wayPoint.isStopOver) { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!! - } else { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_red_24dp)!! + newMarker = when { + wayPoint.starred -> { + ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!! + } + wayPoint.isStopOver -> { + 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 else -> { - if (wayPoint.starred) { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!! - } else if (wayPoint.isStopOver) { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!! - } else { - newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_blue_24dp)!! + newMarker = when { + wayPoint.starred -> { + ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!! + } + wayPoint.isStopOver -> { + 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 - 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) overlayItems.add(overlayItem) } @@ -131,30 +187,60 @@ class MapOverlay (private var markerListener: MarkerListener) { /* Creates a marker overlay item */ - private fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem { - val title: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}" + private fun createOverlayItem( + 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_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 = GeoPoint(latitude, longitude) + val description = + "${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) } /* Creates an overlay */ - private fun createOverlay(context: Context, overlayItems: ArrayList): ItemizedIconOverlay { + private fun createOverlay( + context: Context, + overlayItems: ArrayList + ): ItemizedIconOverlay { return ItemizedIconOverlay(context, overlayItems, object : ItemizedIconOverlay.OnItemGestureListener { override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean { markerListener.onMarkerTapped(item.point.latitude, item.point.longitude) return true } + override fun onItemLongPress(index: Int, item: OverlayItem): Boolean { val v = 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) + } Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show() return true } }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.kt index 23157bc..99398a2 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.kt @@ -41,11 +41,17 @@ class NotificationHelper(private val trackerService: TrackerService) { /* 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 */ - 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 if (shouldCreateNotificationChannel()) { @@ -53,7 +59,8 @@ class NotificationHelper(private val trackerService: TrackerService) { } // Build notification - val builder = NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING) + val builder = + NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING) builder.setContentIntent(showActionPendingIntent) builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp) builder.setContentText(getContentString(trackerService, duration, trackLength, useImperial)) @@ -63,13 +70,23 @@ class NotificationHelper(private val trackerService: TrackerService) { Keys.STATE_TRACKING_ACTIVE -> { builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_running)) 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 -> { builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_not_running)) builder.addAction(resumeAction) 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,56 +96,77 @@ class NotificationHelper(private val trackerService: TrackerService) { /* Build context text for notification builder */ - private fun getContentString(context: Context, duration: Long, trackLength: Float, useImperial: Boolean): String { - return "${LengthUnitHelper.convertDistanceToString(trackLength, useImperial)} • ${DateTimeHelper.convertToReadableTime(context, duration)}" + private fun getContentString( + 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 */ - 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 */ @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 */ @RequiresApi(Build.VERSION_CODES.O) 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), - NotificationManager.IMPORTANCE_LOW) - .apply { description = trackerService.getString(R.string.notification_channel_recording_description) } + NotificationManager.IMPORTANCE_LOW + ) + .apply { + description = + trackerService.getString(R.string.notification_channel_recording_description) + } notificationManager.createNotificationChannel(notificationChannel) } /* Notification pending intents */ private val stopActionPendingIntent = PendingIntent.getService( - trackerService,14, - Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP),0) + trackerService, 14, + Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP), 0 + ) private val resumeActionPendingIntent = PendingIntent.getService( trackerService, 16, - Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME),0) - private val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(trackerService).run { - addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java)) - getPendingIntent(10, PendingIntent.FLAG_UPDATE_CURRENT) - } + Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME), 0 + ) + private val showActionPendingIntent: PendingIntent? = + TaskStackBuilder.create(trackerService).run { + addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java)) + getPendingIntent(10, PendingIntent.FLAG_UPDATE_CURRENT) + } /* Notification actions */ private val stopAction = NotificationCompat.Action( R.drawable.ic_notification_action_stop_24dp, trackerService.getString(R.string.notification_stop), - stopActionPendingIntent) + stopActionPendingIntent + ) private val resumeAction = NotificationCompat.Action( R.drawable.ic_notification_action_resume_36dp, trackerService.getString(R.string.notification_resume), - resumeActionPendingIntent) + resumeActionPendingIntent + ) private val showAction = NotificationCompat.Action( R.drawable.ic_notification_action_show_36dp, trackerService.getString(R.string.notification_show), - showActionPendingIntent) + showActionPendingIntent + ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/PreferencesHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/PreferencesHelper.kt index 40bba24..e2abe5d 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/PreferencesHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/PreferencesHelper.kt @@ -80,7 +80,10 @@ object PreferencesHelper { // get preferences val settings = PreferenceManager.getDefaultSharedPreferences(context) // 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 val settings = PreferenceManager.getDefaultSharedPreferences(context) // 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 { // get preferences 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 - val currentBestLocation: Location = Location(provider) + val currentBestLocation = Location(provider) // load location attributes - currentBestLocation.latitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE) - currentBestLocation.longitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE) - 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) + currentBestLocation.latitude = + settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE) + currentBestLocation.longitude = + settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE) + 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 } @@ -135,7 +149,9 @@ object PreferencesHelper { /* Load currently selected app theme */ 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 } @@ -154,4 +170,4 @@ object PreferencesHelper { editor.apply() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/TrackHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/TrackHelper.kt index 9e525cc..5f155ba 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/TrackHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/TrackHelper.kt @@ -47,8 +47,13 @@ object TrackHelper { tracklistElement.date.time - /* Adds given locatiom as waypoint to track */ - fun addWayPointToTrack(context: Context, track: Track, location: Location, locationAccuracyThreshold: Int, resumed: Boolean): Pair { + /* Adds given location as waypoint to track */ + fun addWayPointToTrack( + track: Track, + location: Location, + locationAccuracyThreshold: Int, + resumed: Boolean + ): Pair { // get previous location val previousLocation: Location? var numberOfWayPoints: Int = track.wayPoints.size @@ -58,7 +63,11 @@ object TrackHelper { previousLocation = null } // 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 numberOfWayPoints = 0 track.wayPoints.removeAt(0) @@ -76,8 +85,8 @@ object TrackHelper { // add only if recent and accurate and different val shouldBeAdded: Boolean = (LocationHelper.isRecentEnough(location) && - LocationHelper.isAccurateEnough(location, locationAccuracyThreshold) && - LocationHelper.isDifferentEnough(previousLocation, location)) + LocationHelper.isAccurateEnough(location, locationAccuracyThreshold) && + LocationHelper.isDifferentEnough(previousLocation, location)) // // Debugging for shouldBeAdded - remove for production // val recentEnough: Boolean = LocationHelper.isRecentEnough(location) @@ -95,7 +104,8 @@ object TrackHelper { if (shouldBeAdded) { // update distance (do not update if resumed -> we do not want to add values calculated during a recording pause) if (!resumed) { - track.length = track.length + LocationHelper.calculateDistance(previousLocation, location) + track.length = + track.length + LocationHelper.calculateDistance(previousLocation, location) } if (location.altitude != 0.0) { @@ -105,12 +115,23 @@ object TrackHelper { track.minAltitude = location.altitude } else { // calculate elevation values (upwards / downwards movements) - val elevationDifferences: Pair = LocationHelper.calculateElevationDifferences(previousLocation, location, track) + val elevationDifferences: Pair = + LocationHelper.calculateElevationDifferences( + previousLocation, + location, + track + ) // check if any differences were calculated - if (elevationDifferences != Pair(track.positiveElevation, track.negativeElevation)) { + if (elevationDifferences != Pair( + track.positiveElevation, + track.negativeElevation + ) + ) { // update altitude values - if (location.altitude > track.maxAltitude) track.maxAltitude = location.altitude - if (location.altitude < track.minAltitude) track.minAltitude = location.altitude + if (location.altitude > track.maxAltitude) track.maxAltitude = + 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) if (!resumed) { track.positiveElevation = elevationDifferences.first @@ -122,16 +143,17 @@ object TrackHelper { // toggle stop over status, if necessary 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 val numberOfSatellites: Int val extras = location.extras - if (extras != null && extras.containsKey("satellites")) { - numberOfSatellites = extras.getInt("satellites", 0) + numberOfSatellites = if (extras != null && extras.containsKey("satellites")) { + extras.getInt("satellites", 0) } else { - numberOfSatellites = 0 + 0 } // add current location as point to center on for later display @@ -139,7 +161,18 @@ object TrackHelper { track.longitude = location.longitude // 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) @@ -153,13 +186,12 @@ object TrackHelper { /* Creates GPX string for given track */ fun createGpxString(track: Track): String { - var gpxString: String // add header - gpxString = "\n" + - "\n" + var gpxString: String = "\n" + + "\n" // add track gpxString += createGpxTrk(track) @@ -227,12 +259,20 @@ object TrackHelper { if (waypoint.latitude == latitude && waypoint.longitude == longitude) { waypoint.starred = !waypoint.starred when (waypoint.starred) { - true -> Toast.makeText(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() + true -> Toast.makeText( + 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() } } } return track } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/UiHelper.kt b/app/src/main/java/org/y20k/trackbook/helpers/UiHelper.kt index 540ecf0..0e2af28 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/UiHelper.kt +++ b/app/src/main/java/org/y20k/trackbook/helpers/UiHelper.kt @@ -41,7 +41,14 @@ object UiHelper { /* 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 l: Int = (left * scalingFactor).toInt() val r: Int = (right * scalingFactor).toInt() @@ -56,7 +63,16 @@ object UiHelper { /* 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 r: Int = ((width / 100.0f) * right).toInt() val t: Int = ((height / 100.0f) * top).toInt() @@ -69,28 +85,58 @@ object UiHelper { * 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 */ - 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 intrinsicHeight: Int = deleteIcon?.intrinsicHeight ?: 0 private val background: ColorDrawable = ColorDrawable() - private val backgroundColor = context.resources.getColor(R.color.list_card_delete_background, null) - private val clearPaint: Paint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } + private val backgroundColor = + 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 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 itemHeight = itemView.bottom - itemView.top val isCanceled = dX == 0f && !isCurrentlyActive if (isCanceled) { - clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat()) - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + clearCanvas( + c, + itemView.right + dX, + itemView.top.toFloat(), + itemView.right.toFloat(), + itemView.bottom.toFloat() + ) + super.onChildDraw( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) return } @@ -126,4 +172,4 @@ object UiHelper { * End of inner class */ -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/tracklist/TracklistAdapter.kt b/app/src/main/java/org/y20k/trackbook/tracklist/TracklistAdapter.kt index aace758..8f3307f 100644 --- a/app/src/main/java/org/y20k/trackbook/tracklist/TracklistAdapter.kt +++ b/app/src/main/java/org/y20k/trackbook/tracklist/TracklistAdapter.kt @@ -39,7 +39,8 @@ import java.util.* /* * TracklistAdapter class */ -class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter() { +class TracklistAdapter(private val fragment: Fragment) : + RecyclerView.Adapter() { /* Define log tag */ private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java) @@ -54,7 +55,7 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter tracklistElement.date } + tracklist.tracklistElements.sortByDescending { tracklistElement -> tracklistElement.date } } @@ -111,7 +112,8 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter = async { FileHelper.deleteTrackSuspended(context, position, tracklist) } + val deferred: Deferred = + async { FileHelper.deleteTrackSuspended(context, position, tracklist) } // wait for result and store in tracklist tracklist = deferred.await() notifyItemRemoved(position) @@ -122,7 +124,7 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter + tracklist.tracklistElements.forEachIndexed { index, tracklistElement -> if (tracklistElement.getTrackId() == trackId) return index } return -1 @@ -143,7 +145,11 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter trackDataString = "${LengthUnitHelper.convertDistanceToString(tracklistElement.length, useImperial)} • ${tracklistElement.durationString}" + true -> "${LengthUnitHelper.convertDistanceToString( + tracklistElement.length, + useImperial + )} • ${tracklistElement.durationString}" // 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 } @@ -165,7 +177,8 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter + mapView.setOnTouchListener { _, _ -> userInteraction = true 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 */ fun saveState(currentBestLocation: Location) { PreferencesHelper.saveCurrentBestLocation(context, currentBestLocation) - PreferencesHelper.saveZoomLevel(context, mapView.getZoomLevelDouble()) + PreferencesHelper.saveZoomLevel(context, mapView.zoomLevelDouble) // reset user interaction state userInteraction = false } @@ -157,7 +168,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar /* Mark current position on map */ fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_NOT) { mapView.overlays.remove(currentPositionOverlay) - currentPositionOverlay = MapOverlay(markerListener).createMyLocationOverlay(context, location, trackingState) + currentPositionOverlay = + MapOverlay(markerListener).createMyLocationOverlay(context, location, trackingState) mapView.overlays.add(currentPositionOverlay) } @@ -168,7 +180,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar mapView.overlays.remove(currentTrackOverlay) } if (track.wayPoints.isNotEmpty()) { - currentTrackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, trackingState) + currentTrackOverlay = + MapOverlay(markerListener).createTrackOverlay(context, track, trackingState) 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 */ 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 locationErrorBar.setText(R.string.snackbar_message_location_permission_denied) locationErrorBar.show() @@ -218,4 +234,4 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt b/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt index 5609dac..d418fda 100644 --- a/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt +++ b/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt @@ -50,14 +50,20 @@ import kotlin.math.roundToInt /* * 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 */ private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java) /* Main class variables */ - val rootView: View + val rootView: View = inflater.inflate(R.layout.fragment_track, container, false) val shareButton: ImageButton val deleteButton: ImageButton val editButton: ImageButton @@ -90,7 +96,6 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m /* Init block */ init { // find views - rootView = inflater.inflate(R.layout.fragment_track, container, false) mapView = rootView.findViewById(R.id.map) shareButton = rootView.findViewById(R.id.save_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 - val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView) + val compassOverlay = + CompassOverlay(context, InternalCompassOrientationProvider(context), mapView) compassOverlay.enableCompass() compassOverlay.setCompassCenter(36f, 60f) mapView.overlays.add(compassOverlay) // 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()) { mapView.overlays.add(trackOverlay) } @@ -168,7 +175,11 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m mapView.overlays.remove(trackOverlay) } 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) } } @@ -190,9 +201,9 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m private fun setupStatisticsViews() { // get step count string - val steps: String - if (track.stepCount == -1f) steps = context.getString(R.string.statistics_sheet_p_steps_no_pedometer) - else steps = track.stepCount.roundToInt().toString() + val steps: String = + if (track.stepCount == -1f) context.getString(R.string.statistics_sheet_p_steps_no_pedometer) + else track.stepCount.roundToInt().toString() // populate views trackNameView.text = track.name @@ -200,19 +211,29 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m stepsView.text = steps waypointsView.text = track.wayPoints.size.toString() 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) recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart) - maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits) - minAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits) - positiveElevationView.text = LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits) - negativeElevationView.text = LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits) + maxAltitudeView.text = + LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits) + minAltitudeView.text = + LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits) + positiveElevationView.text = + LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits) + negativeElevationView.text = + LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits) // show / hide recording pause if (track.recordingPaused != 0L) { recordingPausedLabelView.visibility = View.VISIBLE recordingPausedView.visibility = View.VISIBLE - recordingPausedView.text = DateTimeHelper.convertToReadableTime(context, track.recordingPaused) + recordingPausedView.text = + DateTimeHelper.convertToReadableTime(context, track.recordingPaused) } else { recordingPausedLabelView.visibility = View.GONE recordingPausedView.visibility = View.GONE @@ -220,8 +241,9 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m // inform user about possible accuracy issues with altitude measurements elevationDataViews.referencedIds.forEach { id -> - (rootView.findViewById(id) as View).setOnClickListener{ - Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG).show() + (rootView.findViewById(id) as View).setOnClickListener { + Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG) + .show() } } // 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 */ private fun toggleStatisticsSheetVisibility() { 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 } } @@ -246,26 +269,31 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { 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 shareButton.visibility = View.GONE // bottomSheet.setPadding(0,24,0,0) } 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 shareButton.visibility = View.VISIBLE // bottomSheet.setPadding(0,0,0,0) } } } + override fun onSlide(bottomSheet: View, slideOffset: Float) { 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 shareButton.visibility = View.VISIBLE } 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 shareButton.visibility = View.GONE } @@ -274,4 +302,4 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/ic_bug_report_24dp.xml b/app/src/main/res/drawable/ic_bug_report_24dp.xml index d3767ca..8491cb2 100644 --- a/app/src/main/res/drawable/ic_bug_report_24dp.xml +++ b/app/src/main/res/drawable/ic_bug_report_24dp.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_clear_white_24dp.xml b/app/src/main/res/drawable/ic_clear_white_24dp.xml index 27ca255..9d53cbb 100755 --- a/app/src/main/res/drawable/ic_clear_white_24dp.xml +++ b/app/src/main/res/drawable/ic_clear_white_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + 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" /> diff --git a/app/src/main/res/drawable/ic_compass_needle_black_24dp.xml b/app/src/main/res/drawable/ic_compass_needle_black_24dp.xml index df07973..9c4a959 100755 --- a/app/src/main/res/drawable/ic_compass_needle_black_24dp.xml +++ b/app/src/main/res/drawable/ic_compass_needle_black_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21,3L3,10.53v0.98l6.84,2.65L12.48,21h0.98L21,3z" /> diff --git a/app/src/main/res/drawable/ic_current_location_24dp.xml b/app/src/main/res/drawable/ic_current_location_24dp.xml index fa34fbf..01bccaa 100755 --- a/app/src/main/res/drawable/ic_current_location_24dp.xml +++ b/app/src/main/res/drawable/ic_current_location_24dp.xml @@ -1,9 +1,9 @@ - + + android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z" /> diff --git a/app/src/main/res/drawable/ic_delete_24dp.xml b/app/src/main/res/drawable/ic_delete_24dp.xml index 8698fa6..bdbbc77 100644 --- a/app/src/main/res/drawable/ic_delete_24dp.xml +++ b/app/src/main/res/drawable/ic_delete_24dp.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_edit_24dp.xml b/app/src/main/res/drawable/ic_edit_24dp.xml index 6c65efa..c6b470b 100644 --- a/app/src/main/res/drawable/ic_edit_24dp.xml +++ b/app/src/main/res/drawable/ic_edit_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M14.06,9.02l0.92,0.92L5.92,19L5,19v-0.92l9.06,-9.06M17.66,3c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.06,6.19L3,17.25L3,21h3.75L17.81,9.94l-3.75,-3.75z" /> diff --git a/app/src/main/res/drawable/ic_fiber_manual_record_red_24dp.xml b/app/src/main/res/drawable/ic_fiber_manual_record_red_24dp.xml index 0bcd70e..8f72922 100755 --- a/app/src/main/res/drawable/ic_fiber_manual_record_red_24dp.xml +++ b/app/src/main/res/drawable/ic_fiber_manual_record_red_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="#DC2B00" + android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" /> diff --git a/app/src/main/res/drawable/ic_fiber_manual_record_white_24dp.xml b/app/src/main/res/drawable/ic_fiber_manual_record_white_24dp.xml index d734aef..805b512 100755 --- a/app/src/main/res/drawable/ic_fiber_manual_record_white_24dp.xml +++ b/app/src/main/res/drawable/ic_fiber_manual_record_white_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="@color/trackbook_white" + android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" /> diff --git a/app/src/main/res/drawable/ic_gps_24dp.xml b/app/src/main/res/drawable/ic_gps_24dp.xml index a86f88c..08998f4 100755 --- a/app/src/main/res/drawable/ic_gps_24dp.xml +++ b/app/src/main/res/drawable/ic_gps_24dp.xml @@ -1,9 +1,9 @@ - + + android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z" /> diff --git a/app/src/main/res/drawable/ic_info_24dp.xml b/app/src/main/res/drawable/ic_info_24dp.xml index 31b21e7..945af95 100644 --- a/app/src/main/res/drawable/ic_info_24dp.xml +++ b/app/src/main/res/drawable/ic_info_24dp.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_marker_location_blue_24dp.xml b/app/src/main/res/drawable/ic_marker_location_blue_24dp.xml index 730c19e..de64c1f 100755 --- a/app/src/main/res/drawable/ic_marker_location_blue_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_location_blue_24dp.xml @@ -1,13 +1,13 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0" /> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_location_blue_grey_24dp.xml b/app/src/main/res/drawable/ic_marker_location_blue_grey_24dp.xml index 95a2828..fe2b9d4 100644 --- a/app/src/main/res/drawable/ic_marker_location_blue_grey_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_location_blue_grey_24dp.xml @@ -1,13 +1,13 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0" /> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_location_red_24dp.xml b/app/src/main/res/drawable/ic_marker_location_red_24dp.xml index 9c79b58..35a26bb 100755 --- a/app/src/main/res/drawable/ic_marker_location_red_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_location_red_24dp.xml @@ -1,13 +1,13 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0" /> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_location_red_grey_24dp.xml b/app/src/main/res/drawable/ic_marker_location_red_grey_24dp.xml index b470e56..86d4f9e 100644 --- a/app/src/main/res/drawable/ic_marker_location_red_grey_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_location_red_grey_24dp.xml @@ -1,13 +1,13 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0" /> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_track_location_blue_24dp.xml b/app/src/main/res/drawable/ic_marker_track_location_blue_24dp.xml index 8994111..da49a6b 100755 --- a/app/src/main/res/drawable/ic_marker_track_location_blue_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_track_location_blue_24dp.xml @@ -1,10 +1,10 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_track_location_grey_24dp.xml b/app/src/main/res/drawable/ic_marker_track_location_grey_24dp.xml index 079ca2b..57c81f1 100755 --- a/app/src/main/res/drawable/ic_marker_track_location_grey_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_track_location_grey_24dp.xml @@ -1,10 +1,10 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_marker_track_location_red_24dp.xml b/app/src/main/res/drawable/ic_marker_track_location_red_24dp.xml index 29baa58..4264365 100755 --- a/app/src/main/res/drawable/ic_marker_track_location_red_24dp.xml +++ b/app/src/main/res/drawable/ic_marker_track_location_red_24dp.xml @@ -1,10 +1,10 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" /> diff --git a/app/src/main/res/drawable/ic_notebook_black_24dp.xml b/app/src/main/res/drawable/ic_notebook_black_24dp.xml index d3d8e2a..64c93dd 100755 --- a/app/src/main/res/drawable/ic_notebook_black_24dp.xml +++ b/app/src/main/res/drawable/ic_notebook_black_24dp.xml @@ -1,12 +1,12 @@ + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + 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="M90.88,5.89L75.31,8.74L53.12,12.79L52,12.99v78.46l23.31,-4.26l15.57,-2.85c2.83,-0.52 5.12,-3.41 5.12,-6.47V10.49C96,7.44 93.71,5.38 90.88,5.89z" /> diff --git a/app/src/main/res/drawable/ic_notification_action_resume_36dp.xml b/app/src/main/res/drawable/ic_notification_action_resume_36dp.xml index f1017e0..835a5d8 100644 --- a/app/src/main/res/drawable/ic_notification_action_resume_36dp.xml +++ b/app/src/main/res/drawable/ic_notification_action_resume_36dp.xml @@ -1,9 +1,9 @@ + android:viewportHeight="24.0"> - \ No newline at end of file + android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" /> + diff --git a/app/src/main/res/drawable/ic_notification_action_show_36dp.xml b/app/src/main/res/drawable/ic_notification_action_show_36dp.xml index 4c1ec9c..0ebbbd7 100644 --- a/app/src/main/res/drawable/ic_notification_action_show_36dp.xml +++ b/app/src/main/res/drawable/ic_notification_action_show_36dp.xml @@ -1,9 +1,9 @@ + android:viewportHeight="24.0"> + 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" /> diff --git a/app/src/main/res/drawable/ic_notification_action_stop_24dp.xml b/app/src/main/res/drawable/ic_notification_action_stop_24dp.xml index cecebba..b436dde 100644 --- a/app/src/main/res/drawable/ic_notification_action_stop_24dp.xml +++ b/app/src/main/res/drawable/ic_notification_action_stop_24dp.xml @@ -1,9 +1,9 @@ + android:viewportHeight="24.0"> + android:pathData="M6,6h12v12H6z" /> diff --git a/app/src/main/res/drawable/ic_notification_icon_large_tracking_active_48dp.xml b/app/src/main/res/drawable/ic_notification_icon_large_tracking_active_48dp.xml index 787e5cf..eb643b1 100755 --- a/app/src/main/res/drawable/ic_notification_icon_large_tracking_active_48dp.xml +++ b/app/src/main/res/drawable/ic_notification_icon_large_tracking_active_48dp.xml @@ -1,12 +1,12 @@ + android:viewportHeight="192.0"> + android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" /> + android:pathData="M48,96a48,46.5 0,1 0,96 0a48,46.5 0,1 0,-96 0z" /> diff --git a/app/src/main/res/drawable/ic_notification_icon_large_tracking_stopped_48dp.xml b/app/src/main/res/drawable/ic_notification_icon_large_tracking_stopped_48dp.xml index 7d270c1..9c687ac 100755 --- a/app/src/main/res/drawable/ic_notification_icon_large_tracking_stopped_48dp.xml +++ b/app/src/main/res/drawable/ic_notification_icon_large_tracking_stopped_48dp.xml @@ -1,12 +1,12 @@ + android:viewportHeight="192.0"> + android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0" /> + android:pathData="M48,96a48,46.5 0,1 0,96 0a48,46.5 0,1 0,-96 0z" /> diff --git a/app/src/main/res/drawable/ic_notification_icon_small_24dp.xml b/app/src/main/res/drawable/ic_notification_icon_small_24dp.xml index e481483..3d3abc8 100755 --- a/app/src/main/res/drawable/ic_notification_icon_small_24dp.xml +++ b/app/src/main/res/drawable/ic_notification_icon_small_24dp.xml @@ -1,12 +1,12 @@ + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + 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="M90.88,5.89L75.31,8.74L53.12,12.79L52,12.99v78.46l23.31,-4.26l15.57,-2.85c2.83,-0.52 5.12,-3.41 5.12,-6.47V10.49C96,7.44 93.71,5.38 90.88,5.89z" /> diff --git a/app/src/main/res/drawable/ic_remove_circle_24dp.xml b/app/src/main/res/drawable/ic_remove_circle_24dp.xml index 31439c3..088e113 100644 --- a/app/src/main/res/drawable/ic_remove_circle_24dp.xml +++ b/app/src/main/res/drawable/ic_remove_circle_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z" /> diff --git a/app/src/main/res/drawable/ic_save_24dp.xml b/app/src/main/res/drawable/ic_save_24dp.xml index 14e3ad6..561583f 100644 --- a/app/src/main/res/drawable/ic_save_24dp.xml +++ b/app/src/main/res/drawable/ic_save_24dp.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_save_white_24dp.xml b/app/src/main/res/drawable/ic_save_white_24dp.xml index 1f1ec00..abc7dbe 100755 --- a/app/src/main/res/drawable/ic_save_white_24dp.xml +++ b/app/src/main/res/drawable/ic_save_white_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" /> diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml index 48597cc..07d2cd0 100644 --- a/app/src/main/res/drawable/ic_settings_black_24dp.xml +++ b/app/src/main/res/drawable/ic_settings_black_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" /> diff --git a/app/src/main/res/drawable/ic_smartphone_24dp.xml b/app/src/main/res/drawable/ic_smartphone_24dp.xml index 23254dc..465baa1 100644 --- a/app/src/main/res/drawable/ic_smartphone_24dp.xml +++ b/app/src/main/res/drawable/ic_smartphone_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z" /> diff --git a/app/src/main/res/drawable/ic_square_foot_24px.xml b/app/src/main/res/drawable/ic_square_foot_24px.xml index 2017e45..4f291a0 100644 --- a/app/src/main/res/drawable/ic_square_foot_24px.xml +++ b/app/src/main/res/drawable/ic_square_foot_24px.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_star_blue_24dp.xml b/app/src/main/res/drawable/ic_star_blue_24dp.xml index 6ba138a..889e9f3 100644 --- a/app/src/main/res/drawable/ic_star_blue_24dp.xml +++ b/app/src/main/res/drawable/ic_star_blue_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" /> diff --git a/app/src/main/res/drawable/ic_star_filled_24dp.xml b/app/src/main/res/drawable/ic_star_filled_24dp.xml index 5e1387f..feb34af 100644 --- a/app/src/main/res/drawable/ic_star_filled_24dp.xml +++ b/app/src/main/res/drawable/ic_star_filled_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" /> diff --git a/app/src/main/res/drawable/ic_star_outline_24dp.xml b/app/src/main/res/drawable/ic_star_outline_24dp.xml index 81babd1..9eba63b 100644 --- a/app/src/main/res/drawable/ic_star_outline_24dp.xml +++ b/app/src/main/res/drawable/ic_star_outline_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" /> diff --git a/app/src/main/res/drawable/ic_star_red_24dp.xml b/app/src/main/res/drawable/ic_star_red_24dp.xml index c30d723..3fb5217 100644 --- a/app/src/main/res/drawable/ic_star_red_24dp.xml +++ b/app/src/main/res/drawable/ic_star_red_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" /> diff --git a/app/src/main/res/drawable/ic_timeline_24dp.xml b/app/src/main/res/drawable/ic_timeline_24dp.xml index 967f85e..5dfa1d4 100644 --- a/app/src/main/res/drawable/ic_timeline_24dp.xml +++ b/app/src/main/res/drawable/ic_timeline_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,8h2v4h2L7,8h2v4h2L11,8h2v4h2L15,8h2v4h2L19,8h2v8z" /> diff --git a/app/src/main/res/drawable/ic_undo_24dp.xml b/app/src/main/res/drawable/ic_undo_24dp.xml index 97edd0c..fda630a 100644 --- a/app/src/main/res/drawable/ic_undo_24dp.xml +++ b/app/src/main/res/drawable/ic_undo_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z" /> diff --git a/app/src/main/res/drawable/selector_bottom_navigation.xml b/app/src/main/res/drawable/selector_bottom_navigation.xml index 1c4350c..2e71ece 100755 --- a/app/src/main/res/drawable/selector_bottom_navigation.xml +++ b/app/src/main/res/drawable/selector_bottom_navigation.xml @@ -1,5 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/shape_statistics_background_collapsed.xml b/app/src/main/res/drawable/shape_statistics_background_collapsed.xml index 3f75958..6ac8451 100644 --- a/app/src/main/res/drawable/shape_statistics_background_collapsed.xml +++ b/app/src/main/res/drawable/shape_statistics_background_collapsed.xml @@ -2,17 +2,20 @@ + android:top="0dp"> - + - - + + diff --git a/app/src/main/res/drawable/shape_statistics_background_expanded.xml b/app/src/main/res/drawable/shape_statistics_background_expanded.xml index 682b9e8..74d4e51 100644 --- a/app/src/main/res/drawable/shape_statistics_background_expanded.xml +++ b/app/src/main/res/drawable/shape_statistics_background_expanded.xml @@ -2,17 +2,20 @@ + android:top="0dp"> - + - - + + diff --git a/app/src/main/res/layout/dialog_generic_with_details.xml b/app/src/main/res/layout/dialog_generic_with_details.xml index 89af16d..44e19db 100644 --- a/app/src/main/res/layout/dialog_generic_with_details.xml +++ b/app/src/main/res/layout/dialog_generic_with_details.xml @@ -1,6 +1,5 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_rename_track.xml b/app/src/main/res/layout/dialog_rename_track.xml index 04c2a7c..bcf666a 100644 --- a/app/src/main/res/layout/dialog_rename_track.xml +++ b/app/src/main/res/layout/dialog_rename_track.xml @@ -1,6 +1,5 @@ - @@ -29,4 +28,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml index 60e023b..deb5b7d 100644 --- a/app/src/main/res/layout/fragment_map.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -1,6 +1,5 @@ - - + android:visibility="visible" /> - - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_tracklist.xml b/app/src/main/res/layout/fragment_tracklist.xml index cc535b7..cd00fec 100644 --- a/app/src/main/res/layout/fragment_tracklist.xml +++ b/app/src/main/res/layout/fragment_tracklist.xml @@ -1,6 +1,5 @@ - - + - - + + diff --git a/app/src/main/res/layout/track_onboarding.xml b/app/src/main/res/layout/track_onboarding.xml index fcedbd1..2367c1e 100755 --- a/app/src/main/res/layout/track_onboarding.xml +++ b/app/src/main/res/layout/track_onboarding.xml @@ -1,10 +1,9 @@ - + android:layout_height="match_parent"> - - - \ No newline at end of file + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 4ae7d12..8ae0e29 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/navigation/nav_graph_main.xml b/app/src/main/res/navigation/nav_graph_main.xml index caf5eef..a3f2e1a 100644 --- a/app/src/main/res/navigation/nav_graph_main.xml +++ b/app/src/main/res/navigation/nav_graph_main.xml @@ -27,7 +27,7 @@ + android:label="Settings" /> + tools:layout="@layout/fragment_track"> + android:defaultValue="-1L" + app:argType="long" /> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0459398..6a1bd19 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,95 +1,95 @@ - - - Trackbook - - - 地图 - 记录 - 设置 - - Trackbook 正在运行 - Trackbook 已暂停 - 暂停 - 恢复 - 显示 - 运动记录状态 - 显示持续时间和距离.停止移动录制的选项. - - 位置服务已关闭,Trackbook 将不会运行. - 位置权限未被授予 Trackbook 无法工作. - - 删除 - 保存 - 恢复 - - 取消 - OK - 显示详情 - 无法保存 - Trackbook目前没有记录到任何航点. - 恢复记录 - 重命名 - 输入一个新名称 - 分享GPX文件到 - - 删除 - 删除此记录? - - 提示:海拔数据的准确性取决于您的设备.测量整个路线的上坡和下坡海拔. - 请先安装一个文件管理器或GPX轨迹查看器. + + + Trackbook + + + 地图 + 记录 + 设置 + + Trackbook 正在运行 + Trackbook 已暂停 + 暂停 + 恢复 + 显示 + 运动记录状态 + 显示持续时间和距离.停止移动录制的选项. + + 位置服务已关闭,Trackbook 将不会运行. + 位置权限未被授予 Trackbook 无法工作. + + 删除 + 保存 + 恢复 + + 取消 + OK + 显示详情 + 无法保存 + Trackbook目前没有记录到任何航点. + 恢复记录 + 重命名 + 输入一个新名称 + 分享GPX文件到 + + 删除 + 删除此记录? + + 提示:海拔数据的准确性取决于您的设备.测量整个路线的上坡和下坡海拔. + 请先安装一个文件管理器或GPX轨迹查看器. - 时间 - 误差 - - 总里程: - 步数: - 计步器不可用 - 航点数量: - 用时: - 追踪开始: - 追踪结束: - 最高航点: - 最低航点: - 海拔(上坡): - 海拔(下坡): - - - Trackbook 应用图标 - - 你的运动记录 - ... 会显示在这里. - - 丢弃误差大于 - 误差的阈值 - 高级 - 一般 - 仅限于GPS - 当前使用GPS和网络进行定位. - 当前使用GPS进行定位. - 当前使用公制单位(千米,米). - 当前使用英制单位(英里,英尺). - 使用英制 - 高级设置重置为默认值. - 重置 - - - - - - 航迹图 - 最后一个轨迹的航迹图 - 开始录制按钮 - 保存按钮 - 删除按钮 - 恢复按钮 - 加星按钮 - 删除记录按钮 - 记录编辑按钮 - 以GPX文件分享按钮 - - 23.0 km • 5 时 23 分 42 秒 - 1969年7月20日 - 轨迹数据丢失 + 时间 + 误差 + + 总里程: + 步数: + 计步器不可用 + 航点数量: + 用时: + 追踪开始: + 追踪结束: + 最高航点: + 最低航点: + 海拔(上坡): + 海拔(下坡): + + + Trackbook 应用图标 + + 你的运动记录 + ... 会显示在这里. + + 丢弃误差大于 + 误差的阈值 + 高级 + 一般 + 仅限于GPS + 当前使用GPS和网络进行定位. + 当前使用GPS进行定位. + 当前使用公制单位(千米,米). + 当前使用英制单位(英里,英尺). + 使用英制 + 高级设置重置为默认值. + 重置 + + + + + + 航迹图 + 最后一个轨迹的航迹图 + 开始录制按钮 + 保存按钮 + 删除按钮 + 恢复按钮 + 加星按钮 + 删除记录按钮 + 记录编辑按钮 + 以GPX文件分享按钮 + + 23.0 km • 5 时 23 分 42 秒 + 1969年7月20日 + 轨迹数据丢失 diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml index 3ea04e7..545704f 100644 --- a/app/src/main/res/values/bools.xml +++ b/app/src/main/res/values/bools.xml @@ -1,2 +1,2 @@ - + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e60c985..b6c1e47 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -31,17 +31,21 @@ - #FF595959 + + + #FF595959 #FF7D7D7D #FFDADADA #FFF2F2F2 #FF414141 #FF2D2D2D - #DC3D33 + + #DC3D33 #FFCA2D23 - #FF121212 + + #FF121212 #FFFFFFFF #00ffffff diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e2c897d..4928a7e 100755 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,6 +1,9 @@ - -1 + + + -1 + 16dp @dimen/custom_match_parent diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 724e360..5bbbd09 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -20,9 +20,11 @@ @style/NegativeButtonStyle @style/PositiveButtonStyle + + diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 4495c28..a075ef9 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,4 +1,6 @@ - - \ No newline at end of file + + diff --git a/build.gradle b/build.gradle index 99001d1..fddfa3d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,19 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext { + kotlin_version = '1.3.72' + navigation_version = '2.3.0' + } repositories { google() jcenter() - } dependencies { classpath 'com.android.tools.build:gradle:4.0.1' 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 // in the individual module build.gradle files } @@ -20,7 +23,6 @@ allprojects { repositories { google() jcenter() - } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1fa8765..ac33e99 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sat May 30 22:16:24 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/gradlew b/gradlew index cccdd3d..fbd7c51 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/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 @@ -28,7 +44,7 @@ APP_NAME="Gradle" 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. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..5093609 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -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 @rem ########################################################################## @rem @@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -65,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% diff --git a/metadata/README.md b/metadata/README.md index 224bf8d..5e0559c 100644 --- a/metadata/README.md +++ b/metadata/README.md @@ -1,2 +1,3 @@ # 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/). diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index 0b4b128..90ff5f6 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -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 ------------ 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: -https://support.google.com/android/answer/9083864?hl=en +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 https://www.xda-developers.com/get-custom-quick-settings-tiles/ 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 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. 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? ---------------------------- @@ -31,6 +31,6 @@ The F-Droid version of Trackbook features an auto-importer for old recordings. S 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.