checkpoint
This commit is contained in:
parent
47531768d1
commit
2568af3bb1
49 changed files with 825 additions and 1512 deletions
|
@ -4,12 +4,12 @@ apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'net.voussoir.trackbook'
|
applicationId 'net.voussoir.trkpt'
|
||||||
minSdkVersion 25
|
minSdkVersion 25
|
||||||
targetSdkVersion 31
|
targetSdk 32
|
||||||
versionCode 50
|
versionCode 50
|
||||||
versionName '2.1.2'
|
versionName '2.1.2'
|
||||||
resConfigs "en", "da", "de", "fr", "hr", "id", "it", "ja", "nb-rNO", "nl", "pl", "pt-rBR", "ru", "sv", "tr", "zh-rCN"
|
resConfigs "en", "da", "de", "fr", "hr", "id", "it", "ja", "nb-rNO", "nl", "pl", "pt-rBR", "ru", "sv", "tr", "zh-rCN"
|
||||||
|
@ -51,25 +51,25 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kotlin
|
// Kotlin
|
||||||
def coroutinesVersion = "1.5.2"
|
def coroutinesVersion = '1.6.4'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||||
|
|
||||||
// AndroidX
|
// AndroidX
|
||||||
def navigationVersion = "2.3.5"
|
def navigationVersion = '2.5.3'
|
||||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
implementation 'androidx.activity:activity-ktx:1.6.1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.core:core-ktx:1.7.0'
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
|
implementation "androidx.navigation:navigation-ui-ktx:2.5.3"
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'com.google.android.material:material:1.6.0-alpha03'
|
implementation 'com.google.android.material:material:1.9.0-alpha02'
|
||||||
|
|
||||||
// Gson
|
// Gson
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
|
|
||||||
// OpenStreetMap
|
// OpenStreetMap
|
||||||
implementation 'org.osmdroid:osmdroid-android:6.1.11'
|
implementation 'org.osmdroid:osmdroid-android:6.1.14'
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<application
|
<application
|
||||||
android:name=".Trackbook"
|
android:name="org.y20k.trackbook.Trackbook"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.util.*
|
||||||
object Keys {
|
object Keys {
|
||||||
|
|
||||||
// application name
|
// application name
|
||||||
const val APPLICATION_NAME: String = "Trackbook"
|
const val APPLICATION_NAME: String = "trkpt"
|
||||||
|
|
||||||
// version numbers
|
// version numbers
|
||||||
const val CURRENT_TRACK_FORMAT_VERSION: Int = 4
|
const val CURRENT_TRACK_FORMAT_VERSION: Int = 4
|
||||||
|
@ -39,9 +39,10 @@ object Keys {
|
||||||
|
|
||||||
// args
|
// args
|
||||||
const val ARG_TRACK_TITLE: String = "ArgTrackTitle"
|
const val ARG_TRACK_TITLE: String = "ArgTrackTitle"
|
||||||
const val ARG_TRACK_FILE_URI: String = "ArgTrackFileUri"
|
const val ARG_TRACK_ID: String = "ArgTrackID"
|
||||||
const val ARG_GPX_FILE_URI: String = "ArgGpxFileUri"
|
const val ARG_TRACK_DEVICE_ID: String = "ArgTrackDeviceID"
|
||||||
const val ARG_TRACK_ID: String = "ArgTrackId"
|
const val ARG_TRACK_START_TIME: String = "ArgTrackStartTime"
|
||||||
|
const val ARG_TRACK_STOP_TIME: String = "ArgTrackStopTime"
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_38" // increment to current app version code to trigger housekeeping that runs only once
|
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_38" // increment to current app version code to trigger housekeeping that runs only once
|
||||||
|
@ -57,15 +58,12 @@ object Keys {
|
||||||
const val PREF_USE_IMPERIAL_UNITS: String = "prefUseImperialUnits"
|
const val PREF_USE_IMPERIAL_UNITS: String = "prefUseImperialUnits"
|
||||||
const val PREF_GPS_ONLY: String = "prefGpsOnly"
|
const val PREF_GPS_ONLY: String = "prefGpsOnly"
|
||||||
const val PREF_OMIT_RESTS: String = "prefOmitRests"
|
const val PREF_OMIT_RESTS: String = "prefOmitRests"
|
||||||
const val PREF_AUTO_EXPORT_INTERVAL: String = "prefAutoExportInterval"
|
const val PREF_COMMIT_INTERVAL: String = "prefCommitInterval"
|
||||||
const val PREF_ALTITUDE_SMOOTHING_VALUE: String = "prefAltitudeSmoothingValue"
|
const val PREF_DEVICE_ID: String = "prefDeviceID"
|
||||||
const val PREF_LOCATION_ACCURACY_THRESHOLD: String = "prefLocationAccuracyThreshold"
|
|
||||||
const val PREF_LOCATION_AGE_THRESHOLD: String = "prefLocationAgeThreshold"
|
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const val STATE_TRACKING_NOT_STARTED: Int = 0
|
const val STATE_TRACKING_STOPPED: Int = 0
|
||||||
const val STATE_TRACKING_ACTIVE: Int = 1
|
const val STATE_TRACKING_ACTIVE: Int = 1
|
||||||
const val STATE_TRACKING_PAUSED: Int = 2
|
|
||||||
const val STATE_THEME_FOLLOW_SYSTEM: String = "stateFollowSystem"
|
const val STATE_THEME_FOLLOW_SYSTEM: String = "stateFollowSystem"
|
||||||
const val STATE_THEME_LIGHT_MODE: String = "stateLightMode"
|
const val STATE_THEME_LIGHT_MODE: String = "stateLightMode"
|
||||||
const val STATE_THEME_DARK_MODE: String = "stateDarkMode"
|
const val STATE_THEME_DARK_MODE: String = "stateDarkMode"
|
||||||
|
@ -105,9 +103,9 @@ object Keys {
|
||||||
const val ONE_HOUR_IN_MILLISECONDS: Long = 60 * ONE_MINUTE_IN_MILLISECONDS
|
const val ONE_HOUR_IN_MILLISECONDS: Long = 60 * ONE_MINUTE_IN_MILLISECONDS
|
||||||
const val EMPTY_STRING_RESOURCE: Int = 0
|
const val EMPTY_STRING_RESOURCE: Int = 0
|
||||||
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
const val TRACKING_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val SAVE_TEMP_TRACK_INTERVAL: Long = 30 * ONE_SECOND_IN_MILLISECONDS
|
const val SAVE_TEMP_TRACK_INTERVAL: Long = 30 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 2 * ONE_MINUTE_IN_MILLISECONDS
|
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 1 * ONE_MINUTE_IN_MILLISECONDS
|
||||||
const val STOP_OVER_THRESHOLD: Long = 5 * ONE_MINUTE_IN_MILLISECONDS
|
const val STOP_OVER_THRESHOLD: Long = 5 * ONE_MINUTE_IN_MILLISECONDS
|
||||||
const val IMPLAUSIBLE_TRACK_START_SPEED: Double = 250.0 // 250 km/h
|
const val IMPLAUSIBLE_TRACK_START_SPEED: Double = 250.0 // 250 km/h
|
||||||
const val DEFAULT_LATITUDE: Double = 71.172500 // latitude Nordkapp, Norway
|
const val DEFAULT_LATITUDE: Double = 71.172500 // latitude Nordkapp, Norway
|
||||||
|
@ -115,14 +113,12 @@ object Keys {
|
||||||
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
||||||
const val DEFAULT_ALTITUDE: Double = 0.0
|
const val DEFAULT_ALTITUDE: Double = 0.0
|
||||||
const val DEFAULT_TIME: Long = 0L
|
const val DEFAULT_TIME: Long = 0L
|
||||||
const val DEFAULT_AUTO_EXPORT_INTERVAL: Int = 24
|
const val COMMIT_INTERVAL: Int = 30
|
||||||
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 13
|
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 13
|
||||||
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
|
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
|
||||||
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 60_000_000_000L // one minute in nanoseconds
|
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 60_000_000_000L // one minute in nanoseconds
|
||||||
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
|
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
|
||||||
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
|
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
|
||||||
const val MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION: Int = 5
|
|
||||||
const val MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION: Int = 20
|
|
||||||
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
|
||||||
|
|
||||||
// notification
|
// notification
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import YesNoDialog
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
@ -28,14 +27,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
||||||
|
@ -43,34 +35,34 @@ import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
||||||
/*
|
/*
|
||||||
* MapFragment class
|
* MapFragment class
|
||||||
*/
|
*/
|
||||||
class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
class MapFragment : Fragment(), MapOverlayHelper.MarkerListener
|
||||||
|
{
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(MapFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(MapFragment::class.java)
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private var bound: Boolean = false
|
private var bound: Boolean = false
|
||||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||||
private var trackingState: Int = Keys.STATE_TRACKING_NOT_STARTED
|
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
|
||||||
private var gpsProviderActive: Boolean = false
|
private var gpsProviderActive: Boolean = false
|
||||||
private var networkProviderActive: Boolean = false
|
private var networkProviderActive: Boolean = false
|
||||||
private var track: Track = Track()
|
private lateinit var track: Track
|
||||||
private lateinit var currentBestLocation: Location
|
private lateinit var currentBestLocation: Location
|
||||||
private lateinit var layout: MapFragmentLayoutHolder
|
private lateinit var layout: MapFragmentLayoutHolder
|
||||||
private lateinit var trackerService: TrackerService
|
private lateinit var trackerService: TrackerService
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreate from Fragment */
|
/* Overrides onCreate from Fragment */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// TODO make only MapFragment's status bar transparent - see: https://gist.github.com/Dvik/a3de88d39da9d1d6d175025a56c5e797#file-viewextension-kt and https://proandroiddev.com/android-full-screen-ui-with-transparent-status-bar-ef52f3adde63
|
// TODO make only MapFragment's status bar transparent - see:
|
||||||
|
// https://gist.github.com/Dvik/a3de88d39da9d1d6d175025a56c5e797#file-viewextension-kt and
|
||||||
|
// https://proandroiddev.com/android-full-screen-ui-with-transparent-status-bar-ef52f3adde63
|
||||||
// get current best location
|
// get current best location
|
||||||
currentBestLocation = LocationHelper.getLastKnownLocation(activity as Context)
|
currentBestLocation = LocationHelper.getLastKnownLocation(activity as Context)
|
||||||
// get saved tracking state
|
// get saved tracking state
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
// initialize layout
|
// initialize layout
|
||||||
|
@ -84,30 +76,10 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
layout.mainButton.setOnClickListener {
|
layout.mainButton.setOnClickListener {
|
||||||
handleTrackingManagementMenu()
|
handleTrackingManagementMenu()
|
||||||
}
|
}
|
||||||
layout.saveButton.setOnClickListener {
|
|
||||||
saveTrack()
|
|
||||||
}
|
|
||||||
layout.clearButton.setOnClickListener {
|
|
||||||
if (track.wayPoints.isEmpty())
|
|
||||||
{
|
|
||||||
// This might seem useless since it's already empty, but there are other UI updates
|
|
||||||
// that occur as part of cleartrack so we may as well take advantage of those.
|
|
||||||
trackerService.clearTrack()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(
|
|
||||||
context=activity as Context,
|
|
||||||
type = Keys.DIALOG_DELETE_CURRENT_RECORDING,
|
|
||||||
message = R.string.dialog_delete_current_recording_message,
|
|
||||||
yesButton = R.string.dialog_delete_current_recording_button_discard
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout.rootView
|
return layout.rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStart from Fragment */
|
/* Overrides onStart from Fragment */
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
@ -119,7 +91,6 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
activity?.bindService(Intent(activity, TrackerService::class.java), connection, Context.BIND_AUTO_CREATE)
|
activity?.bindService(Intent(activity, TrackerService::class.java), connection, Context.BIND_AUTO_CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onResume from Fragment */
|
/* Overrides onResume from Fragment */
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
@ -129,7 +100,6 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onPause from Fragment */
|
/* Overrides onPause from Fragment */
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
@ -137,17 +107,20 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) {
|
if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) {
|
||||||
trackerService.removeGpsLocationListener()
|
trackerService.removeGpsLocationListener()
|
||||||
trackerService.removeNetworkLocationListener()
|
trackerService.removeNetworkLocationListener()
|
||||||
|
trackerService.trackbook.database.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
// unbind from TrackerService
|
// unbind from TrackerService
|
||||||
|
if (bound)
|
||||||
|
{
|
||||||
activity?.unbindService(connection)
|
activity?.unbindService(connection)
|
||||||
handleServiceUnbind()
|
handleServiceUnbind()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Register the permission launcher for requesting location */
|
/* Register the permission launcher for requesting location */
|
||||||
private val requestLocationPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
private val requestLocationPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
||||||
|
@ -171,14 +144,6 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
trackerService.startTracking()
|
trackerService.startTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Register the permission launcher for resuming the tracking service */
|
|
||||||
private val resumeTrackingPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
|
||||||
logPermissionRequestResult(isGranted)
|
|
||||||
// start service via intent so that it keeps running after unbind
|
|
||||||
startTrackerService()
|
|
||||||
trackerService.resumeTracking()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logs the request result of the Activity Recognition permission launcher */
|
/* Logs the request result of the Activity Recognition permission launcher */
|
||||||
private fun logPermissionRequestResult(isGranted: Boolean) {
|
private fun logPermissionRequestResult(isGranted: Boolean) {
|
||||||
if (isGranted) {
|
if (isGranted) {
|
||||||
|
@ -188,68 +153,24 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
|
||||||
super.onYesNoDialog(type, dialogResult, payload, payloadString)
|
|
||||||
when (type) {
|
|
||||||
Keys.DIALOG_RESUME_EMPTY_RECORDING -> {
|
|
||||||
when (dialogResult) {
|
|
||||||
// user tapped resume
|
|
||||||
true -> {
|
|
||||||
trackerService.resumeTracking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.DIALOG_DELETE_CURRENT_RECORDING -> {
|
|
||||||
when (dialogResult) {
|
|
||||||
true -> {
|
|
||||||
trackerService.clearTrack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onMarkerTapped from MarkerListener */
|
|
||||||
override fun onMarkerTapped(latitude: Double, longitude: Double) {
|
|
||||||
super.onMarkerTapped(latitude, longitude)
|
|
||||||
if (bound) {
|
|
||||||
TrackHelper.toggle_waypoint_starred(activity as Context, track, latitude, longitude)
|
|
||||||
layout.overlayCurrentTrack(track, trackingState)
|
|
||||||
trackerService.track = track
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Start recording waypoints */
|
/* Start recording waypoints */
|
||||||
private fun startTracking() {
|
private fun startTracking() {
|
||||||
// request activity recognition permission on Android Q+ if denied
|
// request activity recognition permission on Android Q+ if denied
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_DENIED) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_DENIED)
|
||||||
|
{
|
||||||
startTrackingPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
|
startTrackingPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// start service via intent so that it keeps running after unbind
|
// start service via intent so that it keeps running after unbind
|
||||||
startTrackerService()
|
startTrackerService()
|
||||||
trackerService.startTracking()
|
trackerService.startTracking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Resume recording waypoints */
|
|
||||||
private fun resumeTracking() {
|
|
||||||
// request activity recognition permission on Android Q+ if denied
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_DENIED) {
|
|
||||||
resumeTrackingPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
|
|
||||||
} else {
|
|
||||||
// start service via intent so that it keeps running after unbind
|
|
||||||
startTrackerService()
|
|
||||||
trackerService.resumeTracking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Start tracker service */
|
/* Start tracker service */
|
||||||
private fun startTrackerService() {
|
private fun startTrackerService()
|
||||||
|
{
|
||||||
val intent = Intent(activity, TrackerService::class.java)
|
val intent = Intent(activity, TrackerService::class.java)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
// ... start service in foreground to prevent it being killed on Oreo
|
// ... start service in foreground to prevent it being killed on Oreo
|
||||||
|
@ -259,9 +180,9 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Handles state when service is being unbound */
|
/* Handles state when service is being unbound */
|
||||||
private fun handleServiceUnbind() {
|
private fun handleServiceUnbind()
|
||||||
|
{
|
||||||
bound = false
|
bound = false
|
||||||
// unregister listener for changes in shared preferences
|
// unregister listener for changes in shared preferences
|
||||||
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
|
@ -269,61 +190,25 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
handler.removeCallbacks(periodicLocationRequestRunnable)
|
handler.removeCallbacks(periodicLocationRequestRunnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Starts / pauses tracking and toggles the recording sub menu_bottom_navigation */
|
/* Starts / pauses tracking and toggles the recording sub menu_bottom_navigation */
|
||||||
private fun handleTrackingManagementMenu() {
|
private fun handleTrackingManagementMenu()
|
||||||
|
{
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
Keys.STATE_TRACKING_PAUSED -> resumeTracking()
|
|
||||||
Keys.STATE_TRACKING_ACTIVE -> trackerService.pauseTracking()
|
Keys.STATE_TRACKING_ACTIVE -> trackerService.pauseTracking()
|
||||||
Keys.STATE_TRACKING_NOT_STARTED -> startTracking()
|
Keys.STATE_TRACKING_STOPPED -> startTracking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Saves track - shows dialog, if recording is still empty */
|
|
||||||
private fun saveTrack()
|
|
||||||
{
|
|
||||||
if (track.wayPoints.isEmpty())
|
|
||||||
{
|
|
||||||
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(
|
|
||||||
context = activity as Context,
|
|
||||||
type = Keys.DIALOG_RESUME_EMPTY_RECORDING,
|
|
||||||
message = R.string.dialog_error_empty_recording_message,
|
|
||||||
yesButton = R.string.dialog_error_empty_recording_button_resume
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CoroutineScope(IO).launch {
|
|
||||||
trackerService.saveTrackAndClear(activity as Context)
|
|
||||||
withContext(Main) {
|
|
||||||
// step 4: open track in TrackFragement
|
|
||||||
openTrack(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Opens a track in TrackFragment */
|
|
||||||
private fun openTrack(track: Track) {
|
|
||||||
val bundle: Bundle = Bundle()
|
|
||||||
bundle.putString(Keys.ARG_TRACK_TITLE, track.name)
|
|
||||||
bundle.putString(Keys.ARG_TRACK_FILE_URI, track.get_json_file(activity as Context).toUri().toString())
|
|
||||||
bundle.putString(Keys.ARG_GPX_FILE_URI, track.get_gpx_file(activity as Context).toUri().toString())
|
|
||||||
bundle.putLong(Keys.ARG_TRACK_ID, track.id)
|
|
||||||
findNavController().navigate(R.id.fragment_track, bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||||
when (key) {
|
when (key)
|
||||||
Keys.PREF_TRACKING_STATE -> {
|
{
|
||||||
if (activity != null) {
|
Keys.PREF_TRACKING_STATE ->
|
||||||
|
{
|
||||||
|
if (activity != null)
|
||||||
|
{
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
layout.updateMainButton(trackingState)
|
layout.updateMainButton(trackingState)
|
||||||
}
|
}
|
||||||
|
@ -334,7 +219,6 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
* End of declaration
|
* End of declaration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defines callbacks for service binding, passed to bindService()
|
* Defines callbacks for service binding, passed to bindService()
|
||||||
*/
|
*/
|
||||||
|
@ -362,7 +246,6 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
* End of declaration
|
* End of declaration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Runnable: Periodically requests location
|
* Runnable: Periodically requests location
|
||||||
*/
|
*/
|
||||||
|
@ -377,9 +260,11 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
// update location and track
|
// update location and track
|
||||||
layout.markCurrentPosition(currentBestLocation, trackingState)
|
layout.markCurrentPosition(currentBestLocation, trackingState)
|
||||||
layout.overlayCurrentTrack(track, trackingState)
|
layout.overlayCurrentTrack(track, trackingState)
|
||||||
layout.updateLiveStatics(distance = track.distance, duration = track.duration, trackingState = trackingState)
|
|
||||||
// center map, if it had not been dragged/zoomed before
|
// center map, if it had not been dragged/zoomed before
|
||||||
if (!layout.userInteraction) { layout.centerMap(currentBestLocation, true)}
|
if (!layout.userInteraction)
|
||||||
|
{
|
||||||
|
layout.centerMap(currentBestLocation, true)
|
||||||
|
}
|
||||||
// show error snackbar if necessary
|
// show error snackbar if necessary
|
||||||
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
||||||
// use the handler to start runnable again after specified delay
|
// use the handler to start runnable again after specified delay
|
||||||
|
@ -389,5 +274,4 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
/*
|
/*
|
||||||
* End of declaration
|
* End of declaration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.*
|
import androidx.preference.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -33,13 +34,13 @@ import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.y20k.trackbook.core.Tracklist
|
|
||||||
import org.y20k.trackbook.core.load_tracklist
|
|
||||||
import org.y20k.trackbook.helpers.AppThemeHelper
|
import org.y20k.trackbook.helpers.AppThemeHelper
|
||||||
import org.y20k.trackbook.helpers.FileHelper
|
import org.y20k.trackbook.helpers.FileHelper
|
||||||
import org.y20k.trackbook.helpers.LengthUnitHelper
|
import org.y20k.trackbook.helpers.LengthUnitHelper
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
|
import org.y20k.trackbook.helpers.random_int
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SettingsFragment class
|
* SettingsFragment class
|
||||||
|
@ -64,6 +65,10 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
val context = preferenceManager.context
|
val context = preferenceManager.context
|
||||||
val screen = preferenceManager.createPreferenceScreen(context)
|
val screen = preferenceManager.createPreferenceScreen(context)
|
||||||
|
|
||||||
|
val preferenceCategoryGeneral: PreferenceCategory = PreferenceCategory(activity as Context)
|
||||||
|
preferenceCategoryGeneral.title = getString(R.string.pref_general_title)
|
||||||
|
screen.addPreference(preferenceCategoryGeneral)
|
||||||
|
|
||||||
// set up "Restrict to GPS" preference
|
// set up "Restrict to GPS" preference
|
||||||
val preferenceGpsOnly: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
val preferenceGpsOnly: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
||||||
preferenceGpsOnly.isSingleLineTitle = false
|
preferenceGpsOnly.isSingleLineTitle = false
|
||||||
|
@ -73,6 +78,8 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceGpsOnly.summaryOn = getString(R.string.pref_gps_only_summary_gps_only)
|
preferenceGpsOnly.summaryOn = getString(R.string.pref_gps_only_summary_gps_only)
|
||||||
preferenceGpsOnly.summaryOff = getString(R.string.pref_gps_only_summary_gps_and_network)
|
preferenceGpsOnly.summaryOff = getString(R.string.pref_gps_only_summary_gps_and_network)
|
||||||
preferenceGpsOnly.setDefaultValue(false)
|
preferenceGpsOnly.setDefaultValue(false)
|
||||||
|
preferenceCategoryGeneral.contains(preferenceGpsOnly)
|
||||||
|
screen.addPreference(preferenceGpsOnly)
|
||||||
|
|
||||||
// set up "Use Imperial Measurements" preference
|
// set up "Use Imperial Measurements" preference
|
||||||
val preferenceImperialMeasurementUnits: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
val preferenceImperialMeasurementUnits: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
|
||||||
|
@ -83,6 +90,8 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceImperialMeasurementUnits.summaryOn = getString(R.string.pref_imperial_measurement_units_summary_imperial)
|
preferenceImperialMeasurementUnits.summaryOn = getString(R.string.pref_imperial_measurement_units_summary_imperial)
|
||||||
preferenceImperialMeasurementUnits.summaryOff = getString(R.string.pref_imperial_measurement_units_summary_metric)
|
preferenceImperialMeasurementUnits.summaryOff = getString(R.string.pref_imperial_measurement_units_summary_metric)
|
||||||
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
|
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
|
||||||
|
preferenceCategoryGeneral.contains(preferenceImperialMeasurementUnits)
|
||||||
|
screen.addPreference(preferenceImperialMeasurementUnits)
|
||||||
|
|
||||||
// set up "App Theme" preference
|
// set up "App Theme" preference
|
||||||
val preferenceThemeSelection: ListPreference = ListPreference(activity as Context)
|
val preferenceThemeSelection: ListPreference = ListPreference(activity as Context)
|
||||||
|
@ -101,16 +110,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
return@setOnPreferenceChangeListener false
|
return@setOnPreferenceChangeListener false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
screen.addPreference(preferenceThemeSelection)
|
||||||
// set up "Delete Non-Starred" preference
|
|
||||||
val preferenceDeleteNonStarred: Preference = 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)
|
|
||||||
return@setOnPreferenceClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up "Recording Accuracy" preference
|
// set up "Recording Accuracy" preference
|
||||||
val DEFAULT_OMIT_RESTS = true
|
val DEFAULT_OMIT_RESTS = true
|
||||||
|
@ -122,46 +122,27 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceOmitRests.summaryOn = getString(R.string.pref_omit_rests_on)
|
preferenceOmitRests.summaryOn = getString(R.string.pref_omit_rests_on)
|
||||||
preferenceOmitRests.summaryOff = getString(R.string.pref_omit_rests_off)
|
preferenceOmitRests.summaryOff = getString(R.string.pref_omit_rests_off)
|
||||||
preferenceOmitRests.setDefaultValue(DEFAULT_OMIT_RESTS)
|
preferenceOmitRests.setDefaultValue(DEFAULT_OMIT_RESTS)
|
||||||
|
preferenceCategoryGeneral.contains(preferenceOmitRests)
|
||||||
|
screen.addPreference(preferenceOmitRests)
|
||||||
|
|
||||||
val preferenceAutoExportInterval: SeekBarPreference = SeekBarPreference(activity as Context)
|
val preferenceDeviceID: EditTextPreference = EditTextPreference(activity as Context)
|
||||||
preferenceAutoExportInterval.title = getString(R.string.pref_auto_export_interval_title)
|
preferenceDeviceID.title = getString(R.string.pref_device_id)
|
||||||
preferenceAutoExportInterval.setIcon(R.drawable.ic_bar_chart_24)
|
preferenceDeviceID.setIcon(R.drawable.ic_smartphone_24dp)
|
||||||
preferenceAutoExportInterval.key = Keys.PREF_AUTO_EXPORT_INTERVAL
|
preferenceDeviceID.key = Keys.PREF_DEVICE_ID
|
||||||
preferenceAutoExportInterval.summary = getString(R.string.pref_auto_export_interval_summary)
|
preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + PreferencesHelper.load_device_id()
|
||||||
preferenceAutoExportInterval.showSeekBarValue = true
|
preferenceDeviceID.setDefaultValue(random_int().toString())
|
||||||
preferenceAutoExportInterval.min = 1
|
preferenceCategoryGeneral.contains(preferenceDeviceID)
|
||||||
preferenceAutoExportInterval.max = 24
|
screen.addPreference(preferenceDeviceID)
|
||||||
preferenceAutoExportInterval.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
|
||||||
|
|
||||||
// set up "Altitude Smoothing" preference
|
val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
|
||||||
// val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context)
|
preferenceCategoryAbout.title = getString(R.string.pref_about_title)
|
||||||
// preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title)
|
screen.addPreference(preferenceCategoryAbout)
|
||||||
// preferenceAltitudeSmoothingValue.setIcon(R.drawable.ic_bar_chart_24)
|
|
||||||
// preferenceAltitudeSmoothingValue.key = Keys.PREF_ALTITUDE_SMOOTHING_VALUE
|
|
||||||
// preferenceAltitudeSmoothingValue.summary = getString(R.string.pref_altitude_smoothing_value_summary)
|
|
||||||
// preferenceAltitudeSmoothingValue.showSeekBarValue = true
|
|
||||||
// preferenceAltitudeSmoothingValue.min = Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION
|
|
||||||
// preferenceAltitudeSmoothingValue.max = Keys.MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION
|
|
||||||
// preferenceAltitudeSmoothingValue.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
|
||||||
|
|
||||||
|
|
||||||
// set up "Reset" preference
|
|
||||||
val preferenceResetAdvanced: Preference = 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{
|
|
||||||
preferenceOmitRests.isChecked = DEFAULT_OMIT_RESTS
|
|
||||||
// preferenceAltitudeSmoothingValue.value = Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE
|
|
||||||
return@setOnPreferenceClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up "App Version" preference
|
// set up "App Version" preference
|
||||||
val preferenceAppVersion: Preference = Preference(context)
|
val preferenceAppVersion: Preference = Preference(context)
|
||||||
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
||||||
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
|
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
|
||||||
preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
|
preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME}"
|
||||||
R.string.app_version_name)})"
|
|
||||||
preferenceAppVersion.setOnPreferenceClickListener {
|
preferenceAppVersion.setOnPreferenceClickListener {
|
||||||
// copy to clipboard
|
// copy to clipboard
|
||||||
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
||||||
|
@ -170,40 +151,10 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
Toast.makeText(activity as Context, R.string.toast_message_copied_to_clipboard, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity as Context, R.string.toast_message_copied_to_clipboard, Toast.LENGTH_LONG).show()
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
// set preference categories
|
|
||||||
val preferenceCategoryGeneral: PreferenceCategory = 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)
|
|
||||||
preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title)
|
|
||||||
preferenceCategoryMaintenance.contains(preferenceDeleteNonStarred)
|
|
||||||
|
|
||||||
val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context)
|
|
||||||
preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title)
|
|
||||||
preferenceCategoryAdvanced.contains(preferenceOmitRests)
|
|
||||||
// preferenceCategoryAdvanced.contains(preferenceAltitudeSmoothingValue)
|
|
||||||
preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
|
|
||||||
|
|
||||||
val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
|
|
||||||
preferenceCategoryAbout.title = getString(R.string.pref_about_title)
|
|
||||||
preferenceCategoryAbout.contains(preferenceAppVersion)
|
preferenceCategoryAbout.contains(preferenceAppVersion)
|
||||||
|
screen.addPreference(preferenceAppVersion)
|
||||||
|
|
||||||
// setup preference screen
|
// setup preference screen
|
||||||
screen.addPreference(preferenceCategoryGeneral)
|
|
||||||
screen.addPreference(preferenceGpsOnly)
|
|
||||||
screen.addPreference(preferenceImperialMeasurementUnits)
|
|
||||||
screen.addPreference(preferenceThemeSelection)
|
|
||||||
screen.addPreference(preferenceCategoryMaintenance)
|
|
||||||
screen.addPreference(preferenceDeleteNonStarred)
|
|
||||||
screen.addPreference(preferenceCategoryAdvanced)
|
|
||||||
screen.addPreference(preferenceOmitRests)
|
|
||||||
// screen.addPreference(preferenceAltitudeSmoothingValue)
|
|
||||||
screen.addPreference(preferenceAutoExportInterval)
|
|
||||||
screen.addPreference(preferenceResetAdvanced)
|
|
||||||
screen.addPreference(preferenceCategoryAbout)
|
|
||||||
screen.addPreference(preferenceAppVersion)
|
|
||||||
preferenceScreen = screen
|
preferenceScreen = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,25 +163,11 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
||||||
when (type) {
|
when (type) {
|
||||||
Keys.DIALOG_DELETE_NON_STARRED -> {
|
Keys.DIALOG_DELETE_NON_STARRED -> {
|
||||||
when (dialogResult) {
|
|
||||||
// user tapped delete
|
|
||||||
true -> {
|
|
||||||
deleteNonStarred(activity as Context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
super.onYesNoDialog(type, dialogResult, payload, payloadString)
|
super.onYesNoDialog(type, dialogResult, payload, payloadString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Removes track and track files for given position - used by TracklistFragment */
|
|
||||||
private fun deleteNonStarred(context: Context) {
|
|
||||||
val tracklist: Tracklist = load_tracklist(context)
|
|
||||||
tracklist.delete_non_starred(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,25 +31,17 @@ import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import androidx.core.net.toFile
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import org.y20k.trackbook.core.Database
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.core.track_from_file
|
|
||||||
import org.y20k.trackbook.dialogs.RenameTrackDialog
|
import org.y20k.trackbook.dialogs.RenameTrackDialog
|
||||||
import org.y20k.trackbook.helpers.DateTimeHelper
|
|
||||||
import org.y20k.trackbook.helpers.FileHelper
|
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.MapOverlayHelper
|
import org.y20k.trackbook.helpers.MapOverlayHelper
|
||||||
import org.y20k.trackbook.helpers.TrackHelper
|
import org.y20k.trackbook.helpers.TrackHelper
|
||||||
|
import org.y20k.trackbook.helpers.iso8601_format
|
||||||
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||||
import java.io.File
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
||||||
|
|
||||||
|
@ -59,39 +51,24 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private lateinit var layout: TrackFragmentLayoutHolder
|
private lateinit var layout: TrackFragmentLayoutHolder
|
||||||
private lateinit var trackFileUriString: String
|
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreate from Fragment */
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?)
|
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
trackFileUriString = arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreateView from Fragment */
|
/* Overrides onCreateView from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
// initialize layout
|
// initialize layout
|
||||||
val track: Track
|
val database: Database = (requireActivity().applicationContext as Trackbook).database
|
||||||
if (this::trackFileUriString.isInitialized && trackFileUriString.isNotBlank())
|
val track: Track = Track(
|
||||||
{
|
database=database,
|
||||||
track = track_from_file(activity as Context, Uri.parse(trackFileUriString).toFile())
|
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
||||||
} else {
|
start_time= iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!),
|
||||||
track = Track()
|
stop_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!),
|
||||||
}
|
)
|
||||||
|
track.load_trkpts()
|
||||||
layout = TrackFragmentLayoutHolder(activity as Context, this as MapOverlayHelper.MarkerListener, inflater, container, track)
|
layout = TrackFragmentLayoutHolder(activity as Context, this as MapOverlayHelper.MarkerListener, inflater, container, track)
|
||||||
|
|
||||||
// set up share button
|
// set up share button
|
||||||
layout.shareButton.setOnClickListener {
|
layout.shareButton.setOnClickListener {
|
||||||
openSaveGpxDialog()
|
openSaveGpxDialog()
|
||||||
}
|
}
|
||||||
// layout.shareButton.setOnLongClickListener {
|
|
||||||
// val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
||||||
// v.vibrate(50)
|
|
||||||
// shareGpxTrack()
|
|
||||||
// return@setOnLongClickListener true
|
|
||||||
// }
|
|
||||||
// set up delete button
|
// set up delete button
|
||||||
layout.deleteButton.setOnClickListener {
|
layout.deleteButton.setOnClickListener {
|
||||||
val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}"
|
val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}"
|
||||||
|
@ -110,14 +87,12 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
return layout.rootView
|
return layout.rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onResume from Fragment */
|
/* Overrides onResume from Fragment */
|
||||||
override fun onResume()
|
override fun onResume()
|
||||||
{
|
{
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onPause from Fragment */
|
/* Overrides onPause from Fragment */
|
||||||
override fun onPause()
|
override fun onPause()
|
||||||
{
|
{
|
||||||
|
@ -131,38 +106,25 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
|
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
|
||||||
|
|
||||||
|
|
||||||
/* Pass the activity result */
|
|
||||||
private fun requestSaveGpxResult(result: ActivityResult)
|
private fun requestSaveGpxResult(result: ActivityResult)
|
||||||
{
|
{
|
||||||
// save GPX file to result file location
|
if (result.resultCode != Activity.RESULT_OK || result.data == null)
|
||||||
if (result.resultCode == Activity.RESULT_OK && result.data != null)
|
|
||||||
{
|
{
|
||||||
val sourceUri: Uri = layout.track.get_gpx_file(activity as Context).toUri()
|
return
|
||||||
Toast.makeText(activity as Context, sourceUri.toString(), Toast.LENGTH_LONG).show()
|
}
|
||||||
|
|
||||||
val targetUri: Uri? = result.data?.data
|
val targetUri: Uri? = result.data?.data
|
||||||
if (targetUri != null)
|
if (targetUri == null)
|
||||||
{
|
{
|
||||||
// copy file async (= fire & forget - no return value needed)
|
return
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
FileHelper.saveCopyOfFileSuspended(activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri)
|
|
||||||
}
|
|
||||||
Toast.makeText(activity as Context, targetUri.toString(), Toast.LENGTH_LONG).show()
|
|
||||||
// Toast.makeText(activity as Context, R.string.toast_message_save_gpx, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val outputsuccess: Uri? = layout.track.export_gpx(activity as Context, targetUri)
|
||||||
/* Overrides onRenameTrackDialog from RenameTrackDialog */
|
if (outputsuccess == null)
|
||||||
override fun onRenameTrackDialog(textInput: String)
|
|
||||||
{
|
{
|
||||||
// rename track async (= fire & forget - no return value needed)
|
Toast.makeText(activity as Context, "failed to export for some reason", Toast.LENGTH_LONG).show()
|
||||||
CoroutineScope(Dispatchers.IO).launch { FileHelper.renameTrackSuspended(activity as Context, layout.track, textInput) }
|
}
|
||||||
// update name in layout
|
|
||||||
layout.track.name = textInput
|
|
||||||
layout.trackNameView.text = textInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String)
|
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String)
|
||||||
|
@ -175,15 +137,18 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
// user tapped remove track
|
// user tapped remove track
|
||||||
true -> {
|
true -> {
|
||||||
// switch to TracklistFragment and remove track there
|
// switch to TracklistFragment and remove track there
|
||||||
val bundle: Bundle = bundleOf(Keys.ARG_TRACK_ID to layout.track.id)
|
// val bundle: Bundle = bundleOf(Keys.ARG_TRACK_ID to layout.track.id)
|
||||||
findNavController().navigate(R.id.tracklist_fragment, bundle)
|
// findNavController().navigate(R.id.tracklist_fragment, bundle)
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
{
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onMarkerTapped from MarkerListener */
|
/* Overrides onMarkerTapped from MarkerListener */
|
||||||
override fun onMarkerTapped(latitude: Double, longitude: Double)
|
override fun onMarkerTapped(latitude: Double, longitude: Double)
|
||||||
{
|
{
|
||||||
|
@ -192,12 +157,11 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
layout.updateTrackOverlay()
|
layout.updateTrackOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Opens up a file picker to select the save location */
|
/* Opens up a file picker to select the save location */
|
||||||
private fun openSaveGpxDialog()
|
private fun openSaveGpxDialog()
|
||||||
{
|
{
|
||||||
val context = this.activity as Context
|
val context = this.activity as Context
|
||||||
val export_name: String = DateTimeHelper.convertToSortableDateString(layout.track.recordingStart) + Keys.GPX_FILE_EXTENSION
|
val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(layout.track.start_time) + Keys.GPX_FILE_EXTENSION
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = Keys.MIME_TYPE_GPX
|
type = Keys.MIME_TYPE_GPX
|
||||||
|
@ -213,42 +177,5 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
LogHelper.e(TAG, "Unable to save GPX.")
|
LogHelper.e(TAG, "Unable to save GPX.")
|
||||||
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()
|
||||||
}
|
}
|
||||||
// val context = this.activity as Context
|
|
||||||
// val export_name: String = DateTimeHelper.convertToSortableDateString(layout.track.recordingStart) + Keys.GPX_FILE_EXTENSION
|
|
||||||
// val sourceUri: Uri = layout.track.get_gpx_file(activity as Context).toUri()
|
|
||||||
// // val targetUri: Uri = "file:///storage/emulated/0/Syncthing/GPX".toUri()
|
|
||||||
// val targetUri: Uri = File(File("/storage/emulated/0/Syncthing/GPX"), export_name).toUri()
|
|
||||||
// Toast.makeText(activity as Context, targetUri.toString(), Toast.LENGTH_LONG).show()
|
|
||||||
// CoroutineScope(Dispatchers.IO).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()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Share track as GPX via share sheet */
|
|
||||||
private fun shareGpxTrack()
|
|
||||||
{
|
|
||||||
val gpxFile = layout.track.get_gpx_file(this.activity as Context)
|
|
||||||
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
|
|
||||||
type = Keys.MIME_TYPE_GPX
|
|
||||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
putExtra(Intent.EXTRA_STREAM, gpxShareUri)
|
|
||||||
}, null)
|
|
||||||
|
|
||||||
// show share sheet - if file helper is available
|
|
||||||
val packageManager: PackageManager? = activity?.packageManager
|
|
||||||
if (packageManager != null && shareIntent.resolveActivity(packageManager) != null)
|
|
||||||
{
|
|
||||||
startActivity(shareIntent)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Toast.makeText(activity, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,40 +18,100 @@
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import org.y20k.trackbook.core.Database
|
||||||
|
import org.y20k.trackbook.core.Homepoint
|
||||||
|
import org.y20k.trackbook.core.Trkpt
|
||||||
import org.y20k.trackbook.helpers.AppThemeHelper
|
import org.y20k.trackbook.helpers.AppThemeHelper
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper.initPreferences
|
import org.y20k.trackbook.helpers.PreferencesHelper.initPreferences
|
||||||
|
import org.y20k.trackbook.helpers.iso8601
|
||||||
|
import org.y20k.trackbook.helpers.iso8601_format
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Trackbook.class
|
* Trackbook.class
|
||||||
*/
|
*/
|
||||||
class Trackbook: Application() {
|
class Trackbook(): Application() {
|
||||||
|
val database: Database = Database()
|
||||||
|
val homepoints: ArrayList<Homepoint> = ArrayList()
|
||||||
|
|
||||||
|
override fun onCreate()
|
||||||
/* Define log tag */
|
{
|
||||||
private val TAG: String = LogHelper.makeLogTag(Trackbook::class.java)
|
|
||||||
|
|
||||||
|
|
||||||
/* Implements onCreate */
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
LogHelper.v(TAG, "Trackbook application started.")
|
LogHelper.v("VOUSSOIR", "Trackbook application started.")
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
// initialize single sharedPreferences object when app is launched
|
// initialize single sharedPreferences object when app is launched
|
||||||
initPreferences()
|
initPreferences()
|
||||||
// set Dark / Light theme state
|
// set Dark / Light theme state
|
||||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
||||||
|
|
||||||
|
ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
Log.i("VOUSSOIR", "Device ID = ${PreferencesHelper.load_device_id()}")
|
||||||
|
if (ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
||||||
|
{
|
||||||
|
this.database.connect(File("/storage/emulated/0/Syncthing/GPX/trkpt_${PreferencesHelper.load_device_id()}.db"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun load_homepoints()
|
||||||
|
{
|
||||||
|
this.homepoints.clear()
|
||||||
|
homepoint_generator().forEach { homepoint -> this.homepoints.add(homepoint) }
|
||||||
|
}
|
||||||
|
|
||||||
/* Implements onTerminate */
|
fun homepoint_generator() = iterator<Homepoint>
|
||||||
override fun onTerminate() {
|
{
|
||||||
|
if (! database.ready)
|
||||||
|
{
|
||||||
|
return@iterator
|
||||||
|
}
|
||||||
|
val cursor: Cursor = database.connection.query(
|
||||||
|
"homepoints",
|
||||||
|
arrayOf("lat", "lon", "radius", "name"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
||||||
|
val COLUMN_LON = cursor.getColumnIndex("lon")
|
||||||
|
val COLUMN_RADIUS = cursor.getColumnIndex("radius")
|
||||||
|
val COLUMN_NAME = cursor.getColumnIndex("name")
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (cursor.moveToNext())
|
||||||
|
{
|
||||||
|
val homepoint = Homepoint(
|
||||||
|
latitude=cursor.getDouble(COLUMN_LAT),
|
||||||
|
longitude=cursor.getDouble(COLUMN_LON),
|
||||||
|
radius=cursor.getDouble(COLUMN_RADIUS),
|
||||||
|
name=cursor.getString(COLUMN_NAME),
|
||||||
|
)
|
||||||
|
yield(homepoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTerminate()
|
||||||
|
{
|
||||||
super.onTerminate()
|
super.onTerminate()
|
||||||
LogHelper.v(TAG, "Trackbook application terminated.")
|
LogHelper.v("VOUSSOIR", "Trackbook application terminated.")
|
||||||
|
database.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,17 +31,17 @@ import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.ContentValues
|
||||||
import android.os.*
|
import android.os.*
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.Runnable
|
import kotlinx.coroutines.Runnable
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.core.load_temp_track
|
import org.y20k.trackbook.core.Database
|
||||||
|
import org.y20k.trackbook.core.Trkpt
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TrackerService class
|
* TrackerService class
|
||||||
|
@ -52,24 +52,25 @@ class TrackerService: Service(), SensorEventListener
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java)
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
var trackingState: Int = Keys.STATE_TRACKING_NOT_STARTED
|
var trackingState: Int = Keys.STATE_TRACKING_STOPPED
|
||||||
var gpsProviderActive: Boolean = false
|
var gpsProviderActive: Boolean = false
|
||||||
var networkProviderActive: Boolean = false
|
var networkProviderActive: Boolean = false
|
||||||
var useImperial: Boolean = false
|
var useImperial: Boolean = false
|
||||||
var gpsOnly: Boolean = false
|
var gpsOnly: Boolean = false
|
||||||
var omitRests: Boolean = true
|
var omitRests: Boolean = true
|
||||||
var autoExportInterval: Int = Keys.DEFAULT_AUTO_EXPORT_INTERVAL
|
var device_id: String = random_int().toString()
|
||||||
|
var recording_started: Date = GregorianCalendar.getInstance().time
|
||||||
|
var commitInterval: Int = Keys.COMMIT_INTERVAL
|
||||||
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
||||||
var lastTempSave: Date = Keys.DEFAULT_DATE
|
var lastCommit: Date = Keys.DEFAULT_DATE
|
||||||
var lastAutoExport: Date = Keys.DEFAULT_DATE
|
|
||||||
var stepCountOffset: Float = 0f
|
var stepCountOffset: Float = 0f
|
||||||
var track: Track = Track()
|
lateinit var track: Track
|
||||||
var gpsLocationListenerRegistered: Boolean = false
|
var gpsLocationListenerRegistered: Boolean = false
|
||||||
var networkLocationListenerRegistered: Boolean = false
|
var networkLocationListenerRegistered: Boolean = false
|
||||||
var bound: Boolean = false
|
var bound: Boolean = false
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||||
private var altitudeValues: SimpleMovingAverageQueue = SimpleMovingAverageQueue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
lateinit var trackbook: Trackbook
|
||||||
private lateinit var locationManager: LocationManager
|
private lateinit var locationManager: LocationManager
|
||||||
private lateinit var sensorManager: SensorManager
|
private lateinit var sensorManager: SensorManager
|
||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
|
@ -111,7 +112,8 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adds a Network location listener to location manager */
|
/* Adds a Network location listener to location manager */
|
||||||
private fun addNetworkLocationListener() {
|
private fun addNetworkLocationListener()
|
||||||
|
{
|
||||||
if (gpsOnly)
|
if (gpsOnly)
|
||||||
{
|
{
|
||||||
LogHelper.v(TAG, "Skipping Network listener. User prefers GPS-only.")
|
LogHelper.v(TAG, "Skipping Network listener. User prefers GPS-only.")
|
||||||
|
@ -150,12 +152,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
|
|
||||||
fun clearTrack()
|
fun clearTrack()
|
||||||
{
|
{
|
||||||
track = Track()
|
trackingState = Keys.STATE_TRACKING_STOPPED
|
||||||
stepCountOffset = 0f
|
|
||||||
FileHelper.delete_temp_file(this as Context)
|
|
||||||
trackingState = Keys.STATE_TRACKING_NOT_STARTED
|
|
||||||
PreferencesHelper.saveTrackingState(trackingState)
|
PreferencesHelper.saveTrackingState(trackingState)
|
||||||
stopForeground(true)
|
track.delete()
|
||||||
|
track = Track(trackbook.database, device_id, start_time=GregorianCalendar.getInstance().time, stop_time=Date(GregorianCalendar.getInstance().time.time + 86400))
|
||||||
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
|
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,9 +206,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
private fun displayNotification(): Notification {
|
private fun displayNotification(): Notification {
|
||||||
val notification: Notification = notificationHelper.createNotification(
|
val notification: Notification = notificationHelper.createNotification(
|
||||||
trackingState,
|
trackingState,
|
||||||
track.distance,
|
iso8601(GregorianCalendar.getInstance().time)
|
||||||
track.duration,
|
|
||||||
useImperial
|
|
||||||
)
|
)
|
||||||
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
|
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
|
||||||
return notification
|
return notification
|
||||||
|
@ -233,11 +232,14 @@ class TrackerService: Service(), SensorEventListener
|
||||||
override fun onCreate()
|
override fun onCreate()
|
||||||
{
|
{
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
trackbook = (applicationContext as Trackbook)
|
||||||
|
trackbook.load_homepoints()
|
||||||
gpsOnly = PreferencesHelper.loadGpsOnly()
|
gpsOnly = PreferencesHelper.loadGpsOnly()
|
||||||
|
device_id = PreferencesHelper.load_device_id()
|
||||||
|
track = Track(trackbook.database, device_id, start_time=GregorianCalendar.getInstance().time, stop_time=Date(GregorianCalendar.getInstance().time.time + 86400))
|
||||||
useImperial = PreferencesHelper.loadUseImperialUnits()
|
useImperial = PreferencesHelper.loadUseImperialUnits()
|
||||||
omitRests = PreferencesHelper.loadOmitRests()
|
omitRests = PreferencesHelper.loadOmitRests()
|
||||||
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
commitInterval = PreferencesHelper.loadCommitInterval()
|
||||||
|
|
||||||
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
@ -248,8 +250,6 @@ class TrackerService: Service(), SensorEventListener
|
||||||
networkLocationListener = createLocationListener()
|
networkLocationListener = createLocationListener()
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
currentBestLocation = LocationHelper.getLastKnownLocation(this)
|
currentBestLocation = LocationHelper.getLastKnownLocation(this)
|
||||||
track = load_temp_track(this)
|
|
||||||
// altitudeValues.capacity = PreferencesHelper.loadAltitudeSmoothingValue()
|
|
||||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,13 +282,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
if (sensorEvent != null) {
|
if (sensorEvent != null) {
|
||||||
if (stepCountOffset == 0f) {
|
if (stepCountOffset == 0f) {
|
||||||
// store steps previously recorded by the system
|
// store steps previously recorded by the system
|
||||||
stepCountOffset = (sensorEvent.values[0] - 1) - track.stepCount // subtract any steps recorded during this session in case the app was killed
|
stepCountOffset = (sensorEvent.values[0] - 1) - 0 // subtract any steps recorded during this session in case the app was killed
|
||||||
}
|
}
|
||||||
// calculate step count - subtract steps previously recorded
|
// calculate step count - subtract steps previously recorded
|
||||||
steps = sensorEvent.values[0] - stepCountOffset
|
steps = sensorEvent.values[0] - stepCountOffset
|
||||||
}
|
}
|
||||||
// update step count in track
|
|
||||||
track.stepCount = steps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onStartCommand from Service */
|
/* Overrides onStartCommand from Service */
|
||||||
|
@ -300,7 +298,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
||||||
{
|
{
|
||||||
LogHelper.w(TAG, "Trackbook has been killed by the operating system. Trying to resume recording.")
|
LogHelper.w(TAG, "Trackbook has been killed by the operating system. Trying to resume recording.")
|
||||||
resumeTracking()
|
startTracking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (intent.action == Keys.ACTION_STOP)
|
else if (intent.action == Keys.ACTION_STOP)
|
||||||
|
@ -311,10 +309,6 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
startTracking()
|
startTracking()
|
||||||
}
|
}
|
||||||
else if (intent.action == Keys.ACTION_RESUME)
|
|
||||||
{
|
|
||||||
resumeTracking()
|
|
||||||
}
|
|
||||||
|
|
||||||
// START_STICKY is used for services that are explicitly started and stopped as needed
|
// START_STICKY is used for services that are explicitly started and stopped as needed
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
|
@ -358,42 +352,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resume tracking after stop/pause */
|
|
||||||
fun resumeTracking()
|
|
||||||
{
|
|
||||||
// load temp track - returns an empty track if there is no temp file.
|
|
||||||
track = load_temp_track(this)
|
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
|
||||||
track.wayPoints.last().isStopOver = true
|
|
||||||
}
|
|
||||||
track.resumed = true
|
|
||||||
track.recordingPaused += (GregorianCalendar.getInstance().time.time - track.recordingStop.time)
|
|
||||||
startTracking(newTrack = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveTrackAndClear(context: Context)
|
|
||||||
{
|
|
||||||
this.pauseTracking()
|
|
||||||
track.save_all_files(context)
|
|
||||||
this.clearTrack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveTrackAndStartNew(context: Context)
|
|
||||||
{
|
|
||||||
if (track.wayPoints.isNotEmpty())
|
|
||||||
{
|
|
||||||
track.save_all_files(context)
|
|
||||||
}
|
|
||||||
track = Track()
|
|
||||||
FileHelper.delete_temp_file(this as Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startStepCounter()
|
private fun startStepCounter()
|
||||||
{
|
{
|
||||||
val stepCounterAvailable = sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER), SensorManager.SENSOR_DELAY_UI)
|
val stepCounterAvailable = sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER), SensorManager.SENSOR_DELAY_UI)
|
||||||
if (!stepCounterAvailable) {
|
if (!stepCounterAvailable) {
|
||||||
LogHelper.w(TAG, "Pedometer sensor not available.")
|
LogHelper.w(TAG, "Pedometer sensor not available.")
|
||||||
track.stepCount = -1f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,12 +364,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
addGpsLocationListener()
|
addGpsLocationListener()
|
||||||
addNetworkLocationListener()
|
addNetworkLocationListener()
|
||||||
// set up new track
|
|
||||||
if (newTrack) {
|
|
||||||
track = Track()
|
|
||||||
stepCountOffset = 0f
|
|
||||||
}
|
|
||||||
trackingState = Keys.STATE_TRACKING_ACTIVE
|
trackingState = Keys.STATE_TRACKING_ACTIVE
|
||||||
|
if (newTrack)
|
||||||
|
{
|
||||||
|
this.recording_started = GregorianCalendar.getInstance().time
|
||||||
|
}
|
||||||
PreferencesHelper.saveTrackingState(trackingState)
|
PreferencesHelper.saveTrackingState(trackingState)
|
||||||
startStepCounter()
|
startStepCounter()
|
||||||
handler.postDelayed(periodicTrackUpdate, 0)
|
handler.postDelayed(periodicTrackUpdate, 0)
|
||||||
|
@ -415,19 +377,16 @@ class TrackerService: Service(), SensorEventListener
|
||||||
|
|
||||||
fun pauseTracking()
|
fun pauseTracking()
|
||||||
{
|
{
|
||||||
track.recordingStop = GregorianCalendar.getInstance().time
|
trackbook.database.commit()
|
||||||
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
|
||||||
|
|
||||||
trackingState = Keys.STATE_TRACKING_PAUSED
|
trackingState = Keys.STATE_TRACKING_STOPPED
|
||||||
PreferencesHelper.saveTrackingState(trackingState)
|
PreferencesHelper.saveTrackingState(trackingState)
|
||||||
|
|
||||||
altitudeValues.reset()
|
|
||||||
|
|
||||||
sensorManager.unregisterListener(this)
|
sensorManager.unregisterListener(this)
|
||||||
handler.removeCallbacks(periodicTrackUpdate)
|
handler.removeCallbacks(periodicTrackUpdate)
|
||||||
|
|
||||||
displayNotification()
|
displayNotification()
|
||||||
stopForeground(false)
|
stopForeground(STOP_FOREGROUND_DETACH)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -435,24 +394,25 @@ class TrackerService: Service(), SensorEventListener
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
// preference "Restrict to GPS"
|
Keys.PREF_GPS_ONLY ->
|
||||||
Keys.PREF_GPS_ONLY -> {
|
{
|
||||||
gpsOnly = PreferencesHelper.loadGpsOnly()
|
gpsOnly = PreferencesHelper.loadGpsOnly()
|
||||||
when (gpsOnly) {
|
when (gpsOnly) {
|
||||||
true -> removeNetworkLocationListener()
|
true -> removeNetworkLocationListener()
|
||||||
false -> addNetworkLocationListener()
|
false -> addNetworkLocationListener()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// preference "Use Imperial Measurements"
|
Keys.PREF_USE_IMPERIAL_UNITS ->
|
||||||
Keys.PREF_USE_IMPERIAL_UNITS -> {
|
{
|
||||||
useImperial = PreferencesHelper.loadUseImperialUnits()
|
useImperial = PreferencesHelper.loadUseImperialUnits()
|
||||||
}
|
}
|
||||||
// preference "Recording Accuracy"
|
Keys.PREF_OMIT_RESTS ->
|
||||||
Keys.PREF_OMIT_RESTS -> {
|
{
|
||||||
omitRests = PreferencesHelper.loadOmitRests()
|
omitRests = PreferencesHelper.loadOmitRests()
|
||||||
}
|
}
|
||||||
Keys.PREF_AUTO_EXPORT_INTERVAL -> {
|
Keys.PREF_DEVICE_ID ->
|
||||||
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
{
|
||||||
|
device_id = PreferencesHelper.load_device_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,84 +430,96 @@ class TrackerService: Service(), SensorEventListener
|
||||||
* End of inner class
|
* End of inner class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
fun should_keep_point(location: Location): Boolean
|
||||||
|
{
|
||||||
|
if(! trackbook.database.ready)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to database not ready.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (location.latitude == 0.0 || location.longitude == 0.0)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to 0,0 location.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (! LocationHelper.isRecentEnough(location))
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to not recent enough.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (! LocationHelper.isAccurateEnough(location, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY))
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to not accurate enough.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (homepoint in trackbook.homepoints)
|
||||||
|
{
|
||||||
|
if (LocationHelper.calculateDistance(homepoint.location, location) < homepoint.radius)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to homepoint ${homepoint}.")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (track.trkpts.isEmpty())
|
||||||
|
{
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (! LocationHelper.isDifferentEnough(track.trkpts.last().toLocation(), location, omitRests))
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "Omitting due to too close to previous.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Runnable: Periodically track updates (if recording active)
|
* Runnable: Periodically track updates (if recording active)
|
||||||
*/
|
*/
|
||||||
private val periodicTrackUpdate: Runnable = object : Runnable
|
private val periodicTrackUpdate: Runnable = object : Runnable
|
||||||
{
|
{
|
||||||
override fun run() {
|
override fun run() {
|
||||||
// add waypoint to track - step count is continuously updated in onSensorChanged
|
|
||||||
val success = track.add_waypoint(currentBestLocation, omitRests, track.resumed)
|
|
||||||
val now: Date = GregorianCalendar.getInstance().time
|
val now: Date = GregorianCalendar.getInstance().time
|
||||||
if (success) {
|
val nowstr: String = iso8601(now)
|
||||||
track.resumed = false
|
val trkpt: Trkpt = Trkpt(location=currentBestLocation)
|
||||||
|
Log.i("VOUSSOIR", "Processing point ${currentBestLocation.latitude}, ${currentBestLocation.longitude} ${nowstr}.")
|
||||||
// store previous smoothed altitude
|
if (should_keep_point((currentBestLocation)))
|
||||||
val previousAltitude: Double = altitudeValues.getAverage()
|
{
|
||||||
// put current altitude into queue
|
val values = ContentValues().apply {
|
||||||
val currentBestLocationAltitude: Double = currentBestLocation.altitude
|
put("device_id", device_id)
|
||||||
if (currentBestLocationAltitude != Keys.DEFAULT_ALTITUDE) altitudeValues.add(currentBestLocationAltitude)
|
put("lat", trkpt.latitude)
|
||||||
// TODO remove
|
put("lon", trkpt.longitude)
|
||||||
// uncomment to use test altitude values - useful if testing with an emulator
|
put("time", nowstr)
|
||||||
//altitudeValues.add(getTestAltitude()) // TODO remove
|
put("accuracy", trkpt.accuracy)
|
||||||
// TODO remove
|
put("sat", trkpt.numberSatellites)
|
||||||
|
put("ele", trkpt.altitude)
|
||||||
// only start calculating elevation differences, if enough data has been added to queue
|
put("star", 0)
|
||||||
if (altitudeValues.prepared) {
|
}
|
||||||
// get current smoothed altitude
|
if (! trackbook.database.connection.inTransaction())
|
||||||
val currentAltitude: Double = altitudeValues.getAverage()
|
{
|
||||||
// calculate and store elevation differences
|
trackbook.database.connection.beginTransaction()
|
||||||
track = LocationHelper.calculateElevationDifferences(currentAltitude, previousAltitude, track)
|
}
|
||||||
// TODO remove
|
trackbook.database.connection.insert("trkpt", null, values)
|
||||||
LogHelper.d(TAG, "Elevation Calculation || prev = $previousAltitude | curr = $currentAltitude | pos = ${track.positiveElevation} | neg = ${track.negativeElevation}")
|
track.trkpts.add(trkpt)
|
||||||
// TODO remove
|
if (track.trkpts.size > track.dequelimit)
|
||||||
|
{
|
||||||
|
track.trkpts.removeFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
// save a temp track
|
if (now.time - lastCommit.time > Keys.SAVE_TEMP_TRACK_INTERVAL)
|
||||||
if (now.time - lastTempSave.time > Keys.SAVE_TEMP_TRACK_INTERVAL) {
|
{
|
||||||
lastTempSave = now
|
if (trackbook.database.connection.inTransaction())
|
||||||
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
{
|
||||||
|
trackbook.database.commit()
|
||||||
}
|
}
|
||||||
|
lastCommit = now
|
||||||
}
|
}
|
||||||
if (now.time - track.recordingStart.time > (autoExportInterval * Keys.ONE_HOUR_IN_MILLISECONDS)) {
|
|
||||||
saveTrackAndStartNew(this@TrackerService)
|
|
||||||
}
|
}
|
||||||
// update notification
|
// update notification
|
||||||
displayNotification()
|
displayNotification()
|
||||||
// re-run this in set interval
|
// re-run this in set interval
|
||||||
handler.postDelayed(this, Keys.ADD_WAYPOINT_TO_TRACK_INTERVAL)
|
handler.postDelayed(this, Keys.TRACKING_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* End of declaration
|
* End of declaration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Simple queue that evicts older elements and holds an average */
|
|
||||||
/* Credit: CircularQueue https://stackoverflow.com/a/51923797 */
|
|
||||||
class SimpleMovingAverageQueue(var capacity: Int) : LinkedList<Double>()
|
|
||||||
{
|
|
||||||
var prepared: Boolean = false
|
|
||||||
private var sum: Double = 0.0
|
|
||||||
override fun add(element: Double): Boolean
|
|
||||||
{
|
|
||||||
prepared = this.size + 1 >= Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION
|
|
||||||
if (this.size >= capacity) {
|
|
||||||
sum -= this.first
|
|
||||||
removeFirst()
|
|
||||||
}
|
|
||||||
sum += element
|
|
||||||
return super.add(element)
|
|
||||||
}
|
|
||||||
fun getAverage(): Double
|
|
||||||
{
|
|
||||||
return sum / this.size
|
|
||||||
}
|
|
||||||
fun reset()
|
|
||||||
{
|
|
||||||
this.clear()
|
|
||||||
prepared = false
|
|
||||||
sum = 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class TrackingToggleTileService: TileService() {
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private var bound: Boolean = false
|
private var bound: Boolean = false
|
||||||
private var trackingState: Int = Keys.STATE_TRACKING_NOT_STARTED
|
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
|
||||||
private lateinit var trackerService: TrackerService
|
private lateinit var trackerService: TrackerService
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.UiHelper
|
import org.y20k.trackbook.helpers.UiHelper
|
||||||
|
import org.y20k.trackbook.helpers.iso8601_format
|
||||||
import org.y20k.trackbook.tracklist.TracklistAdapter
|
import org.y20k.trackbook.tracklist.TracklistAdapter
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,21 +49,18 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private lateinit var tracklistAdapter: TracklistAdapter
|
private lateinit var tracklistAdapter: TracklistAdapter
|
||||||
private lateinit var trackElementList: RecyclerView
|
private lateinit var trackElementList: RecyclerView
|
||||||
private lateinit var tracklistOnboarding: ConstraintLayout
|
private lateinit var tracklistOnboarding: ConstraintLayout
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreateView from Fragment */
|
/* Overrides onCreateView from Fragment */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// create tracklist adapter
|
// create tracklist adapter
|
||||||
tracklistAdapter = TracklistAdapter(this)
|
tracklistAdapter = TracklistAdapter(this, (requireActivity().applicationContext as Trackbook).database)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreateView from Fragment */
|
/* Overrides onCreateView from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
// find views
|
// find views
|
||||||
|
@ -97,9 +95,9 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
override fun onTrackElementTapped(track: Track) {
|
override fun onTrackElementTapped(track: Track) {
|
||||||
val bundle: Bundle = bundleOf(
|
val bundle: Bundle = bundleOf(
|
||||||
Keys.ARG_TRACK_TITLE to track.name,
|
Keys.ARG_TRACK_TITLE to track.name,
|
||||||
Keys.ARG_TRACK_FILE_URI to track.get_json_file(activity as Context).toUri().toString(),
|
Keys.ARG_TRACK_DEVICE_ID to track.device_id,
|
||||||
Keys.ARG_GPX_FILE_URI to track.get_gpx_file(activity as Context).toUri().toString(),
|
Keys.ARG_TRACK_START_TIME to iso8601_format.format(track.start_time),
|
||||||
Keys.ARG_TRACK_ID to track.id
|
Keys.ARG_TRACK_STOP_TIME to iso8601_format.format(track.stop_time),
|
||||||
)
|
)
|
||||||
findNavController().navigate(R.id.fragment_track, bundle)
|
findNavController().navigate(R.id.fragment_track, bundle)
|
||||||
}
|
}
|
||||||
|
@ -130,7 +128,6 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// toggle onboarding layout
|
// toggle onboarding layout
|
||||||
private fun toggleOnboardingLayout() {
|
private fun toggleOnboardingLayout() {
|
||||||
when (tracklistAdapter.isEmpty()) {
|
when (tracklistAdapter.isEmpty()) {
|
||||||
|
@ -147,13 +144,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Inner class: custom LinearLayoutManager that overrides onLayoutCompleted
|
* Inner class: custom LinearLayoutManager that overrides onLayoutCompleted
|
||||||
*/
|
*/
|
||||||
inner class CustomLinearLayoutManager(context: Context): LinearLayoutManager(context, VERTICAL, false) {
|
inner class CustomLinearLayoutManager(context: Context): LinearLayoutManager(context, VERTICAL, false)
|
||||||
|
{
|
||||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
52
app/src/main/java/org/y20k/trackbook/core/Database.kt
Normal file
52
app/src/main/java/org/y20k/trackbook/core/Database.kt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package org.y20k.trackbook.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteDatabase.openOrCreateDatabase
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class Database()
|
||||||
|
{
|
||||||
|
var ready: Boolean = false
|
||||||
|
lateinit var file: File
|
||||||
|
lateinit var connection: SQLiteDatabase
|
||||||
|
fun close()
|
||||||
|
{
|
||||||
|
this.connection.close()
|
||||||
|
this.ready = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(file: File)
|
||||||
|
{
|
||||||
|
this.file = file
|
||||||
|
this.connection = openOrCreateDatabase(file, null)
|
||||||
|
this.initialize_tables()
|
||||||
|
this.ready = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commit()
|
||||||
|
{
|
||||||
|
if (! this.ready)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (! this.connection.inTransaction())
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.i("VOUSSOIR", "Committing.")
|
||||||
|
this.connection.setTransactionSuccessful()
|
||||||
|
this.connection.endTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initialize_tables()
|
||||||
|
{
|
||||||
|
this.connection.beginTransaction()
|
||||||
|
this.connection.execSQL("CREATE TABLE IF NOT EXISTS meta(name TEXT PRIMARY KEY, value TEXT)")
|
||||||
|
this.connection.execSQL("CREATE TABLE IF NOT EXISTS trkpt(lat REAL NOT NULL, lon REAL NOT NULL, time TEXT NOT NULL, accuracy REAL, device_id INTEGER NOT NULL, ele INTEGER, sat INTEGER, star INTEGER, PRIMARY KEY(lat, lon, time, device_id))")
|
||||||
|
this.connection.execSQL("CREATE TABLE IF NOT EXISTS homepoints(lat REAL NOT NULL, lon REAL NOT NULL, radius REAL NOT NULL, name TEXT, PRIMARY KEY(lat, lon))")
|
||||||
|
this.connection.setTransactionSuccessful()
|
||||||
|
this.connection.endTransaction()
|
||||||
|
}
|
||||||
|
}
|
20
app/src/main/java/org/y20k/trackbook/core/Homepoint.kt
Normal file
20
app/src/main/java/org/y20k/trackbook/core/Homepoint.kt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package org.y20k.trackbook.core
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Homepoint(val latitude: Double, val longitude: Double, val radius: Double, val name: String)
|
||||||
|
{
|
||||||
|
val location: Location = this.to_location()
|
||||||
|
|
||||||
|
private fun to_location(): Location
|
||||||
|
{
|
||||||
|
val location: Location = Location("homepoint")
|
||||||
|
location.latitude = latitude
|
||||||
|
location.longitude = longitude
|
||||||
|
location.altitude = 0.0
|
||||||
|
location.accuracy = radius.toFloat()
|
||||||
|
location.time = GregorianCalendar.getInstance().time.time
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,260 +16,78 @@
|
||||||
|
|
||||||
package org.y20k.trackbook.core
|
package org.y20k.trackbook.core
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.database.Cursor
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.Keep
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
|
||||||
import org.y20k.trackbook.helpers.DateTimeHelper
|
import org.y20k.trackbook.helpers.DateTimeHelper
|
||||||
import org.y20k.trackbook.helpers.FileHelper
|
|
||||||
import org.y20k.trackbook.helpers.LocationHelper
|
import org.y20k.trackbook.helpers.LocationHelper
|
||||||
|
import org.y20k.trackbook.helpers.iso8601
|
||||||
|
import org.y20k.trackbook.helpers.iso8601_format
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Track data class
|
* Track data class
|
||||||
*/
|
*/
|
||||||
@Keep
|
|
||||||
@Parcelize
|
|
||||||
data class Track (
|
data class Track (
|
||||||
@Expose val id: Long = make_random_id(),
|
val database: Database,
|
||||||
@Expose var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
val device_id: String,
|
||||||
@Expose val wayPoints: MutableList<WayPoint> = mutableListOf<WayPoint>(),
|
val start_time: Date,
|
||||||
@Expose var distance: Float = 0f,
|
val stop_time: Date,
|
||||||
@Expose var duration: Long = 0L,
|
var name: String = "",
|
||||||
@Expose var recordingPaused: Long = 0L,
|
var dequelimit: Int = 7200,
|
||||||
@Expose var stepCount: Float = 0f,
|
var view_latitude: Double = Keys.DEFAULT_LATITUDE,
|
||||||
@Expose var recordingStart: Date = GregorianCalendar.getInstance().time,
|
var view_longitude: Double = Keys.DEFAULT_LONGITUDE,
|
||||||
@Expose var dateString: String = DateTimeHelper.convertToReadableDate(recordingStart),
|
var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
||||||
@Expose var recordingStop: Date = recordingStart,
|
val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(dequelimit),
|
||||||
// The resumed flag will be true for the first point that is received after unpausing a
|
var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
||||||
// recording, so that the distance travelled while paused is not added to the track.distance.
|
|
||||||
@Expose var resumed: Boolean = false,
|
|
||||||
@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 latitude: Double = Keys.DEFAULT_LATITUDE,
|
|
||||||
@Expose var longitude: Double = Keys.DEFAULT_LONGITUDE,
|
|
||||||
@Expose var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
|
||||||
@Expose var name: String = DateTimeHelper.convertToReadableDate(recordingStart),
|
|
||||||
@Expose var starred: Boolean = false,
|
|
||||||
): Parcelable
|
|
||||||
{
|
|
||||||
fun add_waypoint(location: Location, omitRests: Boolean, resumed: Boolean): Boolean
|
|
||||||
{
|
|
||||||
// Step 1: Get previous location
|
|
||||||
val previousLocation: Location?
|
|
||||||
var numberOfWayPoints: Int = this.wayPoints.size
|
|
||||||
|
|
||||||
// CASE: First location
|
|
||||||
if (numberOfWayPoints == 0)
|
|
||||||
{
|
|
||||||
previousLocation = null
|
|
||||||
}
|
|
||||||
// CASE: Second location - check if first location was plausible & remove implausible location
|
|
||||||
else if (numberOfWayPoints == 1 && !LocationHelper.isFirstLocationPlausible(location, this))
|
|
||||||
{
|
|
||||||
previousLocation = null
|
|
||||||
numberOfWayPoints = 0
|
|
||||||
this.wayPoints.removeAt(0)
|
|
||||||
}
|
|
||||||
// CASE: Third location or second location (if first was plausible)
|
|
||||||
else
|
|
||||||
{
|
|
||||||
previousLocation = this.wayPoints[numberOfWayPoints - 1].toLocation()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Update duration
|
|
||||||
val now: Date = GregorianCalendar.getInstance().time
|
|
||||||
val difference: Long = now.time - this.recordingStop.time
|
|
||||||
this.duration += difference
|
|
||||||
this.recordingStop = now
|
|
||||||
|
|
||||||
// Step 3: Add waypoint, if recent and accurate and different enough
|
|
||||||
val shouldBeAdded: Boolean = (
|
|
||||||
LocationHelper.isRecentEnough(location) &&
|
|
||||||
LocationHelper.isAccurateEnough(location, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY) &&
|
|
||||||
LocationHelper.isDifferentEnough(previousLocation, location, omitRests)
|
|
||||||
)
|
)
|
||||||
if (! shouldBeAdded)
|
|
||||||
{
|
{
|
||||||
return false
|
fun delete()
|
||||||
}
|
|
||||||
// Step 3.1: Update distance (do not update if resumed -> we do not want to add values calculated during a recording pause)
|
|
||||||
if (!resumed)
|
|
||||||
{
|
{
|
||||||
this.distance = this.distance + LocationHelper.calculateDistance(previousLocation, location)
|
|
||||||
}
|
|
||||||
// Step 3.2: Update altitude values
|
|
||||||
val altitude: Double = location.altitude
|
|
||||||
if (altitude != 0.0)
|
|
||||||
{
|
|
||||||
if (numberOfWayPoints == 0)
|
|
||||||
{
|
|
||||||
this.maxAltitude = altitude
|
|
||||||
this.minAltitude = altitude
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (altitude > this.maxAltitude) this.maxAltitude = altitude
|
|
||||||
if (altitude < this.minAltitude) this.minAltitude = altitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Step 3.3: Toggle stop over status, if necessary
|
|
||||||
if (this.wayPoints.size < 0)
|
|
||||||
{
|
|
||||||
this.wayPoints[this.wayPoints.size - 1].isStopOver = LocationHelper.isStopOver(previousLocation, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3.4: Add current location as point to center on for later display
|
|
||||||
this.latitude = location.latitude
|
|
||||||
this.longitude = location.longitude
|
|
||||||
|
|
||||||
// Step 3.5: Add location as new waypoint
|
|
||||||
this.wayPoints.add(WayPoint(location = location, distanceToStartingPoint = this.distance))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun delete(context: Context)
|
|
||||||
{
|
|
||||||
Log.i("VOUSSOIR", "Deleting track ${this.id}.")
|
|
||||||
val json_file: File = this.get_json_file(context)
|
|
||||||
if (json_file.isFile)
|
|
||||||
{
|
|
||||||
json_file.delete()
|
|
||||||
}
|
|
||||||
val gpx_file: File = this.get_gpx_file(context)
|
|
||||||
if (gpx_file.isFile)
|
|
||||||
{
|
|
||||||
gpx_file.delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete_suspended(context: Context)
|
suspend fun delete_suspended(context: Context)
|
||||||
{
|
{
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
cont.resume(this.delete(context))
|
cont.resume(this.delete())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get_gpx_file(context: Context): File
|
|
||||||
{
|
|
||||||
val basename: String = this.id.toString() + Keys.GPX_FILE_EXTENSION
|
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_GPX), basename)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get_export_gpx_file(context: Context): File
|
fun get_export_gpx_file(context: Context): File
|
||||||
{
|
{
|
||||||
val basename: String = DateTimeHelper.convertToSortableDateString(this.recordingStart) + Keys.GPX_FILE_EXTENSION
|
val basename: String = DateTimeHelper.convertToSortableDateString(this.start_time) + " " + DateTimeHelper.convertToSortableDateString(this.start_time) + Keys.GPX_FILE_EXTENSION
|
||||||
return File(File("/storage/emulated/0/Syncthing/GPX"), basename)
|
return File(File("/storage/emulated/0/Syncthing/GPX"), basename)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get_json_file(context: Context): File
|
fun export_gpx(context: Context, fileuri: Uri): Uri?
|
||||||
{
|
{
|
||||||
val basename: String = this.id.toString() + Keys.TRACKBOOK_FILE_EXTENSION
|
if (! database.ready)
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), basename)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save_all_files(context: Context)
|
|
||||||
{
|
{
|
||||||
this.save_json(context)
|
Log.i("VOUSSOIR", "Failed to export due to database not ready.")
|
||||||
this.save_gpx(context)
|
return null
|
||||||
this.save_export_gpx(context)
|
|
||||||
}
|
}
|
||||||
|
Log.i("VOUSSOIR", "Let's export to " + fileuri.toString())
|
||||||
suspend fun save_all_files_suspended(context: Context)
|
val writer = context.contentResolver.openOutputStream(fileuri)
|
||||||
|
if (writer == null)
|
||||||
{
|
{
|
||||||
return suspendCoroutine { cont ->
|
return null
|
||||||
cont.resume(this.save_all_files(context))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun save_gpx(context: Context)
|
|
||||||
{
|
|
||||||
val gpx: String = this.to_gpx()
|
|
||||||
FileHelper.write_text_file_noblank(gpx, this.get_gpx_file(context))
|
|
||||||
Log.i("VOUSSOIR", "Saved ${this.id}.gpx")
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun save_gpx_suspended(context: Context)
|
|
||||||
{
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
cont.resume(this.save_gpx(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save_export_gpx(context: Context)
|
|
||||||
{
|
|
||||||
val gpx: String = this.to_gpx()
|
|
||||||
val outputfile: File = this.get_export_gpx_file(context)
|
|
||||||
FileHelper.write_text_file_noblank(gpx, outputfile)
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
|
||||||
Toast.makeText(context, outputfile.toString(), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun save_export_gpx_suspended(context: Context)
|
|
||||||
{
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
cont.resume(this.save_export_gpx(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save_json(context: Context)
|
|
||||||
{
|
|
||||||
val json: String = this.to_json()
|
|
||||||
FileHelper.write_text_file_noblank(json, this.get_json_file(context))
|
|
||||||
Log.i("VOUSSOIR", "Saved ${this.id}.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun save_json_suspended(context: Context)
|
|
||||||
{
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
cont.resume(this.save_json(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save_temp(context: Context)
|
|
||||||
{
|
|
||||||
val json: String = this.to_json()
|
|
||||||
FileHelper.write_text_file_noblank(json, FileHelper.get_temp_file(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun save_temp_suspended(context: Context)
|
|
||||||
{
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
cont.resume(this.save_temp(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun to_gpx(): String {
|
|
||||||
val gpxString = StringBuilder("")
|
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
gpxString.appendLine("""
|
val write = {x: String -> writer.write(x.encodeToByteArray()); writer.write("\n".encodeToByteArray())}
|
||||||
|
|
||||||
|
write("""
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
<gpx
|
<gpx
|
||||||
version="1.1" creator="Trackbook App (Android)"
|
version="1.1" creator="Trackbook App (Android)"
|
||||||
|
@ -278,62 +96,140 @@ data class Track (
|
||||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||||
>
|
>
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
gpxString.appendLine("\t<metadata>")
|
write("\t<metadata>")
|
||||||
gpxString.appendLine("\t\t<name>Trackbook Recording: ${this.name}</name>")
|
write("\t\t<name>Trackbook Recording: ${this.name}</name>")
|
||||||
gpxString.appendLine("\t</metadata>")
|
write("\t\t<device>${this.device_id}</device>")
|
||||||
|
write("\t</metadata>")
|
||||||
// POIs
|
|
||||||
val poiList: List<WayPoint> = this.wayPoints.filter { it.starred }
|
|
||||||
poiList.forEach { poi ->
|
|
||||||
gpxString.appendLine("\t<wpt lat=\"${poi.latitude}\" lon=\"${poi.longitude}\">")
|
|
||||||
gpxString.appendLine("\t\t<name>Point of interest</name>")
|
|
||||||
gpxString.appendLine("\t\t<ele>${poi.altitude}</ele>")
|
|
||||||
gpxString.appendLine("\t</wpt>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TRK
|
// TRK
|
||||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
|
||||||
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
gpxString.appendLine("\t<trk>")
|
write("\t<trk>")
|
||||||
gpxString.appendLine("\t\t<name>${this.name}</name>")
|
write("\t\t<name>${this.name}</name>")
|
||||||
gpxString.appendLine("\t\t<trkseg>")
|
write("\t\t<trkseg>")
|
||||||
this.wayPoints.forEach { wayPoint ->
|
|
||||||
gpxString.appendLine("\t\t\t<trkpt lat=\"${wayPoint.latitude}\" lon=\"${wayPoint.longitude}\">")
|
|
||||||
gpxString.appendLine("\t\t\t\t<ele>${wayPoint.altitude}</ele>")
|
|
||||||
gpxString.appendLine("\t\t\t\t<time>${dateFormat.format(Date(wayPoint.time))}</time>")
|
|
||||||
gpxString.appendLine("\t\t\t\t<sat>${wayPoint.numberSatellites}</sat>")
|
|
||||||
gpxString.appendLine("\t\t\t</trkpt>")
|
|
||||||
}
|
|
||||||
gpxString.appendLine("\t\t</trkseg>")
|
|
||||||
gpxString.appendLine("\t</trk>")
|
|
||||||
gpxString.appendLine("</gpx>")
|
|
||||||
|
|
||||||
return gpxString.toString()
|
trkpt_generator().forEach { trkpt ->
|
||||||
|
write("\t\t\t<trkpt lat=\"${trkpt.latitude}\" lon=\"${trkpt.longitude}\">")
|
||||||
|
write("\t\t\t\t<ele>${trkpt.altitude}</ele>")
|
||||||
|
write("\t\t\t\t<time>${iso8601_format.format(trkpt.time)}</time>")
|
||||||
|
write("\t\t\t\t<sat>${trkpt.numberSatellites}</sat>")
|
||||||
|
write("\t\t\t</trkpt>")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun to_json(): String
|
write("\t\t</trkseg>")
|
||||||
|
write("\t</trk>")
|
||||||
|
write("</gpx>")
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
Toast.makeText(context, fileuri.toString(), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return fileuri
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load_trkpts()
|
||||||
{
|
{
|
||||||
return FileHelper.getCustomGson().toJson(this)
|
this.trkpts.clear()
|
||||||
|
trkpt_generator().forEach { trkpt -> this.trkpts.add(trkpt) }
|
||||||
|
if (this.trkpts.size > 0)
|
||||||
|
{
|
||||||
|
this.view_latitude = this.trkpts.first().latitude
|
||||||
|
this.view_longitude = this.trkpts.first().longitude
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load_temp_track(context: Context): Track
|
fun statistics(): TrackStatistics
|
||||||
{
|
{
|
||||||
return track_from_file(context, FileHelper.get_temp_file(context))
|
var first: Trkpt? = null
|
||||||
|
var last: Trkpt? = null
|
||||||
|
var previous: Trkpt? = null
|
||||||
|
val stats = TrackStatistics()
|
||||||
|
for (trkpt in trkpt_generator())
|
||||||
|
{
|
||||||
|
if (previous == null)
|
||||||
|
{
|
||||||
|
first = trkpt
|
||||||
|
previous = trkpt
|
||||||
|
stats.max_altitude = trkpt.altitude
|
||||||
|
stats.min_altitude = trkpt.altitude
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stats.distance += LocationHelper.calculateDistance(previous.toLocation(), trkpt.toLocation())
|
||||||
|
val ascentdiff = trkpt.altitude - previous.altitude
|
||||||
|
if (ascentdiff > 0)
|
||||||
|
{
|
||||||
|
stats.total_ascent += ascentdiff
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stats.total_descent += ascentdiff
|
||||||
|
}
|
||||||
|
if (trkpt.altitude > stats.max_altitude)
|
||||||
|
{
|
||||||
|
stats.max_altitude = trkpt.altitude
|
||||||
|
}
|
||||||
|
if (trkpt.altitude < stats.min_altitude)
|
||||||
|
{
|
||||||
|
stats.min_altitude = trkpt.altitude
|
||||||
|
}
|
||||||
|
last = trkpt
|
||||||
|
}
|
||||||
|
if (first == null || last == null)
|
||||||
|
{
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
stats.duration = last.time.time - first.time.time
|
||||||
|
stats.velocity = stats.distance / stats.duration
|
||||||
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
fun track_from_file(context: Context, file: File): Track
|
fun trkpt_generator() = iterator<Trkpt>
|
||||||
{
|
{
|
||||||
// get JSON from text file
|
val cursor: Cursor = database.connection.query(
|
||||||
val json: String = FileHelper.readTextFile(context, file)
|
"trkpt",
|
||||||
if (json.isEmpty())
|
arrayOf("lat", "lon", "time", "ele", "accuracy", "sat"),
|
||||||
|
"device_id = ? AND time > ? AND time < ?",
|
||||||
|
arrayOf(device_id, iso8601(start_time), iso8601(stop_time)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"time ASC",
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
||||||
|
val COLUMN_LON = cursor.getColumnIndex("lon")
|
||||||
|
val COLUMN_ELE = cursor.getColumnIndex("ele")
|
||||||
|
val COLUMN_SAT = cursor.getColumnIndex("sat")
|
||||||
|
val COLUMN_ACCURACY = cursor.getColumnIndex("accuracy")
|
||||||
|
val COLUMN_TIME = cursor.getColumnIndex("time")
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return Track()
|
while (cursor.moveToNext())
|
||||||
|
{
|
||||||
|
val trkpt: Trkpt = Trkpt(
|
||||||
|
provider="",
|
||||||
|
latitude=cursor.getDouble(COLUMN_LAT),
|
||||||
|
longitude=cursor.getDouble(COLUMN_LON),
|
||||||
|
altitude=cursor.getDouble(COLUMN_ELE),
|
||||||
|
accuracy=cursor.getFloat(COLUMN_ACCURACY),
|
||||||
|
time=iso8601_format.parse(cursor.getString(COLUMN_TIME)),
|
||||||
|
distanceToStartingPoint=0F,
|
||||||
|
numberSatellites=cursor.getInt(COLUMN_SAT),
|
||||||
|
)
|
||||||
|
yield(trkpt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return FileHelper.getCustomGson().fromJson(json, Track::class.java)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun make_random_id(): Long
|
data class TrackStatistics(
|
||||||
{
|
var distance: Double = 0.0,
|
||||||
return (Random.nextBits(31).toLong() shl 32) + Random.nextBits(32)
|
var duration: Long = 0,
|
||||||
}
|
var velocity: Double = 0.0,
|
||||||
|
var total_ascent: Double = 0.0,
|
||||||
|
var total_descent: Double = 0.0,
|
||||||
|
var max_altitude: Double = 0.0,
|
||||||
|
var min_altitude: Double = 0.0,
|
||||||
|
)
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Tracklist.kt
|
|
||||||
* Implements the Tracklist data class
|
|
||||||
* A Tracklist stores a list of Tracks
|
|
||||||
*
|
|
||||||
* This file is part of
|
|
||||||
* TRACKBOOK - Movement Recorder for Android
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016-22 - Y20K.org
|
|
||||||
* Licensed under the MIT-License
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
* Trackbook uses osmdroid - OpenStreetMap-Tools for Android
|
|
||||||
* https://github.com/osmdroid/osmdroid
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.core
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import com.google.gson.annotations.Expose
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import org.y20k.trackbook.Keys
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tracklist data class
|
|
||||||
*/
|
|
||||||
@Keep
|
|
||||||
@Parcelize
|
|
||||||
data class Tracklist (
|
|
||||||
@Expose val tracklistFormatVersion: Int = Keys.CURRENT_TRACKLIST_FORMAT_VERSION,
|
|
||||||
@Expose val tracks: MutableList<Track> = mutableListOf<Track>()
|
|
||||||
): Parcelable
|
|
||||||
{
|
|
||||||
fun delete_non_starred(context: Context)
|
|
||||||
{
|
|
||||||
val to_delete: List<Track> = this.tracks.filter{! it.starred}
|
|
||||||
to_delete.forEach { track ->
|
|
||||||
if (!track.starred)
|
|
||||||
{
|
|
||||||
track.delete(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.tracks.removeIf{! it.starred}
|
|
||||||
}
|
|
||||||
suspend fun delete_non_starred_suspended(context: Context)
|
|
||||||
{
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
cont.resume(this.delete_non_starred(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get_total_distance(): Double
|
|
||||||
{
|
|
||||||
return this.tracks.sumOf {it.distance.toDouble()}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get_total_duration(): Long
|
|
||||||
{
|
|
||||||
return this.tracks.sumOf {it.duration}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deepCopy(): Tracklist
|
|
||||||
{
|
|
||||||
return Tracklist(tracklistFormatVersion, mutableListOf<Track>().apply { addAll(tracks) })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load_tracklist(context: Context): Tracklist {
|
|
||||||
Log.i("VOUSSOIR", "Loading tracklist.")
|
|
||||||
val folder = context.getExternalFilesDir("tracks")
|
|
||||||
val tracklist: Tracklist = Tracklist()
|
|
||||||
if (folder == null)
|
|
||||||
{
|
|
||||||
return tracklist
|
|
||||||
}
|
|
||||||
folder.walk().filter{ f: File -> f.isFile }.forEach{ json_file ->
|
|
||||||
val track = track_from_file(context, json_file)
|
|
||||||
tracklist.tracks.add(track)
|
|
||||||
}
|
|
||||||
tracklist.tracks.sortByDescending {it.recordingStart}
|
|
||||||
return tracklist
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun load_tracklist_suspended(context: Context): Tracklist
|
|
||||||
{
|
|
||||||
return suspendCoroutine {cont -> cont.resume(load_tracklist(context))}
|
|
||||||
}
|
|
|
@ -23,19 +23,19 @@ import androidx.annotation.Keep
|
||||||
import com.google.gson.annotations.Expose
|
import com.google.gson.annotations.Expose
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.y20k.trackbook.helpers.LocationHelper
|
import org.y20k.trackbook.helpers.LocationHelper
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WayPoint data class
|
* WayPoint data class
|
||||||
*/
|
*/
|
||||||
@Keep
|
@Keep
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class WayPoint(@Expose val provider: String,
|
data class Trkpt(@Expose val provider: String,
|
||||||
@Expose val latitude: Double,
|
@Expose val latitude: Double,
|
||||||
@Expose val longitude: Double,
|
@Expose val longitude: Double,
|
||||||
@Expose val altitude: Double,
|
@Expose val altitude: Double,
|
||||||
@Expose val accuracy: Float,
|
@Expose val accuracy: Float,
|
||||||
@Expose val time: Long,
|
@Expose val time: Date,
|
||||||
@Expose val distanceToStartingPoint: Float = 0f,
|
@Expose val distanceToStartingPoint: Float = 0f,
|
||||||
@Expose val numberSatellites: Int = 0,
|
@Expose val numberSatellites: Int = 0,
|
||||||
@Expose var isStopOver: Boolean = false,
|
@Expose var isStopOver: Boolean = false,
|
||||||
|
@ -43,28 +43,16 @@ data class WayPoint(@Expose val provider: String,
|
||||||
|
|
||||||
/* Constructor using just Location */
|
/* Constructor using just Location */
|
||||||
constructor(location: Location) : this (
|
constructor(location: Location) : this (
|
||||||
provider=location.provider,
|
provider=location.provider.toString(),
|
||||||
latitude=location.latitude,
|
latitude=location.latitude,
|
||||||
longitude=location.longitude,
|
longitude=location.longitude,
|
||||||
altitude=location.altitude,
|
altitude=location.altitude,
|
||||||
accuracy=location.accuracy,
|
accuracy=location.accuracy,
|
||||||
time=location.time,
|
time=Date(location.time),
|
||||||
distanceToStartingPoint=0F,
|
distanceToStartingPoint=0F,
|
||||||
numberSatellites=LocationHelper.getNumberOfSatellites(location),
|
numberSatellites=LocationHelper.getNumberOfSatellites(location),
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Constructor using Location plus distanceToStartingPoint and numberSatellites */
|
|
||||||
constructor(location: Location, distanceToStartingPoint: Float) : this (
|
|
||||||
provider=location.provider,
|
|
||||||
latitude=location.latitude,
|
|
||||||
longitude=location.longitude,
|
|
||||||
altitude=location. altitude,
|
|
||||||
accuracy=location.accuracy,
|
|
||||||
time=location.time,
|
|
||||||
distanceToStartingPoint=distanceToStartingPoint,
|
|
||||||
numberSatellites=LocationHelper.getNumberOfSatellites(location),
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Converts WayPoint into Location */
|
/* Converts WayPoint into Location */
|
||||||
fun toLocation(): Location {
|
fun toLocation(): Location {
|
||||||
val location: Location = Location(provider)
|
val location: Location = Location(provider)
|
||||||
|
@ -72,7 +60,7 @@ data class WayPoint(@Expose val provider: String,
|
||||||
location.longitude = longitude
|
location.longitude = longitude
|
||||||
location.altitude = altitude
|
location.altitude = altitude
|
||||||
location.accuracy = accuracy
|
location.accuracy = accuracy
|
||||||
location.time = time
|
location.time = this.time.time
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,31 +38,6 @@ object FileHelper {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(FileHelper::class.java)
|
private val TAG: String = LogHelper.makeLogTag(FileHelper::class.java)
|
||||||
|
|
||||||
fun delete_temp_file(context: Context)
|
|
||||||
{
|
|
||||||
val temp: File = get_temp_file(context)
|
|
||||||
if (temp.isFile())
|
|
||||||
{
|
|
||||||
temp.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun get_temp_file(context: Context): File
|
|
||||||
{
|
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_TEMP), Keys.TEMP_FILE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for renameTrack */
|
|
||||||
suspend fun renameTrackSuspended(context: Context, track: Track, newName: String) {
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
track.name = newName
|
|
||||||
track.save_json(context)
|
|
||||||
track.save_gpx(context)
|
|
||||||
cont.resume(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Suspend function: Wrapper for copyFile */
|
/* Suspend function: Wrapper for copyFile */
|
||||||
suspend fun saveCopyOfFileSuspended(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
suspend fun saveCopyOfFileSuspended(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
|
|
|
@ -96,15 +96,12 @@ object LengthUnitHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
||||||
fun convertToVelocityString(trackDuration: Long, trackRecordingPause: Long, trackLength: Float, useImperialUnits: Boolean = false) : String {
|
fun convertToVelocityString(velocity: Double, useImperialUnits: Boolean = false) : String {
|
||||||
var speed: String = "0"
|
var speed: String = "0"
|
||||||
|
|
||||||
// duration minus pause in seconds
|
if (velocity > 0.0) {
|
||||||
val duration: Long = (trackDuration - trackRecordingPause) / 1000L
|
|
||||||
|
|
||||||
if (duration > 0L) {
|
|
||||||
// speed in km/h / mph
|
// speed in km/h / mph
|
||||||
val velocity: Double = convertMetersPerSecond((trackLength / duration), useImperialUnits)
|
val velocity: Double = convertMetersPerSecond(velocity, useImperialUnits)
|
||||||
// create readable speed string
|
// create readable speed string
|
||||||
var bd: BigDecimal = BigDecimal.valueOf(velocity)
|
var bd: BigDecimal = BigDecimal.valueOf(velocity)
|
||||||
bd = bd.setScale(1, RoundingMode.HALF_UP)
|
bd = bd.setScale(1, RoundingMode.HALF_UP)
|
||||||
|
@ -119,7 +116,7 @@ object LengthUnitHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Coverts meters per second to either km/h or mph */
|
/* Coverts meters per second to either km/h or mph */
|
||||||
fun convertMetersPerSecond(metersPerSecond: Float, useImperial: Boolean = false): Double {
|
fun convertMetersPerSecond(metersPerSecond: Double, useImperial: Boolean = false): Double {
|
||||||
if (useImperial) {
|
if (useImperial) {
|
||||||
// mph
|
// mph
|
||||||
return metersPerSecond * 2.2369362920544
|
return metersPerSecond * 2.2369362920544
|
||||||
|
|
|
@ -51,14 +51,6 @@ object LocationHelper {
|
||||||
return defaultLocation
|
return defaultLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if a location is older than one minute */
|
|
||||||
fun isOldLocation(location: Location): Boolean {
|
|
||||||
// check how many milliseconds the given location is old
|
|
||||||
return GregorianCalendar.getInstance().time.time - location.time > Keys.SIGNIFICANT_TIME_DIFFERENCE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Tries to return the last location that the system has stored */
|
/* Tries to return the last location that the system has stored */
|
||||||
fun getLastKnownLocation(context: Context): Location {
|
fun getLastKnownLocation(context: Context): Location {
|
||||||
// get last location that Trackbook has stored
|
// get last location that Trackbook has stored
|
||||||
|
@ -117,7 +109,6 @@ object LocationHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if GPS location provider is available and enabled */
|
/* Checks if GPS location provider is available and enabled */
|
||||||
fun isGpsEnabled(locationManager: LocationManager): Boolean {
|
fun isGpsEnabled(locationManager: LocationManager): Boolean {
|
||||||
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
|
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
|
||||||
|
@ -156,27 +147,6 @@ object LocationHelper {
|
||||||
return isAccurate
|
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)
|
|
||||||
// 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 {
|
|
||||||
// time difference in seconds
|
|
||||||
val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L
|
|
||||||
// distance in meters
|
|
||||||
val distance: Float = calculateDistance(firstLocation, secondLocation)
|
|
||||||
// speed in either km/h (default) or mph
|
|
||||||
return LengthUnitHelper.convertMetersPerSecond(distance / timeDifference, useImperial)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Checks if given location is different enough compared to previous location */
|
/* Checks if given location is different enough compared to previous location */
|
||||||
fun isDifferentEnough(previousLocation: Location?, location: Location, omitRests: Boolean): Boolean {
|
fun isDifferentEnough(previousLocation: Location?, location: Location, omitRests: Boolean): Boolean {
|
||||||
// check if previous location is (not) available
|
// check if previous location is (not) available
|
||||||
|
@ -216,51 +186,6 @@ object LocationHelper {
|
||||||
return distance
|
return distance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Calculate elevation differences */
|
|
||||||
fun calculateElevationDifferencesOld(previousLocation: Location?, location: Location, track: Track): Pair<Double, Double> {
|
|
||||||
// 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
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Pair(positiveElevation, negativeElevation)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Calculate elevation differences */
|
|
||||||
fun calculateElevationDifferences(currentAltitude: Double, previousAltitude: Double, track: Track): Track {
|
|
||||||
if (currentAltitude != Keys.DEFAULT_ALTITUDE && previousAltitude != Keys.DEFAULT_ALTITUDE) {
|
|
||||||
val altitudeDifference: Double = currentAltitude - previousAltitude
|
|
||||||
if (altitudeDifference > 0) {
|
|
||||||
track.positiveElevation += altitudeDifference // upwards movement
|
|
||||||
}
|
|
||||||
if (altitudeDifference < 0) {
|
|
||||||
track.negativeElevation += altitudeDifference // downwards movement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Checks if given location is a stop over */
|
|
||||||
fun isStopOver(previousLocation: Location?, location: Location): Boolean {
|
|
||||||
if (previousLocation == null) return false
|
|
||||||
// check how many milliseconds the given locations are apart
|
|
||||||
return location.time - previousLocation.time > Keys.STOP_OVER_THRESHOLD
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Get number of satellites from Location extras */
|
/* Get number of satellites from Location extras */
|
||||||
fun getNumberOfSatellites(location: Location): Int {
|
fun getNumberOfSatellites(location: Location): Int {
|
||||||
val numberOfSatellites: Int
|
val numberOfSatellites: Int
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.core.WayPoint
|
import org.y20k.trackbook.core.Trkpt
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -58,10 +58,10 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates icon overlay for current position (used in MapFragment) */
|
/* Creates icon overlay for current position (used in MapFragment) */
|
||||||
fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay<OverlayItem> {
|
fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay<OverlayItem>
|
||||||
|
{
|
||||||
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
||||||
val locationIsOld:Boolean = LocationHelper.isOldLocation(location)
|
val locationIsOld: Boolean = !(LocationHelper.isRecentEnough(location))
|
||||||
|
|
||||||
// create marker
|
// create marker
|
||||||
val newMarker: Drawable
|
val newMarker: Drawable
|
||||||
|
@ -83,7 +83,24 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add marker to list of overlay items
|
// add marker to list of overlay items
|
||||||
val overlayItem: OverlayItem = createOverlayItem(context, location.latitude, location.longitude, location.accuracy, location.provider, location.time)
|
val overlayItem: OverlayItem = createOverlayItem(context, location.latitude, location.longitude, location.accuracy, location.provider.toString(), location.time)
|
||||||
|
overlayItem.setMarker(newMarker)
|
||||||
|
overlayItems.add(overlayItem)
|
||||||
|
|
||||||
|
// create and return overlay for current position
|
||||||
|
return createOverlay(context, overlayItems, enableStarring = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates icon overlay for current position (used in MapFragment) */
|
||||||
|
fun createHomepointOverlay(context: Context, location: Location): ItemizedIconOverlay<OverlayItem>
|
||||||
|
{
|
||||||
|
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
||||||
|
|
||||||
|
// create marker
|
||||||
|
val newMarker: Drawable = ContextCompat.getDrawable(context, R.drawable.ic_homepoint_24dp)!!
|
||||||
|
|
||||||
|
// add marker to list of overlay items
|
||||||
|
val overlayItem: OverlayItem = createOverlayItem(context, location.latitude, location.longitude, location.accuracy, location.provider.toString(), location.time)
|
||||||
overlayItem.setMarker(newMarker)
|
overlayItem.setMarker(newMarker)
|
||||||
overlayItems.add(overlayItem)
|
overlayItems.add(overlayItem)
|
||||||
|
|
||||||
|
@ -93,13 +110,14 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates icon overlay for track */
|
/* Creates icon overlay for track */
|
||||||
fun createTrackOverlay(context: Context, track: Track, trackingState: Int): SimpleFastPointOverlay {
|
fun createTrackOverlay(context: Context, track: Track, trackingState: Int): SimpleFastPointOverlay
|
||||||
|
{
|
||||||
// get marker color
|
// get marker color
|
||||||
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red)
|
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red)
|
||||||
else context.getColor(R.color.default_blue)
|
else context.getColor(R.color.default_blue)
|
||||||
// gather points for overlay
|
// gather points for overlay
|
||||||
val points: MutableList<IGeoPoint> = mutableListOf()
|
val points: MutableList<IGeoPoint> = mutableListOf()
|
||||||
track.wayPoints.forEach { wayPoint ->
|
track.trkpts.forEach { wayPoint ->
|
||||||
val label: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(wayPoint.time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(wayPoint.accuracy)} (${wayPoint.provider})"
|
val label: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(wayPoint.time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(wayPoint.accuracy)} (${wayPoint.provider})"
|
||||||
// only add normal points
|
// only add normal points
|
||||||
if (!wayPoint.starred && !wayPoint.isStopOver) {
|
if (!wayPoint.starred && !wayPoint.isStopOver) {
|
||||||
|
@ -133,45 +151,60 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
return overlay
|
return overlay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates overlay containing start, stop, stopover and starred markers for track */
|
/* Creates overlay containing start, stop, stopover and starred markers for track */
|
||||||
fun createSpecialMakersTrackOverlay(context: Context, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false): ItemizedIconOverlay<OverlayItem> {
|
fun createSpecialMakersTrackOverlay(context: Context, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false): ItemizedIconOverlay<OverlayItem>
|
||||||
|
{
|
||||||
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
||||||
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
|
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
|
||||||
val maxIndex: Int = track.wayPoints.size - 1
|
val maxIndex: Int = track.trkpts.size - 1
|
||||||
|
|
||||||
track.wayPoints.forEachIndexed { index: Int, wayPoint: WayPoint ->
|
track.trkpts.forEachIndexed { index: Int, trkpt: Trkpt ->
|
||||||
var overlayItem: OverlayItem? = null
|
var overlayItem: OverlayItem? = null
|
||||||
if (!trackingActive && index == 0 && displayStartEndMarker && wayPoint.starred) {
|
if (!trackingActive && index == 0 && displayStartEndMarker && trkpt.starred)
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_starred_blue_48dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_starred_blue_48dp)!!)
|
||||||
} else if (!trackingActive && index == 0 && displayStartEndMarker && !wayPoint.starred) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (!trackingActive && index == 0 && displayStartEndMarker && !trkpt.starred)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!)
|
||||||
} else if (!trackingActive && index == maxIndex && displayStartEndMarker && wayPoint.starred) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (!trackingActive && index == maxIndex && displayStartEndMarker && trkpt.starred)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_starred_blue_48dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_starred_blue_48dp)!!)
|
||||||
} else if (!trackingActive && index == maxIndex && displayStartEndMarker && !wayPoint.starred) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (!trackingActive && index == maxIndex && displayStartEndMarker && !trkpt.starred)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!)
|
||||||
} else if (!trackingActive && wayPoint.starred) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (!trackingActive && trkpt.starred)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
|
||||||
} else if (trackingActive && wayPoint.starred) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (trackingActive && trkpt.starred)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!)
|
||||||
} else if (wayPoint.isStopOver) {
|
}
|
||||||
overlayItem = createOverlayItem(context, wayPoint.latitude, wayPoint.longitude, wayPoint.accuracy, wayPoint.provider, wayPoint.time)
|
else if (trkpt.isStopOver)
|
||||||
|
{
|
||||||
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!!)
|
||||||
}
|
}
|
||||||
// add overlay item, if it was created
|
// add overlay item, if it was created
|
||||||
if (overlayItem != null) overlayItems.add(overlayItem)
|
if (overlayItem != null)
|
||||||
|
{
|
||||||
|
overlayItems.add(overlayItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// create and return overlay for current position
|
// create and return overlay for current position
|
||||||
return createOverlay(context, overlayItems, enableStarring = true)
|
return createOverlay(context, overlayItems, enableStarring = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates a marker overlay item */
|
/* Creates a marker overlay item */
|
||||||
private fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem {
|
private fun createOverlayItem(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)}"
|
val title: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}"
|
||||||
|
@ -183,7 +216,6 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates an overlay */
|
/* Creates an overlay */
|
||||||
private fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>, enableStarring: Boolean): ItemizedIconOverlay<OverlayItem> {
|
private fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>, enableStarring: Boolean): ItemizedIconOverlay<OverlayItem> {
|
||||||
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
||||||
|
|
|
@ -45,7 +45,7 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
|
|
||||||
|
|
||||||
/* Creates notification */
|
/* Creates notification */
|
||||||
fun createNotification(trackingState: Int, trackLength: Float, duration: Long, useImperial: Boolean): Notification {
|
fun createNotification(trackingState: Int, timestamp: String): Notification {
|
||||||
|
|
||||||
// create notification channel if necessary
|
// create notification channel if necessary
|
||||||
if (shouldCreateNotificationChannel()) {
|
if (shouldCreateNotificationChannel()) {
|
||||||
|
@ -56,7 +56,7 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
val builder = NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING)
|
val builder = NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING)
|
||||||
builder.setContentIntent(showActionPendingIntent)
|
builder.setContentIntent(showActionPendingIntent)
|
||||||
builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
|
builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
|
||||||
builder.setContentText(getContentString(trackerService, duration, trackLength, useImperial))
|
builder.setContentText(timestamp)
|
||||||
|
|
||||||
// add icon and actions for stop, resume and show
|
// add icon and actions for stop, resume and show
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
|
@ -77,22 +77,13 @@ 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)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Checks if notification channel should be created */
|
/* Checks if notification channel should be created */
|
||||||
private fun shouldCreateNotificationChannel() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
|
private fun shouldCreateNotificationChannel() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
|
||||||
|
|
||||||
|
|
||||||
/* Checks if notification channel exists */
|
/* Checks if notification channel exists */
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
|
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
|
||||||
|
|
||||||
|
|
||||||
/* Create a notification channel */
|
/* Create a notification channel */
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
|
@ -103,20 +94,24 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
notificationManager.createNotificationChannel(notificationChannel)
|
notificationManager.createNotificationChannel(notificationChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Notification pending intents */
|
/* Notification pending intents */
|
||||||
private val stopActionPendingIntent = PendingIntent.getService(
|
private val stopActionPendingIntent = PendingIntent.getService(
|
||||||
trackerService,14,
|
trackerService,
|
||||||
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP),PendingIntent.FLAG_IMMUTABLE)
|
14,
|
||||||
|
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
private val resumeActionPendingIntent = PendingIntent.getService(
|
private val resumeActionPendingIntent = PendingIntent.getService(
|
||||||
trackerService, 16,
|
trackerService,
|
||||||
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME),PendingIntent.FLAG_IMMUTABLE)
|
16,
|
||||||
|
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_RESUME),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
private val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(trackerService).run {
|
private val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(trackerService).run {
|
||||||
addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java))
|
addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java))
|
||||||
getPendingIntent(10, PendingIntent.FLAG_IMMUTABLE)
|
getPendingIntent(10, PendingIntent.FLAG_IMMUTABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Notification actions */
|
/* Notification actions */
|
||||||
private val stopAction = NotificationCompat.Action(
|
private val stopAction = NotificationCompat.Action(
|
||||||
R.drawable.ic_notification_action_stop_24dp,
|
R.drawable.ic_notification_action_stop_24dp,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
|
@ -43,6 +44,13 @@ object PreferencesHelper {
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun load_device_id(): String
|
||||||
|
{
|
||||||
|
val v = sharedPreferences.getString(Keys.PREF_DEVICE_ID, random_int().toString()).toString();
|
||||||
|
Log.i("VOUSSOIR", "Loaded device_id ${v}.")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
fun loadZoomLevel(): Double {
|
fun loadZoomLevel(): Double {
|
||||||
return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL)
|
return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +60,7 @@ object PreferencesHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadTrackingState(): Int {
|
fun loadTrackingState(): Int {
|
||||||
return sharedPreferences.getInt(Keys.PREF_TRACKING_STATE, Keys.STATE_TRACKING_NOT_STARTED)
|
return sharedPreferences.getInt(Keys.PREF_TRACKING_STATE, Keys.STATE_TRACKING_STOPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTrackingState(trackingState: Int) {
|
fun saveTrackingState(trackingState: Int) {
|
||||||
|
@ -71,14 +79,10 @@ object PreferencesHelper {
|
||||||
return sharedPreferences.getBoolean(Keys.PREF_OMIT_RESTS, true)
|
return sharedPreferences.getBoolean(Keys.PREF_OMIT_RESTS, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAutoExportInterval(): Int {
|
fun loadCommitInterval(): Int {
|
||||||
return sharedPreferences.getInt(Keys.PREF_AUTO_EXPORT_INTERVAL, Keys.DEFAULT_AUTO_EXPORT_INTERVAL)
|
return sharedPreferences.getInt(Keys.PREF_COMMIT_INTERVAL, Keys.COMMIT_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun loadAltitudeSmoothingValue(): Int {
|
|
||||||
// return sharedPreferences.getInt(Keys.PREF_ALTITUDE_SMOOTHING_VALUE, Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Loads the state of a map */
|
/* Loads the state of a map */
|
||||||
fun loadCurrentBestLocation(): Location {
|
fun loadCurrentBestLocation(): Location {
|
||||||
val provider: String = sharedPreferences.getString(Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER, LocationManager.NETWORK_PROVIDER) ?: LocationManager.NETWORK_PROVIDER
|
val provider: String = sharedPreferences.getString(Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER, LocationManager.NETWORK_PROVIDER) ?: LocationManager.NETWORK_PROVIDER
|
||||||
|
|
|
@ -18,7 +18,6 @@ package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import java.util.*
|
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ object TrackHelper {
|
||||||
/* Toggles starred flag for given position */
|
/* Toggles starred flag for given position */
|
||||||
fun toggle_waypoint_starred(context: Context, track: Track, latitude: Double, longitude: Double)
|
fun toggle_waypoint_starred(context: Context, track: Track, latitude: Double, longitude: Double)
|
||||||
{
|
{
|
||||||
track.wayPoints.forEach { waypoint ->
|
track.trkpts.forEach { waypoint ->
|
||||||
if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
|
if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
|
||||||
waypoint.starred = !waypoint.starred
|
waypoint.starred = !waypoint.starred
|
||||||
when (waypoint.starred) {
|
when (waypoint.starred) {
|
||||||
|
|
22
app/src/main/java/org/y20k/trackbook/helpers/functions.kt
Normal file
22
app/src/main/java/org/y20k/trackbook/helpers/functions.kt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
|
||||||
|
|
||||||
|
fun iso8601(datetime: Date): String
|
||||||
|
{
|
||||||
|
return iso8601_format.format(datetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun random_long(): Long
|
||||||
|
{
|
||||||
|
return (Random.nextBits(31).toLong() shl 32) + Random.nextBits(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun random_int(): Int
|
||||||
|
{
|
||||||
|
return Random.nextBits(31)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.y20k.trackbook.tracklist
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -26,49 +27,72 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
|
import org.y20k.trackbook.core.Database
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.core.Tracklist
|
|
||||||
import org.y20k.trackbook.core.load_tracklist
|
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TracklistAdapter class
|
* TracklistAdapter class
|
||||||
*/
|
*/
|
||||||
class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class TracklistAdapter(val fragment: Fragment, val database: Database) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
|
||||||
|
{
|
||||||
/* Define log tag */
|
|
||||||
private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java)
|
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private val context: Context = fragment.activity as Context
|
private val context: Context = fragment.activity as Context
|
||||||
private lateinit var tracklistListener: TracklistAdapterListener
|
private lateinit var tracklistListener: TracklistAdapterListener
|
||||||
private var useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
|
private var useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
|
||||||
private var tracklist: Tracklist = Tracklist()
|
val tracks: ArrayList<Track> = ArrayList<Track>()
|
||||||
|
|
||||||
|
|
||||||
/* Listener Interface */
|
/* Listener Interface */
|
||||||
interface TracklistAdapterListener {
|
interface TracklistAdapterListener
|
||||||
|
{
|
||||||
fun onTrackElementTapped(track: Track) { }
|
fun onTrackElementTapped(track: Track) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView)
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView)
|
||||||
{
|
{
|
||||||
tracklistListener = fragment as TracklistAdapterListener
|
tracklistListener = fragment as TracklistAdapterListener
|
||||||
tracklist = load_tracklist(context)
|
tracks.clear()
|
||||||
|
if (! database.ready)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val cursor: Cursor = database.connection.query(
|
||||||
|
"trkpt",
|
||||||
|
arrayOf("distinct strftime('%Y-%m-%d', time)", "device_id"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"time DESC",
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
|
||||||
|
while (cursor.moveToNext())
|
||||||
|
{
|
||||||
|
val start_time: Date? = df.parse(cursor.getString(0) + "T00:00:00.000")
|
||||||
|
val stop_time: Date? = df.parse(cursor.getString(0) + "T23:59:59.999")
|
||||||
|
Log.i("VOUSSOIR", "TracklistAdapter prep track ${cursor.getString(0)}")
|
||||||
|
if (start_time != null && stop_time != null)
|
||||||
|
{
|
||||||
|
tracks.add(Track(database=database, device_id=cursor.getString(1), start_time=start_time, stop_time=stop_time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,14 +105,16 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemViewType */
|
/* Overrides getItemViewType */
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int
|
||||||
|
{
|
||||||
return Keys.VIEW_TYPE_TRACK
|
return Keys.VIEW_TYPE_TRACK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemCount from RecyclerView.Adapter */
|
/* Overrides getItemCount from RecyclerView.Adapter */
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int
|
||||||
return tracklist.tracks.size
|
{
|
||||||
|
return tracks.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,17 +123,10 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
{
|
{
|
||||||
val positionInTracklist: Int = position
|
val positionInTracklist: Int = position
|
||||||
val elementTrackViewHolder: ElementTrackViewHolder = holder as ElementTrackViewHolder
|
val elementTrackViewHolder: ElementTrackViewHolder = holder as ElementTrackViewHolder
|
||||||
elementTrackViewHolder.trackNameView.text = tracklist.tracks[positionInTracklist].name
|
elementTrackViewHolder.trackNameView.text = getTrackName(positionInTracklist)
|
||||||
elementTrackViewHolder.trackDataView.text = createTrackDataString(positionInTracklist)
|
elementTrackViewHolder.trackDataView.text = createTrackDataString(positionInTracklist)
|
||||||
when (tracklist.tracks[positionInTracklist].starred) {
|
|
||||||
true -> elementTrackViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_filled_24dp))
|
|
||||||
false -> elementTrackViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_outline_24dp))
|
|
||||||
}
|
|
||||||
elementTrackViewHolder.trackElement.setOnClickListener {
|
elementTrackViewHolder.trackElement.setOnClickListener {
|
||||||
tracklistListener.onTrackElementTapped(tracklist.tracks[positionInTracklist])
|
tracklistListener.onTrackElementTapped(tracks[positionInTracklist])
|
||||||
}
|
|
||||||
elementTrackViewHolder.starButton.setOnClickListener {
|
|
||||||
toggleStarred(it, positionInTracklist)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,16 +134,16 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
/* Get track name for given position */
|
/* Get track name for given position */
|
||||||
fun getTrackName(positionInRecyclerView: Int): String
|
fun getTrackName(positionInRecyclerView: Int): String
|
||||||
{
|
{
|
||||||
return tracklist.tracks[positionInRecyclerView].name
|
return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(tracks[positionInRecyclerView].start_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete_track_at_position(context: Context, index: Int)
|
fun delete_track_at_position(context: Context, index: Int)
|
||||||
{
|
{
|
||||||
val track = tracklist.tracks[index]
|
// val track = tracklist.tracks[index]
|
||||||
track.delete(context)
|
// track.delete()
|
||||||
tracklist.tracks.remove(track)
|
// tracklist.tracks.remove(track)
|
||||||
notifyItemRemoved(index)
|
// notifyItemRemoved(index)
|
||||||
notifyItemRangeChanged(index, this.itemCount);
|
// notifyItemRangeChanged(index, this.itemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete_track_at_position_suspended(context: Context, position: Int)
|
suspend fun delete_track_at_position_suspended(context: Context, position: Int)
|
||||||
|
@ -136,79 +155,38 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
|
|
||||||
fun delete_track_by_id(context: Context, trackId: Long)
|
fun delete_track_by_id(context: Context, trackId: Long)
|
||||||
{
|
{
|
||||||
val index: Int = tracklist.tracks.indexOfFirst {it.id == trackId}
|
// val index: Int = tracklist.tracks.indexOfFirst {it.id == trackId}
|
||||||
if (index == -1) {
|
// if (index == -1) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
tracklist.tracks[index].delete(context)
|
// tracklist.tracks[index].delete()
|
||||||
tracklist.tracks.removeAt(index)
|
// tracklist.tracks.removeAt(index)
|
||||||
notifyItemRemoved(index)
|
// notifyItemRemoved(index)
|
||||||
notifyItemRangeChanged(index, this.itemCount);
|
// notifyItemRangeChanged(index, this.itemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEmpty(): Boolean {
|
fun isEmpty(): Boolean
|
||||||
return tracklist.tracks.size == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggles the starred state of tracklist element - and saves tracklist */
|
|
||||||
private fun toggleStarred(view: View, position: Int) {
|
|
||||||
val starButton: ImageButton = view as ImageButton
|
|
||||||
if (tracklist.tracks[position].starred)
|
|
||||||
{
|
{
|
||||||
starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_outline_24dp))
|
return tracks.size == 0
|
||||||
tracklist.tracks[position].starred = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_filled_24dp))
|
|
||||||
tracklist.tracks[position].starred = true
|
|
||||||
}
|
|
||||||
tracklist.tracks[position].save_json(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Creates the track data string */
|
/* Creates the track data string */
|
||||||
private fun createTrackDataString(position: Int): String {
|
private fun createTrackDataString(position: Int): String
|
||||||
val track: Track = tracklist.tracks[position]
|
{
|
||||||
val track_duration_string = DateTimeHelper.convertToReadableTime(context, track.duration)
|
val track: Track = tracks[position]
|
||||||
val trackDataString: String
|
return "device: " + track.device_id
|
||||||
when (track.name == track.dateString) {
|
// val track_duration_string = DateTimeHelper.convertToReadableTime(context, track.duration)
|
||||||
// CASE: no individual name set - exclude date
|
// val trackDataString: String
|
||||||
true -> trackDataString = "${LengthUnitHelper.convertDistanceToString(track.distance, useImperial)} • ${track_duration_string}"
|
// if (track.name == track.dateString)
|
||||||
// CASE: no individual name set - include date
|
// {
|
||||||
false -> trackDataString = "${track.dateString} • ${LengthUnitHelper.convertDistanceToString(track.distance, useImperial)} • ${track_duration_string}"
|
// trackDataString = "${LengthUnitHelper.convertDistanceToString(track.distance, useImperial)} • ${track_duration_string}"
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// trackDataString = "${track.dateString} • ${LengthUnitHelper.convertDistanceToString(track.distance, useImperial)} • ${track_duration_string}"
|
||||||
|
// }
|
||||||
|
// return trackDataString
|
||||||
}
|
}
|
||||||
return trackDataString
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Inner class: DiffUtil.Callback that determines changes in data - improves list performance
|
|
||||||
*/
|
|
||||||
private inner class DiffCallback(val oldList: Tracklist, val newList: Tracklist): DiffUtil.Callback() {
|
|
||||||
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
||||||
val oldItem = oldList.tracks[oldItemPosition]
|
|
||||||
val newItem = newList.tracks[newItemPosition]
|
|
||||||
return oldItem.id == newItem.id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOldListSize(): Int {
|
|
||||||
return oldList.tracks.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNewListSize(): Int {
|
|
||||||
return newList.tracks.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
||||||
val oldItem = oldList.tracks[oldItemPosition]
|
|
||||||
val newItem = newList.tracks[newItemPosition]
|
|
||||||
return (oldItem.id == newItem.id) && (oldItem.distance == newItem.distance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* End of inner class
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -218,8 +196,6 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
val trackElement: ConstraintLayout = elementTrackLayout.findViewById(R.id.track_element)
|
val trackElement: ConstraintLayout = elementTrackLayout.findViewById(R.id.track_element)
|
||||||
val trackNameView: TextView = elementTrackLayout.findViewById(R.id.track_name)
|
val trackNameView: TextView = elementTrackLayout.findViewById(R.id.track_name)
|
||||||
val trackDataView: TextView = elementTrackLayout.findViewById(R.id.track_data)
|
val trackDataView: TextView = elementTrackLayout.findViewById(R.id.track_data)
|
||||||
val starButton: ImageButton = elementTrackLayout.findViewById(R.id.star_button)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* End of inner class
|
* End of inner class
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
|
import org.y20k.trackbook.Trackbook
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
|
|
||||||
|
@ -66,17 +67,11 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
var userInteraction: Boolean = false
|
var userInteraction: Boolean = false
|
||||||
val currentLocationButton: FloatingActionButton
|
val currentLocationButton: FloatingActionButton
|
||||||
val mainButton: ExtendedFloatingActionButton
|
val mainButton: ExtendedFloatingActionButton
|
||||||
val saveButton: FloatingActionButton
|
|
||||||
val clearButton: FloatingActionButton
|
|
||||||
private val additionalButtons: Group
|
|
||||||
private val mapView: MapView
|
private val mapView: MapView
|
||||||
|
private val homepoint_overlays: ArrayList<ItemizedIconOverlay<OverlayItem>> = ArrayList()
|
||||||
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
||||||
private var currentTrackOverlay: SimpleFastPointOverlay?
|
private var currentTrackOverlay: SimpleFastPointOverlay?
|
||||||
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
|
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
|
||||||
private val liveStatisticsDistanceView: MaterialTextView
|
|
||||||
private val liveStatisticsDistanceOutlineView: MaterialTextView
|
|
||||||
private val liveStatisticsDurationView: MaterialTextView
|
|
||||||
private val liveStatisticsDurationOutlineView: MaterialTextView
|
|
||||||
private val useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
|
private val useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
|
||||||
private var locationErrorBar: Snackbar
|
private var locationErrorBar: Snackbar
|
||||||
private var controller: IMapController
|
private var controller: IMapController
|
||||||
|
@ -90,13 +85,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
mapView = rootView.findViewById(R.id.map)
|
mapView = rootView.findViewById(R.id.map)
|
||||||
currentLocationButton = rootView.findViewById(R.id.location_button)
|
currentLocationButton = rootView.findViewById(R.id.location_button)
|
||||||
mainButton = rootView.findViewById(R.id.main_button)
|
mainButton = rootView.findViewById(R.id.main_button)
|
||||||
additionalButtons = rootView.findViewById(R.id.additional_buttons)
|
|
||||||
saveButton = rootView.findViewById(R.id.button_save)
|
|
||||||
clearButton = rootView.findViewById(R.id.button_clear)
|
|
||||||
liveStatisticsDistanceView = rootView.findViewById(R.id.live_statistics_distance)
|
|
||||||
liveStatisticsDistanceOutlineView = rootView.findViewById(R.id.live_statistics_distance_outline)
|
|
||||||
liveStatisticsDurationView = rootView.findViewById(R.id.live_statistics_duration)
|
|
||||||
liveStatisticsDurationOutlineView = rootView.findViewById(R.id.live_statistics_duration_outline)
|
|
||||||
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
|
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
|
||||||
|
|
||||||
// basic map setup
|
// basic map setup
|
||||||
|
@ -124,11 +112,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
compassOverlay.setCompassCenter((screen_width / densityScalingFactor) - 36f, 36f)
|
compassOverlay.setCompassCenter((screen_width / densityScalingFactor) - 36f, 36f)
|
||||||
mapView.overlays.add(compassOverlay)
|
mapView.overlays.add(compassOverlay)
|
||||||
|
|
||||||
// position the live statistics
|
val app: Trackbook = (context.applicationContext as Trackbook)
|
||||||
(liveStatisticsDistanceView.layoutParams as ConstraintLayout.LayoutParams).apply {
|
app.homepoint_generator().forEach { homepoint -> mapView.overlays.add(MapOverlayHelper(markerListener).createHomepointOverlay(context, homepoint.location))}
|
||||||
// topMargin = (12 * densityScalingFactor).toInt() + statusBarHeight // TODO uncomment when transparent status bar is re-implemented
|
|
||||||
topMargin = (12 * densityScalingFactor).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// add my location overlay
|
// add my location overlay
|
||||||
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, startLocation, trackingState)
|
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, startLocation, trackingState)
|
||||||
|
@ -178,7 +163,7 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
|
|
||||||
|
|
||||||
/* Mark current position on map */
|
/* Mark current position on map */
|
||||||
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_NOT_STARTED) {
|
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED) {
|
||||||
mapView.overlays.remove(currentPositionOverlay)
|
mapView.overlays.remove(currentPositionOverlay)
|
||||||
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, location, trackingState)
|
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, location, trackingState)
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
mapView.overlays.add(currentPositionOverlay)
|
||||||
|
@ -193,7 +178,7 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
if (currentTrackSpecialMarkerOverlay != null) {
|
if (currentTrackSpecialMarkerOverlay != null) {
|
||||||
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
||||||
}
|
}
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.trkpts.isNotEmpty()) {
|
||||||
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
||||||
currentTrackOverlay = mapOverlayHelper.createTrackOverlay(context, track, trackingState)
|
currentTrackOverlay = mapOverlayHelper.createTrackOverlay(context, track, trackingState)
|
||||||
currentTrackSpecialMarkerOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, trackingState)
|
currentTrackSpecialMarkerOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, trackingState)
|
||||||
|
@ -202,59 +187,20 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update live statics */
|
|
||||||
fun updateLiveStatics(distance: Float, duration: Long, trackingState: Int) {
|
|
||||||
// toggle visibility
|
|
||||||
if (trackingState == Keys.STATE_TRACKING_NOT_STARTED)
|
|
||||||
{
|
|
||||||
liveStatisticsDistanceView.isGone = true
|
|
||||||
liveStatisticsDurationView.isGone = true
|
|
||||||
liveStatisticsDistanceOutlineView.isGone = true
|
|
||||||
liveStatisticsDurationOutlineView.isGone = true
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
liveStatisticsDistanceView.isVisible = true
|
|
||||||
liveStatisticsDurationView.isVisible = true
|
|
||||||
liveStatisticsDistanceOutlineView.isVisible = true
|
|
||||||
liveStatisticsDurationOutlineView.isVisible = true
|
|
||||||
// update distance and duration (and add outline)
|
|
||||||
val distanceString: String = LengthUnitHelper.convertDistanceToString(distance, useImperial)
|
|
||||||
liveStatisticsDistanceView.text = distanceString
|
|
||||||
liveStatisticsDistanceOutlineView.text = distanceString
|
|
||||||
liveStatisticsDistanceOutlineView.paint.strokeWidth = 5f
|
|
||||||
liveStatisticsDistanceOutlineView.paint.style = Paint.Style.STROKE
|
|
||||||
val durationString: String = DateTimeHelper.convertToReadableTime(context, duration, compactFormat = true)
|
|
||||||
liveStatisticsDurationView.text = durationString
|
|
||||||
liveStatisticsDurationOutlineView.text = durationString
|
|
||||||
liveStatisticsDurationOutlineView.paint.strokeWidth = 5f
|
|
||||||
liveStatisticsDurationOutlineView.paint.style = Paint.Style.STROKE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggles state of main button and additional buttons (save & resume) */
|
/* Toggles state of main button and additional buttons (save & resume) */
|
||||||
fun updateMainButton(trackingState: Int)
|
fun updateMainButton(trackingState: Int)
|
||||||
{
|
{
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
Keys.STATE_TRACKING_NOT_STARTED -> {
|
Keys.STATE_TRACKING_STOPPED -> {
|
||||||
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
|
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
|
||||||
mainButton.text = context.getString(R.string.button_start)
|
mainButton.text = context.getString(R.string.button_start)
|
||||||
mainButton.contentDescription = context.getString(R.string.descr_button_start)
|
mainButton.contentDescription = context.getString(R.string.descr_button_start)
|
||||||
additionalButtons.isGone = true
|
|
||||||
currentLocationButton.isVisible = true
|
currentLocationButton.isVisible = true
|
||||||
}
|
}
|
||||||
Keys.STATE_TRACKING_ACTIVE -> {
|
Keys.STATE_TRACKING_ACTIVE -> {
|
||||||
mainButton.setIconResource(R.drawable.ic_pause_24dp)
|
mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp)
|
||||||
mainButton.text = context.getString(R.string.button_pause)
|
mainButton.text = context.getString(R.string.button_pause)
|
||||||
mainButton.contentDescription = context.getString(R.string.descr_button_start)
|
mainButton.contentDescription = context.getString(R.string.descr_button_pause)
|
||||||
additionalButtons.isGone = true
|
|
||||||
currentLocationButton.isVisible = true
|
|
||||||
}
|
|
||||||
Keys.STATE_TRACKING_PAUSED -> {
|
|
||||||
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
|
|
||||||
mainButton.text = context.getString(R.string.button_resume)
|
|
||||||
mainButton.contentDescription = context.getString(R.string.descr_button_resume)
|
|
||||||
additionalButtons.isVisible = true
|
|
||||||
currentLocationButton.isVisible = true
|
currentLocationButton.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,6 @@ import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.osmdroid.api.IGeoPoint
|
import org.osmdroid.api.IGeoPoint
|
||||||
import org.osmdroid.api.IMapController
|
import org.osmdroid.api.IMapController
|
||||||
import org.osmdroid.events.MapListener
|
import org.osmdroid.events.MapListener
|
||||||
|
@ -50,6 +47,7 @@ import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.core.Track
|
||||||
|
import org.y20k.trackbook.core.TrackStatistics
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@ -58,12 +56,14 @@ import kotlin.math.roundToInt
|
||||||
* TrackFragmentLayoutHolder class
|
* TrackFragmentLayoutHolder class
|
||||||
*/
|
*/
|
||||||
//data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var statusBarHeight: Int, private var container: ViewGroup?, var track: Track): MapListener { TODO REMOVE
|
//data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var statusBarHeight: Int, private var container: ViewGroup?, var track: Track): MapListener { TODO REMOVE
|
||||||
data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var container: ViewGroup?, var track: Track): MapListener {
|
data class TrackFragmentLayoutHolder(
|
||||||
|
private var context: Context,
|
||||||
/* Define log tag */
|
private var markerListener: MapOverlayHelper.MarkerListener,
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java)
|
private var inflater: LayoutInflater,
|
||||||
|
private var container: ViewGroup?,
|
||||||
|
var track: Track
|
||||||
|
): MapListener
|
||||||
|
{
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
val rootView: View
|
val rootView: View
|
||||||
val shareButton: ImageButton
|
val shareButton: ImageButton
|
||||||
|
@ -78,7 +78,6 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
private val statisticsSheetBehavior: BottomSheetBehavior<View>
|
private val statisticsSheetBehavior: BottomSheetBehavior<View>
|
||||||
private val statisticsSheet: NestedScrollView
|
private val statisticsSheet: NestedScrollView
|
||||||
private val statisticsView: View
|
private val statisticsView: View
|
||||||
private val trackidView: MaterialTextView
|
|
||||||
private val distanceView: MaterialTextView
|
private val distanceView: MaterialTextView
|
||||||
private val stepsTitleView: MaterialTextView
|
private val stepsTitleView: MaterialTextView
|
||||||
private val stepsView: MaterialTextView
|
private val stepsView: MaterialTextView
|
||||||
|
@ -115,13 +114,12 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
||||||
mapView.setMultiTouchControls(true)
|
mapView.setMultiTouchControls(true)
|
||||||
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
||||||
controller.setCenter(GeoPoint(track.latitude, track.longitude))
|
controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude))
|
||||||
controller.setZoom(track.zoomLevel)
|
controller.setZoom(track.zoomLevel)
|
||||||
|
|
||||||
// get views for statistics sheet
|
// get views for statistics sheet
|
||||||
statisticsSheet = rootView.findViewById(R.id.statistics_sheet)
|
statisticsSheet = rootView.findViewById(R.id.statistics_sheet)
|
||||||
statisticsView = rootView.findViewById(R.id.statistics_view)
|
statisticsView = rootView.findViewById(R.id.statistics_view)
|
||||||
trackidView = rootView.findViewById(R.id.statistics_data_trackid)
|
|
||||||
distanceView = rootView.findViewById(R.id.statistics_data_distance)
|
distanceView = rootView.findViewById(R.id.statistics_data_distance)
|
||||||
stepsTitleView = rootView.findViewById(R.id.statistics_p_steps)
|
stepsTitleView = rootView.findViewById(R.id.statistics_p_steps)
|
||||||
stepsView = rootView.findViewById(R.id.statistics_data_steps)
|
stepsView = rootView.findViewById(R.id.statistics_data_steps)
|
||||||
|
@ -156,9 +154,9 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
|
|
||||||
// create map overlay
|
// create map overlay
|
||||||
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
||||||
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT_STARTED)
|
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED)
|
||||||
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_NOT_STARTED, displayStartEndMarker = true)
|
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.trkpts.isNotEmpty()) {
|
||||||
mapView.overlays.add(trackSpecialMarkersOverlay)
|
mapView.overlays.add(trackSpecialMarkersOverlay)
|
||||||
mapView.overlays.add(trackOverlay)
|
mapView.overlays.add(trackOverlay)
|
||||||
}
|
}
|
||||||
|
@ -172,77 +170,48 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
|
|
||||||
|
|
||||||
/* Updates map overlay */
|
/* Updates map overlay */
|
||||||
fun updateTrackOverlay() {
|
fun updateTrackOverlay()
|
||||||
|
{
|
||||||
if (trackOverlay != null) {
|
if (trackOverlay != null) {
|
||||||
mapView.overlays.remove(trackOverlay)
|
mapView.overlays.remove(trackOverlay)
|
||||||
}
|
}
|
||||||
if (trackSpecialMarkersOverlay != null) {
|
if (trackSpecialMarkersOverlay != null) {
|
||||||
mapView.overlays.remove(trackSpecialMarkersOverlay)
|
mapView.overlays.remove(trackSpecialMarkersOverlay)
|
||||||
}
|
}
|
||||||
if (track.wayPoints.isNotEmpty()) {
|
if (track.trkpts.isNotEmpty()) {
|
||||||
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
||||||
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT_STARTED)
|
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED)
|
||||||
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_NOT_STARTED, displayStartEndMarker = true)
|
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
|
||||||
mapView.overlays.add(trackOverlay)
|
mapView.overlays.add(trackOverlay)
|
||||||
mapView.overlays.add(trackSpecialMarkersOverlay)
|
mapView.overlays.add(trackSpecialMarkersOverlay)
|
||||||
}
|
}
|
||||||
// save track
|
|
||||||
CoroutineScope(Dispatchers.IO).launch { track.save_all_files_suspended(context) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Saves zoom level and center of this map */
|
/* Saves zoom level and center of this map */
|
||||||
fun saveViewStateToTrack()
|
fun saveViewStateToTrack()
|
||||||
{
|
{
|
||||||
if (track.latitude != 0.0 && track.longitude != 0.0)
|
if (track.view_latitude != 0.0 && track.view_longitude != 0.0)
|
||||||
{
|
{
|
||||||
CoroutineScope(Dispatchers.IO).launch { track.save_json_suspended(context) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Sets up the statistics sheet */
|
/* Sets up the statistics sheet */
|
||||||
private fun setupStatisticsViews() {
|
private fun setupStatisticsViews()
|
||||||
|
|
||||||
// get step count string - hide step count if not available
|
|
||||||
val steps: String
|
|
||||||
if (track.stepCount == -1f)
|
|
||||||
{
|
{
|
||||||
steps = context.getString(R.string.statistics_sheet_p_steps_no_pedometer)
|
|
||||||
stepsTitleView.isGone = true
|
|
||||||
stepsView.isGone = true
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
steps = track.stepCount.roundToInt().toString()
|
|
||||||
stepsTitleView.isVisible = true
|
|
||||||
stepsView.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate views
|
// populate views
|
||||||
|
val stats: TrackStatistics = track.statistics()
|
||||||
trackNameView.text = track.name
|
trackNameView.text = track.name
|
||||||
trackidView.text = track.id.toString()
|
distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits)
|
||||||
distanceView.text = LengthUnitHelper.convertDistanceToString(track.distance, useImperialUnits)
|
waypointsView.text = track.trkpts.size.toString()
|
||||||
stepsView.text = steps
|
durationView.text = DateTimeHelper.convertToReadableTime(context, stats.duration)
|
||||||
waypointsView.text = track.wayPoints.size.toString()
|
velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits)
|
||||||
durationView.text = DateTimeHelper.convertToReadableTime(context, track.duration)
|
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time)
|
||||||
velocityView.text = LengthUnitHelper.convertToVelocityString(track.duration, track.recordingPaused, track.distance, useImperialUnits)
|
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.stop_time)
|
||||||
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
|
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits)
|
||||||
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStop)
|
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits)
|
||||||
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits)
|
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits)
|
||||||
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits)
|
negativeElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_descent, useImperialUnits)
|
||||||
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits)
|
|
||||||
negativeElevationView.text = LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits)
|
|
||||||
|
|
||||||
// show / hide recording pause
|
|
||||||
if (track.recordingPaused != 0L) {
|
|
||||||
recordingPausedLabelView.isVisible = true
|
|
||||||
recordingPausedView.isVisible = true
|
|
||||||
recordingPausedView.text = DateTimeHelper.convertToReadableTime(context, track.recordingPaused)
|
|
||||||
} else {
|
|
||||||
recordingPausedLabelView.isGone = true
|
|
||||||
recordingPausedView.isGone = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// inform user about possible accuracy issues with altitude measurements
|
// inform user about possible accuracy issues with altitude measurements
|
||||||
elevationDataViews.referencedIds.forEach { id ->
|
elevationDataViews.referencedIds.forEach { id ->
|
||||||
|
@ -265,7 +234,6 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Defines the behavior of the statistics sheet */
|
/* Defines the behavior of the statistics sheet */
|
||||||
private fun getStatisticsSheetCallback(): BottomSheetBehavior.BottomSheetCallback {
|
private fun getStatisticsSheetCallback(): BottomSheetBehavior.BottomSheetCallback {
|
||||||
return object : BottomSheetBehavior.BottomSheetCallback() {
|
return object : BottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
@ -313,8 +281,8 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
val center: IGeoPoint = mapView.mapCenter
|
val center: IGeoPoint = mapView.mapCenter
|
||||||
track.latitude = center.latitude
|
track.view_latitude = center.latitude
|
||||||
track.longitude = center.longitude
|
track.view_longitude = center.longitude
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
app/src/main/res/drawable/ic_fiber_manual_stop_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_fiber_manual_stop_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="36dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:width="36dp">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/recording_management_buttons_icon"
|
||||||
|
android:pathData="M5,5h14v14H5z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_homepoint_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_homepoint_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/homepoint"
|
||||||
|
android:pathData="M 12.01 2.5 L 5.883 6.936 L 5.883 3.604 L 3.674 3.604 L 3.674 8.535 L 2.01 9.74 L 1.99 21.5 L 12.35 21.5 L 12.35 16.047 L 17.215 16.047 L 17.215 21.5 L 22.01 21.5 L 22.01 9.74 Z"/>
|
||||||
|
</vector>
|
|
@ -28,7 +28,6 @@
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||||
android:textColor="@color/text_default"
|
android:textColor="@color/text_default"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/track_data"
|
app:layout_constraintBottom_toTopOf="@+id/track_data"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/star_button"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="@string/sample_text_track_name" />
|
tools:text="@string/sample_text_track_name" />
|
||||||
|
@ -37,28 +36,17 @@
|
||||||
android:id="@+id/track_data"
|
android:id="@+id/track_data"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||||
android:textColor="@color/text_lightweight"
|
android:textColor="@color/text_lightweight"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/track_name"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@+id/track_name"
|
app:layout_constraintStart_toStartOf="@+id/track_name"
|
||||||
tools:text="@string/sample_text_track_data" />
|
tools:text="@string/sample_text_track_data" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/star_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:backgroundTint="@color/default_transparent"
|
|
||||||
android:contentDescription="@string/descr_mark_starred_button"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_star_outline_24dp" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
|
@ -22,40 +22,11 @@
|
||||||
app:layout_dodgeInsetEdges="bottom">
|
app:layout_dodgeInsetEdges="bottom">
|
||||||
|
|
||||||
<!-- BUTTON SAVE -->
|
<!-- BUTTON SAVE -->
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/button_save"
|
|
||||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/descr_button_save"
|
|
||||||
app:backgroundTint="@color/recording_management_buttons_background"
|
|
||||||
app:fabSize="mini"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/main_button"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.15"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/main_button"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/main_button"
|
|
||||||
app:srcCompat="@drawable/ic_save_24dp"
|
|
||||||
app:tint="@color/recording_management_buttons_icon" />
|
|
||||||
|
|
||||||
<!-- BUTTON CLEAR -->
|
<!-- BUTTON CLEAR -->
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/button_clear"
|
|
||||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/descr_button_delete"
|
|
||||||
app:backgroundTint="@color/recording_management_buttons_background"
|
|
||||||
app:fabSize="mini"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/main_button"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/main_button"
|
|
||||||
app:layout_constraintHorizontal_bias="0.85"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/main_button"
|
|
||||||
app:srcCompat="@drawable/ic_delete_24dp"
|
|
||||||
app:tint="@color/recording_management_buttons_icon" />
|
|
||||||
|
|
||||||
<!-- MAIN BUTTON -->
|
<!-- MAIN BUTTON -->
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/main_button"
|
android:id="@+id/main_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -76,69 +47,19 @@
|
||||||
android:id="@+id/location_button"
|
android:id="@+id/location_button"
|
||||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:layout_marginTop="64dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
android:contentDescription="@string/descr_button_location"
|
android:contentDescription="@string/descr_button_location"
|
||||||
android:src="@drawable/ic_current_location_24dp"
|
android:src="@drawable/ic_current_location_24dp"
|
||||||
app:backgroundTint="@color/location_button_background"
|
app:backgroundTint="@color/location_button_background"
|
||||||
app:fabSize="mini"
|
app:fabSize="mini"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:tint="@color/location_button_icon" />
|
app:tint="@color/location_button_icon" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/live_statistics_distance_outline"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
|
||||||
android:textColor="@color/text_outline_default"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/live_statistics_distance"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/live_statistics_distance" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/live_statistics_distance"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
|
||||||
android:textColor="@color/text_default"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/live_statistics_duration"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/live_statistics_duration"
|
|
||||||
tools:text="@string/sample_text_default_live_statistics_distance" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/live_statistics_duration_outline"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
|
||||||
android:textColor="@color/text_outline_default"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/live_statistics_duration"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/live_statistics_duration" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/live_statistics_duration"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:textAlignment="textEnd"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
|
||||||
android:textColor="@color/text_default"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/location_button"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/location_button"
|
|
||||||
tools:text="@string/sample_text_default_live_statistics_duration" />
|
|
||||||
|
|
||||||
<!-- GROUPS -->
|
<!-- GROUPS -->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
|
||||||
android:id="@+id/additional_buttons"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:constraint_referenced_ids="button_clear,button_save" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
@ -74,30 +74,6 @@
|
||||||
app:layout_constraintTop_toTopOf="@+id/statistics_track_name_headline"
|
app:layout_constraintTop_toTopOf="@+id/statistics_track_name_headline"
|
||||||
app:srcCompat="@drawable/ic_save_to_storage_24dp" />
|
app:srcCompat="@drawable/ic_save_to_storage_24dp" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/statistics_p_trackid"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="@string/statistics_sheet_p_trackid"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
|
||||||
android:textColor="@color/text_lightweight"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/statistics_track_name_headline"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/statistics_track_name_headline" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/statistics_data_trackid"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
|
||||||
android:textColor="@color/text_default"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/statistics_p_trackid"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/statistics_p_trackid"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/statistics_p_trackid"
|
|
||||||
tools:text="@string/sample_text_default_data" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/statistics_p_distance"
|
android:id="@+id/statistics_p_distance"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -108,7 +84,7 @@
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||||
android:textColor="@color/text_lightweight"
|
android:textColor="@color/text_lightweight"
|
||||||
app:layout_constraintStart_toStartOf="@+id/statistics_track_name_headline"
|
app:layout_constraintStart_toStartOf="@+id/statistics_track_name_headline"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/statistics_p_trackid" />
|
app:layout_constraintTop_toBottomOf="@+id/statistics_track_name_headline" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/statistics_data_distance"
|
android:id="@+id/statistics_data_distance"
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Ryd</string>
|
<string name="button_delete">Ryd</string>
|
||||||
<string name="button_save">Gem</string>
|
<string name="button_save">Gem</string>
|
||||||
<string name="button_resume">Fortsæt</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">Del GPX fil med</string>
|
<string name="dialog_share_gpx">Del GPX fil med</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook har ingen rutepunkter endnu.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook har ingen rutepunkter endnu.</string>
|
||||||
|
@ -86,9 +85,6 @@
|
||||||
<string name="pref_about_title">Om</string>
|
<string name="pref_about_title">Om</string>
|
||||||
<string name="track_list_p_element_statistics">Samlet registreret distance</string>
|
<string name="track_list_p_element_statistics">Samlet registreret distance</string>
|
||||||
<string name="pref_report_issue_summary">Rapporter fejl og foreslå forbedringer på GitHub.</string>
|
<string name="pref_report_issue_summary">Rapporter fejl og foreslå forbedringer på GitHub.</string>
|
||||||
<string name="pref_recording_accuracy_title">Nøjagtighed af optagelser</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Waypoints har lavere nøjagtighed, men er mere hyppige.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Waypoints har større nøjagtighed, men er mindre hyppige.</string>
|
|
||||||
<string name="pref_imperial_measurement_units_summary_metric">I øjeblikket anvendes metriske enheder (kilometer, meter).</string>
|
<string name="pref_imperial_measurement_units_summary_metric">I øjeblikket anvendes metriske enheder (kilometer, meter).</string>
|
||||||
<string name="pref_gps_only_summary_gps_only">I øjeblikket bruger vi kun GPS til lokalisering.</string>
|
<string name="pref_gps_only_summary_gps_only">I øjeblikket bruger vi kun GPS til lokalisering.</string>
|
||||||
<string name="pref_gps_only_summary_gps_and_network">I øjeblikket bruges GPS og netværk til lokalisering.</string>
|
<string name="pref_gps_only_summary_gps_and_network">I øjeblikket bruges GPS og netværk til lokalisering.</string>
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Zurücksetzen</string>
|
<string name="button_delete">Zurücksetzen</string>
|
||||||
<string name="button_save">Speichern</string>
|
<string name="button_save">Speichern</string>
|
||||||
<string name="button_resume">Fortsetzen</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">GPX-Datei teilen mit</string>
|
<string name="dialog_share_gpx">GPX-Datei teilen mit</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook hat noch keine Wegpunkte aufgenommen.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook hat noch keine Wegpunkte aufgenommen.</string>
|
||||||
|
@ -110,7 +109,4 @@
|
||||||
<string name="toast_message_poi_removed">Markierung für Ort von Interesse entfernt.</string>
|
<string name="toast_message_poi_removed">Markierung für Ort von Interesse entfernt.</string>
|
||||||
<string name="toast_message_poi_added">Markierung für Ort von Interesse hinzugefügt.</string>
|
<string name="toast_message_poi_added">Markierung für Ort von Interesse hinzugefügt.</string>
|
||||||
<string name="track_list_p_element_statistics">Erfasste Gesamtentfernung</string>
|
<string name="track_list_p_element_statistics">Erfasste Gesamtentfernung</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Wegpunkte haben eine höhere Genauigkeit, sind aber weniger häufig.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Wegpunkte haben eine geringere Genauigkeit, sind aber häufiger.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Aufzeichnungsgenauigkeit</string>
|
|
||||||
</resources>
|
</resources>
|
|
@ -28,7 +28,6 @@
|
||||||
<string name="snackbar_message_location_permission_denied">Permiso de ubicación no concedido. Trackbook no funcionará.</string>
|
<string name="snackbar_message_location_permission_denied">Permiso de ubicación no concedido. Trackbook no funcionará.</string>
|
||||||
<string name="button_delete">Limpiar</string>
|
<string name="button_delete">Limpiar</string>
|
||||||
<string name="button_save">Guardar</string>
|
<string name="button_save">Guardar</string>
|
||||||
<string name="button_resume">Resumir</string>
|
|
||||||
<string name="dialog_generic_button_okay">OK</string>
|
<string name="dialog_generic_button_okay">OK</string>
|
||||||
<string name="dialog_generic_details_button">Mostrar detalles</string>
|
<string name="dialog_generic_details_button">Mostrar detalles</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook no registró ningún punto de referencia hasta el momento.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook no registró ningún punto de referencia hasta el momento.</string>
|
||||||
|
@ -77,9 +76,6 @@
|
||||||
<string name="pref_gps_only_summary_gps_and_network">Actualmente usando GPS y Red para localización.</string>
|
<string name="pref_gps_only_summary_gps_and_network">Actualmente usando GPS y Red para localización.</string>
|
||||||
<string name="pref_imperial_measurement_units_summary_metric">Actualmente se utilizan unidades métricas (Kilómetro, Metro).</string>
|
<string name="pref_imperial_measurement_units_summary_metric">Actualmente se utilizan unidades métricas (Kilómetro, Metro).</string>
|
||||||
<string name="pref_imperial_measurement_units_title">Utilizar medidas imperiales</string>
|
<string name="pref_imperial_measurement_units_title">Utilizar medidas imperiales</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Los puntos de referencia tienen mayor precisión pero son menos frecuentes.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Los waypoints tienen menor precisión pero son más frecuentes.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Precisión de grabación</string>
|
|
||||||
<string name="pref_report_issue_title">Reportar problema</string>
|
<string name="pref_report_issue_title">Reportar problema</string>
|
||||||
<string name="pref_reset_advanced_summary">Restablece la configuración avanzada a los valores predeterminados.</string>
|
<string name="pref_reset_advanced_summary">Restablece la configuración avanzada a los valores predeterminados.</string>
|
||||||
<string name="pref_reset_advanced_title">Reiniciar</string>
|
<string name="pref_reset_advanced_title">Reiniciar</string>
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
<string name="snackbar_message_location_offline">Localisation désactivée. Le suivi ne fonctionnera pas.</string>
|
<string name="snackbar_message_location_offline">Localisation désactivée. Le suivi ne fonctionnera pas.</string>
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Supprimer</string>
|
<string name="button_delete">Supprimer</string>
|
||||||
<string name="button_resume">Reprendre</string>
|
|
||||||
<string name="button_save">Sauvegarder</string>
|
<string name="button_save">Sauvegarder</string>
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_error_empty_recording_button_resume">Reprendre l\'enregistrement</string>
|
<string name="dialog_error_empty_recording_button_resume">Reprendre l\'enregistrement</string>
|
||||||
|
@ -109,9 +108,6 @@
|
||||||
<string name="toast_message_copied_to_clipboard">Copié dans le presse-papiers.</string>
|
<string name="toast_message_copied_to_clipboard">Copié dans le presse-papiers.</string>
|
||||||
<string name="toast_message_poi_removed">Marqueur de point d\'intérêt supprimé.</string>
|
<string name="toast_message_poi_removed">Marqueur de point d\'intérêt supprimé.</string>
|
||||||
<string name="toast_message_poi_added">Marqueur de point d\'intérêt ajouté.</string>
|
<string name="toast_message_poi_added">Marqueur de point d\'intérêt ajouté.</string>
|
||||||
<string name="pref_recording_accuracy_title">Précision de l\'enregistrement</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Les points de cheminements ont une précision plus faible mais sont plus fréquents.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Les points de cheminement ont une plus grande précision mais sont moins fréquents.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Distance totale enregistrée</string>
|
<string name="track_list_p_element_statistics">Distance totale enregistrée</string>
|
||||||
<string name="descr_button_location">Centrer sur la position actuelle</string>
|
<string name="descr_button_location">Centrer sur la position actuelle</string>
|
||||||
</resources>
|
</resources>
|
|
@ -32,7 +32,6 @@
|
||||||
<string name="button_save">Spremi</string>
|
<string name="button_save">Spremi</string>
|
||||||
<string name="pref_general_title">Opće</string>
|
<string name="pref_general_title">Opće</string>
|
||||||
<string name="pref_accuracy_threshold_title">Prag točnosti</string>
|
<string name="pref_accuracy_threshold_title">Prag točnosti</string>
|
||||||
<string name="button_resume">Nastavi</string>
|
|
||||||
<string name="pref_theme_selection_title">Tema programa</string>
|
<string name="pref_theme_selection_title">Tema programa</string>
|
||||||
<string name="tab_tracks">Rute</string>
|
<string name="tab_tracks">Rute</string>
|
||||||
<string name="descr_map_last_track">Mapa zadnje rute</string>
|
<string name="descr_map_last_track">Mapa zadnje rute</string>
|
||||||
|
@ -96,9 +95,6 @@
|
||||||
<string name="toast_message_copied_to_clipboard">Kopirano u međuspremnik.</string>
|
<string name="toast_message_copied_to_clipboard">Kopirano u međuspremnik.</string>
|
||||||
<string name="toast_message_poi_removed">Oznaka točke interesa uklonjena.</string>
|
<string name="toast_message_poi_removed">Oznaka točke interesa uklonjena.</string>
|
||||||
<string name="toast_message_poi_added">Oznaka točke interesa dodana.</string>
|
<string name="toast_message_poi_added">Oznaka točke interesa dodana.</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Točke rute imaju veću točnost ali su rjeđe.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Točnost snimanja</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Točke rute imaju manju točnost ali su češće.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Ukupna udaljenost snimljena</string>
|
<string name="track_list_p_element_statistics">Ukupna udaljenost snimljena</string>
|
||||||
<string name="descr_button_location">Centriraj na trenutačno mjesto</string>
|
<string name="descr_button_location">Centriraj na trenutačno mjesto</string>
|
||||||
</resources>
|
</resources>
|
|
@ -182,7 +182,6 @@
|
||||||
<string name="dialog_generic_details_button">Tampilkan detil</string>
|
<string name="dialog_generic_details_button">Tampilkan detil</string>
|
||||||
<string name="dialog_generic_button_okay">Oke</string>
|
<string name="dialog_generic_button_okay">Oke</string>
|
||||||
<string name="dialog_generic_button_cancel">Batalkan</string>
|
<string name="dialog_generic_button_cancel">Batalkan</string>
|
||||||
<string name="button_resume">Lanjutkan</string>
|
|
||||||
<string name="button_save">Simpan</string>
|
<string name="button_save">Simpan</string>
|
||||||
<string name="button_delete">Bersihkan</string>
|
<string name="button_delete">Bersihkan</string>
|
||||||
<string name="snackbar_message_location_permission_denied">Izin lokasi tak diberikan. Trackbook tidak akan berfungsi.</string>
|
<string name="snackbar_message_location_permission_denied">Izin lokasi tak diberikan. Trackbook tidak akan berfungsi.</string>
|
||||||
|
@ -192,8 +191,5 @@
|
||||||
<string name="notification_show">Tampilkan</string>
|
<string name="notification_show">Tampilkan</string>
|
||||||
<string name="notification_resume">Lanjutkan</string>
|
<string name="notification_resume">Lanjutkan</string>
|
||||||
<string name="tab_settings">Pengaturan</string>
|
<string name="tab_settings">Pengaturan</string>
|
||||||
<string name="pref_recording_accuracy_summary_default">Waypoints memiliki akurasi yang lebih rendah tetapi lebih sering.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Waypoints memiliki akurasi yang lebih tinggi tetapi lebih jarang.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Akurasi Perekaman</string>
|
|
||||||
<string name="track_list_p_element_statistics">Jarak Total Terekam</string>
|
<string name="track_list_p_element_statistics">Jarak Total Terekam</string>
|
||||||
</resources>
|
</resources>
|
|
@ -21,7 +21,6 @@
|
||||||
<!-- FAB Sub Menu -->
|
<!-- FAB Sub Menu -->
|
||||||
<string name="button_delete">Cancella</string>
|
<string name="button_delete">Cancella</string>
|
||||||
<string name="button_save">Salva</string>
|
<string name="button_save">Salva</string>
|
||||||
<string name="button_resume">Riprendi</string>
|
|
||||||
<!-- Dialogs -->
|
<!-- Dialogs -->
|
||||||
<string name="dialog_generic_button_cancel">Annulla</string>
|
<string name="dialog_generic_button_cancel">Annulla</string>
|
||||||
<string name="dialog_generic_button_okay">OK</string>
|
<string name="dialog_generic_button_okay">OK</string>
|
||||||
|
@ -116,8 +115,5 @@
|
||||||
<string name="sample_text_track_data" translatable="false">23.0 km • 5 hrs 23 min 42 sec</string>
|
<string name="sample_text_track_data" translatable="false">23.0 km • 5 hrs 23 min 42 sec</string>
|
||||||
<string name="sample_text_track_name" translatable="false">July 20, 1969</string>
|
<string name="sample_text_track_name" translatable="false">July 20, 1969</string>
|
||||||
<string name="sample_text_default_data" translatable="false">track data missing</string>
|
<string name="sample_text_default_data" translatable="false">track data missing</string>
|
||||||
<string name="pref_recording_accuracy_title">Precisione di registrazione</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">I waypoint hanno una maggiore precisione ma sono meno frequenti.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">I waypoint hanno una precisione inferiore ma sono più frequenti.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Distanza totale registrata</string>
|
<string name="track_list_p_element_statistics">Distanza totale registrata</string>
|
||||||
</resources>
|
</resources>
|
|
@ -18,7 +18,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">クリア</string>
|
<string name="button_delete">クリア</string>
|
||||||
<string name="button_save">保存してクリア</string>
|
<string name="button_save">保存してクリア</string>
|
||||||
<string name="button_resume">再開</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">GPX ファイルを共有...</string>
|
<string name="dialog_share_gpx">GPX ファイルを共有...</string>
|
||||||
<string name="dialog_error_empty_recording_message">トラックブックはこれまでウェイポイントを記録していません。</string>
|
<string name="dialog_error_empty_recording_message">トラックブックはこれまでウェイポイントを記録していません。</string>
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Tøm</string>
|
<string name="button_delete">Tøm</string>
|
||||||
<string name="button_save">Lagre og tøm</string>
|
<string name="button_save">Lagre og tøm</string>
|
||||||
<string name="button_resume">Fortsett</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">Del GPX-fil med</string>
|
<string name="dialog_share_gpx">Del GPX-fil med</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook har ikke registrert noen veipunkter så langt.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook har ikke registrert noen veipunkter så langt.</string>
|
||||||
|
@ -109,9 +108,6 @@
|
||||||
<string name="toast_message_poi_added">Interessepunktmarkør lagt til.</string>
|
<string name="toast_message_poi_added">Interessepunktmarkør lagt til.</string>
|
||||||
<string name="toast_message_poi_removed">Interessepunktmarkør fjernet.</string>
|
<string name="toast_message_poi_removed">Interessepunktmarkør fjernet.</string>
|
||||||
<string name="toast_message_copied_to_clipboard">Kopiert til utklippstavle.</string>
|
<string name="toast_message_copied_to_clipboard">Kopiert til utklippstavle.</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Veipunkter har høyere nøyaktighet, men er mindre hyppige.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Totalavstand registrert</string>
|
<string name="track_list_p_element_statistics">Totalavstand registrert</string>
|
||||||
<string name="pref_recording_accuracy_summary_default">Veipunkter har lavere nøyaktighet, men er hyppigere.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Opptaksnøyaktighet</string>
|
|
||||||
<string name="descr_button_location">Sentrer på nåværende sted</string>
|
<string name="descr_button_location">Sentrer på nåværende sted</string>
|
||||||
</resources>
|
</resources>
|
|
@ -18,7 +18,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Wissen</string>
|
<string name="button_delete">Wissen</string>
|
||||||
<string name="button_save">Opslaan</string>
|
<string name="button_save">Opslaan</string>
|
||||||
<string name="button_resume">Hervatten</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">GPX-bestand delen met</string>
|
<string name="dialog_share_gpx">GPX-bestand delen met</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook heeft nog geen routepunten vastgelegd.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook heeft nog geen routepunten vastgelegd.</string>
|
||||||
|
@ -109,9 +108,6 @@
|
||||||
<string name="toast_message_copied_to_clipboard">Gekopieerd naar klembord.</string>
|
<string name="toast_message_copied_to_clipboard">Gekopieerd naar klembord.</string>
|
||||||
<string name="toast_message_poi_removed">POI-indicatie verwijderd.</string>
|
<string name="toast_message_poi_removed">POI-indicatie verwijderd.</string>
|
||||||
<string name="toast_message_poi_added">POI-indicatie toegevoegd.</string>
|
<string name="toast_message_poi_added">POI-indicatie toegevoegd.</string>
|
||||||
<string name="pref_recording_accuracy_title">Opname nauwkeurigheid</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Routepunten zijn minder nauwkeurig, maar frequenter.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Routepunten zijn nauwkeuriger, maar minder frequent.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Totaal afgelegde afstand</string>
|
<string name="track_list_p_element_statistics">Totaal afgelegde afstand</string>
|
||||||
<string name="descr_button_location">Centreren op huidige locatie</string>
|
<string name="descr_button_location">Centreren op huidige locatie</string>
|
||||||
</resources>
|
</resources>
|
|
@ -3,7 +3,6 @@
|
||||||
<string name="dialog_generic_details_button">Pokaż szczegóły</string>
|
<string name="dialog_generic_details_button">Pokaż szczegóły</string>
|
||||||
<string name="dialog_generic_button_okay">OK</string>
|
<string name="dialog_generic_button_okay">OK</string>
|
||||||
<string name="dialog_generic_button_cancel">Anuluj</string>
|
<string name="dialog_generic_button_cancel">Anuluj</string>
|
||||||
<string name="button_resume">Kontynuuj</string>
|
|
||||||
<string name="button_save">Zapisz</string>
|
<string name="button_save">Zapisz</string>
|
||||||
<string name="button_delete">Wyczyść</string>
|
<string name="button_delete">Wyczyść</string>
|
||||||
<string name="snackbar_message_location_offline">Lokalizacja jest wyłączona. Trackbook nie będzie działać.</string>
|
<string name="snackbar_message_location_offline">Lokalizacja jest wyłączona. Trackbook nie będzie działać.</string>
|
||||||
|
@ -76,9 +75,6 @@
|
||||||
<string name="pref_reset_advanced_title">Resetuj</string>
|
<string name="pref_reset_advanced_title">Resetuj</string>
|
||||||
<string name="pref_reset_advanced_summary">Przywróć ustawienia zaawansowane do wartości domyślnych.</string>
|
<string name="pref_reset_advanced_summary">Przywróć ustawienia zaawansowane do wartości domyślnych.</string>
|
||||||
<string name="pref_report_issue_summary">Zgłaszaj błędy i proponuj ulepszenia na GitHub.</string>
|
<string name="pref_report_issue_summary">Zgłaszaj błędy i proponuj ulepszenia na GitHub.</string>
|
||||||
<string name="pref_recording_accuracy_title">Dokładność zapisu</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Punkty trasy mają mniejszą dokładność, ale są częstsze.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Punkty trasy mają większą dokładność, ale występują rzadziej.</string>
|
|
||||||
<string name="descr_button_resume">Przycisk Wznów</string>
|
<string name="descr_button_resume">Przycisk Wznów</string>
|
||||||
<string name="descr_map_current_track">Odwzorowanie bieżącego toru</string>
|
<string name="descr_map_current_track">Odwzorowanie bieżącego toru</string>
|
||||||
<string name="abbreviation_minutes">min</string>
|
<string name="abbreviation_minutes">min</string>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<!-- App Name -->
|
<!-- App Name -->
|
||||||
<string name="app_name">Trackbook</string>
|
<string name="app_name">Trackbook</string>
|
||||||
<!-- please do not translate app_name - transcription into different alphabet types is fine though -->
|
<!-- please do not translate app_name - transcription into different alphabet types is fine though -->
|
||||||
<string name="app_version_name" translatable="false">\"Echoes\"</string>
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<string name="tab_map">Mapa</string>
|
<string name="tab_map">Mapa</string>
|
||||||
<string name="tab_tracks">Caminhos</string>
|
<string name="tab_tracks">Caminhos</string>
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
<!-- FAB Sub Menu -->
|
<!-- FAB Sub Menu -->
|
||||||
<string name="button_delete">Limpar</string>
|
<string name="button_delete">Limpar</string>
|
||||||
<string name="button_save">Salvar</string>
|
<string name="button_save">Salvar</string>
|
||||||
<string name="button_resume">Retomar</string>
|
|
||||||
<!-- Dialogs -->
|
<!-- Dialogs -->
|
||||||
<string name="dialog_generic_button_cancel">Cancelar</string>
|
<string name="dialog_generic_button_cancel">Cancelar</string>
|
||||||
<string name="dialog_generic_button_okay">OK</string>
|
<string name="dialog_generic_button_okay">OK</string>
|
||||||
|
@ -113,8 +111,5 @@
|
||||||
<string name="descr_statistics_sheet_delete_button">Botão apagar rota</string>
|
<string name="descr_statistics_sheet_delete_button">Botão apagar rota</string>
|
||||||
<string name="descr_statistics_sheet_edit_button">Botão editar rota</string>
|
<string name="descr_statistics_sheet_edit_button">Botão editar rota</string>
|
||||||
<string name="descr_statistics_sheet_save_button">Botão salvar como GPX</string>
|
<string name="descr_statistics_sheet_save_button">Botão salvar como GPX</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Os waypoints têm maior precisão, mas são menos freqüentes.</string>
|
|
||||||
<string name="pref_recording_accuracy_title">Precisão de Gravação</string>
|
|
||||||
<string name="track_list_p_element_statistics">Distância Total Registrada</string>
|
<string name="track_list_p_element_statistics">Distância Total Registrada</string>
|
||||||
<string name="pref_recording_accuracy_summary_default">Os waypoints têm menor precisão, mas são mais freqüentes.</string>
|
|
||||||
</resources>
|
</resources>
|
|
@ -33,7 +33,6 @@
|
||||||
<string name="dialog_generic_details_button">Показать подробности</string>
|
<string name="dialog_generic_details_button">Показать подробности</string>
|
||||||
<string name="dialog_generic_button_okay">ОЕ</string>
|
<string name="dialog_generic_button_okay">ОЕ</string>
|
||||||
<string name="dialog_generic_button_cancel">Отмена</string>
|
<string name="dialog_generic_button_cancel">Отмена</string>
|
||||||
<string name="button_resume">Продолжить</string>
|
|
||||||
<string name="button_save">Сохранить</string>
|
<string name="button_save">Сохранить</string>
|
||||||
<string name="button_delete">Очистить</string>
|
<string name="button_delete">Очистить</string>
|
||||||
<string name="snackbar_message_location_permission_denied">Разрешение на определение местоположения не предоставлено. Trackbook работать не будет.</string>
|
<string name="snackbar_message_location_permission_denied">Разрешение на определение местоположения не предоставлено. Trackbook работать не будет.</string>
|
||||||
|
@ -67,8 +66,6 @@
|
||||||
<string name="pref_gps_only_summary_gps_and_network">В настоящее время для локализации используется GPS и сеть.</string>
|
<string name="pref_gps_only_summary_gps_and_network">В настоящее время для локализации используется GPS и сеть.</string>
|
||||||
<string name="pref_imperial_measurement_units_summary_metric">В настоящее время используются метрические единицы (километр, метр).</string>
|
<string name="pref_imperial_measurement_units_summary_metric">В настоящее время используются метрические единицы (километр, метр).</string>
|
||||||
<string name="pref_gps_only_summary_gps_only">В настоящее время для локализации используется только GPS.</string>
|
<string name="pref_gps_only_summary_gps_only">В настоящее время для локализации используется только GPS.</string>
|
||||||
<string name="pref_recording_accuracy_title">Точность записи</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Путевые точки имеют более низкую точность, но встречаются чаще.</string>
|
|
||||||
<string name="pref_reset_advanced_summary">Сброс расширенных настроек до значений по умолчанию.</string>
|
<string name="pref_reset_advanced_summary">Сброс расширенных настроек до значений по умолчанию.</string>
|
||||||
<string name="pref_report_issue_title">Выпуск отчета</string>
|
<string name="pref_report_issue_title">Выпуск отчета</string>
|
||||||
<string name="pref_theme_selection_mode_dark">Темный режим</string>
|
<string name="pref_theme_selection_mode_dark">Темный режим</string>
|
||||||
|
@ -81,7 +78,6 @@
|
||||||
<string name="abbreviation_seconds">сек</string>
|
<string name="abbreviation_seconds">сек</string>
|
||||||
<string name="abbreviation_minutes">мин</string>
|
<string name="abbreviation_minutes">мин</string>
|
||||||
<string name="pref_report_issue_summary">Сообщайте об ошибках и предлагайте улучшения на GitHub.</string>
|
<string name="pref_report_issue_summary">Сообщайте об ошибках и предлагайте улучшения на GitHub.</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Путевые точки имеют более высокую точность, но встречаются реже.</string>
|
|
||||||
<string name="pref_imperial_measurement_units_title">Используйте имперские меры</string>
|
<string name="pref_imperial_measurement_units_title">Используйте имперские меры</string>
|
||||||
<string name="pref_imperial_measurement_units_summary_imperial">В настоящее время используются имперские единицы (мили, футы).</string>
|
<string name="pref_imperial_measurement_units_summary_imperial">В настоящее время используются имперские единицы (мили, футы).</string>
|
||||||
<string name="pref_gps_only_title">Ограничение на GPS</string>
|
<string name="pref_gps_only_title">Ограничение на GPS</string>
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
<!-- fab sub menu_bottom_navigation -->
|
<!-- fab sub menu_bottom_navigation -->
|
||||||
<string name="button_delete">Rensa</string>
|
<string name="button_delete">Rensa</string>
|
||||||
<string name="button_save">Spara</string>
|
<string name="button_save">Spara</string>
|
||||||
<string name="button_resume">Återuppta</string>
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="dialog_share_gpx">Dela GPX-fil med</string>
|
<string name="dialog_share_gpx">Dela GPX-fil med</string>
|
||||||
<string name="dialog_error_empty_recording_message">Trackbook spelade inte in några vägpunkter så här långt.</string>
|
<string name="dialog_error_empty_recording_message">Trackbook spelade inte in några vägpunkter så här långt.</string>
|
||||||
|
@ -96,7 +95,6 @@
|
||||||
<string name="descr_statistics_sheet_edit_button">Spårredigeringsknapp</string>
|
<string name="descr_statistics_sheet_edit_button">Spårredigeringsknapp</string>
|
||||||
<string name="pref_reset_advanced_title">Återställ</string>
|
<string name="pref_reset_advanced_title">Återställ</string>
|
||||||
<string name="toast_message_elevation_info">Tips: Noggrannheten hos höjddata beror på din enhet. Höjden i upp- och nedförsbacke för hela rutten mäts.</string>
|
<string name="toast_message_elevation_info">Tips: Noggrannheten hos höjddata beror på din enhet. Höjden i upp- och nedförsbacke för hela rutten mäts.</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">Vägpunkter har högre noggrannhet men är mindre frekventa.</string>
|
|
||||||
<string name="pref_imperial_measurement_units_summary_metric">För närvarande används metriska enheter (kilometer, meter).</string>
|
<string name="pref_imperial_measurement_units_summary_metric">För närvarande används metriska enheter (kilometer, meter).</string>
|
||||||
<string name="pref_advanced_title">Avancerad</string>
|
<string name="pref_advanced_title">Avancerad</string>
|
||||||
<string name="pref_accuracy_threshold_title">Tröskel för noggrannhet</string>
|
<string name="pref_accuracy_threshold_title">Tröskel för noggrannhet</string>
|
||||||
|
@ -115,7 +113,6 @@
|
||||||
<string name="pref_reset_advanced_summary">Återställ avancerade inställningar till standardinställningarna.</string>
|
<string name="pref_reset_advanced_summary">Återställ avancerade inställningar till standardinställningarna.</string>
|
||||||
<string name="pref_report_issue_title">Rapportera frågan</string>
|
<string name="pref_report_issue_title">Rapportera frågan</string>
|
||||||
<string name="pref_report_issue_summary">Rapportera fel och föreslå förbättringar på GitHub.</string>
|
<string name="pref_report_issue_summary">Rapportera fel och föreslå förbättringar på GitHub.</string>
|
||||||
<string name="pref_recording_accuracy_title">Noggrannhet vid inspelning</string>
|
|
||||||
<string name="track_list_onboarding_h1_part_2">... kommer att visas här.</string>
|
<string name="track_list_onboarding_h1_part_2">... kommer att visas här.</string>
|
||||||
<string name="track_list_onboarding_h1_part_1">Dina inspelade spår</string>
|
<string name="track_list_onboarding_h1_part_1">Dina inspelade spår</string>
|
||||||
<string name="layout_onboarding_description_app_icon">Ikon för Trackbook-appen</string>
|
<string name="layout_onboarding_description_app_icon">Ikon för Trackbook-appen</string>
|
||||||
|
@ -156,7 +153,6 @@
|
||||||
<string name="descr_quick_settings_tile_title_pause">Stoppa inspelningen</string>
|
<string name="descr_quick_settings_tile_title_pause">Stoppa inspelningen</string>
|
||||||
<string name="descr_mark_starred_button">Markera som stjärnmärkt</string>
|
<string name="descr_mark_starred_button">Markera som stjärnmärkt</string>
|
||||||
<string name="descr_button_resume">Återuppta inspelning</string>
|
<string name="descr_button_resume">Återuppta inspelning</string>
|
||||||
<string name="pref_recording_accuracy_summary_default">Vägpunkter har lägre noggrannhet men är mer frekventa.</string>
|
|
||||||
<string name="statistics_sheet_p_duration">Total varaktighet:</string>
|
<string name="statistics_sheet_p_duration">Total varaktighet:</string>
|
||||||
<string name="pref_gps_only_summary_gps_only">För närvarande används endast GPS för lokalisering.</string>
|
<string name="pref_gps_only_summary_gps_only">För närvarande används endast GPS för lokalisering.</string>
|
||||||
<string name="pref_delete_non_starred_summary">Ta bort alla inspelningar i \"Tracks\" som inte har stjärnor.</string>
|
<string name="pref_delete_non_starred_summary">Ta bort alla inspelningar i \"Tracks\" som inte har stjärnor.</string>
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
<string name="descr_mark_starred_button">Yıldızlı olarak işaretle düğmesi</string>
|
<string name="descr_mark_starred_button">Yıldızlı olarak işaretle düğmesi</string>
|
||||||
<string name="descr_button_resume">Kaydı devam ettir</string>
|
<string name="descr_button_resume">Kaydı devam ettir</string>
|
||||||
<string name="notification_resume">Devam ettir</string>
|
<string name="notification_resume">Devam ettir</string>
|
||||||
<string name="button_resume">Devam ettir</string>
|
|
||||||
<string name="descr_button_delete">Kaydı temizle</string>
|
<string name="descr_button_delete">Kaydı temizle</string>
|
||||||
<string name="descr_button_save">Kaydı kaydet</string>
|
<string name="descr_button_save">Kaydı kaydet</string>
|
||||||
<string name="descr_button_start">Kaydı başlat düğmesi</string>
|
<string name="descr_button_start">Kaydı başlat düğmesi</string>
|
||||||
|
@ -96,9 +95,6 @@
|
||||||
<string name="tab_tracks">Yollar</string>
|
<string name="tab_tracks">Yollar</string>
|
||||||
<string name="tab_map">Harita</string>
|
<string name="tab_map">Harita</string>
|
||||||
<string name="app_name">Trackbook</string>
|
<string name="app_name">Trackbook</string>
|
||||||
<string name="pref_recording_accuracy_title">Kayıt Doğruluğu</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">Ara noktalar daha düşük doğruluğa sahiptir ancak daha sıktır.</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_high">Ara noktalar daha yüksek doğruluğa sahiptir ancak daha az sıklıktadır.</string>
|
|
||||||
<string name="track_list_p_element_statistics">Kaydedilen Toplam Mesafe</string>
|
<string name="track_list_p_element_statistics">Kaydedilen Toplam Mesafe</string>
|
||||||
<string name="descr_button_location">Geçerli konuma ortala</string>
|
<string name="descr_button_location">Geçerli konuma ortala</string>
|
||||||
</resources>
|
</resources>
|
|
@ -21,7 +21,6 @@
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<string name="button_delete">删除</string>
|
<string name="button_delete">删除</string>
|
||||||
<string name="button_pause">暂停</string>
|
<string name="button_pause">暂停</string>
|
||||||
<string name="button_resume">恢复</string>
|
|
||||||
<string name="button_save">保存</string>
|
<string name="button_save">保存</string>
|
||||||
<string name="button_start">开始</string>
|
<string name="button_start">开始</string>
|
||||||
<!-- Dialogs -->
|
<!-- Dialogs -->
|
||||||
|
@ -92,9 +91,6 @@
|
||||||
<string name="pref_imperial_measurement_units_summary_metric">当前正使用公制单位(千米,米)。</string>
|
<string name="pref_imperial_measurement_units_summary_metric">当前正使用公制单位(千米,米)。</string>
|
||||||
<string name="pref_imperial_measurement_units_summary_imperial">当前正使用英制单位(英里,英尺)。</string>
|
<string name="pref_imperial_measurement_units_summary_imperial">当前正使用英制单位(英里,英尺)。</string>
|
||||||
<string name="pref_imperial_measurement_units_title">使用英制测量</string>
|
<string name="pref_imperial_measurement_units_title">使用英制测量</string>
|
||||||
<string name="pref_recording_accuracy_summary_high">航点的精确度较高,但频率较低。</string>
|
|
||||||
<string name="pref_recording_accuracy_summary_default">航点的精确度较低,但频率较高。</string>
|
|
||||||
<string name="pref_recording_accuracy_title">记录精确度</string>
|
|
||||||
<string name="pref_report_issue_summary">在 Github 上报告漏洞并提出改进建议。</string>
|
<string name="pref_report_issue_summary">在 Github 上报告漏洞并提出改进建议。</string>
|
||||||
<string name="pref_report_issue_title">报告问题</string>
|
<string name="pref_report_issue_title">报告问题</string>
|
||||||
<string name="pref_reset_advanced_summary">重置高级设置为默认值。</string>
|
<string name="pref_reset_advanced_summary">重置高级设置为默认值。</string>
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
<color name="default_red">#FFDC3D33</color> <!-- Slightly muted variant of -> Material Design 2: Red 600 -->
|
<color name="default_red">#FFDC3D33</color> <!-- Slightly muted variant of -> Material Design 2: Red 600 -->
|
||||||
<color name="default_red_dark">#FFCA2D23</color>
|
<color name="default_red_dark">#FFCA2D23</color>
|
||||||
<color name="default_blue">#FF3C98DB</color>
|
<color name="default_blue">#FF3C98DB</color>
|
||||||
|
<color name="homepoint">#FFFFC107</color>
|
||||||
<color name="default_green">#FF4CAF50</color>
|
<color name="default_green">#FF4CAF50</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- App Name -->
|
<!-- App Name -->
|
||||||
<string name="app_name">Trackbook-v</string>
|
<string name="app_name">trkpt</string>
|
||||||
<!-- please do not translate app_name - transcription into different alphabet types is fine though -->
|
<!-- please do not translate app_name - transcription into different alphabet types is fine though -->
|
||||||
<string name="app_version_name" translatable="false">\"See Emily Play\"</string>
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<string name="tab_map">Map</string>
|
<string name="tab_map">Map</string>
|
||||||
<string name="tab_tracks">Tracks</string>
|
<string name="tab_tracks">Tracks</string>
|
||||||
<string name="tab_settings">Settings</string>
|
<string name="tab_settings">Settings</string>
|
||||||
<!-- Notification -->
|
<!-- Notification -->
|
||||||
<string name="notification_title_trackbook_running">Trackbook running</string>
|
<string name="notification_title_trackbook_running">Trackbook running</string>
|
||||||
<string name="notification_title_trackbook_not_running">Trackbook not running</string>
|
<string name="notification_title_trackbook_not_running">Trackbook stopped</string>
|
||||||
<string name="notification_pause">Pause</string>
|
<string name="notification_pause">Stop</string>
|
||||||
<string name="notification_resume">Resume</string>
|
<string name="notification_resume">Record</string>
|
||||||
<string name="notification_show">Show</string>
|
<string name="notification_show">Show</string>
|
||||||
<string name="notification_channel_recording_name">Movement Recording State</string>
|
<string name="notification_channel_recording_name">Movement Recording State</string>
|
||||||
<string name="notification_channel_recording_description">Display duration and distance. Option to pause movement recording.</string>
|
<string name="notification_channel_recording_description">Display duration and distance. Option to pause movement recording.</string>
|
||||||
|
@ -21,10 +20,9 @@
|
||||||
<string name="snackbar_message_location_permission_denied">Location permission not granted. Trackbook will not work.</string>
|
<string name="snackbar_message_location_permission_denied">Location permission not granted. Trackbook will not work.</string>
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<string name="button_delete">Delete</string>
|
<string name="button_delete">Delete</string>
|
||||||
<string name="button_pause">Pause</string>
|
<string name="button_pause">Stop</string>
|
||||||
<string name="button_resume">Resume</string>
|
|
||||||
<string name="button_save">Save</string>
|
<string name="button_save">Save</string>
|
||||||
<string name="button_start">Start</string>
|
<string name="button_start">Record</string>
|
||||||
<!-- Dialogs -->
|
<!-- Dialogs -->
|
||||||
<string name="dialog_delete_current_recording_message">Discard current recording?</string>
|
<string name="dialog_delete_current_recording_message">Discard current recording?</string>
|
||||||
<string name="dialog_delete_current_recording_button_discard">Discard</string>
|
<string name="dialog_delete_current_recording_button_discard">Discard</string>
|
||||||
|
@ -86,7 +84,9 @@
|
||||||
<string name="pref_altitude_smoothing_value_summary" translatable="false">Number of waypoints used to smooth the elevation curve.</string>
|
<string name="pref_altitude_smoothing_value_summary" translatable="false">Number of waypoints used to smooth the elevation curve.</string>
|
||||||
<string name="pref_altitude_smoothing_value_title" translatable="false">Altitude Smoothing</string>
|
<string name="pref_altitude_smoothing_value_title" translatable="false">Altitude Smoothing</string>
|
||||||
<string name="pref_auto_export_interval_summary">Automatically export GPX file after this many hours.</string>
|
<string name="pref_auto_export_interval_summary">Automatically export GPX file after this many hours.</string>
|
||||||
|
<string name="pref_device_id_summary">A unique ID to distinguish tracks recorded across multiple devices.</string>
|
||||||
<string name="pref_auto_export_interval_title">Auto Export Interval</string>
|
<string name="pref_auto_export_interval_title">Auto Export Interval</string>
|
||||||
|
<string name="pref_device_id">Device ID</string>
|
||||||
<string name="pref_advanced_title">Advanced</string>
|
<string name="pref_advanced_title">Advanced</string>
|
||||||
<string name="pref_delete_non_starred_summary">Delete all recordings in \"Tracks\" that are not starred.</string>
|
<string name="pref_delete_non_starred_summary">Delete all recordings in \"Tracks\" that are not starred.</string>
|
||||||
<string name="pref_delete_non_starred_title">Delete Non-Starred Recordings</string>
|
<string name="pref_delete_non_starred_title">Delete Non-Starred Recordings</string>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<!-- Descriptions -->
|
<!-- Descriptions -->
|
||||||
<string name="descr_button_delete">Discard recording</string>
|
<string name="descr_button_delete">Discard recording</string>
|
||||||
<string name="descr_button_location">Center on current location</string>
|
<string name="descr_button_location">Center on current location</string>
|
||||||
<string name="descr_button_pause">Pause recording</string>
|
<string name="descr_button_pause">Stop recording</string>
|
||||||
<string name="descr_button_resume">Resume recording</string>
|
<string name="descr_button_resume">Resume recording</string>
|
||||||
<string name="descr_button_save">Save recording</string>
|
<string name="descr_button_save">Save recording</string>
|
||||||
<string name="descr_button_start">Start recording</string>
|
<string name="descr_button_start">Start recording</string>
|
||||||
|
|
Loading…
Reference in a new issue