Was experimenting with automatic GPX export. Hardcoded path.
These changes are several months old, I am just now committing them because I want to move on to a different experiment.
This commit is contained in:
parent
edcb149ac7
commit
7956f44ce4
15 changed files with 204 additions and 120 deletions
|
@ -2,6 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.y20k.trackbook">
|
package="org.y20k.trackbook">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES -->
|
<!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES -->
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
|
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
|
||||||
<uses-feature android:name="android.hardware.location.network" />
|
<uses-feature android:name="android.hardware.location.network" />
|
||||||
|
@ -17,13 +19,15 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<application
|
<application
|
||||||
android:name=".Trackbook"
|
android:name=".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"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<!-- MAIN ACTIVITY -->
|
<!-- MAIN ACTIVITY -->
|
||||||
|
|
|
@ -57,6 +57,7 @@ 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_ALTITUDE_SMOOTHING_VALUE: String = "prefAltitudeSmoothingValue"
|
const val PREF_ALTITUDE_SMOOTHING_VALUE: String = "prefAltitudeSmoothingValue"
|
||||||
const val PREF_LOCATION_ACCURACY_THRESHOLD: String = "prefLocationAccuracyThreshold"
|
const val PREF_LOCATION_ACCURACY_THRESHOLD: String = "prefLocationAccuracyThreshold"
|
||||||
const val PREF_LOCATION_AGE_THRESHOLD: String = "prefLocationAgeThreshold"
|
const val PREF_LOCATION_AGE_THRESHOLD: String = "prefLocationAgeThreshold"
|
||||||
|
@ -99,22 +100,25 @@ object Keys {
|
||||||
// default values
|
// default values
|
||||||
val DEFAULT_DATE: Date = Date(0L)
|
val DEFAULT_DATE: Date = Date(0L)
|
||||||
const val DEFAULT_RFC2822_DATE: String = "Thu, 01 Jan 1970 01:00:00 +0100" // --> Date(0)
|
const val DEFAULT_RFC2822_DATE: String = "Thu, 01 Jan 1970 01:00:00 +0100" // --> Date(0)
|
||||||
const val ONE_HOUR_IN_MILLISECONDS: Int = 3600000
|
const val ONE_SECOND_IN_MILLISECONDS: Long = 1000
|
||||||
|
const val ONE_MINUTE_IN_MILLISECONDS: Long = 60 * ONE_SECOND_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 = 1000L // 1 second in milliseconds
|
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 1000L // 1 second in milliseconds
|
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val SAVE_TEMP_TRACK_INTERVAL: Long = 9000L // 9 seconds in milliseconds
|
const val SAVE_TEMP_TRACK_INTERVAL: Long = 30 * ONE_SECOND_IN_MILLISECONDS
|
||||||
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 120000L // 2 minutes in milliseconds
|
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 2 * ONE_MINUTE_IN_MILLISECONDS
|
||||||
const val STOP_OVER_THRESHOLD: Long = 300000L // 5 minutes 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
|
||||||
const val DEFAULT_LONGITUDE: Double = 25.784444 // longitude Nordkapp, Norway
|
const val DEFAULT_LONGITUDE: Double = 25.784444 // longitude Nordkapp, Norway
|
||||||
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
||||||
const val DEFAULT_ALTITUDE: Double = 0.0
|
const val DEFAULT_ALTITUDE: Double = 0.0
|
||||||
const val DEFAULT_TIME: Long = 0L
|
const val DEFAULT_TIME: Long = 0L
|
||||||
|
const val DEFAULT_AUTO_EXPORT_INTERVAL: Int = 24
|
||||||
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 = 60000000000L // 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 MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION: Int = 5
|
||||||
|
|
|
@ -17,12 +17,16 @@
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.VmPolicy
|
import android.os.StrictMode.VmPolicy
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
@ -31,6 +35,35 @@ 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
|
||||||
|
|
||||||
|
private const val REQUEST_EXTERNAL_STORAGE = 1
|
||||||
|
private val PERMISSIONS_STORAGE = arrayOf<String>(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the app has permission to write to device storage
|
||||||
|
*
|
||||||
|
* If the app does not has permission then the user will be prompted to grant permissions
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
*/
|
||||||
|
fun verifyStoragePermissions(activity: Activity?)
|
||||||
|
{
|
||||||
|
// Check if we have write permission
|
||||||
|
val permission = ActivityCompat.checkSelfPermission(activity!!,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
if (permission != PackageManager.PERMISSION_GRANTED)
|
||||||
|
{
|
||||||
|
// We don't have permission so prompt the user
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
PERMISSIONS_STORAGE,
|
||||||
|
REQUEST_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MainActivity class
|
* MainActivity class
|
||||||
*/
|
*/
|
||||||
|
@ -48,7 +81,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
/* Overrides onCreate from AppCompatActivity */
|
/* Overrides onCreate from AppCompatActivity */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
verifyStoragePermissions(this)
|
||||||
// todo: remove after testing finished
|
// todo: remove after testing finished
|
||||||
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
StrictMode.setVmPolicy(
|
StrictMode.setVmPolicy(
|
||||||
|
|
|
@ -275,7 +275,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
private fun handleTrackingManagementMenu() {
|
private fun handleTrackingManagementMenu() {
|
||||||
when (trackingState) {
|
when (trackingState) {
|
||||||
Keys.STATE_TRACKING_PAUSED -> resumeTracking()
|
Keys.STATE_TRACKING_PAUSED -> resumeTracking()
|
||||||
Keys.STATE_TRACKING_ACTIVE -> trackerService.stopTracking()
|
Keys.STATE_TRACKING_ACTIVE -> trackerService.pauseTracking()
|
||||||
Keys.STATE_TRACKING_NOT_STARTED -> startTracking()
|
Keys.STATE_TRACKING_NOT_STARTED -> startTracking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,9 +296,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CoroutineScope(IO).launch {
|
CoroutineScope(IO).launch {
|
||||||
track.save_json(activity as Context)
|
trackerService.saveTrackAndClear(activity as Context)
|
||||||
track.save_gpx(activity as Context)
|
|
||||||
trackerService.clearTrack()
|
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
// step 4: open track in TrackFragement
|
// step 4: open track in TrackFragement
|
||||||
openTrack(track)
|
openTrack(track)
|
||||||
|
|
|
@ -123,6 +123,16 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
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)
|
||||||
|
|
||||||
|
val preferenceAutoExportInterval: SeekBarPreference = SeekBarPreference(activity as Context)
|
||||||
|
preferenceAutoExportInterval.title = getString(R.string.pref_auto_export_interval_title)
|
||||||
|
preferenceAutoExportInterval.setIcon(R.drawable.ic_bar_chart_24)
|
||||||
|
preferenceAutoExportInterval.key = Keys.PREF_AUTO_EXPORT_INTERVAL
|
||||||
|
preferenceAutoExportInterval.summary = getString(R.string.pref_auto_export_interval_summary)
|
||||||
|
preferenceAutoExportInterval.showSeekBarValue = true
|
||||||
|
preferenceAutoExportInterval.min = 1
|
||||||
|
preferenceAutoExportInterval.max = 24
|
||||||
|
preferenceAutoExportInterval.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
||||||
|
|
||||||
// set up "Altitude Smoothing" preference
|
// set up "Altitude Smoothing" preference
|
||||||
// val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context)
|
// val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context)
|
||||||
// preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title)
|
// preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title)
|
||||||
|
@ -190,6 +200,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
screen.addPreference(preferenceCategoryAdvanced)
|
screen.addPreference(preferenceCategoryAdvanced)
|
||||||
screen.addPreference(preferenceOmitRests)
|
screen.addPreference(preferenceOmitRests)
|
||||||
// screen.addPreference(preferenceAltitudeSmoothingValue)
|
// screen.addPreference(preferenceAltitudeSmoothingValue)
|
||||||
|
screen.addPreference(preferenceAutoExportInterval)
|
||||||
screen.addPreference(preferenceResetAdvanced)
|
screen.addPreference(preferenceResetAdvanced)
|
||||||
screen.addPreference(preferenceCategoryAbout)
|
screen.addPreference(preferenceCategoryAbout)
|
||||||
screen.addPreference(preferenceAppVersion)
|
screen.addPreference(preferenceAppVersion)
|
||||||
|
|
|
@ -49,7 +49,7 @@ 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.ui.TrackFragmentLayoutHolder
|
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
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()
|
val sourceUri: Uri = layout.track.get_gpx_file(activity as Context).toUri()
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
@ -145,7 +146,8 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
FileHelper.saveCopyOfFileSuspended(activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri)
|
FileHelper.saveCopyOfFileSuspended(activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri)
|
||||||
}
|
}
|
||||||
Toast.makeText(activity as Context, R.string.toast_message_save_gpx, Toast.LENGTH_LONG).show()
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,6 +213,16 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,12 +58,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
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 currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
||||||
var lastSave: Date = Keys.DEFAULT_DATE
|
var lastTempSave: Date = Keys.DEFAULT_DATE
|
||||||
|
var lastAutoExport: Date = Keys.DEFAULT_DATE
|
||||||
var stepCountOffset: Float = 0f
|
var stepCountOffset: Float = 0f
|
||||||
// The resumed flag will be true for the first point that is received after unpausing a
|
|
||||||
// recording, so that the distance travelled while paused is not added to the track.distance.
|
|
||||||
var resumed: Boolean = false
|
|
||||||
var track: Track = Track()
|
var track: Track = Track()
|
||||||
var gpsLocationListenerRegistered: Boolean = false
|
var gpsLocationListenerRegistered: Boolean = false
|
||||||
var networkLocationListenerRegistered: Boolean = false
|
var networkLocationListenerRegistered: Boolean = false
|
||||||
|
@ -152,7 +151,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
fun clearTrack()
|
fun clearTrack()
|
||||||
{
|
{
|
||||||
track = Track()
|
track = Track()
|
||||||
resumed = false
|
stepCountOffset = 0f
|
||||||
FileHelper.delete_temp_file(this as Context)
|
FileHelper.delete_temp_file(this as Context)
|
||||||
trackingState = Keys.STATE_TRACKING_NOT_STARTED
|
trackingState = Keys.STATE_TRACKING_NOT_STARTED
|
||||||
PreferencesHelper.saveTrackingState(trackingState)
|
PreferencesHelper.saveTrackingState(trackingState)
|
||||||
|
@ -237,6 +236,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
gpsOnly = PreferencesHelper.loadGpsOnly()
|
gpsOnly = PreferencesHelper.loadGpsOnly()
|
||||||
useImperial = PreferencesHelper.loadUseImperialUnits()
|
useImperial = PreferencesHelper.loadUseImperialUnits()
|
||||||
omitRests = PreferencesHelper.loadOmitRests()
|
omitRests = PreferencesHelper.loadOmitRests()
|
||||||
|
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
||||||
|
|
||||||
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
|
||||||
|
@ -259,7 +259,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
LogHelper.i(TAG, "onDestroy called.")
|
LogHelper.i(TAG, "onDestroy called.")
|
||||||
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
||||||
{
|
{
|
||||||
stopTracking()
|
pauseTracking()
|
||||||
}
|
}
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
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
|
||||||
|
@ -305,7 +305,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
else if (intent.action == Keys.ACTION_STOP)
|
else if (intent.action == Keys.ACTION_STOP)
|
||||||
{
|
{
|
||||||
stopTracking()
|
pauseTracking()
|
||||||
}
|
}
|
||||||
else if (intent.action == Keys.ACTION_START)
|
else if (intent.action == Keys.ACTION_START)
|
||||||
{
|
{
|
||||||
|
@ -363,17 +363,31 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
// load temp track - returns an empty track if there is no temp file.
|
// load temp track - returns an empty track if there is no temp file.
|
||||||
track = load_temp_track(this)
|
track = load_temp_track(this)
|
||||||
// try to mark last waypoint as stopover
|
if (track.wayPoints.isNotEmpty()) {
|
||||||
if (track.wayPoints.size > 0) {
|
track.wayPoints.last().isStopOver = true
|
||||||
val lastWayPointIndex = track.wayPoints.size - 1
|
|
||||||
track.wayPoints[lastWayPointIndex].isStopOver = true
|
|
||||||
}
|
}
|
||||||
resumed = true
|
track.resumed = true
|
||||||
// calculate length of recording break
|
track.recordingPaused += (GregorianCalendar.getInstance().time.time - track.recordingStop.time)
|
||||||
track.recordingPaused += TrackHelper.calculateDurationOfPause(track.recordingStop)
|
|
||||||
startTracking(newTrack = false)
|
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)
|
||||||
|
@ -390,7 +404,6 @@ class TrackerService: Service(), SensorEventListener
|
||||||
// set up new track
|
// set up new track
|
||||||
if (newTrack) {
|
if (newTrack) {
|
||||||
track = Track()
|
track = Track()
|
||||||
resumed = false
|
|
||||||
stepCountOffset = 0f
|
stepCountOffset = 0f
|
||||||
}
|
}
|
||||||
trackingState = Keys.STATE_TRACKING_ACTIVE
|
trackingState = Keys.STATE_TRACKING_ACTIVE
|
||||||
|
@ -400,11 +413,10 @@ class TrackerService: Service(), SensorEventListener
|
||||||
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
|
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopTracking()
|
fun pauseTracking()
|
||||||
{
|
{
|
||||||
track.recordingStop = GregorianCalendar.getInstance().time
|
track.recordingStop = GregorianCalendar.getInstance().time
|
||||||
val context: Context = this
|
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
||||||
CoroutineScope(IO).launch { track.save_temp_suspended(context) }
|
|
||||||
|
|
||||||
trackingState = Keys.STATE_TRACKING_PAUSED
|
trackingState = Keys.STATE_TRACKING_PAUSED
|
||||||
PreferencesHelper.saveTrackingState(trackingState)
|
PreferencesHelper.saveTrackingState(trackingState)
|
||||||
|
@ -439,6 +451,9 @@ class TrackerService: Service(), SensorEventListener
|
||||||
Keys.PREF_OMIT_RESTS -> {
|
Keys.PREF_OMIT_RESTS -> {
|
||||||
omitRests = PreferencesHelper.loadOmitRests()
|
omitRests = PreferencesHelper.loadOmitRests()
|
||||||
}
|
}
|
||||||
|
Keys.PREF_AUTO_EXPORT_INTERVAL -> {
|
||||||
|
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -462,9 +477,10 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
override fun run() {
|
override fun run() {
|
||||||
// add waypoint to track - step count is continuously updated in onSensorChanged
|
// add waypoint to track - step count is continuously updated in onSensorChanged
|
||||||
val success = track.add_waypoint(currentBestLocation, omitRests, resumed)
|
val success = track.add_waypoint(currentBestLocation, omitRests, track.resumed)
|
||||||
|
val now: Date = GregorianCalendar.getInstance().time
|
||||||
if (success) {
|
if (success) {
|
||||||
resumed = false
|
track.resumed = false
|
||||||
|
|
||||||
// store previous smoothed altitude
|
// store previous smoothed altitude
|
||||||
val previousAltitude: Double = altitudeValues.getAverage()
|
val previousAltitude: Double = altitudeValues.getAverage()
|
||||||
|
@ -488,11 +504,14 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// save a temp track
|
// save a temp track
|
||||||
val now: Date = GregorianCalendar.getInstance().time
|
if (now.time - lastTempSave.time > Keys.SAVE_TEMP_TRACK_INTERVAL) {
|
||||||
if (now.time - lastSave.time > Keys.SAVE_TEMP_TRACK_INTERVAL) {
|
lastTempSave = now
|
||||||
lastSave = now
|
|
||||||
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (now.time - track.recordingStart.time > (autoExportInterval * Keys.ONE_HOUR_IN_MILLISECONDS)) {
|
||||||
|
saveTrackAndStartNew(this@TrackerService)
|
||||||
}
|
}
|
||||||
// update notification
|
// update notification
|
||||||
displayNotification()
|
displayNotification()
|
||||||
|
|
|
@ -16,23 +16,36 @@
|
||||||
|
|
||||||
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.location.Location
|
import android.location.Location
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.google.gson.annotations.Expose
|
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.R
|
||||||
|
import org.y20k.trackbook.helpers.DateTimeHelper
|
||||||
|
import org.y20k.trackbook.helpers.FileHelper
|
||||||
|
import org.y20k.trackbook.helpers.LocationHelper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
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
|
import kotlin.random.Random
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import org.y20k.trackbook.Keys
|
|
||||||
import org.y20k.trackbook.helpers.DateTimeHelper
|
|
||||||
import org.y20k.trackbook.helpers.FileHelper
|
|
||||||
import org.y20k.trackbook.helpers.LocationHelper
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Track data class
|
* Track data class
|
||||||
|
@ -50,6 +63,9 @@ data class Track (
|
||||||
@Expose var recordingStart: Date = GregorianCalendar.getInstance().time,
|
@Expose var recordingStart: Date = GregorianCalendar.getInstance().time,
|
||||||
@Expose var dateString: String = DateTimeHelper.convertToReadableDate(recordingStart),
|
@Expose var dateString: String = DateTimeHelper.convertToReadableDate(recordingStart),
|
||||||
@Expose var recordingStop: Date = recordingStart,
|
@Expose var recordingStop: Date = recordingStart,
|
||||||
|
// The resumed flag will be true for the first point that is received after unpausing a
|
||||||
|
// 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 maxAltitude: Double = 0.0,
|
||||||
@Expose var minAltitude: Double = 0.0,
|
@Expose var minAltitude: Double = 0.0,
|
||||||
@Expose var positiveElevation: Double = 0.0,
|
@Expose var positiveElevation: Double = 0.0,
|
||||||
|
@ -165,22 +181,29 @@ data class Track (
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_GPX), basename)
|
return File(context.getExternalFilesDir(Keys.FOLDER_GPX), basename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun get_export_gpx_file(context: Context): File
|
||||||
|
{
|
||||||
|
val basename: String = DateTimeHelper.convertToSortableDateString(this.recordingStart) + Keys.GPX_FILE_EXTENSION
|
||||||
|
return File(File("/storage/emulated/0/Syncthing/GPX"), basename)
|
||||||
|
}
|
||||||
|
|
||||||
fun get_json_file(context: Context): File
|
fun get_json_file(context: Context): File
|
||||||
{
|
{
|
||||||
val basename: String = this.id.toString() + Keys.TRACKBOOK_FILE_EXTENSION
|
val basename: String = this.id.toString() + Keys.TRACKBOOK_FILE_EXTENSION
|
||||||
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), basename)
|
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), basename)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save_both(context: Context)
|
fun save_all_files(context: Context)
|
||||||
{
|
{
|
||||||
this.save_json(context)
|
this.save_json(context)
|
||||||
this.save_gpx(context)
|
this.save_gpx(context)
|
||||||
|
this.save_export_gpx(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun save_both_suspended(context: Context)
|
suspend fun save_all_files_suspended(context: Context)
|
||||||
{
|
{
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
cont.resume(this.save_both(context))
|
cont.resume(this.save_all_files(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +221,23 @@ data class Track (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
fun save_json(context: Context)
|
||||||
{
|
{
|
||||||
val json: String = this.to_json()
|
val json: String = this.to_json()
|
||||||
|
|
|
@ -55,7 +55,8 @@ object FileHelper {
|
||||||
suspend fun renameTrackSuspended(context: Context, track: Track, newName: String) {
|
suspend fun renameTrackSuspended(context: Context, track: Track, newName: String) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
track.name = newName
|
track.name = newName
|
||||||
track.save_both(context)
|
track.save_json(context)
|
||||||
|
track.save_gpx(context)
|
||||||
cont.resume(Unit)
|
cont.resume(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +72,7 @@ object FileHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Copies file to specified target */
|
/* Copies file to specified target */
|
||||||
private fun copyFile(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
fun copyFile(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
|
||||||
val inputStream = context.contentResolver.openInputStream(originalFileUri)
|
val inputStream = context.contentResolver.openInputStream(originalFileUri)
|
||||||
val outputStream = context.contentResolver.openOutputStream(targetFileUri)
|
val outputStream = context.contentResolver.openOutputStream(targetFileUri)
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
|
|
|
@ -71,9 +71,11 @@ object PreferencesHelper {
|
||||||
return sharedPreferences.getBoolean(Keys.PREF_OMIT_RESTS, true)
|
return sharedPreferences.getBoolean(Keys.PREF_OMIT_RESTS, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /* Load altitude smoothing value */
|
fun loadAutoExportInterval(): Int {
|
||||||
|
return sharedPreferences.getInt(Keys.PREF_AUTO_EXPORT_INTERVAL, Keys.DEFAULT_AUTO_EXPORT_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
// fun loadAltitudeSmoothingValue(): Int {
|
// fun loadAltitudeSmoothingValue(): Int {
|
||||||
// // load current setting
|
|
||||||
// return sharedPreferences.getInt(Keys.PREF_ALTITUDE_SMOOTHING_VALUE, Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
// return sharedPreferences.getInt(Keys.PREF_ALTITUDE_SMOOTHING_VALUE, Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,6 @@ object TrackHelper {
|
||||||
|
|
||||||
/* Adds given locatiom as waypoint to track */
|
/* Adds given locatiom as waypoint to track */
|
||||||
|
|
||||||
|
|
||||||
/* Calculates time passed since last stop of recording */
|
|
||||||
fun calculateDurationOfPause(recordingStop: Date): Long = GregorianCalendar.getInstance().time.time - recordingStop.time
|
|
||||||
|
|
||||||
/* 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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -104,7 +104,6 @@ object UiHelper {
|
||||||
|
|
||||||
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||||
// disable swipe for statistics element
|
// disable swipe for statistics element
|
||||||
if (viewHolder is TracklistAdapter.ElementStatisticsViewHolder) return 0
|
|
||||||
return super.getSwipeDirs(recyclerView, viewHolder)
|
return super.getSwipeDirs(recyclerView, viewHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,64 +75,39 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
|
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
|
||||||
{
|
{
|
||||||
when (viewType) {
|
val v = LayoutInflater.from(parent.context).inflate(R.layout.element_track, parent, false)
|
||||||
Keys.VIEW_TYPE_STATISTICS -> {
|
return ElementTrackViewHolder(v)
|
||||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.element_statistics, parent, false)
|
|
||||||
return ElementStatisticsViewHolder(v)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.element_track, parent, false)
|
|
||||||
return ElementTrackViewHolder(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemViewType */
|
/* Overrides getItemViewType */
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
if (position == 0) {
|
return Keys.VIEW_TYPE_TRACK
|
||||||
return Keys.VIEW_TYPE_STATISTICS
|
|
||||||
} else {
|
|
||||||
return Keys.VIEW_TYPE_TRACK
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemCount from RecyclerView.Adapter */
|
/* Overrides getItemCount from RecyclerView.Adapter */
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
// +1 because of the total statistics element
|
return tracklist.tracks.size
|
||||||
return tracklist.tracks.size + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
|
||||||
{
|
{
|
||||||
when (holder)
|
val positionInTracklist: Int = position
|
||||||
{
|
val elementTrackViewHolder: ElementTrackViewHolder = holder as ElementTrackViewHolder
|
||||||
// CASE STATISTICS ELEMENT
|
elementTrackViewHolder.trackNameView.text = tracklist.tracks[positionInTracklist].name
|
||||||
is ElementStatisticsViewHolder -> {
|
elementTrackViewHolder.trackDataView.text = createTrackDataString(positionInTracklist)
|
||||||
val elementStatisticsViewHolder: ElementStatisticsViewHolder = holder
|
when (tracklist.tracks[positionInTracklist].starred) {
|
||||||
elementStatisticsViewHolder.totalDistanceView.text = LengthUnitHelper.convertDistanceToString(tracklist.get_total_distance(), useImperial)
|
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))
|
||||||
|
}
|
||||||
// CASE TRACK ELEMENT
|
elementTrackViewHolder.trackElement.setOnClickListener {
|
||||||
is ElementTrackViewHolder -> {
|
tracklistListener.onTrackElementTapped(tracklist.tracks[positionInTracklist])
|
||||||
val positionInTracklist: Int = position - 1 // Element 0 is the statistics element.
|
}
|
||||||
val elementTrackViewHolder: ElementTrackViewHolder = holder
|
elementTrackViewHolder.starButton.setOnClickListener {
|
||||||
elementTrackViewHolder.trackNameView.text = tracklist.tracks[positionInTracklist].name
|
toggleStarred(it, 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 {
|
|
||||||
tracklistListener.onTrackElementTapped(tracklist.tracks[positionInTracklist])
|
|
||||||
}
|
|
||||||
elementTrackViewHolder.starButton.setOnClickListener {
|
|
||||||
toggleStarred(it, positionInTracklist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,18 +115,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
|
||||||
{
|
{
|
||||||
// Minus 1 because first position is always the statistics element
|
return tracklist.tracks[positionInRecyclerView].name
|
||||||
return tracklist.tracks[positionInRecyclerView - 1].name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete_track_at_position(context: Context, ui_index: Int)
|
fun delete_track_at_position(context: Context, index: Int)
|
||||||
{
|
{
|
||||||
val track_index = ui_index - 1 // position 0 is the statistics element
|
val track = tracklist.tracks[index]
|
||||||
val track = tracklist.tracks[track_index]
|
|
||||||
track.delete(context)
|
track.delete(context)
|
||||||
tracklist.tracks.remove(track)
|
tracklist.tracks.remove(track)
|
||||||
notifyItemChanged(0)
|
notifyItemRemoved(index)
|
||||||
notifyItemRemoved(ui_index)
|
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)
|
||||||
|
@ -167,10 +140,12 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete_track_at_position(context, index + 1)
|
tracklist.tracks[index].delete(context)
|
||||||
|
tracklist.tracks.removeAt(index)
|
||||||
|
notifyItemRemoved(index)
|
||||||
|
notifyItemRangeChanged(index, this.itemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns if the adapter is empty */
|
|
||||||
fun isEmpty(): Boolean {
|
fun isEmpty(): Boolean {
|
||||||
return tracklist.tracks.size == 0
|
return tracklist.tracks.size == 0
|
||||||
}
|
}
|
||||||
|
@ -249,16 +224,4 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
||||||
/*
|
/*
|
||||||
* End of inner class
|
* End of inner class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Inner class: ViewHolder for a statistics element
|
|
||||||
*/
|
|
||||||
inner class ElementStatisticsViewHolder (elementStatisticsLayout: View): RecyclerView.ViewHolder(elementStatisticsLayout) {
|
|
||||||
val totalDistanceView: TextView = elementStatisticsLayout.findViewById(R.id.total_distance_data)
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* End of inner class
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ data class TrackFragmentLayoutHolder(private var context: Context, private var m
|
||||||
mapView.overlays.add(trackSpecialMarkersOverlay)
|
mapView.overlays.add(trackSpecialMarkersOverlay)
|
||||||
}
|
}
|
||||||
// save track
|
// save track
|
||||||
CoroutineScope(Dispatchers.IO).launch { track.save_both_suspended(context) }
|
CoroutineScope(Dispatchers.IO).launch { track.save_all_files_suspended(context) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,8 @@
|
||||||
<string name="pref_accuracy_threshold_title">Accuracy Threshold</string>
|
<string name="pref_accuracy_threshold_title">Accuracy Threshold</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_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_title">Auto Export Interval</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>
|
||||||
|
@ -98,7 +100,7 @@
|
||||||
<string name="pref_imperial_measurement_units_title">Use Imperial Measurements</string>
|
<string name="pref_imperial_measurement_units_title">Use Imperial Measurements</string>
|
||||||
<string name="pref_omit_rests_on">Waypoints will not be recorded if they are too close to the previous waypoint.</string>
|
<string name="pref_omit_rests_on">Waypoints will not be recorded if they are too close to the previous waypoint.</string>
|
||||||
<string name="pref_omit_rests_off">All waypoints will be recorded, even while standing still.</string>
|
<string name="pref_omit_rests_off">All waypoints will be recorded, even while standing still.</string>
|
||||||
<string name="pref_omit_rests_title">Omit points during rests</string>
|
<string name="pref_omit_rests_title">Omit repeated points</string>
|
||||||
<string name="pref_report_issue_summary">Report bugs and suggest improvements on GitHub.</string>
|
<string name="pref_report_issue_summary">Report bugs and suggest improvements on GitHub.</string>
|
||||||
<string name="pref_report_issue_title">Report Issue</string>
|
<string name="pref_report_issue_title">Report Issue</string>
|
||||||
<string name="pref_reset_advanced_summary">Reset advanced settings to defaults.</string>
|
<string name="pref_reset_advanced_summary">Reset advanced settings to defaults.</string>
|
||||||
|
|
Loading…
Reference in a new issue