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:
voussoir 2023-03-04 21:29:18 -08:00
parent edcb149ac7
commit 7956f44ce4
15 changed files with 204 additions and 120 deletions

View file

@ -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 -->

View file

@ -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

View file

@ -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(

View file

@ -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)

View file

@ -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)

View file

@ -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()
}

View file

@ -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()

View file

@ -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()

View file

@ -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) {

View file

@ -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)
// }

View file

@ -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)
{

View file

@ -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)
}

View file

@ -75,51 +75,28 @@ 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)
}
}
}
/* Overrides getItemViewType */
override fun getItemViewType(position: Int): Int {
if (position == 0) {
return Keys.VIEW_TYPE_STATISTICS
} else {
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
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) {
@ -133,25 +110,21 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
toggleStarred(it, positionInTracklist)
}
}
}
}
/* 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
*/
}

View file

@ -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) }
}

View file

@ -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>