removes the Smothing slider (see #99) & added a "Total Distance Recorded" info at the top of the list of recordings

This commit is contained in:
y20k 2021-09-16 15:53:31 +02:00
parent a9ecdcc3ab
commit d22638da92
No known key found for this signature in database
GPG key ID: 824D4259F41FAFF6
17 changed files with 213 additions and 109 deletions

View file

@ -58,18 +58,18 @@ dependencies {
// AndroidX // AndroidX
def navigationVersion = "2.3.5" def navigationVersion = "2.3.5"
implementation "androidx.activity:activity-ktx:1.2.3" implementation "androidx.activity:activity-ktx:1.3.1"
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.core:core-ktx:1.6.0'
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
// Gson // Gson
implementation 'com.google.code.gson:gson:2.8.7' implementation 'com.google.code.gson:gson:2.8.8'
// OpenStreetMap // OpenStreetMap
implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.osmdroid:osmdroid-android:6.1.11'
} }

View file

@ -91,6 +91,9 @@ object Keys {
const val TEMP_FILE: String = "temp.json" const val TEMP_FILE: String = "temp.json"
const val TRACKLIST_FILE: String = "tracklist.json" const val TRACKLIST_FILE: String = "tracklist.json"
// view types
const val VIEW_TYPE_STATISTICS: Int = 1
const val VIEW_TYPE_TRACK: Int = 2
// default values // default values
val DEFAULT_DATE: Date = Date(0L) val DEFAULT_DATE: Date = Date(0L)
@ -108,7 +111,7 @@ object Keys {
const val DEFAULT_ACCURACY: Float = 300f // in meters const val DEFAULT_ACCURACY: Float = 300f // in meters
const val DEFAULT_ALTITUDE: Double = 0.0 const val DEFAULT_ALTITUDE: Double = 0.0
const val DEFAULT_TIME: Long = 0L const val DEFAULT_TIME: Long = 0L
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 10 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 = 60000000000L // one minute in nanoseconds
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters

View file

@ -22,10 +22,7 @@ import android.Manifest
import android.content.* import android.content.*
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.Location import android.location.Location
import android.os.Build import android.os.*
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -55,7 +52,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe
/* Main class variables */ /* Main class variables */
private var bound: Boolean = false private var bound: Boolean = false
private val handler: Handler = Handler() private val handler: Handler = Handler(Looper.getMainLooper())
private var trackingState: Int = Keys.STATE_TRACKING_NOT private var trackingState: Int = Keys.STATE_TRACKING_NOT
private var gpsProviderActive: Boolean = false private var gpsProviderActive: Boolean = false
private var networkProviderActive: Boolean = false private var networkProviderActive: Boolean = false

View file

@ -116,15 +116,15 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceRecordingAccuracy.setDefaultValue(false) preferenceRecordingAccuracy.setDefaultValue(false)
// 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)
preferenceAltitudeSmoothingValue.setIcon(R.drawable.ic_bar_chart_24) // preferenceAltitudeSmoothingValue.setIcon(R.drawable.ic_bar_chart_24)
preferenceAltitudeSmoothingValue.key = Keys.PREF_ALTITUDE_SMOOTHING_VALUE // preferenceAltitudeSmoothingValue.key = Keys.PREF_ALTITUDE_SMOOTHING_VALUE
preferenceAltitudeSmoothingValue.summary = getString(R.string.pref_altitude_smoothing_value_summary) // preferenceAltitudeSmoothingValue.summary = getString(R.string.pref_altitude_smoothing_value_summary)
preferenceAltitudeSmoothingValue.showSeekBarValue = true // preferenceAltitudeSmoothingValue.showSeekBarValue = true
preferenceAltitudeSmoothingValue.min = Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION // preferenceAltitudeSmoothingValue.min = Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION
preferenceAltitudeSmoothingValue.max = Keys.MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION // preferenceAltitudeSmoothingValue.max = Keys.MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION
preferenceAltitudeSmoothingValue.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) // preferenceAltitudeSmoothingValue.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
// set up "Reset" preference // set up "Reset" preference
@ -135,7 +135,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceResetAdvanced.setOnPreferenceClickListener{ preferenceResetAdvanced.setOnPreferenceClickListener{
// reset "Recording Accuracy" preference // reset "Recording Accuracy" preference
preferenceRecordingAccuracy.isChecked = false preferenceRecordingAccuracy.isChecked = false
preferenceAltitudeSmoothingValue.value = Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE // preferenceAltitudeSmoothingValue.value = Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
@ -181,7 +181,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context) val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context)
preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title) preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title)
preferenceCategoryAdvanced.contains(preferenceRecordingAccuracy) preferenceCategoryAdvanced.contains(preferenceRecordingAccuracy)
preferenceCategoryAdvanced.contains(preferenceAltitudeSmoothingValue) // preferenceCategoryAdvanced.contains(preferenceAltitudeSmoothingValue)
preferenceCategoryAdvanced.contains(preferenceResetAdvanced) preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context) val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
@ -198,7 +198,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
screen.addPreference(preferenceDeleteNonStarred) screen.addPreference(preferenceDeleteNonStarred)
screen.addPreference(preferenceCategoryAdvanced) screen.addPreference(preferenceCategoryAdvanced)
screen.addPreference(preferenceRecordingAccuracy) screen.addPreference(preferenceRecordingAccuracy)
screen.addPreference(preferenceAltitudeSmoothingValue) // screen.addPreference(preferenceAltitudeSmoothingValue)
screen.addPreference(preferenceResetAdvanced) screen.addPreference(preferenceResetAdvanced)
screen.addPreference(preferenceCategoryAbout) screen.addPreference(preferenceCategoryAbout)
screen.addPreference(preferenceAppVersion) screen.addPreference(preferenceAppVersion)

View file

@ -32,10 +32,7 @@ import android.hardware.SensorManager
import android.location.Location import android.location.Location
import android.location.LocationListener import android.location.LocationListener
import android.location.LocationManager import android.location.LocationManager
import android.os.Binder import android.os.*
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -69,7 +66,7 @@ class TrackerService: Service(), SensorEventListener {
var networkLocationListenerRegistered: Boolean = false var networkLocationListenerRegistered: Boolean = false
var bound: Boolean = false var bound: Boolean = false
private val binder = LocalBinder() private val binder = LocalBinder()
private val handler: Handler = Handler() private val handler: Handler = Handler(Looper.getMainLooper())
private var altitudeValues: SimpleMovingAverageQueue = SimpleMovingAverageQueue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) private var altitudeValues: SimpleMovingAverageQueue = SimpleMovingAverageQueue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE)
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
private lateinit var sensorManager: SensorManager private lateinit var sensorManager: SensorManager
@ -97,7 +94,7 @@ class TrackerService: Service(), SensorEventListener {
trackingState = PreferencesHelper.loadTrackingState() trackingState = PreferencesHelper.loadTrackingState()
currentBestLocation = LocationHelper.getLastKnownLocation(this) currentBestLocation = LocationHelper.getLastKnownLocation(this)
track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this)) track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
altitudeValues.capacity = PreferencesHelper.loadAltitudeSmoothingValue() // altitudeValues.capacity = PreferencesHelper.loadAltitudeSmoothingValue()
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
} }
@ -577,16 +574,16 @@ class TrackerService: Service(), SensorEventListener {
} }
// TODO remove // // TODO remove
val testAltitudes: Array<Double> = arrayOf(352.4349365234375, 358.883544921875, 358.6827392578125, 357.31396484375, 354.27459716796875, 354.573486328125, 354.388916015625, 354.6697998046875, 356.534912109375, 355.2772216796875, 356.21246337890625, 352.3499755859375, 350.37646484375, 351.2098388671875, 350.5213623046875, 350.5145263671875, 350.1728515625, 350.9075927734375, 351.5965576171875, 349.55767822265625, 351.548583984375, 357.1195068359375, 362.18634033203125, 366.3153076171875, 366.2218017578125, 362.1046142578125, 357.48291015625, 356.78570556640625, 353.7734375, 352.53936767578125, 351.8125, 353.1099853515625, 354.93035888671875, 355.4337158203125, 354.83270263671875, 352.9859619140625, 352.3006591796875, 351.63470458984375, 350.2501220703125, 351.75726318359375, 350.87664794921875, 350.4185791015625, 350.51568603515625, 349.5537109375, 345.2874755859375, 345.57196044921875, 349.99658203125, 353.3822021484375, 355.19061279296875, 359.1099853515625, 361.74365234375, 363.313232421875, 362.026611328125, 363.20703125, 363.2508544921875, 362.5870361328125, 362.521240234375) // val testAltitudes: Array<Double> = arrayOf(352.4349365234375, 358.883544921875, 358.6827392578125, 357.31396484375, 354.27459716796875, 354.573486328125, 354.388916015625, 354.6697998046875, 356.534912109375, 355.2772216796875, 356.21246337890625, 352.3499755859375, 350.37646484375, 351.2098388671875, 350.5213623046875, 350.5145263671875, 350.1728515625, 350.9075927734375, 351.5965576171875, 349.55767822265625, 351.548583984375, 357.1195068359375, 362.18634033203125, 366.3153076171875, 366.2218017578125, 362.1046142578125, 357.48291015625, 356.78570556640625, 353.7734375, 352.53936767578125, 351.8125, 353.1099853515625, 354.93035888671875, 355.4337158203125, 354.83270263671875, 352.9859619140625, 352.3006591796875, 351.63470458984375, 350.2501220703125, 351.75726318359375, 350.87664794921875, 350.4185791015625, 350.51568603515625, 349.5537109375, 345.2874755859375, 345.57196044921875, 349.99658203125, 353.3822021484375, 355.19061279296875, 359.1099853515625, 361.74365234375, 363.313232421875, 362.026611328125, 363.20703125, 363.2508544921875, 362.5870361328125, 362.521240234375)
var testCounter: Int = 0 // var testCounter: Int = 0
fun getTestAltitude(): Double { // fun getTestAltitude(): Double {
if (testCounter >= testAltitudes.size) testCounter = 0 // if (testCounter >= testAltitudes.size) testCounter = 0
val testAltitude: Double = testAltitudes[testCounter] // val testAltitude: Double = testAltitudes[testCounter]
testCounter ++ // testCounter ++
return testAltitude // return testAltitude
} // }
// TODO remove // // TODO remove
} }

View file

@ -84,7 +84,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
val swipeHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) { val swipeHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// ask user // ask user
val adapterPosition: Int = viewHolder.adapterPosition val adapterPosition: Int = viewHolder.adapterPosition // first position in list is reserved for statistics
val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(adapterPosition)}" val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(adapterPosition)}"
YesNoDialog(this@TracklistFragment as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_TRACK, messageString = dialogMessage, yesButton = R.string.dialog_yes_no_positive_button_delete_recording, payload = adapterPosition) YesNoDialog(this@TracklistFragment as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_TRACK, messageString = dialogMessage, yesButton = R.string.dialog_yes_no_positive_button_delete_recording, payload = adapterPosition)
} }
@ -93,7 +93,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
itemTouchHelper.attachToRecyclerView(rootView.findViewById(R.id.track_element_list)) itemTouchHelper.attachToRecyclerView(rootView.findViewById(R.id.track_element_list))
// toggle onboarding layout // toggle onboarding layout
toggleOnboardingLayout(tracklistAdapter.itemCount) toggleOnboardingLayout()
return rootView return rootView
} }
@ -118,7 +118,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
when (dialogResult) { when (dialogResult) {
// user tapped remove track // user tapped remove track
true -> { true -> {
toggleOnboardingLayout(tracklistAdapter.itemCount -1) toggleOnboardingLayout()
tracklistAdapter.removeTrackAtPosition(activity as Context, payload) tracklistAdapter.removeTrackAtPosition(activity as Context, payload)
} }
// user tapped cancel // user tapped cancel
@ -132,8 +132,8 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
// toggle onboarding layout // toggle onboarding layout
private fun toggleOnboardingLayout(trackCount: Int) { private fun toggleOnboardingLayout() {
when (trackCount == 0) { when (tracklistAdapter.isEmpty()) {
true -> { true -> {
// show onboarding layout // show onboarding layout
tracklistOnboarding.visibility = View.VISIBLE tracklistOnboarding.visibility = View.VISIBLE
@ -166,7 +166,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
if (deleteTrackId != -1L) { if (deleteTrackId != -1L) {
CoroutineScope(Main). launch { CoroutineScope(Main). launch {
tracklistAdapter.removeTrackById(this@TracklistFragment.activity as Context, deleteTrackId) tracklistAdapter.removeTrackById(this@TracklistFragment.activity as Context, deleteTrackId)
toggleOnboardingLayout(tracklistAdapter.itemCount - 1) toggleOnboardingLayout()
} }
} }
} }

View file

@ -84,7 +84,7 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
} }
// handle outside-click as "no" // handle outside-click as "no"
builder.setOnCancelListener{ builder.setOnCancelListener {
yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString) yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString)
} }

View file

@ -182,9 +182,9 @@ object FileHelper {
val tracklist: Tracklist = readTracklist(context) val tracklist: Tracklist = readTracklist(context)
tracklist.tracklistElements.add(track.toTracklistElement(context)) tracklist.tracklistElements.add(track.toTracklistElement(context))
tracklist.totalDistanceAll += track.length tracklist.totalDistanceAll += track.length
tracklist.totalDurationAll += track.duration // tracklist.totalDurationAll += track.duration // note: TracklistElement does not contain duration
tracklist.totalRecordingPausedAll += track.recordingPaused // tracklist.totalRecordingPausedAll += track.recordingPaused // note: TracklistElement does not contain recordingPaused
tracklist.totalStepCountAll += track.stepCount // tracklist.totalStepCountAll += track.stepCount // note: TracklistElement does not contain stepCount
cont.resume(saveTracklist(context, tracklist, modificationDate)) cont.resume(saveTracklist(context, tracklist, modificationDate))
} }
} }
@ -340,6 +340,8 @@ object FileHelper {
// delete track files // delete track files
tracklistElement.trackUriString.toUri().toFile().delete() tracklistElement.trackUriString.toUri().toFile().delete()
tracklistElement.gpxUriString.toUri().toFile().delete() tracklistElement.gpxUriString.toUri().toFile().delete()
// subtract track length from total distance
tracklist.totalDistanceAll -= tracklistElement.length
} }
tracklist.tracklistElements.removeAll{ tracklistElements.contains(it) } tracklist.tracklistElements.removeAll{ tracklistElements.contains(it) }
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)
@ -353,6 +355,8 @@ object FileHelper {
// delete track files // delete track files
tracklistElement.trackUriString.toUri().toFile().delete() tracklistElement.trackUriString.toUri().toFile().delete()
tracklistElement.gpxUriString.toUri().toFile().delete() tracklistElement.gpxUriString.toUri().toFile().delete()
// subtract track length from total distance
tracklist.totalDistanceAll -= tracklistElement.length
// remove track element from list // remove track element from list
tracklist.tracklistElements.removeIf { TrackHelper.getTrackId(it) == TrackHelper.getTrackId(tracklistElement) } tracklist.tracklistElements.removeIf { TrackHelper.getTrackId(it) == TrackHelper.getTrackId(tracklistElement) }
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)

View file

@ -87,19 +87,19 @@ object PreferencesHelper {
return sharedPreferences.getBoolean(Keys.PREF_GPS_ONLY, false) return sharedPreferences.getBoolean(Keys.PREF_GPS_ONLY, false)
} }
/* Loads accuracy threshold used to determine if location is good enough */ // /* Loads accuracy threshold used to determine if location is good enough */
fun loadAccuracyThreshold(): Int { // fun loadAccuracyThreshold(): Int {
// load tracking state // // load tracking state
return sharedPreferences.getInt(Keys.PREF_LOCATION_ACCURACY_THRESHOLD, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY) // return sharedPreferences.getInt(Keys.PREF_LOCATION_ACCURACY_THRESHOLD, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
} // }
/* Loads state of recording accuracy */ // /* Loads state of recording accuracy */
fun loadRecordingAccuracyHigh(): Boolean { // fun loadRecordingAccuracyHigh(): Boolean {
// load current setting // // load current setting
return sharedPreferences.getBoolean(Keys.PREF_RECORDING_ACCURACY_HIGH, false) // return sharedPreferences.getBoolean(Keys.PREF_RECORDING_ACCURACY_HIGH, false)
} // }
/* Loads current accuracy multiplier */ /* Loads current accuracy multiplier */
@ -111,11 +111,11 @@ object PreferencesHelper {
} }
/* Load altitude smoothing value */ // /* Load altitude smoothing value */
fun loadAltitudeSmoothingValue(): Int { // fun loadAltitudeSmoothingValue(): Int {
// load current setting // // 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)
} // }
/* Loads the state of a map */ /* Loads the state of a map */

View file

@ -44,13 +44,11 @@ object TrackHelper {
/* Returns unique ID for Track - currently the start date */ /* Returns unique ID for Track - currently the start date */
fun getTrackId(track: Track): Long = fun getTrackId(track: Track): Long = track.recordingStart.time
track.recordingStart.time
/* Returns unique ID for TracklistElement - currently the start date */ /* Returns unique ID for TracklistElement - currently the start date */
fun getTrackId(tracklistElement: TracklistElement): Long = fun getTrackId(tracklistElement: TracklistElement): Long = tracklistElement.date.time
tracklistElement.date.time
/* Adds given locatiom as waypoint to track */ /* Adds given locatiom as waypoint to track */
@ -258,20 +256,20 @@ object TrackHelper {
fun calculateAndSaveTrackTotals(context: Context, tracklist: Tracklist) { fun calculateAndSaveTrackTotals(context: Context, tracklist: Tracklist) {
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
var totalDistanceAll: Float = 0f var totalDistanceAll: Float = 0f
var totalDurationAll: Long = 0L // var totalDurationAll: Long = 0L
var totalRecordingPausedAll: Long = 0L // var totalRecordingPausedAll: Long = 0L
var totalStepCountAll: Float = 0f // var totalStepCountAll: Float = 0f
tracklist.tracklistElements.forEach { tracklistElement -> tracklist.tracklistElements.forEach { tracklistElement ->
val track: Track = FileHelper.readTrack(context, tracklistElement.trackUriString.toUri()) val track: Track = FileHelper.readTrack(context, tracklistElement.trackUriString.toUri())
totalDistanceAll += track.length totalDistanceAll += track.length
totalDurationAll += track.duration // totalDurationAll += track.duration
totalRecordingPausedAll += track.recordingPaused // totalRecordingPausedAll += track.recordingPaused
totalStepCountAll += track.stepCount // totalStepCountAll += track.stepCount
} }
tracklist.totalDistanceAll = totalDistanceAll tracklist.totalDistanceAll = totalDistanceAll
tracklist.totalDurationAll = totalDurationAll // tracklist.totalDurationAll = totalDurationAll
tracklist.totalRecordingPausedAll = totalRecordingPausedAll // tracklist.totalRecordingPausedAll = totalRecordingPausedAll
tracklist.totalStepCountAll = totalStepCountAll // tracklist.totalStepCountAll = totalStepCountAll
FileHelper.saveTracklistSuspended(context, tracklist, GregorianCalendar.getInstance().time) FileHelper.saveTracklistSuspended(context, tracklist, GregorianCalendar.getInstance().time)
} }
} }

View file

@ -29,6 +29,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.y20k.trackbook.R import org.y20k.trackbook.R
import org.y20k.trackbook.tracklist.TracklistAdapter
/* /*
@ -101,6 +102,12 @@ object UiHelper {
return false return false
} }
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)
}
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
val itemView = viewHolder.itemView val itemView = viewHolder.itemView
val itemHeight = itemView.bottom - itemView.top val itemHeight = itemView.bottom - itemView.top

View file

@ -32,6 +32,7 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import org.y20k.trackbook.Keys
import org.y20k.trackbook.R import org.y20k.trackbook.R
import org.y20k.trackbook.core.Tracklist import org.y20k.trackbook.core.Tracklist
import org.y20k.trackbook.core.TracklistElement import org.y20k.trackbook.core.TracklistElement
@ -78,49 +79,89 @@ 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 {
val v = LayoutInflater.from(parent.context).inflate(R.layout.track_element, parent, false)
return TrackElementViewHolder(v) 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 */ /* Overrides getItemCount from RecyclerView.Adapter */
override fun getItemCount(): Int { override fun getItemCount(): Int {
return tracklist.tracklistElements.size // +1 ==> the total statistics element
return tracklist.tracklistElements.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) {
val trackElementViewHolder: TrackElementViewHolder = holder as TrackElementViewHolder
trackElementViewHolder.trackNameView.text = tracklist.tracklistElements[position].name when (holder) {
trackElementViewHolder.trackDataView.text = createTrackDataString(position)
when (tracklist.tracklistElements[position].starred) { // CASE STATISTICS ELEMENT
true -> trackElementViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_filled_24dp)) is ElementStatisticsViewHolder -> {
false -> trackElementViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_outline_24dp)) val elementStatisticsViewHolder: ElementStatisticsViewHolder = holder as ElementStatisticsViewHolder
} elementStatisticsViewHolder.totalDistanceView.text = LengthUnitHelper.convertDistanceToString(tracklist.totalDistanceAll, useImperial)
trackElementViewHolder.trackElement.setOnClickListener { }
tracklistListener.onTrackElementTapped(tracklist.tracklistElements[position])
} // CASE TRACK ELEMENT
trackElementViewHolder.starButton.setOnClickListener { is ElementTrackViewHolder -> {
toggleStarred(it, position) val positionInTracklist: Int = position -1
val elementTrackViewHolder: ElementTrackViewHolder = holder as ElementTrackViewHolder
elementTrackViewHolder.trackNameView.text = tracklist.tracklistElements[positionInTracklist].name
elementTrackViewHolder.trackDataView.text = createTrackDataString(positionInTracklist)
when (tracklist.tracklistElements[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.tracklistElements[positionInTracklist])
}
elementTrackViewHolder.starButton.setOnClickListener {
toggleStarred(it, positionInTracklist)
}
}
} }
} }
/* Get track name for given position */ /* Get track name for given position */
fun getTrackName(position: Int): String { fun getTrackName(positionInRecyclerView: Int): String {
return tracklist.tracklistElements[position].name // first position is always the statistics element
return tracklist.tracklistElements[positionInRecyclerView - 1].name
} }
/* Removes track and track files for given position - used by TracklistFragment */ /* Removes track and track files for given position - used by TracklistFragment */
fun removeTrackAtPosition(context: Context, position: Int) { fun removeTrackAtPosition(context: Context, position: Int) {
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, position, tracklist) } val positionInTracklist = position - 1
val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, positionInTracklist, tracklist) }
// wait for result and store in tracklist // wait for result and store in tracklist
withContext(Main) { withContext(Main) {
tracklist = deferred.await() tracklist = deferred.await()
notifyItemRemoved(position) } notifyItemRemoved(position)
notifyItemChanged(0)
}
} }
} }
@ -130,16 +171,25 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
// reload tracklist //todo check if necessary // reload tracklist //todo check if necessary
// tracklist = FileHelper.readTracklist(context) // tracklist = FileHelper.readTracklist(context)
val position: Int = findPosition(trackId) val positionInTracklist: Int = findPosition(trackId)
val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, position, tracklist) } val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, positionInTracklist, tracklist) }
// wait for result and store in tracklist // wait for result and store in tracklist
withContext(Main) { withContext(Main) {
tracklist = deferred.await() tracklist = deferred.await()
notifyItemRemoved(position) } val positionInRecyclerView: Int = positionInTracklist + 1 // position 0 is the statistics element
notifyItemRemoved(positionInRecyclerView)
notifyItemChanged(0)
}
} }
} }
/* Returns if the adapter is empty */
fun isEmpty(): Boolean {
return tracklist.tracklistElements.size == 0
}
/* Finds current position of track element in adapter list */ /* Finds current position of track element in adapter list */
private fun findPosition(trackId: Long): Int { private fun findPosition(trackId: Long): Int {
tracklist.tracklistElements.forEachIndexed {index, tracklistElement -> tracklist.tracklistElements.forEachIndexed {index, tracklistElement ->
@ -215,11 +265,23 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re
/* /*
* Inner class: ViewHolder for a track element * Inner class: ViewHolder for a track element
*/ */
private inner class TrackElementViewHolder (trackElementLayout: View): RecyclerView.ViewHolder(trackElementLayout) { inner class ElementTrackViewHolder (elementTrackLayout: View): RecyclerView.ViewHolder(elementTrackLayout) {
val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element) val trackElement: ConstraintLayout = elementTrackLayout.findViewById(R.id.track_element)
val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name) val trackNameView: TextView = elementTrackLayout.findViewById(R.id.track_name)
val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data) val trackDataView: TextView = elementTrackLayout.findViewById(R.id.track_data)
val starButton: ImageButton = trackElementLayout.findViewById(R.id.star_button) val starButton: ImageButton = elementTrackLayout.findViewById(R.id.star_button)
}
/*
* 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 * End of inner class

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/total_distance_p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:text="@string/track_list_p_element_statistics"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/total_distance_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/total_distance_p"
app:layout_constraintStart_toStartOf="@+id/total_distance_p"
app:layout_constraintTop_toBottomOf="@+id/total_distance_p"
tools:text="@string/sample_text_default_total_distance" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -72,6 +72,8 @@
<!-- Track Tab Onboarding --> <!-- Track Tab Onboarding -->
<string name="track_list_onboarding_h1_part_1">Your recorded tracks</string> <string name="track_list_onboarding_h1_part_1">Your recorded tracks</string>
<string name="track_list_onboarding_h1_part_2">… will show up here.</string> <string name="track_list_onboarding_h1_part_2">… will show up here.</string>
<!-- Track List -->
<string name="track_list_p_element_statistics">Total Distance Recorded</string>
<!-- Settings --> <!-- Settings -->
<string name="pref_about_title">About</string> <string name="pref_about_title">About</string>
<string name="pref_app_version_summary">Version</string> <string name="pref_app_version_summary">Version</string>
@ -124,4 +126,5 @@
<string name="sample_text_track_data" translatable="false">23.0 km • 5 hrs 23 min 42 sec</string> <string name="sample_text_track_data" translatable="false">23.0 km • 5 hrs 23 min 42 sec</string>
<string name="sample_text_track_name" translatable="false">July 20, 1969</string> <string name="sample_text_track_name" translatable="false">July 20, 1969</string>
<string name="sample_text_default_data" translatable="false">track data missing</string> <string name="sample_text_default_data" translatable="false">track data missing</string>
<string name="sample_text_default_total_distance" translatable="false">6357.23 km</string>
</resources> </resources>

View file

@ -2,7 +2,7 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.5.20' kotlin_version = '1.5.30'
navigation_version = '2.3.3' navigation_version = '2.3.3'
} }
repositories { repositories {
@ -10,7 +10,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:7.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists