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"
|
||||
package="org.y20k.trackbook">
|
||||
|
||||
|
||||
|
||||
<!-- 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.network" />
|
||||
|
@ -17,13 +19,15 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<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
|
||||
android:name=".Trackbook"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<!-- MAIN ACTIVITY -->
|
||||
|
|
|
@ -57,6 +57,7 @@ object Keys {
|
|||
const val PREF_USE_IMPERIAL_UNITS: String = "prefUseImperialUnits"
|
||||
const val PREF_GPS_ONLY: String = "prefGpsOnly"
|
||||
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_LOCATION_ACCURACY_THRESHOLD: String = "prefLocationAccuracyThreshold"
|
||||
const val PREF_LOCATION_AGE_THRESHOLD: String = "prefLocationAgeThreshold"
|
||||
|
@ -99,22 +100,25 @@ object Keys {
|
|||
// default values
|
||||
val DEFAULT_DATE: Date = Date(0L)
|
||||
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 REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1000L // 1 second in milliseconds
|
||||
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 1000L // 1 second in milliseconds
|
||||
const val SAVE_TEMP_TRACK_INTERVAL: Long = 9000L // 9 seconds in milliseconds
|
||||
const val SIGNIFICANT_TIME_DIFFERENCE: Long = 120000L // 2 minutes in milliseconds
|
||||
const val STOP_OVER_THRESHOLD: Long = 300000L // 5 minutes in milliseconds
|
||||
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
|
||||
const val ADD_WAYPOINT_TO_TRACK_INTERVAL: Long = 1 * 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 STOP_OVER_THRESHOLD: Long = 5 * ONE_MINUTE_IN_MILLISECONDS
|
||||
const val IMPLAUSIBLE_TRACK_START_SPEED: Double = 250.0 // 250 km/h
|
||||
const val DEFAULT_LATITUDE: Double = 71.172500 // latitude Nordkapp, Norway
|
||||
const val DEFAULT_LONGITUDE: Double = 25.784444 // longitude Nordkapp, Norway
|
||||
const val DEFAULT_ACCURACY: Float = 300f // in meters
|
||||
const val DEFAULT_ALTITUDE: Double = 0.0
|
||||
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_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_ZOOM_LEVEL: Double = 16.0
|
||||
const val MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION: Int = 5
|
||||
|
|
|
@ -17,12 +17,16 @@
|
|||
|
||||
package org.y20k.trackbook
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
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.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
|
||||
*/
|
||||
|
@ -48,7 +81,7 @@ class MainActivity : AppCompatActivity() {
|
|||
/* Overrides onCreate from AppCompatActivity */
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
verifyStoragePermissions(this)
|
||||
// todo: remove after testing finished
|
||||
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
StrictMode.setVmPolicy(
|
||||
|
|
|
@ -275,7 +275,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
|||
private fun handleTrackingManagementMenu() {
|
||||
when (trackingState) {
|
||||
Keys.STATE_TRACKING_PAUSED -> resumeTracking()
|
||||
Keys.STATE_TRACKING_ACTIVE -> trackerService.stopTracking()
|
||||
Keys.STATE_TRACKING_ACTIVE -> trackerService.pauseTracking()
|
||||
Keys.STATE_TRACKING_NOT_STARTED -> startTracking()
|
||||
}
|
||||
}
|
||||
|
@ -296,9 +296,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
|
|||
else
|
||||
{
|
||||
CoroutineScope(IO).launch {
|
||||
track.save_json(activity as Context)
|
||||
track.save_gpx(activity as Context)
|
||||
trackerService.clearTrack()
|
||||
trackerService.saveTrackAndClear(activity as Context)
|
||||
withContext(Main) {
|
||||
// step 4: open track in TrackFragement
|
||||
openTrack(track)
|
||||
|
|
|
@ -123,6 +123,16 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||
preferenceOmitRests.summaryOff = getString(R.string.pref_omit_rests_off)
|
||||
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
|
||||
// val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context)
|
||||
// preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title)
|
||||
|
@ -190,6 +200,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||
screen.addPreference(preferenceCategoryAdvanced)
|
||||
screen.addPreference(preferenceOmitRests)
|
||||
// screen.addPreference(preferenceAltitudeSmoothingValue)
|
||||
screen.addPreference(preferenceAutoExportInterval)
|
||||
screen.addPreference(preferenceResetAdvanced)
|
||||
screen.addPreference(preferenceCategoryAbout)
|
||||
screen.addPreference(preferenceAppVersion)
|
||||
|
|
|
@ -49,7 +49,7 @@ import org.y20k.trackbook.helpers.LogHelper
|
|||
import org.y20k.trackbook.helpers.MapOverlayHelper
|
||||
import org.y20k.trackbook.helpers.TrackHelper
|
||||
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||
|
||||
import java.io.File
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
if (targetUri != null)
|
||||
{
|
||||
|
@ -145,7 +146,8 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
|||
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()
|
||||
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.")
|
||||
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 gpsOnly: Boolean = false
|
||||
var omitRests: Boolean = true
|
||||
var autoExportInterval: Int = Keys.DEFAULT_AUTO_EXPORT_INTERVAL
|
||||
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
|
||||
// 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 gpsLocationListenerRegistered: Boolean = false
|
||||
var networkLocationListenerRegistered: Boolean = false
|
||||
|
@ -152,7 +151,7 @@ class TrackerService: Service(), SensorEventListener
|
|||
fun clearTrack()
|
||||
{
|
||||
track = Track()
|
||||
resumed = false
|
||||
stepCountOffset = 0f
|
||||
FileHelper.delete_temp_file(this as Context)
|
||||
trackingState = Keys.STATE_TRACKING_NOT_STARTED
|
||||
PreferencesHelper.saveTrackingState(trackingState)
|
||||
|
@ -237,6 +236,7 @@ class TrackerService: Service(), SensorEventListener
|
|||
gpsOnly = PreferencesHelper.loadGpsOnly()
|
||||
useImperial = PreferencesHelper.loadUseImperialUnits()
|
||||
omitRests = PreferencesHelper.loadOmitRests()
|
||||
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
||||
|
||||
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
|
@ -259,7 +259,7 @@ class TrackerService: Service(), SensorEventListener
|
|||
LogHelper.i(TAG, "onDestroy called.")
|
||||
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
||||
{
|
||||
stopTracking()
|
||||
pauseTracking()
|
||||
}
|
||||
stopForeground(true)
|
||||
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)
|
||||
{
|
||||
stopTracking()
|
||||
pauseTracking()
|
||||
}
|
||||
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.
|
||||
track = load_temp_track(this)
|
||||
// try to mark last waypoint as stopover
|
||||
if (track.wayPoints.size > 0) {
|
||||
val lastWayPointIndex = track.wayPoints.size - 1
|
||||
track.wayPoints[lastWayPointIndex].isStopOver = true
|
||||
if (track.wayPoints.isNotEmpty()) {
|
||||
track.wayPoints.last().isStopOver = true
|
||||
}
|
||||
resumed = true
|
||||
// calculate length of recording break
|
||||
track.recordingPaused += TrackHelper.calculateDurationOfPause(track.recordingStop)
|
||||
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()
|
||||
{
|
||||
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
|
||||
if (newTrack) {
|
||||
track = Track()
|
||||
resumed = false
|
||||
stepCountOffset = 0f
|
||||
}
|
||||
trackingState = Keys.STATE_TRACKING_ACTIVE
|
||||
|
@ -400,11 +413,10 @@ class TrackerService: Service(), SensorEventListener
|
|||
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
|
||||
}
|
||||
|
||||
fun stopTracking()
|
||||
fun pauseTracking()
|
||||
{
|
||||
track.recordingStop = GregorianCalendar.getInstance().time
|
||||
val context: Context = this
|
||||
CoroutineScope(IO).launch { track.save_temp_suspended(context) }
|
||||
CoroutineScope(IO).launch { track.save_temp_suspended(this@TrackerService) }
|
||||
|
||||
trackingState = Keys.STATE_TRACKING_PAUSED
|
||||
PreferencesHelper.saveTrackingState(trackingState)
|
||||
|
@ -439,6 +451,9 @@ class TrackerService: Service(), SensorEventListener
|
|||
Keys.PREF_OMIT_RESTS -> {
|
||||
omitRests = PreferencesHelper.loadOmitRests()
|
||||
}
|
||||
Keys.PREF_AUTO_EXPORT_INTERVAL -> {
|
||||
autoExportInterval = PreferencesHelper.loadAutoExportInterval()
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
@ -462,9 +477,10 @@ class TrackerService: Service(), SensorEventListener
|
|||
{
|
||||
override fun run() {
|
||||
// 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) {
|
||||
resumed = false
|
||||
track.resumed = false
|
||||
|
||||
// store previous smoothed altitude
|
||||
val previousAltitude: Double = altitudeValues.getAverage()
|
||||
|
@ -488,11 +504,14 @@ class TrackerService: Service(), SensorEventListener
|
|||
}
|
||||
|
||||
// save a temp track
|
||||
val now: Date = GregorianCalendar.getInstance().time
|
||||
if (now.time - lastSave.time > Keys.SAVE_TEMP_TRACK_INTERVAL) {
|
||||
lastSave = now
|
||||
if (now.time - lastTempSave.time > Keys.SAVE_TEMP_TRACK_INTERVAL) {
|
||||
lastTempSave = now
|
||||
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
|
||||
displayNotification()
|
||||
|
|
|
@ -16,23 +16,36 @@
|
|||
|
||||
package org.y20k.trackbook.core
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.app.ActivityCompat
|
||||
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.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.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
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
|
||||
|
@ -50,6 +63,9 @@ data class Track (
|
|||
@Expose var recordingStart: Date = GregorianCalendar.getInstance().time,
|
||||
@Expose var dateString: String = DateTimeHelper.convertToReadableDate(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 minAltitude: Double = 0.0,
|
||||
@Expose var positiveElevation: Double = 0.0,
|
||||
|
@ -165,22 +181,29 @@ data class Track (
|
|||
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
|
||||
{
|
||||
val basename: String = this.id.toString() + Keys.TRACKBOOK_FILE_EXTENSION
|
||||
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_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 ->
|
||||
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)
|
||||
{
|
||||
val json: String = this.to_json()
|
||||
|
|
|
@ -55,7 +55,8 @@ object FileHelper {
|
|||
suspend fun renameTrackSuspended(context: Context, track: Track, newName: String) {
|
||||
return suspendCoroutine { cont ->
|
||||
track.name = newName
|
||||
track.save_both(context)
|
||||
track.save_json(context)
|
||||
track.save_gpx(context)
|
||||
cont.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ object FileHelper {
|
|||
|
||||
|
||||
/* 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 outputStream = context.contentResolver.openOutputStream(targetFileUri)
|
||||
if (outputStream != null) {
|
||||
|
|
|
@ -71,9 +71,11 @@ object PreferencesHelper {
|
|||
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 {
|
||||
// // load current setting
|
||||
// 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 */
|
||||
|
||||
|
||||
/* 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 */
|
||||
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 {
|
||||
// disable swipe for statistics element
|
||||
if (viewHolder is TracklistAdapter.ElementStatisticsViewHolder) return 0
|
||||
return super.getSwipeDirs(recyclerView, viewHolder)
|
||||
}
|
||||
|
||||
|
|
|
@ -75,64 +75,39 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
|||
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
|
||||
{
|
||||
when (viewType) {
|
||||
Keys.VIEW_TYPE_STATISTICS -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.element_track, parent, false)
|
||||
return ElementTrackViewHolder(v)
|
||||
}
|
||||
|
||||
|
||||
/* Overrides getItemViewType */
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (position == 0) {
|
||||
return Keys.VIEW_TYPE_STATISTICS
|
||||
} else {
|
||||
return Keys.VIEW_TYPE_TRACK
|
||||
}
|
||||
return Keys.VIEW_TYPE_TRACK
|
||||
}
|
||||
|
||||
|
||||
/* Overrides getItemCount from RecyclerView.Adapter */
|
||||
override fun getItemCount(): Int {
|
||||
// +1 because of the total statistics element
|
||||
return tracklist.tracks.size + 1
|
||||
return tracklist.tracks.size
|
||||
}
|
||||
|
||||
|
||||
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
|
||||
{
|
||||
when (holder)
|
||||
{
|
||||
// CASE STATISTICS ELEMENT
|
||||
is ElementStatisticsViewHolder -> {
|
||||
val elementStatisticsViewHolder: ElementStatisticsViewHolder = holder
|
||||
elementStatisticsViewHolder.totalDistanceView.text = LengthUnitHelper.convertDistanceToString(tracklist.get_total_distance(), useImperial)
|
||||
}
|
||||
|
||||
// CASE TRACK ELEMENT
|
||||
is ElementTrackViewHolder -> {
|
||||
val positionInTracklist: Int = position - 1 // Element 0 is the statistics element.
|
||||
val elementTrackViewHolder: ElementTrackViewHolder = holder
|
||||
elementTrackViewHolder.trackNameView.text = tracklist.tracks[positionInTracklist].name
|
||||
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)
|
||||
}
|
||||
}
|
||||
val positionInTracklist: Int = position
|
||||
val elementTrackViewHolder: ElementTrackViewHolder = holder as ElementTrackViewHolder
|
||||
elementTrackViewHolder.trackNameView.text = tracklist.tracks[positionInTracklist].name
|
||||
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 */
|
||||
fun getTrackName(positionInRecyclerView: Int): String
|
||||
{
|
||||
// Minus 1 because first position is always the statistics element
|
||||
return tracklist.tracks[positionInRecyclerView - 1].name
|
||||
return tracklist.tracks[positionInRecyclerView].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[track_index]
|
||||
val track = tracklist.tracks[index]
|
||||
track.delete(context)
|
||||
tracklist.tracks.remove(track)
|
||||
notifyItemChanged(0)
|
||||
notifyItemRemoved(ui_index)
|
||||
notifyItemRemoved(index)
|
||||
notifyItemRangeChanged(index, this.itemCount);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
return tracklist.tracks.size == 0
|
||||
}
|
||||
|
@ -249,16 +224,4 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
|
|||
/*
|
||||
* 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)
|
||||
}
|
||||
// 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_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_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_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>
|
||||
|
@ -98,7 +100,7 @@
|
|||
<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_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_title">Report Issue</string>
|
||||
<string name="pref_reset_advanced_summary">Reset advanced settings to defaults.</string>
|
||||
|
|
Loading…
Reference in a new issue