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:
		
							parent
							
								
									a9ecdcc3ab
								
							
						
					
					
						commit
						d22638da92
					
				
					 17 changed files with 213 additions and 109 deletions
				
			
		|  | @ -58,18 +58,18 @@ dependencies { | |||
| 
 | ||||
|     // AndroidX | ||||
|     def navigationVersion = "2.3.5" | ||||
|     implementation "androidx.activity:activity-ktx:1.2.3" | ||||
|     implementation 'androidx.appcompat:appcompat:1.3.0' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.0.4' | ||||
|     implementation 'androidx.core:core-ktx:1.5.0' | ||||
|     implementation "androidx.activity:activity-ktx:1.3.1" | ||||
|     implementation 'androidx.appcompat:appcompat:1.3.1' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.0' | ||||
|     implementation 'androidx.core:core-ktx:1.6.0' | ||||
|     implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" | ||||
|     implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" | ||||
|     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 | ||||
|     implementation 'com.google.code.gson:gson:2.8.7' | ||||
|     implementation 'com.google.code.gson:gson:2.8.8' | ||||
| 
 | ||||
|     // OpenStreetMap | ||||
|     implementation 'org.osmdroid:osmdroid-android:6.1.10' | ||||
|     implementation 'org.osmdroid:osmdroid-android:6.1.11' | ||||
| } | ||||
|  |  | |||
|  | @ -91,6 +91,9 @@ object Keys { | |||
|     const val TEMP_FILE: String = "temp.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 | ||||
|     val DEFAULT_DATE: Date = Date(0L) | ||||
|  | @ -108,7 +111,7 @@ object Keys { | |||
|     const val DEFAULT_ACCURACY: Float = 300f                                    // in meters | ||||
|     const val DEFAULT_ALTITUDE: Double = 0.0 | ||||
|     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_AGE: Long = 60000000000L               // one minute in nanoseconds | ||||
|     const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f                           // 15 meters | ||||
|  |  | |||
|  | @ -22,10 +22,7 @@ import android.Manifest | |||
| import android.content.* | ||||
| import android.content.pm.PackageManager | ||||
| import android.location.Location | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.os.Handler | ||||
| import android.os.IBinder | ||||
| import android.os.* | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
|  | @ -55,7 +52,7 @@ class MapFragment : Fragment(), YesNoDialog.YesNoDialogListener, MapOverlayHelpe | |||
| 
 | ||||
|     /* Main class variables */ | ||||
|     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 gpsProviderActive: Boolean = false | ||||
|     private var networkProviderActive: Boolean = false | ||||
|  |  | |||
|  | @ -116,15 +116,15 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList | |||
|         preferenceRecordingAccuracy.setDefaultValue(false) | ||||
| 
 | ||||
|         // set up "Altitude Smoothing" preference | ||||
|         val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context) | ||||
|         preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title) | ||||
|         preferenceAltitudeSmoothingValue.setIcon(R.drawable.ic_bar_chart_24) | ||||
|         preferenceAltitudeSmoothingValue.key = Keys.PREF_ALTITUDE_SMOOTHING_VALUE | ||||
|         preferenceAltitudeSmoothingValue.summary = getString(R.string.pref_altitude_smoothing_value_summary) | ||||
|         preferenceAltitudeSmoothingValue.showSeekBarValue = true | ||||
|         preferenceAltitudeSmoothingValue.min = Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION | ||||
|         preferenceAltitudeSmoothingValue.max = Keys.MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION | ||||
|         preferenceAltitudeSmoothingValue.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) | ||||
| //        val preferenceAltitudeSmoothingValue: SeekBarPreference = SeekBarPreference(activity as Context) | ||||
| //        preferenceAltitudeSmoothingValue.title = getString(R.string.pref_altitude_smoothing_value_title) | ||||
| //        preferenceAltitudeSmoothingValue.setIcon(R.drawable.ic_bar_chart_24) | ||||
| //        preferenceAltitudeSmoothingValue.key = Keys.PREF_ALTITUDE_SMOOTHING_VALUE | ||||
| //        preferenceAltitudeSmoothingValue.summary = getString(R.string.pref_altitude_smoothing_value_summary) | ||||
| //        preferenceAltitudeSmoothingValue.showSeekBarValue = true | ||||
| //        preferenceAltitudeSmoothingValue.min = Keys.MIN_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION | ||||
| //        preferenceAltitudeSmoothingValue.max = Keys.MAX_NUMBER_OF_WAYPOINTS_FOR_ELEVATION_CALCULATION | ||||
| //        preferenceAltitudeSmoothingValue.setDefaultValue(Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) | ||||
| 
 | ||||
| 
 | ||||
|         // set up "Reset" preference | ||||
|  | @ -135,7 +135,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList | |||
|         preferenceResetAdvanced.setOnPreferenceClickListener{ | ||||
|             // reset "Recording Accuracy" preference | ||||
|             preferenceRecordingAccuracy.isChecked = false | ||||
|             preferenceAltitudeSmoothingValue.value = Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE | ||||
| //            preferenceAltitudeSmoothingValue.value = Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE | ||||
|             return@setOnPreferenceClickListener true | ||||
|         } | ||||
| 
 | ||||
|  | @ -181,7 +181,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList | |||
|         val preferenceCategoryAdvanced: PreferenceCategory = PreferenceCategory(activity as Context) | ||||
|         preferenceCategoryAdvanced.title = getString(R.string.pref_advanced_title) | ||||
|         preferenceCategoryAdvanced.contains(preferenceRecordingAccuracy) | ||||
|         preferenceCategoryAdvanced.contains(preferenceAltitudeSmoothingValue) | ||||
| //        preferenceCategoryAdvanced.contains(preferenceAltitudeSmoothingValue) | ||||
|         preferenceCategoryAdvanced.contains(preferenceResetAdvanced) | ||||
| 
 | ||||
|         val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context) | ||||
|  | @ -198,7 +198,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList | |||
|         screen.addPreference(preferenceDeleteNonStarred) | ||||
|         screen.addPreference(preferenceCategoryAdvanced) | ||||
|         screen.addPreference(preferenceRecordingAccuracy) | ||||
|         screen.addPreference(preferenceAltitudeSmoothingValue) | ||||
| //        screen.addPreference(preferenceAltitudeSmoothingValue) | ||||
|         screen.addPreference(preferenceResetAdvanced) | ||||
|         screen.addPreference(preferenceCategoryAbout) | ||||
|         screen.addPreference(preferenceAppVersion) | ||||
|  |  | |||
|  | @ -32,10 +32,7 @@ import android.hardware.SensorManager | |||
| import android.location.Location | ||||
| import android.location.LocationListener | ||||
| import android.location.LocationManager | ||||
| import android.os.Binder | ||||
| import android.os.Bundle | ||||
| import android.os.Handler | ||||
| import android.os.IBinder | ||||
| import android.os.* | ||||
| import androidx.core.content.ContextCompat | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.Dispatchers.IO | ||||
|  | @ -69,7 +66,7 @@ class TrackerService: Service(), SensorEventListener { | |||
|     var networkLocationListenerRegistered: Boolean = false | ||||
|     var bound: Boolean = false | ||||
|     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 lateinit var locationManager: LocationManager | ||||
|     private lateinit var sensorManager: SensorManager | ||||
|  | @ -97,7 +94,7 @@ class TrackerService: Service(), SensorEventListener { | |||
|         trackingState = PreferencesHelper.loadTrackingState() | ||||
|         currentBestLocation = LocationHelper.getLastKnownLocation(this) | ||||
|         track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this)) | ||||
|         altitudeValues.capacity = PreferencesHelper.loadAltitudeSmoothingValue() | ||||
| //        altitudeValues.capacity = PreferencesHelper.loadAltitudeSmoothingValue() | ||||
|         PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) | ||||
|     } | ||||
| 
 | ||||
|  | @ -577,16 +574,16 @@ class TrackerService: Service(), SensorEventListener { | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // 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) | ||||
|     var testCounter: Int = 0 | ||||
|     fun getTestAltitude(): Double { | ||||
|         if (testCounter >= testAltitudes.size) testCounter = 0 | ||||
|         val testAltitude: Double = testAltitudes[testCounter] | ||||
|         testCounter ++ | ||||
|         return testAltitude | ||||
|     } | ||||
|     // 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) | ||||
| //    var testCounter: Int = 0 | ||||
| //    fun getTestAltitude(): Double { | ||||
| //        if (testCounter >= testAltitudes.size) testCounter = 0 | ||||
| //        val testAltitude: Double = testAltitudes[testCounter] | ||||
| //        testCounter ++ | ||||
| //        return testAltitude | ||||
| //    } | ||||
| //    // TODO remove | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -84,7 +84,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|         val swipeHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) { | ||||
|             override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { | ||||
|                 // 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)}" | ||||
|                 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)) | ||||
| 
 | ||||
|         // toggle onboarding layout | ||||
|         toggleOnboardingLayout(tracklistAdapter.itemCount) | ||||
|         toggleOnboardingLayout() | ||||
| 
 | ||||
|         return rootView | ||||
|     } | ||||
|  | @ -118,7 +118,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|                 when (dialogResult) { | ||||
|                     // user tapped remove track | ||||
|                     true -> { | ||||
|                         toggleOnboardingLayout(tracklistAdapter.itemCount -1) | ||||
|                         toggleOnboardingLayout() | ||||
|                         tracklistAdapter.removeTrackAtPosition(activity as Context, payload) | ||||
|                     } | ||||
|                     // user tapped cancel | ||||
|  | @ -132,8 +132,8 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
| 
 | ||||
| 
 | ||||
|     // toggle onboarding layout | ||||
|     private fun toggleOnboardingLayout(trackCount: Int) { | ||||
|         when (trackCount == 0) { | ||||
|     private fun toggleOnboardingLayout() { | ||||
|         when (tracklistAdapter.isEmpty()) { | ||||
|             true -> { | ||||
|                 // show onboarding layout | ||||
|                 tracklistOnboarding.visibility = View.VISIBLE | ||||
|  | @ -166,7 +166,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|             if (deleteTrackId != -1L) { | ||||
|                 CoroutineScope(Main). launch { | ||||
|                     tracklistAdapter.removeTrackById(this@TracklistFragment.activity as Context, deleteTrackId) | ||||
|                     toggleOnboardingLayout(tracklistAdapter.itemCount - 1) | ||||
|                     toggleOnboardingLayout() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -84,7 +84,7 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) { | |||
|         } | ||||
| 
 | ||||
|         // handle outside-click as "no" | ||||
|         builder.setOnCancelListener{ | ||||
|         builder.setOnCancelListener { | ||||
|             yesNoDialogListener.onYesNoDialog(type, false, payload, payloadString) | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -182,9 +182,9 @@ object FileHelper { | |||
|             val tracklist: Tracklist = readTracklist(context) | ||||
|             tracklist.tracklistElements.add(track.toTracklistElement(context)) | ||||
|             tracklist.totalDistanceAll += track.length | ||||
|             tracklist.totalDurationAll += track.duration | ||||
|             tracklist.totalRecordingPausedAll += track.recordingPaused | ||||
|             tracklist.totalStepCountAll += track.stepCount | ||||
| //            tracklist.totalDurationAll += track.duration // note: TracklistElement does not contain duration | ||||
| //            tracklist.totalRecordingPausedAll += track.recordingPaused // note: TracklistElement does not contain recordingPaused | ||||
| //            tracklist.totalStepCountAll += track.stepCount // note: TracklistElement does not contain stepCount | ||||
|             cont.resume(saveTracklist(context, tracklist, modificationDate)) | ||||
|         } | ||||
|     } | ||||
|  | @ -340,6 +340,8 @@ object FileHelper { | |||
|             // delete track files | ||||
|             tracklistElement.trackUriString.toUri().toFile().delete() | ||||
|             tracklistElement.gpxUriString.toUri().toFile().delete() | ||||
|             // subtract track length from total distance | ||||
|             tracklist.totalDistanceAll -= tracklistElement.length | ||||
|         } | ||||
|         tracklist.tracklistElements.removeAll{ tracklistElements.contains(it) } | ||||
|         saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) | ||||
|  | @ -353,6 +355,8 @@ object FileHelper { | |||
|         // delete track files | ||||
|         tracklistElement.trackUriString.toUri().toFile().delete() | ||||
|         tracklistElement.gpxUriString.toUri().toFile().delete() | ||||
|         // subtract track length from total distance | ||||
|         tracklist.totalDistanceAll -= tracklistElement.length | ||||
|         // remove track element from list | ||||
|         tracklist.tracklistElements.removeIf { TrackHelper.getTrackId(it) == TrackHelper.getTrackId(tracklistElement) } | ||||
|         saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) | ||||
|  |  | |||
|  | @ -87,19 +87,19 @@ object PreferencesHelper { | |||
|         return sharedPreferences.getBoolean(Keys.PREF_GPS_ONLY, false) | ||||
|     } | ||||
| 
 | ||||
|     /* Loads accuracy threshold used to determine if location is good enough */ | ||||
|     fun loadAccuracyThreshold(): Int { | ||||
|         // load tracking state | ||||
|         return sharedPreferences.getInt(Keys.PREF_LOCATION_ACCURACY_THRESHOLD, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY) | ||||
|     } | ||||
| //    /* Loads accuracy threshold used to determine if location is good enough */ | ||||
| //    fun loadAccuracyThreshold(): Int { | ||||
| //        // load tracking state | ||||
| //        return sharedPreferences.getInt(Keys.PREF_LOCATION_ACCURACY_THRESHOLD, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY) | ||||
| //    } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /* Loads state of recording accuracy */ | ||||
|     fun loadRecordingAccuracyHigh(): Boolean { | ||||
|         // load current setting | ||||
|         return sharedPreferences.getBoolean(Keys.PREF_RECORDING_ACCURACY_HIGH, false) | ||||
|     } | ||||
| //    /* Loads state of recording accuracy */ | ||||
| //    fun loadRecordingAccuracyHigh(): Boolean { | ||||
| //        // load current setting | ||||
| //        return sharedPreferences.getBoolean(Keys.PREF_RECORDING_ACCURACY_HIGH, false) | ||||
| //    } | ||||
| 
 | ||||
| 
 | ||||
|     /* Loads current accuracy multiplier */ | ||||
|  | @ -111,11 +111,11 @@ object PreferencesHelper { | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* Load altitude smoothing value */ | ||||
|     fun loadAltitudeSmoothingValue(): Int { | ||||
|         // load current setting | ||||
|         return sharedPreferences.getInt(Keys.PREF_ALTITUDE_SMOOTHING_VALUE, Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) | ||||
|     } | ||||
| //    /* Load altitude smoothing value */ | ||||
| //    fun loadAltitudeSmoothingValue(): Int { | ||||
| //        // load current setting | ||||
| //        return sharedPreferences.getInt(Keys.PREF_ALTITUDE_SMOOTHING_VALUE, Keys.DEFAULT_ALTITUDE_SMOOTHING_VALUE) | ||||
| //    } | ||||
| 
 | ||||
| 
 | ||||
|     /* Loads the state of a map */ | ||||
|  |  | |||
|  | @ -44,13 +44,11 @@ object TrackHelper { | |||
| 
 | ||||
| 
 | ||||
|     /* Returns unique ID for Track - currently the start date */ | ||||
|     fun getTrackId(track: Track): Long = | ||||
|         track.recordingStart.time | ||||
|     fun getTrackId(track: Track): Long = track.recordingStart.time | ||||
| 
 | ||||
| 
 | ||||
|     /* Returns unique ID for TracklistElement - currently the start date */ | ||||
|     fun getTrackId(tracklistElement: TracklistElement): Long = | ||||
|         tracklistElement.date.time | ||||
|     fun getTrackId(tracklistElement: TracklistElement): Long = tracklistElement.date.time | ||||
| 
 | ||||
| 
 | ||||
|     /* Adds given locatiom as waypoint to track */ | ||||
|  | @ -258,20 +256,20 @@ object TrackHelper { | |||
|     fun calculateAndSaveTrackTotals(context: Context, tracklist: Tracklist) { | ||||
|         CoroutineScope(IO).launch { | ||||
|             var totalDistanceAll: Float = 0f | ||||
|             var totalDurationAll: Long = 0L | ||||
|             var totalRecordingPausedAll: Long = 0L | ||||
|             var totalStepCountAll: Float = 0f | ||||
| //            var totalDurationAll: Long = 0L | ||||
| //            var totalRecordingPausedAll: Long = 0L | ||||
| //            var totalStepCountAll: Float = 0f | ||||
|             tracklist.tracklistElements.forEach { tracklistElement -> | ||||
|                 val track: Track = FileHelper.readTrack(context, tracklistElement.trackUriString.toUri()) | ||||
|                 totalDistanceAll += track.length | ||||
|                 totalDurationAll += track.duration | ||||
|                 totalRecordingPausedAll += track.recordingPaused | ||||
|                 totalStepCountAll += track.stepCount | ||||
| //                totalDurationAll += track.duration | ||||
| //                totalRecordingPausedAll += track.recordingPaused | ||||
| //                totalStepCountAll += track.stepCount | ||||
|             } | ||||
|             tracklist.totalDistanceAll = totalDistanceAll | ||||
|             tracklist.totalDurationAll = totalDurationAll | ||||
|             tracklist.totalRecordingPausedAll = totalRecordingPausedAll | ||||
|             tracklist.totalStepCountAll = totalStepCountAll | ||||
| //            tracklist.totalDurationAll = totalDurationAll | ||||
| //            tracklist.totalRecordingPausedAll = totalRecordingPausedAll | ||||
| //            tracklist.totalStepCountAll = totalStepCountAll | ||||
|             FileHelper.saveTracklistSuspended(context, tracklist, GregorianCalendar.getInstance().time) | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ import androidx.core.content.ContextCompat | |||
| import androidx.recyclerview.widget.ItemTouchHelper | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import org.y20k.trackbook.R | ||||
| import org.y20k.trackbook.tracklist.TracklistAdapter | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
|  | @ -101,6 +102,12 @@ object UiHelper { | |||
|             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) { | ||||
|             val itemView = viewHolder.itemView | ||||
|             val itemHeight = itemView.bottom - itemView.top | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import androidx.recyclerview.widget.RecyclerView | |||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.Dispatchers.IO | ||||
| import kotlinx.coroutines.Dispatchers.Main | ||||
| import org.y20k.trackbook.Keys | ||||
| import org.y20k.trackbook.R | ||||
| import org.y20k.trackbook.core.Tracklist | ||||
| import org.y20k.trackbook.core.TracklistElement | ||||
|  | @ -78,49 +79,89 @@ class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<Re | |||
| 
 | ||||
|     /* Overrides onCreateViewHolder from RecyclerView.Adapter */ | ||||
|     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 */ | ||||
|     override fun getItemCount(): Int { | ||||
|         return tracklist.tracklistElements.size | ||||
|         // +1 ==> the total statistics element | ||||
|         return tracklist.tracklistElements.size + 1 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* Overrides onBindViewHolder from RecyclerView.Adapter */ | ||||
|     override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { | ||||
|         val trackElementViewHolder: TrackElementViewHolder = holder as TrackElementViewHolder | ||||
|         trackElementViewHolder.trackNameView.text = tracklist.tracklistElements[position].name | ||||
|         trackElementViewHolder.trackDataView.text = createTrackDataString(position) | ||||
|         when (tracklist.tracklistElements[position].starred) { | ||||
|             true -> trackElementViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_filled_24dp)) | ||||
|             false -> trackElementViewHolder.starButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_star_outline_24dp)) | ||||
|         } | ||||
|         trackElementViewHolder.trackElement.setOnClickListener { | ||||
|             tracklistListener.onTrackElementTapped(tracklist.tracklistElements[position]) | ||||
|         } | ||||
|         trackElementViewHolder.starButton.setOnClickListener { | ||||
|             toggleStarred(it, position) | ||||
| 
 | ||||
|         when (holder) { | ||||
| 
 | ||||
|             // CASE STATISTICS ELEMENT | ||||
|             is ElementStatisticsViewHolder -> { | ||||
|                 val elementStatisticsViewHolder: ElementStatisticsViewHolder = holder as ElementStatisticsViewHolder | ||||
|                 elementStatisticsViewHolder.totalDistanceView.text = LengthUnitHelper.convertDistanceToString(tracklist.totalDistanceAll, useImperial) | ||||
|             } | ||||
| 
 | ||||
|             // CASE TRACK ELEMENT | ||||
|             is ElementTrackViewHolder -> { | ||||
|                 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 */ | ||||
|     fun getTrackName(position: Int): String { | ||||
|         return tracklist.tracklistElements[position].name | ||||
|     fun getTrackName(positionInRecyclerView: Int): String { | ||||
|         // first position is always the statistics element | ||||
|         return tracklist.tracklistElements[positionInRecyclerView - 1].name | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* Removes track and track files for given position - used by TracklistFragment */ | ||||
|     fun removeTrackAtPosition(context: Context, position: Int) { | ||||
|         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 | ||||
|             withContext(Main) { | ||||
|                 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 { | ||||
|             // reload tracklist //todo check if necessary | ||||
| //            tracklist = FileHelper.readTracklist(context) | ||||
|             val position: Int = findPosition(trackId) | ||||
|             val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, position, tracklist) } | ||||
|             val positionInTracklist: Int = findPosition(trackId) | ||||
|             val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, positionInTracklist, tracklist) } | ||||
|             // wait for result and store in tracklist | ||||
|             withContext(Main) { | ||||
|                 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 */ | ||||
|     private fun findPosition(trackId: Long): Int { | ||||
|         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 | ||||
|      */ | ||||
|     private inner class TrackElementViewHolder (trackElementLayout: View): RecyclerView.ViewHolder(trackElementLayout) { | ||||
|         val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element) | ||||
|         val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name) | ||||
|         val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data) | ||||
|         val starButton: ImageButton = trackElementLayout.findViewById(R.id.star_button) | ||||
|     inner class ElementTrackViewHolder (elementTrackLayout: View): RecyclerView.ViewHolder(elementTrackLayout) { | ||||
|         val trackElement: ConstraintLayout = elementTrackLayout.findViewById(R.id.track_element) | ||||
|         val trackNameView: TextView = elementTrackLayout.findViewById(R.id.track_name) | ||||
|         val trackDataView: TextView = elementTrackLayout.findViewById(R.id.track_data) | ||||
|         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 | ||||
|  |  | |||
							
								
								
									
										33
									
								
								app/src/main/res/layout/element_statistics.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/src/main/res/layout/element_statistics.xml
									
									
									
									
									
										Normal 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> | ||||
|  | @ -72,6 +72,8 @@ | |||
|     <!-- Track Tab Onboarding --> | ||||
|     <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> | ||||
|     <!-- Track List --> | ||||
|     <string name="track_list_p_element_statistics">Total Distance Recorded</string> | ||||
|     <!-- Settings --> | ||||
|     <string name="pref_about_title">About</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_name" translatable="false">July 20, 1969</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> | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| buildscript { | ||||
|     ext { | ||||
|         kotlin_version = '1.5.20' | ||||
|         kotlin_version = '1.5.30' | ||||
|         navigation_version = '2.3.3' | ||||
|     } | ||||
|     repositories { | ||||
|  | @ -10,7 +10,7 @@ buildscript { | |||
|         mavenCentral() | ||||
|     } | ||||
|     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 "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,5 @@ | |||
| distributionBase=GRADLE_USER_HOME | ||||
| 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 | ||||
| zipStorePath=wrapper/dists | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 y20k
						y20k