checkpoint
This commit is contained in:
parent
2de4e0bae5
commit
bbd23d8fb1
2 changed files with 288 additions and 331 deletions
|
@ -22,44 +22,211 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.DatePicker
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TimePicker
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||
import androidx.constraintlayout.widget.Group
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import org.osmdroid.api.IMapController
|
||||
import org.osmdroid.events.MapListener
|
||||
import org.osmdroid.events.ScrollEvent
|
||||
import org.osmdroid.events.ZoomEvent
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
||||
import org.osmdroid.views.overlay.OverlayItem
|
||||
import org.osmdroid.views.overlay.TilesOverlay
|
||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||
import org.y20k.trackbook.helpers.AppThemeHelper
|
||||
import org.y20k.trackbook.helpers.DateTimeHelper
|
||||
import org.y20k.trackbook.helpers.LengthUnitHelper
|
||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||
import org.y20k.trackbook.helpers.createTrackOverlay
|
||||
import org.y20k.trackbook.helpers.create_start_end_markers
|
||||
import org.y20k.trackbook.helpers.iso8601_format
|
||||
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener
|
||||
class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||
{
|
||||
/* Main class variables */
|
||||
private lateinit var layout: TrackFragmentLayoutHolder
|
||||
lateinit var rootView: View
|
||||
lateinit var save_track_button: ImageButton
|
||||
lateinit var deleteButton: ImageButton
|
||||
lateinit var trackNameView: MaterialTextView
|
||||
lateinit var track_query_start_date: DatePicker
|
||||
lateinit var track_query_start_time: TimePicker
|
||||
lateinit var track_query_end_date: DatePicker
|
||||
lateinit var track_query_end_time: TimePicker
|
||||
var track_query_start_time_previous: Int = 0
|
||||
var track_query_end_time_previous: Int = 0
|
||||
private lateinit var mapView: MapView
|
||||
private lateinit var controller: IMapController
|
||||
private lateinit var statisticsSheetBehavior: BottomSheetBehavior<View>
|
||||
private lateinit var statisticsSheet: NestedScrollView
|
||||
private lateinit var statisticsView: View
|
||||
private lateinit var distanceView: MaterialTextView
|
||||
private lateinit var waypointsView: MaterialTextView
|
||||
private lateinit var durationView: MaterialTextView
|
||||
private lateinit var velocityView: MaterialTextView
|
||||
private lateinit var recordingStartView: MaterialTextView
|
||||
private lateinit var recordingStopView: MaterialTextView
|
||||
private lateinit var recordingPausedView: MaterialTextView
|
||||
private lateinit var recordingPausedLabelView: MaterialTextView
|
||||
private lateinit var maxAltitudeView: MaterialTextView
|
||||
private lateinit var minAltitudeView: MaterialTextView
|
||||
private lateinit var positiveElevationView: MaterialTextView
|
||||
private lateinit var negativeElevationView: MaterialTextView
|
||||
private lateinit var elevationDataViews: Group
|
||||
private lateinit var track: Track
|
||||
private var useImperialUnits: Boolean = false
|
||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||
private var special_points_overlay: ItemizedIconOverlay<OverlayItem>? = null
|
||||
private var track_overlay: SimpleFastPointOverlay? = null
|
||||
val RERENDER_DELAY: Long = 1000
|
||||
|
||||
/* Overrides onCreateView from Fragment */
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
// initialize layout
|
||||
val database: Database = (requireActivity().applicationContext as Trackbook).database
|
||||
val track = Track(
|
||||
track = Track(
|
||||
database=database,
|
||||
name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""),
|
||||
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
||||
start_time= iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!),
|
||||
end_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!),
|
||||
)
|
||||
layout = TrackFragmentLayoutHolder(activity as Context, inflater, container, track)
|
||||
rootView = inflater.inflate(R.layout.fragment_track, container, false)
|
||||
mapView = rootView.findViewById(R.id.map)
|
||||
save_track_button = rootView.findViewById(R.id.save_button)
|
||||
deleteButton = rootView.findViewById(R.id.delete_button)
|
||||
trackNameView = rootView.findViewById(R.id.statistics_track_name_headline)
|
||||
|
||||
// set up share button
|
||||
layout.save_track_button.setOnClickListener {
|
||||
controller = mapView.controller
|
||||
mapView.addMapListener(this)
|
||||
mapView.isTilesScaledToDpi = true
|
||||
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
||||
mapView.isVerticalMapRepetitionEnabled = false
|
||||
mapView.setMultiTouchControls(true)
|
||||
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
||||
controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude))
|
||||
controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL)
|
||||
|
||||
statisticsSheet = rootView.findViewById(R.id.statistics_sheet)
|
||||
statisticsView = rootView.findViewById(R.id.statistics_view)
|
||||
distanceView = rootView.findViewById(R.id.statistics_data_distance)
|
||||
waypointsView = rootView.findViewById(R.id.statistics_data_waypoints)
|
||||
durationView = rootView.findViewById(R.id.statistics_data_duration)
|
||||
velocityView = rootView.findViewById(R.id.statistics_data_velocity)
|
||||
recordingStartView = rootView.findViewById(R.id.statistics_data_recording_start)
|
||||
recordingStopView = rootView.findViewById(R.id.statistics_data_recording_stop)
|
||||
recordingPausedLabelView = rootView.findViewById(R.id.statistics_p_recording_paused)
|
||||
recordingPausedView = rootView.findViewById(R.id.statistics_data_recording_paused)
|
||||
maxAltitudeView = rootView.findViewById(R.id.statistics_data_max_altitude)
|
||||
minAltitudeView = rootView.findViewById(R.id.statistics_data_min_altitude)
|
||||
positiveElevationView = rootView.findViewById(R.id.statistics_data_positive_elevation)
|
||||
negativeElevationView = rootView.findViewById(R.id.statistics_data_negative_elevation)
|
||||
elevationDataViews = rootView.findViewById(R.id.elevation_data)
|
||||
|
||||
useImperialUnits = PreferencesHelper.loadUseImperialUnits()
|
||||
|
||||
if (AppThemeHelper.isDarkModeOn(context as Activity))
|
||||
{
|
||||
mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
|
||||
}
|
||||
|
||||
track.load_trkpts()
|
||||
val actual_start_time: Date = if (track.trkpts.isEmpty()) track.start_time else Date(track.trkpts.first().time)
|
||||
val actual_end_time: Date = if (track.trkpts.isEmpty()) track.end_time else Date(track.trkpts.last().time)
|
||||
|
||||
track_query_start_date = rootView.findViewById(R.id.track_query_start_date)
|
||||
val start_cal = GregorianCalendar()
|
||||
start_cal.time = actual_start_time
|
||||
track_query_start_date.init(start_cal.get(Calendar.YEAR), start_cal.get(Calendar.MONTH), start_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener {
|
||||
override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_start_time = rootView.findViewById(R.id.track_query_start_time)
|
||||
track_query_start_time.setIs24HourView(true)
|
||||
track_query_start_time.hour = actual_start_time.hours
|
||||
track_query_start_time.minute = actual_start_time.minutes
|
||||
track_query_start_time_previous = (actual_start_time.hours * 60) + actual_start_time.minutes
|
||||
track_query_start_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{
|
||||
override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
val newminute = (p1 * 60) + p2
|
||||
Log.i("VOUSSOIR", "End time changed $newminute")
|
||||
if (newminute < track_query_start_time_previous && (track_query_start_time_previous - newminute > 60))
|
||||
{
|
||||
increment_datepicker(track_query_start_date)
|
||||
}
|
||||
else if (newminute > track_query_start_time_previous && (newminute - track_query_start_time_previous > 60))
|
||||
{
|
||||
decrement_datepicker(track_query_start_date)
|
||||
}
|
||||
track_query_start_time_previous = newminute
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_end_date = rootView.findViewById(R.id.track_query_end_date)
|
||||
val end_cal = GregorianCalendar()
|
||||
end_cal.time = actual_end_time
|
||||
track_query_end_date.init(end_cal.get(Calendar.YEAR), end_cal.get(Calendar.MONTH), end_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener {
|
||||
override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_end_time = rootView.findViewById(R.id.track_query_end_time)
|
||||
track_query_end_time.setIs24HourView(true)
|
||||
track_query_end_time.hour = actual_end_time.hours
|
||||
track_query_end_time.minute = actual_end_time.minutes
|
||||
track_query_end_time_previous = (actual_end_time.hours * 60) + actual_end_time.minutes
|
||||
track_query_end_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{
|
||||
override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
val newminute = (p1 * 60) + p2
|
||||
Log.i("VOUSSOIR", "End time changed $newminute")
|
||||
if (newminute < track_query_end_time_previous && (track_query_end_time_previous - newminute > 60))
|
||||
{
|
||||
increment_datepicker(track_query_end_date)
|
||||
}
|
||||
else if (newminute > track_query_end_time_previous && (newminute - track_query_end_time_previous > 60))
|
||||
{
|
||||
decrement_datepicker(track_query_end_date)
|
||||
}
|
||||
track_query_end_time_previous = newminute
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
save_track_button.setOnClickListener {
|
||||
openSaveGpxDialog()
|
||||
}
|
||||
// set up delete button
|
||||
layout.deleteButton.setOnClickListener {
|
||||
val dialogMessage = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n${layout.trackNameView.text}"
|
||||
|
||||
deleteButton.setOnClickListener {
|
||||
val dialogMessage = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n${trackNameView.text}"
|
||||
YesNoDialog(this@TrackFragment as YesNoDialog.YesNoDialogListener).show(
|
||||
context = activity as Context,
|
||||
type = Keys.DIALOG_DELETE_TRACK,
|
||||
|
@ -68,7 +235,12 @@ class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener
|
|||
)
|
||||
}
|
||||
|
||||
return layout.rootView
|
||||
statisticsSheetBehavior = BottomSheetBehavior.from<View>(statisticsSheet)
|
||||
statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
render_track()
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
/* Overrides onResume from Fragment */
|
||||
|
@ -77,6 +249,108 @@ class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener
|
|||
super.onResume()
|
||||
}
|
||||
|
||||
fun render_track()
|
||||
{
|
||||
if (special_points_overlay != null)
|
||||
{
|
||||
mapView.overlays.remove(special_points_overlay)
|
||||
}
|
||||
if (track_overlay != null)
|
||||
{
|
||||
mapView.overlays.remove(track_overlay)
|
||||
}
|
||||
if (track.trkpts.isNotEmpty())
|
||||
{
|
||||
track_overlay = createTrackOverlay(requireContext(), mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED)
|
||||
special_points_overlay = create_start_end_markers(requireContext(), mapView, track.trkpts)
|
||||
}
|
||||
setupStatisticsViews()
|
||||
}
|
||||
|
||||
fun get_datetime(datepicker: DatePicker, timepicker: TimePicker, seconds: Int): Date
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(datepicker.year, datepicker.month, datepicker.dayOfMonth, timepicker.hour, timepicker.minute, seconds)
|
||||
Log.i("VOUSSOIR", cal.time.toString())
|
||||
return cal.time
|
||||
}
|
||||
|
||||
fun decrement_datepicker(picker: DatePicker)
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(picker.year, picker.month, picker.dayOfMonth)
|
||||
cal.add(Calendar.DATE, -1)
|
||||
picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH))
|
||||
}
|
||||
|
||||
fun increment_datepicker(picker: DatePicker)
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(picker.year, picker.month, picker.dayOfMonth)
|
||||
cal.add(Calendar.DATE, 1)
|
||||
picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH))
|
||||
}
|
||||
|
||||
private fun setupStatisticsViews()
|
||||
{
|
||||
val stats: TrackStatistics = track.statistics()
|
||||
trackNameView.text = track.name
|
||||
distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits)
|
||||
waypointsView.text = track.trkpts.size.toString()
|
||||
durationView.text = DateTimeHelper.convertToReadableTime(requireContext(), stats.duration)
|
||||
velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits)
|
||||
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time)
|
||||
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.end_time)
|
||||
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits)
|
||||
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits)
|
||||
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits)
|
||||
negativeElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_descent, useImperialUnits)
|
||||
|
||||
// inform user about possible accuracy issues with altitude measurements
|
||||
elevationDataViews.referencedIds.forEach { id ->
|
||||
(rootView.findViewById(id) as View).setOnClickListener{
|
||||
Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
// make track name on statistics sheet clickable
|
||||
trackNameView.setOnClickListener {
|
||||
toggleStatisticsSheetVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleStatisticsSheetVisibility()
|
||||
{
|
||||
when (statisticsSheetBehavior.state) {
|
||||
BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
/* Overrides onZoom from MapListener */
|
||||
override fun onZoom(event: ZoomEvent?): Boolean
|
||||
{
|
||||
return (event != null)
|
||||
}
|
||||
|
||||
/* Overrides onScroll from MapListener */
|
||||
override fun onScroll(event: ScrollEvent?): Boolean
|
||||
{
|
||||
return (event != null)
|
||||
}
|
||||
|
||||
private val requery_and_render: Runnable = object : Runnable {
|
||||
override fun run()
|
||||
{
|
||||
Log.i("VOUSSOIR", "requery_and_render")
|
||||
track.start_time = get_datetime(track_query_start_date, track_query_start_time, seconds=0)
|
||||
track.end_time = get_datetime(track_query_end_date, track_query_end_time, seconds=59)
|
||||
track.load_trkpts()
|
||||
Log.i("VOUSSOIR", "Reloaded ${track.trkpts.size} trkpts.")
|
||||
render_track()
|
||||
mapView.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/* Register the ActivityResultLauncher for saving GPX */
|
||||
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
|
||||
|
||||
|
@ -93,7 +367,7 @@ class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener
|
|||
return
|
||||
}
|
||||
|
||||
val outputsuccess: Uri? = layout.track.export_gpx(activity as Context, targetUri)
|
||||
val outputsuccess: Uri? = track.export_gpx(activity as Context, targetUri)
|
||||
if (outputsuccess == null)
|
||||
{
|
||||
Toast.makeText(activity as Context, "failed to export for some reason", Toast.LENGTH_LONG).show()
|
||||
|
@ -126,7 +400,7 @@ class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener
|
|||
/* Opens up a file picker to select the save location */
|
||||
private fun openSaveGpxDialog()
|
||||
{
|
||||
val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(layout.track.start_time) + " " + layout.track.device_id + Keys.GPX_FILE_EXTENSION
|
||||
val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(track.start_time) + " " + track.device_id + Keys.GPX_FILE_EXTENSION
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = Keys.MIME_TYPE_GPX
|
||||
|
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* TrackFragmentLayoutHolder.kt
|
||||
* Implements the TrackFragmentLayoutHolder class
|
||||
* A TrackFragmentLayoutHolder hold references to the main views of a track fragment
|
||||
*
|
||||
* This file is part of
|
||||
* TRACKBOOK - Movement Recorder for Android
|
||||
*
|
||||
* Copyright (c) 2016-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Trackbook uses osmdroid - OpenStreetMap-Tools for Android
|
||||
* https://github.com/osmdroid/osmdroid
|
||||
*/
|
||||
|
||||
package org.y20k.trackbook.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.DatePicker
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TimePicker
|
||||
import android.widget.Toast
|
||||
import androidx.constraintlayout.widget.Group
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import org.osmdroid.api.IMapController
|
||||
import org.osmdroid.events.MapListener
|
||||
import org.osmdroid.events.ScrollEvent
|
||||
import org.osmdroid.events.ZoomEvent
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
||||
import org.osmdroid.views.overlay.OverlayItem
|
||||
import org.osmdroid.views.overlay.TilesOverlay
|
||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||
import org.y20k.trackbook.Keys
|
||||
import org.y20k.trackbook.R
|
||||
import org.y20k.trackbook.Track
|
||||
import org.y20k.trackbook.TrackStatistics
|
||||
import org.y20k.trackbook.helpers.*
|
||||
import java.util.*
|
||||
|
||||
data class TrackFragmentLayoutHolder(
|
||||
private var context: Context,
|
||||
private var inflater: LayoutInflater,
|
||||
private var container: ViewGroup?,
|
||||
var track: Track
|
||||
): MapListener
|
||||
{
|
||||
val rootView: View
|
||||
val save_track_button: ImageButton
|
||||
val deleteButton: ImageButton
|
||||
val trackNameView: MaterialTextView
|
||||
val track_query_start_date: DatePicker
|
||||
val track_query_start_time: TimePicker
|
||||
val track_query_end_date: DatePicker
|
||||
val track_query_end_time: TimePicker
|
||||
var track_query_start_time_previous: Int
|
||||
var track_query_end_time_previous: Int
|
||||
private val mapView: MapView
|
||||
private var controller: IMapController
|
||||
private val statisticsSheetBehavior: BottomSheetBehavior<View>
|
||||
private val statisticsSheet: NestedScrollView
|
||||
private val statisticsView: View
|
||||
private val distanceView: MaterialTextView
|
||||
private val waypointsView: MaterialTextView
|
||||
private val durationView: MaterialTextView
|
||||
private val velocityView: MaterialTextView
|
||||
private val recordingStartView: MaterialTextView
|
||||
private val recordingStopView: MaterialTextView
|
||||
private val recordingPausedView: MaterialTextView
|
||||
private val recordingPausedLabelView: MaterialTextView
|
||||
private val maxAltitudeView: MaterialTextView
|
||||
private val minAltitudeView: MaterialTextView
|
||||
private val positiveElevationView: MaterialTextView
|
||||
private val negativeElevationView: MaterialTextView
|
||||
private val elevationDataViews: Group
|
||||
private val useImperialUnits: Boolean
|
||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||
private var special_points_overlay: ItemizedIconOverlay<OverlayItem>? = null
|
||||
private var track_overlay: SimpleFastPointOverlay? = null
|
||||
val RERENDER_DELAY: Long = 1000
|
||||
|
||||
init
|
||||
{
|
||||
rootView = inflater.inflate(R.layout.fragment_track, container, false)
|
||||
mapView = rootView.findViewById(R.id.map)
|
||||
save_track_button = rootView.findViewById(R.id.save_button)
|
||||
deleteButton = rootView.findViewById(R.id.delete_button)
|
||||
trackNameView = rootView.findViewById(R.id.statistics_track_name_headline)
|
||||
|
||||
track.load_trkpts()
|
||||
val actual_start_time: Date = if (track.trkpts.isEmpty()) track.start_time else Date(track.trkpts.first().time)
|
||||
val actual_end_time: Date = if (track.trkpts.isEmpty()) track.end_time else Date(track.trkpts.last().time)
|
||||
|
||||
track_query_start_date = rootView.findViewById(R.id.track_query_start_date)
|
||||
val start_cal = GregorianCalendar()
|
||||
start_cal.time = actual_start_time
|
||||
track_query_start_date.init(start_cal.get(Calendar.YEAR), start_cal.get(Calendar.MONTH), start_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener {
|
||||
override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_start_time = rootView.findViewById(R.id.track_query_start_time)
|
||||
track_query_start_time.setIs24HourView(true)
|
||||
track_query_start_time.hour = actual_start_time.hours
|
||||
track_query_start_time.minute = actual_start_time.minutes
|
||||
track_query_start_time_previous = (actual_start_time.hours * 60) + actual_start_time.minutes
|
||||
track_query_start_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{
|
||||
override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
val newminute = (p1 * 60) + p2
|
||||
Log.i("VOUSSOIR", "End time changed $newminute")
|
||||
if (newminute < track_query_start_time_previous && (track_query_start_time_previous - newminute > 60))
|
||||
{
|
||||
increment_datepicker(track_query_start_date)
|
||||
}
|
||||
else if (newminute > track_query_start_time_previous && (newminute - track_query_start_time_previous > 60))
|
||||
{
|
||||
decrement_datepicker(track_query_start_date)
|
||||
}
|
||||
track_query_start_time_previous = newminute
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_end_date = rootView.findViewById(R.id.track_query_end_date)
|
||||
val end_cal = GregorianCalendar()
|
||||
end_cal.time = actual_end_time
|
||||
track_query_end_date.init(end_cal.get(Calendar.YEAR), end_cal.get(Calendar.MONTH), end_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener {
|
||||
override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
track_query_end_time = rootView.findViewById(R.id.track_query_end_time)
|
||||
track_query_end_time.setIs24HourView(true)
|
||||
track_query_end_time.hour = actual_end_time.hours
|
||||
track_query_end_time.minute = actual_end_time.minutes
|
||||
track_query_end_time_previous = (actual_end_time.hours * 60) + actual_end_time.minutes
|
||||
track_query_end_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{
|
||||
override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int)
|
||||
{
|
||||
handler.removeCallbacks(requery_and_render)
|
||||
val newminute = (p1 * 60) + p2
|
||||
Log.i("VOUSSOIR", "End time changed $newminute")
|
||||
if (newminute < track_query_end_time_previous && (track_query_end_time_previous - newminute > 60))
|
||||
{
|
||||
increment_datepicker(track_query_end_date)
|
||||
}
|
||||
else if (newminute > track_query_end_time_previous && (newminute - track_query_end_time_previous > 60))
|
||||
{
|
||||
decrement_datepicker(track_query_end_date)
|
||||
}
|
||||
track_query_end_time_previous = newminute
|
||||
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||
}
|
||||
})
|
||||
|
||||
controller = mapView.controller
|
||||
mapView.addMapListener(this)
|
||||
mapView.isTilesScaledToDpi = true
|
||||
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
||||
mapView.isVerticalMapRepetitionEnabled = false
|
||||
mapView.setMultiTouchControls(true)
|
||||
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
||||
controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude))
|
||||
controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL)
|
||||
|
||||
statisticsSheet = rootView.findViewById(R.id.statistics_sheet)
|
||||
statisticsView = rootView.findViewById(R.id.statistics_view)
|
||||
distanceView = rootView.findViewById(R.id.statistics_data_distance)
|
||||
waypointsView = rootView.findViewById(R.id.statistics_data_waypoints)
|
||||
durationView = rootView.findViewById(R.id.statistics_data_duration)
|
||||
velocityView = rootView.findViewById(R.id.statistics_data_velocity)
|
||||
recordingStartView = rootView.findViewById(R.id.statistics_data_recording_start)
|
||||
recordingStopView = rootView.findViewById(R.id.statistics_data_recording_stop)
|
||||
recordingPausedLabelView = rootView.findViewById(R.id.statistics_p_recording_paused)
|
||||
recordingPausedView = rootView.findViewById(R.id.statistics_data_recording_paused)
|
||||
maxAltitudeView = rootView.findViewById(R.id.statistics_data_max_altitude)
|
||||
minAltitudeView = rootView.findViewById(R.id.statistics_data_min_altitude)
|
||||
positiveElevationView = rootView.findViewById(R.id.statistics_data_positive_elevation)
|
||||
negativeElevationView = rootView.findViewById(R.id.statistics_data_negative_elevation)
|
||||
elevationDataViews = rootView.findViewById(R.id.elevation_data)
|
||||
|
||||
useImperialUnits = PreferencesHelper.loadUseImperialUnits()
|
||||
|
||||
if (AppThemeHelper.isDarkModeOn(context as Activity)) {
|
||||
mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
|
||||
}
|
||||
|
||||
statisticsSheetBehavior = BottomSheetBehavior.from<View>(statisticsSheet)
|
||||
statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
render_track()
|
||||
}
|
||||
|
||||
fun render_track()
|
||||
{
|
||||
if (special_points_overlay != null)
|
||||
{
|
||||
mapView.overlays.remove(special_points_overlay)
|
||||
}
|
||||
if (track_overlay != null)
|
||||
{
|
||||
mapView.overlays.remove(track_overlay)
|
||||
}
|
||||
if (track.trkpts.isNotEmpty())
|
||||
{
|
||||
track_overlay = createTrackOverlay(context, mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED)
|
||||
special_points_overlay = create_start_end_markers(context, mapView, track.trkpts)
|
||||
}
|
||||
setupStatisticsViews()
|
||||
}
|
||||
|
||||
fun get_datetime(datepicker: DatePicker, timepicker: TimePicker, seconds: Int): Date
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(datepicker.year, datepicker.month, datepicker.dayOfMonth, timepicker.hour, timepicker.minute, seconds)
|
||||
Log.i("VOUSSOIR", cal.time.toString())
|
||||
return cal.time
|
||||
}
|
||||
|
||||
fun decrement_datepicker(picker: DatePicker)
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(picker.year, picker.month, picker.dayOfMonth)
|
||||
cal.add(Calendar.DATE, -1)
|
||||
picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH))
|
||||
}
|
||||
|
||||
fun increment_datepicker(picker: DatePicker)
|
||||
{
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
cal.set(picker.year, picker.month, picker.dayOfMonth)
|
||||
cal.add(Calendar.DATE, 1)
|
||||
picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH))
|
||||
}
|
||||
|
||||
private fun setupStatisticsViews()
|
||||
{
|
||||
val stats: TrackStatistics = track.statistics()
|
||||
trackNameView.text = track.name
|
||||
distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits)
|
||||
waypointsView.text = track.trkpts.size.toString()
|
||||
durationView.text = DateTimeHelper.convertToReadableTime(context, stats.duration)
|
||||
velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits)
|
||||
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time)
|
||||
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.end_time)
|
||||
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits)
|
||||
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits)
|
||||
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits)
|
||||
negativeElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_descent, useImperialUnits)
|
||||
|
||||
// inform user about possible accuracy issues with altitude measurements
|
||||
elevationDataViews.referencedIds.forEach { id ->
|
||||
(rootView.findViewById(id) as View).setOnClickListener{
|
||||
Toast.makeText(context, R.string.toast_message_elevation_info, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
// make track name on statistics sheet clickable
|
||||
trackNameView.setOnClickListener {
|
||||
toggleStatisticsSheetVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
/* Shows/hides the statistics sheet */
|
||||
private fun toggleStatisticsSheetVisibility()
|
||||
{
|
||||
when (statisticsSheetBehavior.state) {
|
||||
BottomSheetBehavior.STATE_EXPANDED -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
else -> statisticsSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
/* Overrides onZoom from MapListener */
|
||||
override fun onZoom(event: ZoomEvent?): Boolean
|
||||
{
|
||||
return (event != null)
|
||||
}
|
||||
|
||||
/* Overrides onScroll from MapListener */
|
||||
override fun onScroll(event: ScrollEvent?): Boolean
|
||||
{
|
||||
return (event != null)
|
||||
}
|
||||
|
||||
private val requery_and_render: Runnable = object : Runnable {
|
||||
override fun run()
|
||||
{
|
||||
Log.i("VOUSSOIR", "requery_and_render")
|
||||
track.start_time = get_datetime(track_query_start_date, track_query_start_time, seconds=0)
|
||||
track.end_time = get_datetime(track_query_end_date, track_query_end_time, seconds=59)
|
||||
track.load_trkpts()
|
||||
Log.i("VOUSSOIR", "Reloaded ${track.trkpts.size} trkpts.")
|
||||
render_track()
|
||||
mapView.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue