diff --git a/app/src/main/java/org/y20k/trackbook/TrackFragment.kt b/app/src/main/java/org/y20k/trackbook/TrackFragment.kt index 08e9b4b..82a2fbe 100644 --- a/app/src/main/java/org/y20k/trackbook/TrackFragment.kt +++ b/app/src/main/java/org/y20k/trackbook/TrackFragment.kt @@ -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 + 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? = 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(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 diff --git a/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt b/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt deleted file mode 100644 index d05e24d..0000000 --- a/app/src/main/java/org/y20k/trackbook/ui/TrackFragmentLayoutHolder.kt +++ /dev/null @@ -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 - 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? = 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(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() - } - } -} -