Reverts code style changes introduced in 4dcea979 and 66f4865d

master
y20k 2020-08-03 11:39:05 +02:00
parent c4d6a0479b
commit 7d47068bb0
No known key found for this signature in database
GPG Key ID: 824D4259F41FAFF6
18 changed files with 294 additions and 681 deletions

View File

@ -63,7 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val screen = preferenceManager.createPreferenceScreen(context) val screen = preferenceManager.createPreferenceScreen(context)
// set up "Restrict to GPS" preference // set up "Restrict to GPS" preference
val preferenceGpsOnly = SwitchPreferenceCompat(activity as Context) val preferenceGpsOnly: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
preferenceGpsOnly.title = getString(R.string.pref_gps_only_title) preferenceGpsOnly.title = getString(R.string.pref_gps_only_title)
preferenceGpsOnly.setIcon(R.drawable.ic_gps_24dp) preferenceGpsOnly.setIcon(R.drawable.ic_gps_24dp)
preferenceGpsOnly.key = Keys.PREF_GPS_ONLY preferenceGpsOnly.key = Keys.PREF_GPS_ONLY
@ -72,41 +72,26 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceGpsOnly.setDefaultValue(false) preferenceGpsOnly.setDefaultValue(false)
// set up "Use Imperial Measurements" preference // set up "Use Imperial Measurements" preference
val preferenceImperialMeasurementUnits = SwitchPreferenceCompat(activity as Context) val preferenceImperialMeasurementUnits: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
preferenceImperialMeasurementUnits.title = preferenceImperialMeasurementUnits.title = getString(R.string.pref_imperial_measurement_units_title)
getString(R.string.pref_imperial_measurement_units_title)
preferenceImperialMeasurementUnits.setIcon(R.drawable.ic_square_foot_24px) preferenceImperialMeasurementUnits.setIcon(R.drawable.ic_square_foot_24px)
preferenceImperialMeasurementUnits.key = Keys.PREF_USE_IMPERIAL_UNITS preferenceImperialMeasurementUnits.key = Keys.PREF_USE_IMPERIAL_UNITS
preferenceImperialMeasurementUnits.summaryOn = preferenceImperialMeasurementUnits.summaryOn = getString(R.string.pref_imperial_measurement_units_summary_imperial)
getString(R.string.pref_imperial_measurement_units_summary_imperial) preferenceImperialMeasurementUnits.summaryOff = getString(R.string.pref_imperial_measurement_units_summary_metric)
preferenceImperialMeasurementUnits.summaryOff =
getString(R.string.pref_imperial_measurement_units_summary_metric)
preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits()) preferenceImperialMeasurementUnits.setDefaultValue(LengthUnitHelper.useImperialUnits())
// set up "App Theme" preference // set up "App Theme" preference
val preferenceThemeSelection = ListPreference(activity as Context) val preferenceThemeSelection: ListPreference = ListPreference(activity as Context)
preferenceThemeSelection.title = getString(R.string.pref_theme_selection_title) preferenceThemeSelection.title = getString(R.string.pref_theme_selection_title)
preferenceThemeSelection.setIcon(R.drawable.ic_smartphone_24dp) preferenceThemeSelection.setIcon(R.drawable.ic_smartphone_24dp)
preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION
preferenceThemeSelection.summary = preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme(activity as Context)}"
"${getString(R.string.pref_theme_selection_summary)} ${AppThemeHelper.getCurrentTheme( preferenceThemeSelection.entries = arrayOf(getString(R.string.pref_theme_selection_mode_device_default), getString(R.string.pref_theme_selection_mode_light), getString(R.string.pref_theme_selection_mode_dark))
activity as Context preferenceThemeSelection.entryValues = arrayOf(Keys.STATE_THEME_FOLLOW_SYSTEM, Keys.STATE_THEME_LIGHT_MODE, Keys.STATE_THEME_DARK_MODE)
)}"
preferenceThemeSelection.entries = arrayOf(
getString(R.string.pref_theme_selection_mode_device_default),
getString(R.string.pref_theme_selection_mode_light),
getString(R.string.pref_theme_selection_mode_dark)
)
preferenceThemeSelection.entryValues = arrayOf(
Keys.STATE_THEME_FOLLOW_SYSTEM,
Keys.STATE_THEME_LIGHT_MODE,
Keys.STATE_THEME_DARK_MODE
)
preferenceThemeSelection.setOnPreferenceChangeListener { preference, newValue -> preferenceThemeSelection.setOnPreferenceChangeListener { preference, newValue ->
if (preference is ListPreference) { if (preference is ListPreference) {
val index: Int = preference.entryValues.indexOf(newValue) val index: Int = preference.entryValues.indexOf(newValue)
preferenceThemeSelection.summary = preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${preference.entries.get(index)}"
"${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}"
return@setOnPreferenceChangeListener true return@setOnPreferenceChangeListener true
} else { } else {
return@setOnPreferenceChangeListener false return@setOnPreferenceChangeListener false
@ -114,22 +99,17 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
} }
// set up "Delete Non-Starred" preference // set up "Delete Non-Starred" preference
val preferenceDeleteNonStarred = Preference(activity as Context) val preferenceDeleteNonStarred: Preference = Preference(activity as Context)
preferenceDeleteNonStarred.title = getString(R.string.pref_delete_non_starred_title) preferenceDeleteNonStarred.title = getString(R.string.pref_delete_non_starred_title)
preferenceDeleteNonStarred.setIcon(R.drawable.ic_delete_24dp) preferenceDeleteNonStarred.setIcon(R.drawable.ic_delete_24dp)
preferenceDeleteNonStarred.summary = getString(R.string.pref_delete_non_starred_summary) preferenceDeleteNonStarred.summary = getString(R.string.pref_delete_non_starred_summary)
preferenceDeleteNonStarred.setOnPreferenceClickListener { preferenceDeleteNonStarred.setOnPreferenceClickListener{
YesNoDialog(this as YesNoDialog.YesNoDialogListener).show( YesNoDialog(this as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_NON_STARRED, message = R.string.dialog_yes_no_message_delete_non_starred, yesButton = R.string.dialog_yes_no_positive_button_delete_non_starred)
context = activity as Context,
type = Keys.DIALOG_DELETE_NON_STARRED,
message = R.string.dialog_yes_no_message_delete_non_starred,
yesButton = R.string.dialog_yes_no_positive_button_delete_non_starred
)
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "Accuracy Threshold" preference // set up "Accuracy Threshold" preference
val preferenceAccuracyThreshold = SeekBarPreference(activity as Context) val preferenceAccuracyThreshold: SeekBarPreference = SeekBarPreference(activity as Context)
preferenceAccuracyThreshold.title = getString(R.string.pref_accuracy_threshold_title) preferenceAccuracyThreshold.title = getString(R.string.pref_accuracy_threshold_title)
preferenceAccuracyThreshold.setIcon(R.drawable.ic_timeline_24dp) preferenceAccuracyThreshold.setIcon(R.drawable.ic_timeline_24dp)
preferenceAccuracyThreshold.key = Keys.PREF_LOCATION_ACCURACY_THRESHOLD preferenceAccuracyThreshold.key = Keys.PREF_LOCATION_ACCURACY_THRESHOLD
@ -139,39 +119,32 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceAccuracyThreshold.setDefaultValue(Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY) preferenceAccuracyThreshold.setDefaultValue(Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)
// set up "Reset" preference // set up "Reset" preference
val preferenceResetAdvanced = Preference(activity as Context) val preferenceResetAdvanced: Preference = Preference(activity as Context)
preferenceResetAdvanced.title = getString(R.string.pref_reset_advanced_title) preferenceResetAdvanced.title = getString(R.string.pref_reset_advanced_title)
preferenceResetAdvanced.setIcon(R.drawable.ic_undo_24dp) preferenceResetAdvanced.setIcon(R.drawable.ic_undo_24dp)
preferenceResetAdvanced.summary = getString(R.string.pref_reset_advanced_summary) preferenceResetAdvanced.summary = getString(R.string.pref_reset_advanced_summary)
preferenceResetAdvanced.setOnPreferenceClickListener { preferenceResetAdvanced.setOnPreferenceClickListener{
preferenceAccuracyThreshold.value = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY preferenceAccuracyThreshold.value = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "App Version" preference // set up "App Version" preference
val preferenceAppVersion = Preference(context) val preferenceAppVersion: Preference = Preference(context)
preferenceAppVersion.title = getString(R.string.pref_app_version_title) preferenceAppVersion.title = getString(R.string.pref_app_version_title)
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp) preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
preferenceAppVersion.summary = preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString(
"${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME} (${getString( R.string.app_version_name)})"
R.string.app_version_name
)})"
preferenceAppVersion.setOnPreferenceClickListener { preferenceAppVersion.setOnPreferenceClickListener {
// copy to clipboard // copy to clipboard
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary) val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
val cm: ClipboardManager = val cm: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(clip) cm.setPrimaryClip(clip)
Toast.makeText( Toast.makeText(activity as Context, R.string.toast_message_copied_to_clipboard, Toast.LENGTH_LONG).show()
activity as Context,
R.string.toast_message_copied_to_clipboard,
Toast.LENGTH_LONG
).show()
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "Report Issue" preference // set up "Report Issue" preference
val preferenceReportIssue = Preference(context) val preferenceReportIssue: Preference = Preference(context)
preferenceReportIssue.title = getString(R.string.pref_report_issue_title) preferenceReportIssue.title = getString(R.string.pref_report_issue_title)
preferenceReportIssue.setIcon(R.drawable.ic_bug_report_24dp) preferenceReportIssue.setIcon(R.drawable.ic_bug_report_24dp)
preferenceReportIssue.summary = getString(R.string.pref_report_issue_summary) preferenceReportIssue.summary = getString(R.string.pref_report_issue_summary)
@ -186,21 +159,20 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
} }
// set preference categories // set preference categories
val preferenceCategoryGeneral = PreferenceCategory(activity as Context) val preferenceCategoryGeneral: PreferenceCategory = PreferenceCategory(activity as Context)
preferenceCategoryGeneral.title = getString(R.string.pref_general_title) preferenceCategoryGeneral.title = getString(R.string.pref_general_title)
preferenceCategoryGeneral.contains(preferenceImperialMeasurementUnits) preferenceCategoryGeneral.contains(preferenceImperialMeasurementUnits)
preferenceCategoryGeneral.contains(preferenceGpsOnly) preferenceCategoryGeneral.contains(preferenceGpsOnly)
val preferenceCategoryMaintenance = val preferenceCategoryMaintenance: PreferenceCategory = PreferenceCategory(activity as Context)
PreferenceCategory(activity as Context)
preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title) preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title)
preferenceCategoryMaintenance.contains(preferenceDeleteNonStarred) preferenceCategoryMaintenance.contains(preferenceDeleteNonStarred)
val preferenceCategoryAdvanced = 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(preferenceAccuracyThreshold) preferenceCategoryAdvanced.contains(preferenceAccuracyThreshold)
preferenceCategoryAdvanced.contains(preferenceResetAdvanced) preferenceCategoryAdvanced.contains(preferenceResetAdvanced)
val preferenceCategoryAbout = PreferenceCategory(context) val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
preferenceCategoryAbout.title = getString(R.string.pref_about_title) preferenceCategoryAbout.title = getString(R.string.pref_about_title)
preferenceCategoryAbout.contains(preferenceAppVersion) preferenceCategoryAbout.contains(preferenceAppVersion)
preferenceCategoryAbout.contains(preferenceReportIssue) preferenceCategoryAbout.contains(preferenceReportIssue)
@ -223,12 +195,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* Overrides onYesNoDialog from YesNoDialogListener */ /* Overrides onYesNoDialog from YesNoDialogListener */
override fun onYesNoDialog( override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
type: Int,
dialogResult: Boolean,
payload: Int,
payloadString: String
) {
when (type) { when (type) {
Keys.DIALOG_DELETE_NON_STARRED -> { Keys.DIALOG_DELETE_NON_STARRED -> {
when (dialogResult) { when (dialogResult) {
@ -246,13 +213,12 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* Removes track and track files for given position - used by TracklistFragment */ /* Removes track and track files for given position - used by TracklistFragment */
private fun deleteNonStarred(context: Context) { fun deleteNonStarred(context: Context) {
val backgroundJob = Job() val backgroundJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob) val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
uiScope.launch { uiScope.launch {
var tracklist: Tracklist = FileHelper.readTracklist(context) var tracklist: Tracklist = FileHelper.readTracklist(context)
val deferred: Deferred<Tracklist> = val deferred: Deferred<Tracklist> = async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
async { FileHelper.deleteNonStarredSuspended(context, tracklist) }
// wait for result and store in tracklist // wait for result and store in tracklist
tracklist = deferred.await() tracklist = deferred.await()
backgroundJob.cancel() backgroundJob.cancel()

View File

@ -24,9 +24,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -48,8 +46,7 @@ import org.y20k.trackbook.helpers.TrackHelper
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlay.MarkerListener {
YesNoDialog.YesNoDialogListener, MapOverlay.MarkerListener {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java) private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
@ -64,30 +61,19 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// get track // get track
val fileUriString: String = val fileUriString: String = arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String()
arguments?.getString(Keys.ARG_TRACK_FILE_URI, String()) ?: String() if (fileUriString.isNotBlank()) {
track = if (fileUriString.isNotBlank()) { track = FileHelper.readTrack(activity as Context, Uri.parse(fileUriString))
FileHelper.readTrack(Uri.parse(fileUriString))
} else { } else {
Track() track = Track()
} }
} }
/* Overrides onCreateView from Fragment */ /* Overrides onCreateView from Fragment */
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// initialize layout // initialize layout
layout = TrackFragmentLayoutHolder( layout = TrackFragmentLayoutHolder(activity as Context, this as MapOverlay.MarkerListener, inflater, container, track)
activity as Context,
this as MapOverlay.MarkerListener,
inflater,
container,
track
)
// set up share button // set up share button
layout.shareButton.setOnClickListener { layout.shareButton.setOnClickListener {
@ -95,31 +81,18 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
} }
layout.shareButton.setOnLongClickListener { layout.shareButton.setOnLongClickListener {
val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator val v = (activity as Context).getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(50)
v.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
v.vibrate(50)
}
shareGpxTrack() shareGpxTrack()
return@setOnLongClickListener true return@setOnLongClickListener true
} }
// set up delete button // set up delete button
layout.deleteButton.setOnClickListener { layout.deleteButton.setOnClickListener {
val dialogMessage = val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}"
"${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${layout.trackNameView.text}" YesNoDialog(this@TrackFragment 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)
YesNoDialog(this@TrackFragment 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
)
} }
// set up rename button // set up rename button
layout.editButton.setOnClickListener { layout.editButton.setOnClickListener {
RenameTrackDialog(this as RenameTrackDialog.RenameTrackListener).show( RenameTrackDialog(this as RenameTrackDialog.RenameTrackListener).show(activity as Context, layout.trackNameView.text.toString())
activity as Context,
layout.trackNameView.text.toString()
)
} }
return layout.rootView return layout.rootView
@ -152,18 +125,8 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
val targetUri: Uri? = data.data val targetUri: Uri? = data.data
if (targetUri != null) { if (targetUri != null) {
// copy file async (= fire & forget - no return value needed) // copy file async (= fire & forget - no return value needed)
GlobalScope.launch { GlobalScope.launch { FileHelper.saveCopyOfFileSuspended( activity as Context, originalFileUri = sourceUri, targetFileUri = targetUri) }
FileHelper.saveCopyOfFileSuspended( Toast.makeText(activity as Context, R.string.toast_message_save_gpx, Toast.LENGTH_LONG).show()
activity as Context,
originalFileUri = sourceUri,
targetFileUri = targetUri
)
}
Toast.makeText(
activity as Context,
R.string.toast_message_save_gpx,
Toast.LENGTH_LONG
).show()
} }
} }
} }
@ -176,13 +139,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
/* Overrides onRenameTrackDialog from RenameTrackDialog */ /* Overrides onRenameTrackDialog from RenameTrackDialog */
override fun onRenameTrackDialog(textInput: String) { override fun onRenameTrackDialog(textInput: String) {
// rename track async (= fire & forget - no return value needed) // rename track async (= fire & forget - no return value needed)
GlobalScope.launch { GlobalScope.launch { FileHelper.renameTrackSuspended(activity as Context, layout.track, textInput) }
FileHelper.renameTrackSuspended(
activity as Context,
layout.track,
textInput
)
}
// update name in layout // update name in layout
layout.track.name = textInput layout.track.name = textInput
layout.trackNameView.text = textInput layout.trackNameView.text = textInput
@ -190,12 +147,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
/* Overrides onYesNoDialog from YesNoDialogListener */ /* Overrides onYesNoDialog from YesNoDialogListener */
override fun onYesNoDialog( override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
type: Int,
dialogResult: Boolean,
payload: Int,
payloadString: String
) {
when (type) { when (type) {
Keys.DIALOG_DELETE_TRACK -> { Keys.DIALOG_DELETE_TRACK -> {
when (dialogResult) { when (dialogResult) {
@ -237,11 +189,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
if (packageManager != null && intent.resolveActivity(packageManager) != null) { if (packageManager != null && intent.resolveActivity(packageManager) != null) {
startActivityForResult(intent, Keys.REQUEST_SAVE_GPX) startActivityForResult(intent, Keys.REQUEST_SAVE_GPX)
} else { } else {
Toast.makeText( Toast.makeText(activity as Context, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
activity as Context,
R.string.toast_message_install_file_helper,
Toast.LENGTH_LONG
).show()
} }
} }
@ -249,11 +197,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
/* Share track as GPX via share sheet */ /* Share track as GPX via share sheet */
private fun shareGpxTrack() { private fun shareGpxTrack() {
val gpxFile = Uri.parse(layout.track.gpxUriString).toFile() val gpxFile = Uri.parse(layout.track.gpxUriString).toFile()
val gpxShareUri = FileProvider.getUriForFile( val gpxShareUri = FileProvider.getUriForFile(this.activity as Context, "${requireActivity().applicationContext.packageName}.provider", gpxFile)
this.activity as Context,
"${requireActivity().applicationContext.packageName}.provider",
gpxFile
)
val shareIntent: Intent = Intent.createChooser(Intent().apply { val shareIntent: Intent = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND action = Intent.ACTION_SEND
data = gpxShareUri data = gpxShareUri
@ -267,8 +211,7 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener,
if (packageManager != null && shareIntent.resolveActivity(packageManager) != null) { if (packageManager != null && shareIntent.resolveActivity(packageManager) != null) {
startActivity(shareIntent) startActivity(shareIntent)
} else { } else {
Toast.makeText(activity, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG) Toast.makeText(activity, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
.show()
} }
} }

View File

@ -48,7 +48,7 @@ import kotlin.coroutines.CoroutineContext
/* /*
* TrackerService class * TrackerService class
*/ */
class TrackerService : Service(), CoroutineScope, SensorEventListener { class TrackerService(): Service(), CoroutineScope, SensorEventListener {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java) private val TAG: String = LogHelper.makeLogTag(TrackerService::class.java)
@ -58,16 +58,16 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
var trackingState: Int = Keys.STATE_TRACKING_NOT var trackingState: Int = Keys.STATE_TRACKING_NOT
var gpsProviderActive: Boolean = false var gpsProviderActive: Boolean = false
var networkProviderActive: Boolean = false var networkProviderActive: Boolean = false
private var useImperial: Boolean = false var useImperial: Boolean = false
private var gpsOnly: Boolean = false var gpsOnly: Boolean = false
var locationAccuracyThreshold: Int = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY var locationAccuracyThreshold: Int = Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY
var currentBestLocation: Location = LocationHelper.getDefaultLocation() var currentBestLocation: Location = LocationHelper.getDefaultLocation()
private var stepCountOffset: Float = 0f var stepCountOffset: Float = 0f
var resumed: Boolean = false var resumed: Boolean = false
var track: Track = Track() var track: Track = Track()
private var gpsLocationListenerRegistered: Boolean = false var gpsLocationListenerRegistered: Boolean = false
private var networkLocationListenerRegistered: Boolean = false var networkLocationListenerRegistered: Boolean = false
private 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()
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
@ -99,10 +99,9 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
networkLocationListener = createLocationListener() networkLocationListener = createLocationListener()
trackingState = PreferencesHelper.loadTrackingState(this) trackingState = PreferencesHelper.loadTrackingState(this)
currentBestLocation = LocationHelper.getLastKnownLocation(this) currentBestLocation = LocationHelper.getLastKnownLocation(this)
track = FileHelper.readTrack(FileHelper.getTempFileUri(this)) track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
backgroundJob = Job() backgroundJob = Job()
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
} }
@ -112,19 +111,16 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
// SERVICE RESTART (via START_STICKY) // SERVICE RESTART (via START_STICKY)
if (intent == null) { if (intent == null) {
if (trackingState == Keys.STATE_TRACKING_ACTIVE) { if (trackingState == Keys.STATE_TRACKING_ACTIVE) {
LogHelper.w( LogHelper.w(TAG, "Trackbook has been killed by the operating system. Trying to resume recording.")
TAG,
"Trackbook has been killed by the operating system. Trying to resume recording."
)
resumeTracking() resumeTracking()
} }
// ACTION STOP // ACTION STOP
} else if (Keys.ACTION_STOP == intent.action) { } else if (Keys.ACTION_STOP == intent.action) {
stopTracking() stopTracking()
// ACTION START // ACTION START
} else if (Keys.ACTION_START == intent.action) { } else if (Keys.ACTION_START == intent.action) {
startTracking() startTracking()
// ACTION RESUME // ACTION RESUME
} else if (Keys.ACTION_RESUME == intent.action) { } else if (Keys.ACTION_RESUME == intent.action) {
resumeTracking() resumeTracking()
} }
@ -176,8 +172,7 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
// remove notification // remove notification
stopForeground(true) stopForeground(true)
// stop listening for changes in shared preferences // stop listening for changes in shared preferences
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
// stop receiving location updates // stop receiving location updates
removeGpsLocationListener() removeGpsLocationListener()
removeNetworkLocationListener() removeNetworkLocationListener()
@ -194,12 +189,11 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
/* Overrides onSensorChanged from SensorEventListener */ /* Overrides onSensorChanged from SensorEventListener */
override fun onSensorChanged(sensorEvent: SensorEvent?) { override fun onSensorChanged(sensorEvent: SensorEvent?) {
var steps = 0f var steps: Float = 0f
if (sensorEvent != null) { if (sensorEvent != null) {
if (stepCountOffset == 0f) { if (stepCountOffset == 0f) {
// store steps previously recorded by the system // store steps previously recorded by the system
stepCountOffset = stepCountOffset = (sensorEvent.values[0] - 1) - track.stepCount // subtract any steps recorded during this session in case the app was killed
(sensorEvent.values[0] - 1) - track.stepCount // subtract any steps recorded during this session in case the app was killed
} }
// calculate step count - subtract steps previously recorded // calculate step count - subtract steps previously recorded
steps = sensorEvent.values[0] - stepCountOffset steps = sensorEvent.values[0] - stepCountOffset
@ -212,17 +206,16 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
/* Resume tracking after stop/pause */ /* Resume tracking after stop/pause */
fun resumeTracking() { fun resumeTracking() {
// load temp track - returns an empty track if not available // load temp track - returns an empty track if not available
track = FileHelper.readTrack(FileHelper.getTempFileUri(this)) track = FileHelper.readTrack(this, FileHelper.getTempFileUri(this))
// try to mark last waypoint as stopover // try to mark last waypoint as stopover
if (track.wayPoints.size > 0) { if (track.wayPoints.size > 0) {
val lastWayPointIndex = track.wayPoints.size - 1 val lastWayPointIndex = track.wayPoints.size - 1
track.wayPoints[lastWayPointIndex].isStopOver = true track.wayPoints.get(lastWayPointIndex).isStopOver = true
} }
// set resumed flag // set resumed flag
resumed = true resumed = true
// calculate length of recording break // calculate length of recording break
track.recordingPaused = track.recordingPaused = track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
track.recordingPaused + TrackHelper.calculateDurationOfPause(track.recordingStop)
// start tracking // start tracking
startTracking(newTrack = false) startTracking(newTrack = false)
} }
@ -308,27 +301,20 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
currentBestLocation = location currentBestLocation = location
} }
} }
override fun onProviderEnabled(provider: String) { override fun onProviderEnabled(provider: String) {
LogHelper.v(TAG, "onProviderEnabled $provider") LogHelper.v(TAG, "onProviderEnabled $provider")
when (provider) { when (provider) {
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
LocationHelper.isGpsEnabled(locationManager) LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
LocationHelper.isNetworkEnabled(locationManager)
} }
} }
override fun onProviderDisabled(provider: String) { override fun onProviderDisabled(provider: String) {
LogHelper.v(TAG, "onProviderDisabled $provider") LogHelper.v(TAG, "onProviderDisabled $provider")
when (provider) { when (provider) {
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
LocationHelper.isGpsEnabled(locationManager) LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
LocationHelper.isNetworkEnabled(locationManager)
} }
} }
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) { override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) {
// deprecated method // deprecated method
} }
@ -343,25 +329,13 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
// check if Network provider is available // check if Network provider is available
if (gpsProviderActive) { if (gpsProviderActive) {
// check for location permission // check for location permission
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
// adds GPS location listener // adds GPS location listener
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f,gpsLocationListener)
LocationManager.GPS_PROVIDER,
0,
0f,
gpsLocationListener
)
gpsLocationListenerRegistered = true gpsLocationListenerRegistered = true
LogHelper.v(TAG, "Added GPS location listener.") LogHelper.v(TAG, "Added GPS location listener.")
} else { } else {
LogHelper.w( LogHelper.w(TAG, "Unable to add GPS location listener. Location permission is not granted.")
TAG,
"Unable to add GPS location listener. Location permission is not granted."
)
} }
} else { } else {
LogHelper.w(TAG, "Unable to add GPS location listener.") LogHelper.w(TAG, "Unable to add GPS location listener.")
@ -379,83 +353,50 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
// check if Network provider is available // check if Network provider is available
if (networkProviderActive && !gpsOnly) { if (networkProviderActive && !gpsOnly) {
// check for location permission // check for location permission
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
// adds Network location listener // adds Network location listener
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f, networkLocationListener)
LocationManager.NETWORK_PROVIDER,
0,
0f,
networkLocationListener
)
networkLocationListenerRegistered = true networkLocationListenerRegistered = true
LogHelper.v(TAG, "Added Network location listener.") LogHelper.v(TAG, "Added Network location listener.")
} else { } else {
LogHelper.w( LogHelper.w(TAG, "Unable to add Network location listener. Location permission is not granted.")
TAG,
"Unable to add Network location listener. Location permission is not granted."
)
} }
} else { } else {
LogHelper.w(TAG, "Unable to add Network location listener.") LogHelper.w(TAG, "Unable to add Network location listener.")
} }
} else { } else {
LogHelper.v( LogHelper.v(TAG, "Skipping registration. Network location listener has already been added.")
TAG,
"Skipping registration. Network location listener has already been added."
)
} }
} }
/* Adds location listeners to location manager */ /* Adds location listeners to location manager */
fun removeGpsLocationListener() { fun removeGpsLocationListener() {
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
locationManager.removeUpdates(gpsLocationListener) locationManager.removeUpdates(gpsLocationListener)
gpsLocationListenerRegistered = false gpsLocationListenerRegistered = false
LogHelper.v(TAG, "Removed GPS location listener.") LogHelper.v(TAG, "Removed GPS location listener.")
} else { } else {
LogHelper.w( LogHelper.w(TAG, "Unable to remove GPS location listener. Location permission is needed.")
TAG,
"Unable to remove GPS location listener. Location permission is needed."
)
} }
} }
/* Adds location listeners to location manager */ /* Adds location listeners to location manager */
fun removeNetworkLocationListener() { fun removeNetworkLocationListener() {
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
locationManager.removeUpdates(gpsLocationListener) locationManager.removeUpdates(gpsLocationListener)
networkLocationListenerRegistered = false networkLocationListenerRegistered = false
LogHelper.v(TAG, "Removed Network location listener.") LogHelper.v(TAG, "Removed Network location listener.")
} else { } else {
LogHelper.w( LogHelper.w(TAG, "Unable to remove Network location listener. Location permission is needed.")
TAG,
"Unable to remove Network location listener. Location permission is needed."
)
} }
} }
/* Registers a step counter listener */ /* Registers a step counter listener */
private fun startStepCounter() { private fun startStepCounter() {
val stepCounterAvailable = sensorManager.registerListener( val stepCounterAvailable = sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER), SensorManager.SENSOR_DELAY_UI)
this,
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
SensorManager.SENSOR_DELAY_UI
)
if (!stepCounterAvailable) { if (!stepCounterAvailable) {
LogHelper.w(TAG, "Pedometer sensor not available.") LogHelper.w(TAG, "Pedometer sensor not available.")
track.stepCount = -1f track.stepCount = -1f
@ -465,12 +406,7 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
/* Displays / updates notification */ /* Displays / updates notification */
private fun displayNotification(): Notification { private fun displayNotification(): Notification {
val notification: Notification = notificationHelper.createNotification( val notification: Notification = notificationHelper.createNotification(trackingState, track.length, track.duration, useImperial)
trackingState,
track.length,
track.duration,
useImperial
)
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification) notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
return notification return notification
} }
@ -479,8 +415,7 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
/* /*
* Defines the listener for changes in shared preferences * Defines the listener for changes in shared preferences
*/ */
private val sharedPreferenceChangeListener = private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
when (key) { when (key) {
// preference "Restrict to GPS" // preference "Restrict to GPS"
Keys.PREF_GPS_ONLY -> { Keys.PREF_GPS_ONLY -> {
@ -496,8 +431,7 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
} }
// preference "Accuracy Threshold" // preference "Accuracy Threshold"
Keys.PREF_LOCATION_ACCURACY_THRESHOLD -> { Keys.PREF_LOCATION_ACCURACY_THRESHOLD -> {
locationAccuracyThreshold = locationAccuracyThreshold = PreferencesHelper.loadAccuracyThreshold(this@TrackerService)
PreferencesHelper.loadAccuracyThreshold(this@TrackerService)
} }
} }
} }
@ -523,12 +457,7 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
private val periodicTrackUpdate: Runnable = object : Runnable { private val periodicTrackUpdate: Runnable = object : Runnable {
override fun run() { override fun run() {
// add waypoint to track - step count is continuously updated in onSensorChanged // add waypoint to track - step count is continuously updated in onSensorChanged
val result: Pair<Track, Boolean> = TrackHelper.addWayPointToTrack( val result: Pair<Track, Boolean> = TrackHelper.addWayPointToTrack(this@TrackerService, track, currentBestLocation, locationAccuracyThreshold, resumed)
track,
currentBestLocation,
locationAccuracyThreshold,
resumed
)
// get track from result // get track from result
track = result.first track = result.first
// check if waypoint was successfully added (= result.second) // check if waypoint was successfully added (= result.second)
@ -549,4 +478,4 @@ class TrackerService : Service(), CoroutineScope, SensorEventListener {
*/ */
} }

View File

@ -31,7 +31,7 @@ import org.y20k.trackbook.helpers.PreferencesHelper
/* /*
* TrackingToggleTileService class * TrackingToggleTileService class
*/ */
class TrackingToggleTileService : TileService() { class TrackingToggleTileService(): TileService() {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java) private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
@ -52,6 +52,11 @@ class TrackingToggleTileService : TileService() {
updateTile() updateTile()
} }
/* Overrides onTileRemoved from TileService */
override fun onTileRemoved() {
super.onTileRemoved()
}
/* Overrides onStartListening from TileService (tile becomes visible) */ /* Overrides onStartListening from TileService (tile becomes visible) */
override fun onStartListening() { override fun onStartListening() {
@ -61,8 +66,7 @@ class TrackingToggleTileService : TileService() {
// set up tile // set up tile
updateTile() updateTile()
// register listener for changes in shared preferences // register listener for changes in shared preferences
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService) PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
} }
@ -80,8 +84,13 @@ class TrackingToggleTileService : TileService() {
override fun onStopListening() { override fun onStopListening() {
super.onStopListening() super.onStopListening()
// unregister listener for changes in shared preferences // unregister listener for changes in shared preferences
PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService) PreferenceManager.getDefaultSharedPreferences(this@TrackingToggleTileService).unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener) }
/* Overrides onDestroy from Service */
override fun onDestroy() {
super.onDestroy()
} }
@ -129,18 +138,19 @@ class TrackingToggleTileService : TileService() {
/* /*
* Defines the listener for changes in shared preferences * Defines the listener for changes in shared preferences
*/ */
private val sharedPreferenceChangeListener = private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
SharedPreferences.OnSharedPreferenceChangeListener { _, key -> when (key) {
when (key) { Keys.PREF_TRACKING_STATE -> {
Keys.PREF_TRACKING_STATE -> { trackingState = PreferencesHelper.loadTrackingState(this)
trackingState = PreferencesHelper.loadTrackingState(this) updateTile()
updateTile()
}
} }
} }
}
/* /*
* End of declaration * End of declaration
*/ */
}
}

View File

@ -40,8 +40,7 @@ import org.y20k.trackbook.tracklist.TracklistAdapter
/* /*
* TracklistFragment class * TracklistFragment class
*/ */
class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, YesNoDialog.YesNoDialogListener {
YesNoDialog.YesNoDialogListener {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java) private val TAG: String = LogHelper.makeLogTag(TracklistFragment::class.java)
@ -62,11 +61,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
/* Overrides onCreateView from Fragment */ /* Overrides onCreateView from Fragment */
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// find views // find views
val rootView = inflater.inflate(R.layout.fragment_tracklist, container, false) val rootView = inflater.inflate(R.layout.fragment_tracklist, container, false)
trackElementList = rootView.findViewById(R.id.track_element_list) trackElementList = rootView.findViewById(R.id.track_element_list)
@ -82,17 +77,8 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
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
val dialogMessage = val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(adapterPosition)}"
"${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName( 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)
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
)
} }
} }
val itemTouchHelper = ItemTouchHelper(swipeHandler) val itemTouchHelper = ItemTouchHelper(swipeHandler)
@ -107,7 +93,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
/* Overrides onTrackElementTapped from TracklistElementAdapterListener */ /* Overrides onTrackElementTapped from TracklistElementAdapterListener */
override fun onTrackElementTapped(tracklistElement: TracklistElement) { override fun onTrackElementTapped(tracklistElement: TracklistElement) {
val bundle = Bundle() val bundle: Bundle = Bundle()
bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name) bundle.putString(Keys.ARG_TRACK_TITLE, tracklistElement.name)
bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString) bundle.putString(Keys.ARG_TRACK_FILE_URI, tracklistElement.trackUriString)
bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString) bundle.putString(Keys.ARG_GPX_FILE_URI, tracklistElement.gpxUriString)
@ -117,18 +103,13 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
/* Overrides onYesNoDialog from YesNoDialogListener */ /* Overrides onYesNoDialog from YesNoDialogListener */
override fun onYesNoDialog( override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
type: Int,
dialogResult: Boolean,
payload: Int,
payloadString: String
) {
when (type) { when (type) {
Keys.DIALOG_DELETE_TRACK -> { Keys.DIALOG_DELETE_TRACK -> {
when (dialogResult) { when (dialogResult) {
// user tapped remove track // user tapped remove track
true -> { true -> {
toggleOnboardingLayout(tracklistAdapter.itemCount - 1) toggleOnboardingLayout(tracklistAdapter.itemCount -1)
tracklistAdapter.removeTrack(activity as Context, payload) tracklistAdapter.removeTrack(activity as Context, payload)
} }
// user tapped cancel // user tapped cancel
@ -158,11 +139,11 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
} }
/* /*
* Inner class: custom LinearLayoutManager that overrides onLayoutCompleted * Inner class: custom LinearLayoutManager that overrides onLayoutCompleted
*/ */
inner class CustomLinearLayoutManager(context: Context) : inner class CustomLinearLayoutManager(context: Context): LinearLayoutManager(context, VERTICAL, false) {
LinearLayoutManager(context, VERTICAL, false) {
override fun supportsPredictiveItemAnimations(): Boolean { override fun supportsPredictiveItemAnimations(): Boolean {
return true return true
@ -176,7 +157,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
if (deleteTrackId != -1L) { if (deleteTrackId != -1L) {
val position: Int = tracklistAdapter.findPosition(deleteTrackId) val position: Int = tracklistAdapter.findPosition(deleteTrackId)
tracklistAdapter.removeTrack(this@TracklistFragment.activity as Context, position) tracklistAdapter.removeTrack(this@TracklistFragment.activity as Context, position)
toggleOnboardingLayout(tracklistAdapter.itemCount - 1) toggleOnboardingLayout(tracklistAdapter.itemCount -1)
} }
} }

View File

@ -43,7 +43,7 @@ data class WayPoint(@Expose val provider: String,
/* Converts WayPoint into Location */ /* Converts WayPoint into Location */
fun toLocation(): Location { fun toLocation(): Location {
val location = Location(provider) val location: Location = Location(provider)
location.latitude = latitude location.latitude = latitude
location.longitude = longitude location.longitude = longitude
location.altitude = altitude location.altitude = altitude

View File

@ -18,6 +18,7 @@
package org.y20k.trackbook.dialogs package org.y20k.trackbook.dialogs
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -37,14 +38,9 @@ object ErrorDialog {
/* Construct and show dialog */ /* Construct and show dialog */
fun show( fun show(context: Context, errorTitle: Int, errorMessage: Int, errorDetails: String = String()) {
context: Context,
errorTitle: Int,
errorMessage: Int,
errorDetails: String = String()
) {
// prepare dialog builder // prepare dialog builder
val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
// set title // set title
builder.setTitle(context.getString(errorTitle)) builder.setTitle(context.getString(errorTitle))
@ -85,14 +81,12 @@ object ErrorDialog {
errorMessageView.text = context.getString(errorMessage) errorMessageView.text = context.getString(errorMessage)
// add okay button // add okay button
builder.setPositiveButton( builder.setPositiveButton(R.string.dialog_generic_button_okay, DialogInterface.OnClickListener { _, _ ->
R.string.dialog_generic_button_okay
) { _, _ ->
// listen for click on okay button // listen for click on okay button
// do nothing // do nothing
} })
// display error dialog // display error dialog
builder.show() builder.show()
} }
} }

View File

@ -24,7 +24,7 @@ import org.y20k.trackbook.helpers.LogHelper
/* /*
* YesNoDialog class * YesNoDialog class
*/ */
class YesNoDialog(private var yesNoDialogListener: YesNoDialogListener) { class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
/* Interface used to communicate back to activity */ /* Interface used to communicate back to activity */
interface YesNoDialogListener { interface YesNoDialogListener {
@ -38,44 +38,31 @@ class YesNoDialog(private var yesNoDialogListener: YesNoDialogListener) {
/* Construct and show dialog - variant: message from string */ /* Construct and show dialog - variant: message from string */
fun show( fun show(context: Context,
context: Context, type: Int,
type: Int, title: Int = Keys.EMPTY_STRING_RESOURCE,
title: Int = Keys.EMPTY_STRING_RESOURCE, message: Int,
message: Int, yesButton: Int = R.string.dialog_yes_no_positive_button_default,
yesButton: Int = R.string.dialog_yes_no_positive_button_default, noButton: Int = R.string.dialog_generic_button_cancel,
noButton: Int = R.string.dialog_generic_button_cancel, payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) {
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING
) {
// extract string from message resource and feed into main show method // extract string from message resource and feed into main show method
show( show(context, type, title, context.getString(message), yesButton, noButton, payload, payloadString)
context,
type,
title,
context.getString(message),
yesButton,
noButton,
payload,
payloadString
)
} }
/* Construct and show dialog */ /* Construct and show dialog */
fun show( fun show(context: Context,
context: Context, type: Int,
type: Int, title: Int = Keys.EMPTY_STRING_RESOURCE,
title: Int = Keys.EMPTY_STRING_RESOURCE, messageString: String,
messageString: String, yesButton: Int = R.string.dialog_yes_no_positive_button_default,
yesButton: Int = R.string.dialog_yes_no_positive_button_default, noButton: Int = R.string.dialog_generic_button_cancel,
noButton: Int = R.string.dialog_generic_button_cancel, payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT,
payload: Int = Keys.DIALOG_EMPTY_PAYLOAD_INT, payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING) {
payloadString: String = Keys.DIALOG_EMPTY_PAYLOAD_STRING
) {
// prepare dialog builder // prepare dialog builder
val builder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme) val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)
// set title and message // set title and message
builder.setMessage(messageString) builder.setMessage(messageString)
@ -97,11 +84,11 @@ 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)
} }
// display dialog // display dialog
builder.show() builder.show()
} }
} }

View File

@ -19,15 +19,11 @@ package org.y20k.trackbook.extensions
import android.content.SharedPreferences import android.content.SharedPreferences
import java.lang.Double.doubleToRawLongBits
import java.lang.Double.longBitsToDouble
/* Puts a Double value in SharedPreferences */ /* Puts a Double value in SharedPreferences */
fun SharedPreferences.Editor.putDouble(key: String, double: Double) = fun SharedPreferences.Editor.putDouble(key: String, double: Double) = putLong(key, java.lang.Double.doubleToRawLongBits(double))
this.putLong(key, doubleToRawLongBits(double))
/* gets a Double value from SharedPreferences */ /* gets a Double value from SharedPreferences */
fun SharedPreferences.getDouble(key: String, default: Double) = fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default)))
longBitsToDouble(getLong(key, doubleToRawLongBits(default)))

View File

@ -35,8 +35,6 @@ import java.text.NumberFormat
import java.util.* import java.util.*
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlin.math.ln
import kotlin.math.pow
/* /*
@ -50,10 +48,10 @@ object FileHelper {
/* Return an InputStream for given Uri */ /* Return an InputStream for given Uri */
fun getTextFileStream(context: Context, uri: Uri): InputStream? { fun getTextFileStream(context: Context, uri: Uri): InputStream? {
var stream: InputStream? = null var stream : InputStream? = null
try { try {
stream = context.contentResolver.openInputStream(uri) stream = context.contentResolver.openInputStream(uri)
} catch (e: Exception) { } catch (e : Exception) {
e.printStackTrace() e.printStackTrace()
} }
return stream return stream
@ -63,14 +61,14 @@ object FileHelper {
/* Get file size for given Uri */ /* Get file size for given Uri */
fun getFileSize(context: Context, uri: Uri): Long { fun getFileSize(context: Context, uri: Uri): Long {
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null) val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
return if (cursor != null) { if (cursor != null) {
val sizeIndex: Int = cursor.getColumnIndex(OpenableColumns.SIZE) val sizeIndex: Int = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst() cursor.moveToFirst()
val size: Long = cursor.getLong(sizeIndex) val size: Long = cursor.getLong(sizeIndex)
cursor.close() cursor.close()
size return size
} else { } else {
0L return 0L
} }
} }
@ -78,14 +76,14 @@ object FileHelper {
/* Get file name for given Uri */ /* Get file name for given Uri */
fun getFileName(context: Context, uri: Uri): String { fun getFileName(context: Context, uri: Uri): String {
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null) val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
return if (cursor != null) { if (cursor != null) {
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst() cursor.moveToFirst()
val name: String = cursor.getString(nameIndex) val name: String = cursor.getString(nameIndex)
cursor.close() cursor.close()
name return name
} else { } else {
String() return String()
} }
} }
@ -112,8 +110,8 @@ object FileHelper {
fun readTracklist(context: Context): Tracklist { fun readTracklist(context: Context): Tracklist {
LogHelper.v(TAG, "Reading Tracklist - Thread: ${Thread.currentThread().name}") LogHelper.v(TAG, "Reading Tracklist - Thread: ${Thread.currentThread().name}")
// get JSON from text file // get JSON from text file
val json: String = readTextFile(getTracklistFileUri(context)) val json: String = readTextFile(context, getTracklistFileUri(context))
var tracklist = Tracklist() var tracklist: Tracklist = Tracklist()
when (json.isNotBlank()) { when (json.isNotBlank()) {
// convert JSON and return as tracklist // convert JSON and return as tracklist
true -> try { true -> try {
@ -127,10 +125,10 @@ object FileHelper {
/* Reads track from storage using GSON */ /* Reads track from storage using GSON */
fun readTrack(fileUri: Uri): Track { fun readTrack(context: Context, fileUri: Uri): Track {
// get JSON from text file // get JSON from text file
val json: String = readTextFile(fileUri) val json: String = readTextFile(context, fileUri)
var track = Track() var track: Track = Track()
when (json.isNotEmpty()) { when (json.isNotEmpty()) {
// convert JSON and return as track // convert JSON and return as track
true -> try { true -> try {
@ -156,19 +154,16 @@ object FileHelper {
/* Creates Uri for Gpx file of a track */ /* Creates Uri for Gpx file of a track */
fun getGpxFileUri(context: Context, track: Track): Uri = fun getGpxFileUri(context: Context, track: Track): Uri = File(context.getExternalFilesDir(Keys.FOLDER_GPX), getGpxFileName(track)).toUri()
File(context.getExternalFilesDir(Keys.FOLDER_GPX), getGpxFileName(track)).toUri()
/* Creates file name for Gpx file of a track */ /* Creates file name for Gpx file of a track */
fun getGpxFileName(track: Track): String = fun getGpxFileName(track: Track): String = DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.GPX_FILE_EXTENSION
DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.GPX_FILE_EXTENSION
/* Creates Uri for json track file */ /* Creates Uri for json track file */
fun getTrackFileUri(context: Context, track: Track): Uri { fun getTrackFileUri(context: Context, track: Track): Uri {
val fileName: String = val fileName: String = DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.TRACKBOOK_FILE_EXTENSION
DateTimeHelper.convertToSortableDateString(track.recordingStart) + Keys.TRACKBOOK_FILE_EXTENSION
return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), fileName).toUri() return File(context.getExternalFilesDir(Keys.FOLDER_TRACKS), fileName).toUri()
} }
@ -180,11 +175,7 @@ object FileHelper {
/* Suspend function: Wrapper for saveTracklist */ /* Suspend function: Wrapper for saveTracklist */
suspend fun addTrackAndSaveTracklistSuspended( suspend fun addTrackAndSaveTracklistSuspended(context: Context, track: Track, modificationDate: Date = track.recordingStop) {
context: Context,
track: Track,
modificationDate: Date = track.recordingStop
) {
return suspendCoroutine { cont -> return suspendCoroutine { cont ->
val tracklist: Tracklist = readTracklist(context) val tracklist: Tracklist = readTracklist(context)
tracklist.tracklistElements.add(track.toTracklistElement(context)) tracklist.tracklistElements.add(track.toTracklistElement(context))
@ -202,11 +193,7 @@ object FileHelper {
/* Suspend function: Wrapper for saveTracklist */ /* Suspend function: Wrapper for saveTracklist */
suspend fun saveTracklistSuspended( suspend fun saveTracklistSuspended(context: Context, tracklist: Tracklist, modificationDate: Date) {
context: Context,
tracklist: Tracklist,
modificationDate: Date
) {
return suspendCoroutine { cont -> return suspendCoroutine { cont ->
cont.resume(saveTracklist(context, tracklist, modificationDate)) cont.resume(saveTracklist(context, tracklist, modificationDate))
} }
@ -230,11 +217,7 @@ object FileHelper {
/* Suspend function: Wrapper for deleteTrack */ /* Suspend function: Wrapper for deleteTrack */
suspend fun deleteTrackSuspended( suspend fun deleteTrackSuspended(context: Context, position: Int, tracklist: Tracklist): Tracklist {
context: Context,
position: Int,
tracklist: Tracklist
): Tracklist {
return suspendCoroutine { cont -> return suspendCoroutine { cont ->
cont.resume(deleteTrack(context, position, tracklist)) cont.resume(deleteTrack(context, position, tracklist))
} }
@ -257,19 +240,14 @@ object FileHelper {
/* Suspend function: Wrapper for readTracklist */ /* Suspend function: Wrapper for readTracklist */
suspend fun readTracklistSuspended(context: Context): Tracklist { suspend fun readTracklistSuspended(context: Context): Tracklist {
return suspendCoroutine { cont -> return suspendCoroutine {cont ->
cont.resume(readTracklist(context)) cont.resume(readTracklist(context))
} }
} }
/* Suspend function: Wrapper for copyFile */ /* Suspend function: Wrapper for copyFile */
suspend fun saveCopyOfFileSuspended( suspend fun saveCopyOfFileSuspended(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
context: Context,
originalFileUri: Uri,
targetFileUri: Uri,
deleteOriginal: Boolean = false
) {
return suspendCoroutine { cont -> return suspendCoroutine { cont ->
cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal)) cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal))
} }
@ -307,7 +285,7 @@ object FileHelper {
tracklist.modificationDate = modificationDate tracklist.modificationDate = modificationDate
// convert to JSON // convert to JSON
val gson: Gson = getCustomGson() val gson: Gson = getCustomGson()
var json = String() var json: String = String()
try { try {
json = gson.toJson(tracklist) json = gson.toJson(tracklist)
} catch (e: Exception) { } catch (e: Exception) {
@ -326,11 +304,12 @@ object FileHelper {
} }
/* Renames track */ /* Renames track */
private fun renameTrack(context: Context, track: Track, newName: String) { private fun renameTrack(context: Context, track: Track, newName: String) {
// search track in tracklist // search track in tracklist
val tracklist: Tracklist = readTracklist(context) val tracklist: Tracklist = readTracklist(context)
var trackUriString = String() var trackUriString: String = String()
tracklist.tracklistElements.forEach { tracklistElement -> tracklist.tracklistElements.forEach { tracklistElement ->
if (tracklistElement.getTrackId() == track.getTrackId()) { if (tracklistElement.getTrackId() == track.getTrackId()) {
// rename tracklist element // rename tracklist element
@ -350,17 +329,13 @@ object FileHelper {
/* Deletes multiple tracks */ /* Deletes multiple tracks */
private fun deleteTracks( private fun deleteTracks(context: Context, tracklistElements: MutableList<TracklistElement>, tracklist: Tracklist): Tracklist {
context: Context,
tracklistElements: MutableList<TracklistElement>,
tracklist: Tracklist
): Tracklist {
tracklistElements.forEach { tracklistElement -> tracklistElements.forEach { tracklistElement ->
// 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()
} }
tracklist.tracklistElements.removeAll { tracklistElements.contains(it) } tracklist.tracklistElements.removeAll{ tracklistElements.contains(it) }
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)
return tracklist return tracklist
} }
@ -373,23 +348,14 @@ object FileHelper {
tracklistElement.trackUriString.toUri().toFile().delete() tracklistElement.trackUriString.toUri().toFile().delete()
tracklistElement.gpxUriString.toUri().toFile().delete() tracklistElement.gpxUriString.toUri().toFile().delete()
// remove track element from list // remove track element from list
tracklist.tracklistElements.removeIf { tracklist.tracklistElements.removeIf { TrackHelper.getTrackId(it) == TrackHelper.getTrackId(tracklistElement) }
TrackHelper.getTrackId(it) == TrackHelper.getTrackId(
tracklistElement
)
}
saveTracklist(context, tracklist, GregorianCalendar.getInstance().time) saveTracklist(context, tracklist, GregorianCalendar.getInstance().time)
return tracklist return tracklist
} }
/* Copies file to specified target */ /* Copies file to specified target */
private fun copyFile( private fun copyFile(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
context: Context,
originalFileUri: Uri,
targetFileUri: Uri,
deleteOriginal: Boolean = false
) {
val inputStream = context.contentResolver.openInputStream(originalFileUri) val inputStream = context.contentResolver.openInputStream(originalFileUri)
val outputStream = context.contentResolver.openOutputStream(targetFileUri) val outputStream = context.contentResolver.openOutputStream(targetFileUri)
if (outputStream != null) { if (outputStream != null) {
@ -404,7 +370,7 @@ object FileHelper {
/* Converts track to JSON */ /* Converts track to JSON */
private fun getTrackJsonString(track: Track): String { private fun getTrackJsonString(track: Track): String {
val gson: Gson = getCustomGson() val gson: Gson = getCustomGson()
var json = String() var json: String = String()
try { try {
json = gson.toJson(track) json = gson.toJson(track)
} catch (e: Exception) { } catch (e: Exception) {
@ -423,6 +389,7 @@ object FileHelper {
} }
/* Converts byte value into a human readable format */ /* Converts byte value into a human readable format */
// Source: https://programming.guide/java/formatting-byte-size-to-human-readable-format.html // Source: https://programming.guide/java/formatting-byte-size-to-human-readable-format.html
fun getReadableByteCount(bytes: Long, si: Boolean = true): String { fun getReadableByteCount(bytes: Long, si: Boolean = true): String {
@ -434,13 +401,13 @@ object FileHelper {
if (bytes < unit) return "$bytes B" if (bytes < unit) return "$bytes B"
// calculate exp // calculate exp
val exp: Int = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() val exp: Int = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
// determine prefix symbol // determine prefix symbol
val prefix: String = ((if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i") val prefix: String = ((if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i")
// calculate result and set number format // calculate result and set number format
val result: Double = bytes / unit.toDouble().pow(exp.toDouble()) val result: Double = bytes / Math.pow(unit.toDouble(), exp.toDouble())
val numberFormat = NumberFormat.getNumberInstance() val numberFormat = NumberFormat.getNumberInstance()
numberFormat.maximumFractionDigits = 1 numberFormat.maximumFractionDigits = 1
@ -449,7 +416,7 @@ object FileHelper {
/* Reads InputStream from file uri and returns it as String */ /* Reads InputStream from file uri and returns it as String */
private fun readTextFile(fileUri: Uri): String { private fun readTextFile(context: Context, fileUri: Uri): String {
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html // todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
// https://developer.android.com/training/secure-file-sharing/retrieve-info // https://developer.android.com/training/secure-file-sharing/retrieve-info
val file: File = fileUri.toFile() val file: File = fileUri.toFile()
@ -459,12 +426,11 @@ object FileHelper {
} }
// read until last line reached // read until last line reached
val stream: InputStream = file.inputStream() val stream: InputStream = file.inputStream()
val reader = BufferedReader(InputStreamReader(stream)) val reader: BufferedReader = BufferedReader(InputStreamReader(stream))
val builder: StringBuilder = StringBuilder() val builder: StringBuilder = StringBuilder()
reader.forEachLine { reader.forEachLine {
builder.append(it) builder.append(it)
builder.append("\n") builder.append("\n") }
}
stream.close() stream.close()
return builder.toString() return builder.toString()
} }
@ -478,13 +444,8 @@ object FileHelper {
/* Writes given bitmap as image file to storage */ /* Writes given bitmap as image file to storage */
private fun writeImageFile( private fun writeImageFile(context: Context, bitmap: Bitmap, file: File, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, quality: Int = 75) {
bitmap: Bitmap, if (file.exists()) file.delete ()
file: File,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
quality: Int = 75
) {
if (file.exists()) file.delete()
try { try {
val out = FileOutputStream(file) val out = FileOutputStream(file)
bitmap.compress(format, quality, out) bitmap.compress(format, quality, out)
@ -495,4 +456,4 @@ object FileHelper {
} }
} }
} }

View File

@ -40,7 +40,7 @@ object LocationHelper {
/* Get default location */ /* Get default location */
fun getDefaultLocation(): Location { fun getDefaultLocation(): Location {
val defaultLocation = Location(LocationManager.NETWORK_PROVIDER) val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
defaultLocation.latitude = Keys.DEFAULT_LATITUDE defaultLocation.latitude = Keys.DEFAULT_LATITUDE
defaultLocation.longitude = Keys.DEFAULT_LONGITUDE defaultLocation.longitude = Keys.DEFAULT_LONGITUDE
defaultLocation.accuracy = Keys.DEFAULT_ACCURACY defaultLocation.accuracy = Keys.DEFAULT_ACCURACY
@ -62,24 +62,14 @@ object LocationHelper {
// get last location that Trackbook has stored // get last location that Trackbook has stored
var lastKnownLocation: Location = PreferencesHelper.loadCurrentBestLocation(context) var lastKnownLocation: Location = PreferencesHelper.loadCurrentBestLocation(context)
// try to get the last location the system has stored - it is probably more recent // try to get the last location the system has stored - it is probably more recent
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
context, val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
Manifest.permission.ACCESS_FINE_LOCATION val lastKnownLocationGps: Location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) ?: lastKnownLocation
) == PackageManager.PERMISSION_GRANTED val lastKnownLocationNetwork: Location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) ?: lastKnownLocation
) { when (isBetterLocation(lastKnownLocationGps, lastKnownLocationNetwork)) {
val locationManager = true -> lastKnownLocation = lastKnownLocationGps
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager false -> lastKnownLocation = lastKnownLocationNetwork
val lastKnownLocationGps: Location = }
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
?: lastKnownLocation
val lastKnownLocationNetwork: Location =
locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
?: lastKnownLocation
lastKnownLocation =
when (isBetterLocation(lastKnownLocationGps, lastKnownLocationNetwork)) {
true -> lastKnownLocationGps
false -> lastKnownLocationNetwork
}
} }
return lastKnownLocation return lastKnownLocation
} }
@ -97,7 +87,7 @@ object LocationHelper {
// check whether the new location fix is newer or older // check whether the new location fix is newer or older
val timeDelta: Long = location.time - currentBestLocation.time val timeDelta: Long = location.time - currentBestLocation.time
val isSignificantlyNewer: Boolean = timeDelta > Keys.SIGNIFICANT_TIME_DIFFERENCE val isSignificantlyNewer: Boolean = timeDelta > Keys.SIGNIFICANT_TIME_DIFFERENCE
val isSignificantlyOlder: Boolean = timeDelta < -Keys.SIGNIFICANT_TIME_DIFFERENCE val isSignificantlyOlder:Boolean = timeDelta < -Keys.SIGNIFICANT_TIME_DIFFERENCE
when { when {
// if it's been more than two minutes since the current location, use the new location because the user has likely moved // if it's been more than two minutes since the current location, use the new location because the user has likely moved
@ -128,24 +118,25 @@ object LocationHelper {
/* Checks if GPS location provider is available and enabled */ /* Checks if GPS location provider is available and enabled */
fun isGpsEnabled(locationManager: LocationManager): Boolean { fun isGpsEnabled(locationManager: LocationManager): Boolean {
return if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) { if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
} else { } else {
false return false
} }
} }
/* Checks if Network location provider is available and enabled */ /* Checks if Network location provider is available and enabled */
fun isNetworkEnabled(locationManager: LocationManager): Boolean { fun isNetworkEnabled(locationManager: LocationManager): Boolean {
return if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) { if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
} else { } else {
false return false
} }
} }
/* Checks if given location is new */ /* Checks if given location is new */
fun isRecentEnough(location: Location): Boolean { fun isRecentEnough(location: Location): Boolean {
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
@ -155,35 +146,26 @@ object LocationHelper {
/* Checks if given location is accurate */ /* Checks if given location is accurate */
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean { fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
return when (location.provider) { val isAccurate: Boolean
LocationManager.GPS_PROVIDER -> location.accuracy < locationAccuracyThreshold when (location.provider) {
else -> location.accuracy < locationAccuracyThreshold + 10 // a bit more relaxed when location comes from network provider LocationManager.GPS_PROVIDER -> isAccurate = location.accuracy < locationAccuracyThreshold
else -> isAccurate = location.accuracy < locationAccuracyThreshold + 10 // a bit more relaxed when location comes from network provider
} }
return isAccurate
} }
/* Checks if the first location of track is plausible */ /* Checks if the first location of track is plausible */
fun isFirstLocationPlausible(secondLocation: Location, track: Track): Boolean { fun isFirstLocationPlausible(secondLocation: Location, track: Track): Boolean {
// speed in km/h // speed in km/h
val speed: Double = calculateSpeed( val speed: Double = calculateSpeed(firstLocation = track.wayPoints[0].toLocation(), secondLocation = secondLocation, firstTimestamp = track.recordingStart.time, secondTimestamp = GregorianCalendar.getInstance().time.time)
firstLocation = track.wayPoints[0].toLocation(),
secondLocation = secondLocation,
firstTimestamp = track.recordingStart.time,
secondTimestamp = GregorianCalendar.getInstance().time.time
)
// plausible = speed under 250 km/h // plausible = speed under 250 km/h
return speed < Keys.IMPLAUSIBLE_TRACK_START_SPEED return speed < Keys.IMPLAUSIBLE_TRACK_START_SPEED
} }
/* Calculates speed */ /* Calculates speed */
private fun calculateSpeed( private fun calculateSpeed(firstLocation: Location, secondLocation: Location, firstTimestamp: Long, secondTimestamp: Long, useImperial: Boolean = false): Double {
firstLocation: Location,
secondLocation: Location,
firstTimestamp: Long,
secondTimestamp: Long,
useImperial: Boolean = false
): Double {
// time difference in seconds // time difference in seconds
val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L val timeDifference: Long = (secondTimestamp - firstTimestamp) / 1000L
// distance in meters // distance in meters
@ -201,10 +183,10 @@ object LocationHelper {
val distanceThreshold: Float val distanceThreshold: Float
val averageAccuracy: Float = (previousLocation.accuracy + location.accuracy) / 2 val averageAccuracy: Float = (previousLocation.accuracy + location.accuracy) / 2
// increase the distance threshold if one or both locations are // increase the distance threshold if one or both locations are
distanceThreshold = if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) { if (averageAccuracy > Keys.DEFAULT_THRESHOLD_DISTANCE) {
averageAccuracy distanceThreshold = averageAccuracy
} else { } else {
Keys.DEFAULT_THRESHOLD_DISTANCE distanceThreshold = Keys.DEFAULT_THRESHOLD_DISTANCE
} }
// location is different when far enough away from previous location // location is different when far enough away from previous location
return calculateDistance(previousLocation, location) > distanceThreshold return calculateDistance(previousLocation, location) > distanceThreshold
@ -212,8 +194,8 @@ object LocationHelper {
/* Calculates distance in meters between two locations */ /* Calculates distance in meters between two locations */
fun calculateDistance(previousLocation: Location?, location: Location): Float { fun calculateDistance(previousLocation: Location?, location: Location): Float {
var distance = 0f var distance: Float = 0f
// two data points needed to calculate distance // two data points needed to calculate distance
if (previousLocation != null) { if (previousLocation != null) {
// add up distance // add up distance
@ -224,26 +206,20 @@ object LocationHelper {
/* Calculate elevation differences */ /* Calculate elevation differences */
fun calculateElevationDifferences( fun calculateElevationDifferences(previousLocation: Location?, location: Location, track: Track): Pair<Double, Double> {
previousLocation: Location?,
location: Location,
track: Track
): Pair<Double, Double> {
// store current values // store current values
var positiveElevation: Double = track.positiveElevation var positiveElevation: Double = track.positiveElevation
var negativeElevation: Double = track.negativeElevation var negativeElevation: Double = track.negativeElevation
if (previousLocation != null) { if (previousLocation != null) {
// factor is bigger than 1 if the time stamp difference is larger than the movement recording interval // factor is bigger than 1 if the time stamp difference is larger than the movement recording interval
val timeDifferenceFactor: Long = val timeDifferenceFactor: Long = (location.time - previousLocation.time) / Keys.ADD_WAYPOINT_TO_TRACK_INTERVAL
(location.time - previousLocation.time) / Keys.ADD_WAYPOINT_TO_TRACK_INTERVAL
// get elevation difference and sum it up // get elevation difference and sum it up
val altitudeDifference: Double = location.altitude - previousLocation.altitude val altitudeDifference: Double = location.altitude - previousLocation.altitude
if (altitudeDifference > 0 && altitudeDifference < Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) { if (altitudeDifference > 0 && altitudeDifference < Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
positiveElevation = track.positiveElevation + altitudeDifference // upwards movement positiveElevation = track.positiveElevation + altitudeDifference // upwards movement
} }
if (altitudeDifference < 0 && altitudeDifference > -Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) { if (altitudeDifference < 0 && altitudeDifference > -Keys.ALTITUDE_MEASUREMENT_ERROR_THRESHOLD * timeDifferenceFactor && location.altitude != Keys.DEFAULT_ALTITUDE) {
negativeElevation = negativeElevation = track.negativeElevation + altitudeDifference // downwards movement
track.negativeElevation + altitudeDifference // downwards movement
} }
} }
return Pair(positiveElevation, negativeElevation) return Pair(positiveElevation, negativeElevation)
@ -258,4 +234,4 @@ object LocationHelper {
} }
} }

View File

@ -78,9 +78,9 @@ object LogHelper {
private fun log(tag: String, level: Int, t: Throwable?, vararg messages: Any) { private fun log(tag: String, level: Int, t: Throwable?, vararg messages: Any) {
val message: String val message: String
message = if (t == null && messages.size == 1) { if (t == null && messages.size == 1) {
// handle this common case without the extra cost of creating a stringbuffer: // handle this common case without the extra cost of creating a stringbuffer:
messages[0].toString() message = messages[0].toString()
} else { } else {
val sb = StringBuilder() val sb = StringBuilder()
for (m in messages) { for (m in messages) {
@ -89,7 +89,7 @@ object LogHelper {
if (t != null) { if (t != null) {
sb.append("\n").append(Log.getStackTraceString(t)) sb.append("\n").append(Log.getStackTraceString(t))
} }
sb.toString() message = sb.toString()
} }
Log.println(level, tag, message) Log.println(level, tag, message)
@ -112,4 +112,4 @@ object LogHelper {
// Log.println(level, tag, message) // Log.println(level, tag, message)
// } // }
} }
} }

View File

@ -107,7 +107,7 @@ object PreferencesHelper {
val settings = PreferenceManager.getDefaultSharedPreferences(context) val settings = PreferenceManager.getDefaultSharedPreferences(context)
val provider: String = settings.getString(Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER, LocationManager.NETWORK_PROVIDER) ?: LocationManager.NETWORK_PROVIDER val provider: String = settings.getString(Keys.PREF_CURRENT_BEST_LOCATION_PROVIDER, LocationManager.NETWORK_PROVIDER) ?: LocationManager.NETWORK_PROVIDER
// create location // create location
val currentBestLocation = Location(provider) val currentBestLocation: Location = Location(provider)
// load location attributes // load location attributes
currentBestLocation.latitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE) currentBestLocation.latitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LATITUDE, Keys.DEFAULT_LATITUDE)
currentBestLocation.longitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE) currentBestLocation.longitude = settings.getDouble(Keys.PREF_CURRENT_BEST_LOCATION_LONGITUDE, Keys.DEFAULT_LONGITUDE)

View File

@ -47,13 +47,8 @@ object TrackHelper {
tracklistElement.date.time tracklistElement.date.time
/* Adds given location as waypoint to track */ /* Adds given locatiom as waypoint to track */
fun addWayPointToTrack( fun addWayPointToTrack(context: Context, track: Track, location: Location, locationAccuracyThreshold: Int, resumed: Boolean): Pair<Track, Boolean> {
track: Track,
location: Location,
locationAccuracyThreshold: Int,
resumed: Boolean
): Pair<Track, Boolean> {
// get previous location // get previous location
val previousLocation: Location? val previousLocation: Location?
var numberOfWayPoints: Int = track.wayPoints.size var numberOfWayPoints: Int = track.wayPoints.size
@ -63,11 +58,7 @@ object TrackHelper {
previousLocation = null previousLocation = null
} }
// CASE: Second location - check if first location was plausible & remove implausible location // CASE: Second location - check if first location was plausible & remove implausible location
else if (numberOfWayPoints == 1 && !LocationHelper.isFirstLocationPlausible( else if (numberOfWayPoints == 1 && !LocationHelper.isFirstLocationPlausible(location, track)) {
location,
track
)
) {
previousLocation = null previousLocation = null
numberOfWayPoints = 0 numberOfWayPoints = 0
track.wayPoints.removeAt(0) track.wayPoints.removeAt(0)
@ -85,8 +76,8 @@ object TrackHelper {
// add only if recent and accurate and different // add only if recent and accurate and different
val shouldBeAdded: Boolean = (LocationHelper.isRecentEnough(location) && val shouldBeAdded: Boolean = (LocationHelper.isRecentEnough(location) &&
LocationHelper.isAccurateEnough(location, locationAccuracyThreshold) && LocationHelper.isAccurateEnough(location, locationAccuracyThreshold) &&
LocationHelper.isDifferentEnough(previousLocation, location)) LocationHelper.isDifferentEnough(previousLocation, location))
// // Debugging for shouldBeAdded - remove for production // // Debugging for shouldBeAdded - remove for production
// val recentEnough: Boolean = LocationHelper.isRecentEnough(location) // val recentEnough: Boolean = LocationHelper.isRecentEnough(location)
@ -104,8 +95,7 @@ object TrackHelper {
if (shouldBeAdded) { if (shouldBeAdded) {
// update distance (do not update if resumed -> we do not want to add values calculated during a recording pause) // update distance (do not update if resumed -> we do not want to add values calculated during a recording pause)
if (!resumed) { if (!resumed) {
track.length = track.length = track.length + LocationHelper.calculateDistance(previousLocation, location)
track.length + LocationHelper.calculateDistance(previousLocation, location)
} }
if (location.altitude != 0.0) { if (location.altitude != 0.0) {
@ -115,23 +105,12 @@ object TrackHelper {
track.minAltitude = location.altitude track.minAltitude = location.altitude
} else { } else {
// calculate elevation values (upwards / downwards movements) // calculate elevation values (upwards / downwards movements)
val elevationDifferences: Pair<Double, Double> = val elevationDifferences: Pair<Double, Double> = LocationHelper.calculateElevationDifferences(previousLocation, location, track)
LocationHelper.calculateElevationDifferences(
previousLocation,
location,
track
)
// check if any differences were calculated // check if any differences were calculated
if (elevationDifferences != Pair( if (elevationDifferences != Pair(track.positiveElevation, track.negativeElevation)) {
track.positiveElevation,
track.negativeElevation
)
) {
// update altitude values // update altitude values
if (location.altitude > track.maxAltitude) track.maxAltitude = if (location.altitude > track.maxAltitude) track.maxAltitude = location.altitude
location.altitude if (location.altitude < track.minAltitude) track.minAltitude = location.altitude
if (location.altitude < track.minAltitude) track.minAltitude =
location.altitude
// update elevation values (do not update if resumed -> we do not want to add values calculated during a recording pause) // update elevation values (do not update if resumed -> we do not want to add values calculated during a recording pause)
if (!resumed) { if (!resumed) {
track.positiveElevation = elevationDifferences.first track.positiveElevation = elevationDifferences.first
@ -143,17 +122,16 @@ object TrackHelper {
// toggle stop over status, if necessary // toggle stop over status, if necessary
if (track.wayPoints.size < 0) { if (track.wayPoints.size < 0) {
track.wayPoints[track.wayPoints.size - 1].isStopOver = track.wayPoints[track.wayPoints.size - 1].isStopOver = LocationHelper.isStopOver(previousLocation, location)
LocationHelper.isStopOver(previousLocation, location)
} }
// save number of satellites // save number of satellites
val numberOfSatellites: Int val numberOfSatellites: Int
val extras = location.extras val extras = location.extras
numberOfSatellites = if (extras != null && extras.containsKey("satellites")) { if (extras != null && extras.containsKey("satellites")) {
extras.getInt("satellites", 0) numberOfSatellites = extras.getInt("satellites", 0)
} else { } else {
0 numberOfSatellites = 0
} }
// add current location as point to center on for later display // add current location as point to center on for later display
@ -161,18 +139,7 @@ object TrackHelper {
track.longitude = location.longitude track.longitude = location.longitude
// add location as new waypoint // add location as new waypoint
track.wayPoints.add( track.wayPoints.add(WayPoint(provider = location.provider, latitude = location.latitude, longitude = location.longitude, altitude = location.altitude, accuracy = location.accuracy, time = location.time, distanceToStartingPoint = track.length, numberSatellites = numberOfSatellites))
WayPoint(
provider = location.provider,
latitude = location.latitude,
longitude = location.longitude,
altitude = location.altitude,
accuracy = location.accuracy,
time = location.time,
distanceToStartingPoint = track.length,
numberSatellites = numberOfSatellites
)
)
} }
return Pair(track, shouldBeAdded) return Pair(track, shouldBeAdded)
@ -186,12 +153,13 @@ object TrackHelper {
/* Creates GPX string for given track */ /* Creates GPX string for given track */
fun createGpxString(track: Track): String { fun createGpxString(track: Track): String {
var gpxString: String
// add header // add header
var gpxString: String = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" + gpxString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
"<gpx version=\"1.1\" creator=\"Trackbook App (Android)\"\n" + "<gpx version=\"1.1\" creator=\"Trackbook App (Android)\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n" " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
// add track // add track
gpxString += createGpxTrk(track) gpxString += createGpxTrk(track)
@ -259,20 +227,12 @@ object TrackHelper {
if (waypoint.latitude == latitude && waypoint.longitude == longitude) { if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
waypoint.starred = !waypoint.starred waypoint.starred = !waypoint.starred
when (waypoint.starred) { when (waypoint.starred) {
true -> Toast.makeText( true -> Toast.makeText(context, R.string.toast_message_poi_added, Toast.LENGTH_LONG).show()
context, false -> Toast.makeText(context, R.string.toast_message_poi_removed, Toast.LENGTH_LONG).show()
R.string.toast_message_poi_added,
Toast.LENGTH_LONG
).show()
false -> Toast.makeText(
context,
R.string.toast_message_poi_removed,
Toast.LENGTH_LONG
).show()
} }
} }
} }
return track return track
} }
} }

View File

@ -41,14 +41,7 @@ object UiHelper {
/* Sets layout margins for given view in DP */ /* Sets layout margins for given view in DP */
private fun setViewMargins( fun setViewMargins(context: Context, view: View, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
context: Context,
view: View,
left: Int = 0,
right: Int = 0,
top: Int = 0,
bottom: Int = 0
) {
val scalingFactor: Float = context.resources.displayMetrics.density val scalingFactor: Float = context.resources.displayMetrics.density
val l: Int = (left * scalingFactor).toInt() val l: Int = (left * scalingFactor).toInt()
val r: Int = (right * scalingFactor).toInt() val r: Int = (right * scalingFactor).toInt()
@ -63,16 +56,7 @@ object UiHelper {
/* Sets layout margins for given view in percent */ /* Sets layout margins for given view in percent */
fun setViewMarginsPercentage( fun setViewMarginsPercentage(context: Context, view: View, height: Int, width: Int, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
context: Context,
view: View,
height: Int,
width: Int,
left: Int = 0,
right: Int = 0,
top: Int = 0,
bottom: Int = 0
) {
val l: Int = ((width / 100.0f) * left).toInt() val l: Int = ((width / 100.0f) * left).toInt()
val r: Int = ((width / 100.0f) * right).toInt() val r: Int = ((width / 100.0f) * right).toInt()
val t: Int = ((height / 100.0f) * top).toInt() val t: Int = ((height / 100.0f) * top).toInt()
@ -85,58 +69,28 @@ object UiHelper {
* Inner class: Callback that detects a left swipe * Inner class: Callback that detects a left swipe
* Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt * Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt
*/ */
abstract class SwipeToDeleteCallback(context: Context) : abstract class SwipeToDeleteCallback(context: Context): ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
private val deleteIcon = private val deleteIcon = ContextCompat.getDrawable(context, R.drawable.ic_remove_circle_24dp)
ContextCompat.getDrawable(context, R.drawable.ic_remove_circle_24dp)
private val intrinsicWidth: Int = deleteIcon?.intrinsicWidth ?: 0 private val intrinsicWidth: Int = deleteIcon?.intrinsicWidth ?: 0
private val intrinsicHeight: Int = deleteIcon?.intrinsicHeight ?: 0 private val intrinsicHeight: Int = deleteIcon?.intrinsicHeight ?: 0
private val background: ColorDrawable = ColorDrawable() private val background: ColorDrawable = ColorDrawable()
private val backgroundColor = private val backgroundColor = context.resources.getColor(R.color.list_card_delete_background, null)
context.resources.getColor(R.color.list_card_delete_background, null) private val clearPaint: Paint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
private val clearPaint: Paint =
Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
override fun onMove( override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// do nothing // do nothing
return false return false
} }
override fun onChildDraw( override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
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
val isCanceled = dX == 0f && !isCurrentlyActive val isCanceled = dX == 0f && !isCurrentlyActive
if (isCanceled) { if (isCanceled) {
clearCanvas( clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat())
c, super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
itemView.right + dX,
itemView.top.toFloat(),
itemView.right.toFloat(),
itemView.bottom.toFloat()
)
super.onChildDraw(
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
return return
} }
@ -172,4 +126,4 @@ object UiHelper {
* End of inner class * End of inner class
*/ */
} }

View File

@ -39,8 +39,7 @@ import java.util.*
/* /*
* TracklistAdapter class * TracklistAdapter class
*/ */
class TracklistAdapter(private val fragment: Fragment) : class TracklistAdapter(private val fragment: Fragment) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java) private val TAG: String = LogHelper.makeLogTag(TracklistAdapter::class.java)
@ -55,7 +54,7 @@ class TracklistAdapter(private val fragment: Fragment) :
/* Listener Interface */ /* Listener Interface */
interface TracklistAdapterListener { interface TracklistAdapterListener {
fun onTrackElementTapped(tracklistElement: TracklistElement) {} fun onTrackElementTapped(tracklistElement: TracklistElement) { }
// fun onTrackElementStarred(trackId: Long, starred: Boolean) // fun onTrackElementStarred(trackId: Long, starred: Boolean)
} }
@ -66,7 +65,7 @@ class TracklistAdapter(private val fragment: Fragment) :
tracklistListener = fragment as TracklistAdapterListener tracklistListener = fragment as TracklistAdapterListener
// load tracklist // load tracklist
tracklist = FileHelper.readTracklist(context) tracklist = FileHelper.readTracklist(context)
tracklist.tracklistElements.sortByDescending { tracklistElement -> tracklistElement.date } tracklist.tracklistElements.sortByDescending { tracklistElement -> tracklistElement.date }
} }
@ -112,8 +111,7 @@ class TracklistAdapter(private val fragment: Fragment) :
val backgroundJob = Job() val backgroundJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob) val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob)
uiScope.launch { uiScope.launch {
val deferred: Deferred<Tracklist> = val deferred: Deferred<Tracklist> = async { FileHelper.deleteTrackSuspended(context, position, tracklist) }
async { FileHelper.deleteTrackSuspended(context, position, tracklist) }
// wait for result and store in tracklist // wait for result and store in tracklist
tracklist = deferred.await() tracklist = deferred.await()
notifyItemRemoved(position) notifyItemRemoved(position)
@ -124,7 +122,7 @@ class TracklistAdapter(private val fragment: Fragment) :
/* Finds current position of track element in adapter list */ /* Finds current position of track element in adapter list */
fun findPosition(trackId: Long): Int { fun findPosition(trackId: Long): Int {
tracklist.tracklistElements.forEachIndexed { index, tracklistElement -> tracklist.tracklistElements.forEachIndexed {index, tracklistElement ->
if (tracklistElement.getTrackId() == trackId) return index if (tracklistElement.getTrackId() == trackId) return index
} }
return -1 return -1
@ -145,11 +143,7 @@ class TracklistAdapter(private val fragment: Fragment) :
} }
} }
GlobalScope.launch { GlobalScope.launch {
FileHelper.saveTracklistSuspended( FileHelper.saveTracklistSuspended(context, tracklist, GregorianCalendar.getInstance().time)
context,
tracklist,
GregorianCalendar.getInstance().time
)
} }
} }
@ -158,17 +152,11 @@ class TracklistAdapter(private val fragment: Fragment) :
private fun createTrackDataString(position: Int): String { private fun createTrackDataString(position: Int): String {
val tracklistElement: TracklistElement = tracklist.tracklistElements[position] val tracklistElement: TracklistElement = tracklist.tracklistElements[position]
val trackDataString: String val trackDataString: String
trackDataString = when (tracklistElement.name == tracklistElement.dateString) { when (tracklistElement.name == tracklistElement.dateString) {
// CASE: no individual name set - exclude date // CASE: no individual name set - exclude date
true -> "${LengthUnitHelper.convertDistanceToString( true -> trackDataString = "${LengthUnitHelper.convertDistanceToString(tracklistElement.length, useImperial)}${tracklistElement.durationString}"
tracklistElement.length,
useImperial
)} ${tracklistElement.durationString}"
// CASE: no individual name set - include date // CASE: no individual name set - include date
false -> "${tracklistElement.dateString}${LengthUnitHelper.convertDistanceToString( false -> trackDataString = "${tracklistElement.dateString}${LengthUnitHelper.convertDistanceToString(tracklistElement.length, useImperial)}${tracklistElement.durationString}"
tracklistElement.length,
useImperial
)} ${tracklistElement.durationString}"
} }
return trackDataString return trackDataString
} }
@ -177,8 +165,7 @@ class TracklistAdapter(private val fragment: Fragment) :
/* /*
* Inner class: DiffUtil.Callback that determines changes in data - improves list performance * Inner class: DiffUtil.Callback that determines changes in data - improves list performance
*/ */
private inner class DiffCallback(val oldList: Tracklist, val newList: Tracklist) : private inner class DiffCallback(val oldList: Tracklist, val newList: Tracklist): DiffUtil.Callback() {
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList.tracklistElements[oldItemPosition] val oldItem = oldList.tracklistElements[oldItemPosition]
@ -208,8 +195,7 @@ class TracklistAdapter(private val fragment: Fragment) :
/* /*
* Inner class: ViewHolder for a track element * Inner class: ViewHolder for a track element
*/ */
private inner class TrackElementViewHolder(trackElementLayout: View) : private inner class TrackElementViewHolder (trackElementLayout: View): RecyclerView.ViewHolder(trackElementLayout) {
RecyclerView.ViewHolder(trackElementLayout) {
val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element) val trackElement: ConstraintLayout = trackElementLayout.findViewById(R.id.track_element)
val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name) val trackNameView: TextView = trackElementLayout.findViewById(R.id.track_name)
val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data) val trackDataView: TextView = trackElementLayout.findViewById(R.id.track_data)

View File

@ -50,20 +50,14 @@ import kotlin.math.roundToInt
/* /*
* TrackFragmentLayoutHolder class * TrackFragmentLayoutHolder class
*/ */
data class TrackFragmentLayoutHolder( data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlay.MarkerListener, private var inflater: LayoutInflater, private var container: ViewGroup?, var track: Track) {
private var context: Context,
private var markerListener: MapOverlay.MarkerListener,
private var inflater: LayoutInflater,
private var container: ViewGroup?,
var track: Track
) {
/* Define log tag */ /* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java) private val TAG: String = LogHelper.makeLogTag(TrackFragmentLayoutHolder::class.java)
/* Main class variables */ /* Main class variables */
val rootView: View = inflater.inflate(R.layout.fragment_track, container, false) val rootView: View
val shareButton: ImageButton val shareButton: ImageButton
val deleteButton: ImageButton val deleteButton: ImageButton
val editButton: ImageButton val editButton: ImageButton
@ -96,6 +90,7 @@ data class TrackFragmentLayoutHolder(
/* Init block */ /* Init block */
init { init {
// find views // find views
rootView = inflater.inflate(R.layout.fragment_track, container, false)
mapView = rootView.findViewById(R.id.map) mapView = rootView.findViewById(R.id.map)
shareButton = rootView.findViewById(R.id.save_button) shareButton = rootView.findViewById(R.id.save_button)
deleteButton = rootView.findViewById(R.id.delete_button) deleteButton = rootView.findViewById(R.id.delete_button)
@ -139,15 +134,13 @@ data class TrackFragmentLayoutHolder(
} }
// add compass to map // add compass to map
val compassOverlay = val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
compassOverlay.enableCompass() compassOverlay.enableCompass()
compassOverlay.setCompassCenter(36f, 60f) compassOverlay.setCompassCenter(36f, 60f)
mapView.overlays.add(compassOverlay) mapView.overlays.add(compassOverlay)
// create map overlay // create map overlay
trackOverlay = trackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
if (track.wayPoints.isNotEmpty()) { if (track.wayPoints.isNotEmpty()) {
mapView.overlays.add(trackOverlay) mapView.overlays.add(trackOverlay)
} }
@ -175,11 +168,7 @@ data class TrackFragmentLayoutHolder(
mapView.overlays.remove(trackOverlay) mapView.overlays.remove(trackOverlay)
} }
if (track.wayPoints.isNotEmpty()) { if (track.wayPoints.isNotEmpty()) {
trackOverlay = MapOverlay(markerListener).createTrackOverlay( trackOverlay = MapOverlay(markerListener).createTrackOverlay(context, track, Keys.STATE_TRACKING_NOT)
context,
track,
Keys.STATE_TRACKING_NOT
)
mapView.overlays.add(trackOverlay) mapView.overlays.add(trackOverlay)
} }
} }
@ -201,9 +190,9 @@ data class TrackFragmentLayoutHolder(
private fun setupStatisticsViews() { private fun setupStatisticsViews() {
// get step count string // get step count string
val steps: String = val steps: String
if (track.stepCount == -1f) context.getString(R.string.statistics_sheet_p_steps_no_pedometer) if (track.stepCount == -1f) steps = context.getString(R.string.statistics_sheet_p_steps_no_pedometer)
else track.stepCount.roundToInt().toString() else steps = track.stepCount.roundToInt().toString()
// populate views // populate views
trackNameView.text = track.name trackNameView.text = track.name
@ -211,29 +200,19 @@ data class TrackFragmentLayoutHolder(
stepsView.text = steps stepsView.text = steps
waypointsView.text = track.wayPoints.size.toString() waypointsView.text = track.wayPoints.size.toString()
durationView.text = DateTimeHelper.convertToReadableTime(context, track.duration) durationView.text = DateTimeHelper.convertToReadableTime(context, track.duration)
velocityView.text = LengthUnitHelper.convertToVelocityString( velocityView.text = LengthUnitHelper.convertToVelocityString(track.duration, track.recordingPaused, track.length, useImperialUnits)
track.duration,
track.recordingPaused,
track.length,
useImperialUnits
)
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart) recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart) recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.recordingStart)
maxAltitudeView.text = maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits)
LengthUnitHelper.convertDistanceToString(track.maxAltitude, useImperialUnits) minAltitudeView.text = LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits)
minAltitudeView.text = positiveElevationView.text = LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits)
LengthUnitHelper.convertDistanceToString(track.minAltitude, useImperialUnits) negativeElevationView.text = LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits)
positiveElevationView.text =
LengthUnitHelper.convertDistanceToString(track.positiveElevation, useImperialUnits)
negativeElevationView.text =
LengthUnitHelper.convertDistanceToString(track.negativeElevation, useImperialUnits)
// show / hide recording pause // show / hide recording pause
if (track.recordingPaused != 0L) { if (track.recordingPaused != 0L) {
recordingPausedLabelView.visibility = View.VISIBLE recordingPausedLabelView.visibility = View.VISIBLE
recordingPausedView.visibility = View.VISIBLE recordingPausedView.visibility = View.VISIBLE
recordingPausedView.text = recordingPausedView.text = DateTimeHelper.convertToReadableTime(context, track.recordingPaused)
DateTimeHelper.convertToReadableTime(context, track.recordingPaused)
} else { } else {
recordingPausedLabelView.visibility = View.GONE recordingPausedLabelView.visibility = View.GONE
recordingPausedView.visibility = View.GONE recordingPausedView.visibility = View.GONE
@ -241,9 +220,8 @@ data class TrackFragmentLayoutHolder(
// inform user about possible accuracy issues with altitude measurements // inform user about possible accuracy issues with altitude measurements
elevationDataViews.referencedIds.forEach { id -> elevationDataViews.referencedIds.forEach { id ->
(rootView.findViewById(id) as View).setOnClickListener { (rootView.findViewById(id) as View).setOnClickListener{
Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG) Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG).show()
.show()
} }
} }
// make track name on statistics sheet clickable // make track name on statistics sheet clickable
@ -256,8 +234,7 @@ data class TrackFragmentLayoutHolder(
/* Shows/hides the statistics sheet */ /* Shows/hides the statistics sheet */
private fun toggleStatisticsSheetVisibility() { private fun toggleStatisticsSheetVisibility() {
when (statisticsSheetBehavior.state) { when (statisticsSheetBehavior.state) {
BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
BottomSheetBehavior.STATE_COLLAPSED
else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
} }
} }
@ -269,31 +246,26 @@ data class TrackFragmentLayoutHolder(
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) { when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> { BottomSheetBehavior.STATE_EXPANDED -> {
statisticsSheet.background = statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_expanded)
context.getDrawable(R.drawable.shape_statistics_background_expanded)
trackManagementViews.visibility = View.VISIBLE trackManagementViews.visibility = View.VISIBLE
shareButton.visibility = View.GONE shareButton.visibility = View.GONE
// bottomSheet.setPadding(0,24,0,0) // bottomSheet.setPadding(0,24,0,0)
} }
else -> { else -> {
statisticsSheet.background = statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_collapsed)
context.getDrawable(R.drawable.shape_statistics_background_collapsed)
trackManagementViews.visibility = View.GONE trackManagementViews.visibility = View.GONE
shareButton.visibility = View.VISIBLE shareButton.visibility = View.VISIBLE
// bottomSheet.setPadding(0,0,0,0) // bottomSheet.setPadding(0,0,0,0)
} }
} }
} }
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
if (slideOffset < 0.125f) { if (slideOffset < 0.125f) {
statisticsSheet.background = statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_collapsed)
context.getDrawable(R.drawable.shape_statistics_background_collapsed)
trackManagementViews.visibility = View.GONE trackManagementViews.visibility = View.GONE
shareButton.visibility = View.VISIBLE shareButton.visibility = View.VISIBLE
} else { } else {
statisticsSheet.background = statisticsSheet.background = context.getDrawable(R.drawable.shape_statistics_background_expanded)
context.getDrawable(R.drawable.shape_statistics_background_expanded)
trackManagementViews.visibility = View.VISIBLE trackManagementViews.visibility = View.VISIBLE
shareButton.visibility = View.GONE shareButton.visibility = View.GONE
} }
@ -302,4 +274,4 @@ data class TrackFragmentLayoutHolder(
} }
} }

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources />