checkpoint
This commit is contained in:
parent
04fa76249b
commit
5dad3d6209
2 changed files with 283 additions and 323 deletions
|
@ -17,20 +17,39 @@
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.os.*
|
import android.os.*
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import org.y20k.trackbook.Track
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.osmdroid.api.IMapController
|
||||||
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||||
|
import org.osmdroid.util.GeoPoint
|
||||||
|
import org.osmdroid.views.MapView
|
||||||
|
import org.osmdroid.views.overlay.FolderOverlay
|
||||||
|
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
||||||
|
import org.osmdroid.views.overlay.OverlayItem
|
||||||
|
import org.osmdroid.views.overlay.Polygon
|
||||||
|
import org.osmdroid.views.overlay.TilesOverlay
|
||||||
|
import org.osmdroid.views.overlay.compass.CompassOverlay
|
||||||
|
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
||||||
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MapFragment class
|
* MapFragment class
|
||||||
|
@ -48,11 +67,25 @@ class MapFragment : Fragment()
|
||||||
private var networkProviderActive: Boolean = false
|
private var networkProviderActive: Boolean = false
|
||||||
private lateinit var track: Track
|
private lateinit var track: Track
|
||||||
private lateinit var currentBestLocation: Location
|
private lateinit var currentBestLocation: Location
|
||||||
private lateinit var layout: MapFragmentLayoutHolder
|
|
||||||
private lateinit var trackerService: TrackerService
|
private lateinit var trackerService: TrackerService
|
||||||
|
|
||||||
|
lateinit var rootView: View
|
||||||
|
var userInteraction: Boolean = false
|
||||||
|
lateinit var currentLocationButton: FloatingActionButton
|
||||||
|
lateinit var mainButton: ExtendedFloatingActionButton
|
||||||
|
private lateinit var mapView: MapView
|
||||||
|
private lateinit var current_position_folder: FolderOverlay
|
||||||
|
private var currentTrackOverlay: SimpleFastPointOverlay? = null
|
||||||
|
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>? = null
|
||||||
|
private lateinit var locationErrorBar: Snackbar
|
||||||
|
private lateinit var controller: IMapController
|
||||||
|
private var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL
|
||||||
|
private lateinit var homepoints_overlay_folder: FolderOverlay
|
||||||
|
|
||||||
/* Overrides onCreate from Fragment */
|
/* Overrides onCreate from Fragment */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragment.onCreate")
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// TODO make only MapFragment's status bar transparent - see:
|
// TODO make only MapFragment's status bar transparent - see:
|
||||||
// https://gist.github.com/Dvik/a3de88d39da9d1d6d175025a56c5e797#file-viewextension-kt and
|
// https://gist.github.com/Dvik/a3de88d39da9d1d6d175025a56c5e797#file-viewextension-kt and
|
||||||
|
@ -61,28 +94,84 @@ class MapFragment : Fragment()
|
||||||
currentBestLocation = getLastKnownLocation(activity as Context)
|
currentBestLocation = getLastKnownLocation(activity as Context)
|
||||||
// get saved tracking state
|
// get saved tracking state
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
|
||||||
// initialize layout
|
{
|
||||||
val statusBarHeight: Int = UiHelper.getStatusBarHeight(activity as Context)
|
Log.i("VOUSSOIR", "MapFragment.onCreateView")
|
||||||
layout = MapFragmentLayoutHolder(activity as Context, inflater, container, statusBarHeight, currentBestLocation, trackingState)
|
val context = activity as Context
|
||||||
|
// find views
|
||||||
|
rootView = inflater.inflate(R.layout.fragment_map, container, false)
|
||||||
|
mapView = rootView.findViewById(R.id.map)
|
||||||
|
currentLocationButton = rootView.findViewById(R.id.location_button)
|
||||||
|
mainButton = rootView.findViewById(R.id.main_button)
|
||||||
|
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
|
||||||
|
|
||||||
|
// basic map setup
|
||||||
|
controller = mapView.controller
|
||||||
|
mapView.isTilesScaledToDpi = true
|
||||||
|
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
||||||
|
mapView.setMultiTouchControls(true)
|
||||||
|
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
||||||
|
zoomLevel = PreferencesHelper.loadZoomLevel()
|
||||||
|
controller.setZoom(zoomLevel)
|
||||||
|
|
||||||
|
// set dark map tiles, if necessary
|
||||||
|
if (AppThemeHelper.isDarkModeOn(requireActivity()))
|
||||||
|
{
|
||||||
|
mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store Density Scaling Factor
|
||||||
|
val densityScalingFactor: Float = UiHelper.getDensityScalingFactor(context)
|
||||||
|
|
||||||
|
// add compass to map
|
||||||
|
val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
||||||
|
compassOverlay.enableCompass()
|
||||||
|
// compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / densityScalingFactor)) // TODO uncomment when transparent status bar is re-implemented
|
||||||
|
val screen_width = Resources.getSystem().displayMetrics.widthPixels;
|
||||||
|
compassOverlay.setCompassCenter((screen_width / densityScalingFactor) - 36f, 36f)
|
||||||
|
mapView.overlays.add(compassOverlay)
|
||||||
|
|
||||||
|
val app: Trackbook = (context.applicationContext as Trackbook)
|
||||||
|
app.load_homepoints()
|
||||||
|
homepoints_overlay_folder = FolderOverlay()
|
||||||
|
mapView.overlays.add(homepoints_overlay_folder)
|
||||||
|
createHomepointOverlays(context, mapView, app.homepoints)
|
||||||
|
|
||||||
|
// add my location overlay
|
||||||
|
current_position_folder = FolderOverlay()
|
||||||
|
mapView.overlays.add(current_position_folder)
|
||||||
|
|
||||||
|
|
||||||
|
centerMap(currentBestLocation)
|
||||||
|
|
||||||
|
// initialize track overlays
|
||||||
|
currentTrackOverlay = null
|
||||||
|
currentTrackSpecialMarkerOverlay = null
|
||||||
|
|
||||||
|
// initialize main button state
|
||||||
|
updateMainButton(trackingState)
|
||||||
|
|
||||||
|
// listen for user interaction
|
||||||
|
addInteractionListener()
|
||||||
|
|
||||||
// set up buttons
|
// set up buttons
|
||||||
layout.currentLocationButton.setOnClickListener {
|
currentLocationButton.setOnClickListener {
|
||||||
layout.centerMap(currentBestLocation, animated = true)
|
centerMap(currentBestLocation, animated = true)
|
||||||
}
|
}
|
||||||
layout.mainButton.setOnClickListener {
|
mainButton.setOnClickListener {
|
||||||
handleTrackingManagementMenu()
|
handleTrackingManagementMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout.rootView
|
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
return rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onStart from Fragment */
|
/* Overrides onStart from Fragment */
|
||||||
override fun onStart() {
|
override fun onStart()
|
||||||
|
{
|
||||||
super.onStart()
|
super.onStart()
|
||||||
// request location permission if denied
|
// request location permission if denied
|
||||||
if (ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
if (ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
||||||
|
@ -93,27 +182,34 @@ class MapFragment : Fragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onResume from Fragment */
|
/* Overrides onResume from Fragment */
|
||||||
override fun onResume() {
|
override fun onResume()
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragment.onResume")
|
||||||
super.onResume()
|
super.onResume()
|
||||||
// if (bound) {
|
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
// trackerService.addGpsLocationListener()
|
// if (bound) {
|
||||||
// trackerService.addNetworkLocationListener()
|
// trackerService.addGpsLocationListener()
|
||||||
// }
|
// trackerService.addNetworkLocationListener()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onPause from Fragment */
|
/* Overrides onPause from Fragment */
|
||||||
override fun onPause() {
|
override fun onPause()
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragment.onPause")
|
||||||
super.onPause()
|
super.onPause()
|
||||||
layout.saveState(currentBestLocation)
|
saveBestLocationState(currentBestLocation)
|
||||||
if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) {
|
if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) {
|
||||||
trackerService.removeGpsLocationListener()
|
trackerService.removeGpsLocationListener()
|
||||||
trackerService.removeNetworkLocationListener()
|
trackerService.removeNetworkLocationListener()
|
||||||
trackerService.trackbook.database.commit()
|
trackerService.trackbook.database.commit()
|
||||||
}
|
}
|
||||||
|
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onStop() {
|
override fun onStop()
|
||||||
|
{
|
||||||
super.onStop()
|
super.onStop()
|
||||||
// unbind from TrackerService
|
// unbind from TrackerService
|
||||||
if (bound)
|
if (bound)
|
||||||
|
@ -123,6 +219,13 @@ class MapFragment : Fragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy()
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragment.onDestroy")
|
||||||
|
super.onDestroy()
|
||||||
|
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
}
|
||||||
|
|
||||||
/* Register the permission launcher for requesting location */
|
/* Register the permission launcher for requesting location */
|
||||||
private val requestLocationPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
private val requestLocationPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
||||||
if (isGranted) {
|
if (isGranted) {
|
||||||
|
@ -134,26 +237,24 @@ class MapFragment : Fragment()
|
||||||
// permission denied - unbind service
|
// permission denied - unbind service
|
||||||
activity?.unbindService(connection)
|
activity?.unbindService(connection)
|
||||||
}
|
}
|
||||||
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Register the permission launcher for starting the tracking service */
|
/* Register the permission launcher for starting the tracking service */
|
||||||
private val startTrackingPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
private val startTrackingPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
||||||
logPermissionRequestResult(isGranted)
|
if (isGranted)
|
||||||
|
{
|
||||||
|
LogHelper.i(TAG, "Request result: Activity Recognition permission has been granted.")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogHelper.i(TAG, "Request result: Activity Recognition permission has NOT been granted.")
|
||||||
|
}
|
||||||
// start service via intent so that it keeps running after unbind
|
// start service via intent so that it keeps running after unbind
|
||||||
startTrackerService()
|
startTrackerService()
|
||||||
trackerService.startTracking()
|
trackerService.startTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logs the request result of the Activity Recognition permission launcher */
|
|
||||||
private fun logPermissionRequestResult(isGranted: Boolean) {
|
|
||||||
if (isGranted) {
|
|
||||||
LogHelper.i(TAG, "Request result: Activity Recognition permission has been granted.")
|
|
||||||
} else {
|
|
||||||
LogHelper.i(TAG, "Request result: Activity Recognition permission has NOT been granted.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start recording waypoints */
|
/* Start recording waypoints */
|
||||||
private fun startTracking() {
|
private fun startTracking() {
|
||||||
// request activity recognition permission on Android Q+ if denied
|
// request activity recognition permission on Android Q+ if denied
|
||||||
|
@ -211,11 +312,153 @@ class MapFragment : Fragment()
|
||||||
if (activity != null)
|
if (activity != null)
|
||||||
{
|
{
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
layout.updateMainButton(trackingState)
|
updateMainButton(trackingState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private fun addInteractionListener() {
|
||||||
|
mapView.setOnTouchListener { v, event ->
|
||||||
|
userInteraction = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun centerMap(location: Location, animated: Boolean = false) {
|
||||||
|
val position = GeoPoint(location.latitude, location.longitude)
|
||||||
|
when (animated) {
|
||||||
|
true -> controller.animateTo(position)
|
||||||
|
false -> controller.setCenter(position)
|
||||||
|
}
|
||||||
|
userInteraction = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveBestLocationState(currentBestLocation: Location) {
|
||||||
|
PreferencesHelper.saveCurrentBestLocation(currentBestLocation)
|
||||||
|
PreferencesHelper.saveZoomLevel(mapView.zoomLevelDouble)
|
||||||
|
// reset user interaction state
|
||||||
|
userInteraction = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark current position on map */
|
||||||
|
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.markCurrentPosition")
|
||||||
|
val locationIsOld: Boolean = !(isRecentEnough(location))
|
||||||
|
|
||||||
|
// create marker
|
||||||
|
val newMarker: Drawable
|
||||||
|
val fillcolor: Int
|
||||||
|
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
||||||
|
{
|
||||||
|
fillcolor = Color.argb(64, 220, 61, 51)
|
||||||
|
if (locationIsOld)
|
||||||
|
{
|
||||||
|
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_black_24dp)!!
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_red_24dp)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fillcolor = Color.argb(64, 60, 152, 219)
|
||||||
|
if(locationIsOld)
|
||||||
|
{
|
||||||
|
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_black_24dp)!!
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_blue_24dp)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_position_folder.items.clear()
|
||||||
|
|
||||||
|
val current_location_radius = Polygon()
|
||||||
|
current_location_radius.points = Polygon.pointsAsCircle(GeoPoint(location.latitude, location.longitude), location.accuracy.toDouble())
|
||||||
|
current_location_radius.fillPaint.color = fillcolor
|
||||||
|
current_location_radius.outlinePaint.color = Color.argb(0, 0, 0, 0)
|
||||||
|
current_position_folder.add(current_location_radius)
|
||||||
|
|
||||||
|
val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>()
|
||||||
|
val overlayItem: OverlayItem = createOverlayItem(requireContext(), location.latitude, location.longitude, location.accuracy, location.provider.toString(), location.time)
|
||||||
|
overlayItem.setMarker(newMarker)
|
||||||
|
overlayItems.add(overlayItem)
|
||||||
|
current_position_folder.add(createOverlay(requireContext(), overlayItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createHomepointOverlays(context: Context, map_view: MapView, homepoints: List<Homepoint>)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.createHomepointOverlays")
|
||||||
|
val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>()
|
||||||
|
|
||||||
|
val newMarker: Drawable = ContextCompat.getDrawable(context, R.drawable.ic_homepoint_24dp)!!
|
||||||
|
|
||||||
|
homepoints_overlay_folder.items.clear()
|
||||||
|
|
||||||
|
for (homepoint in homepoints)
|
||||||
|
{
|
||||||
|
val overlayItem: OverlayItem = createOverlayItem(context, homepoint.location.latitude, homepoint.location.longitude, homepoint.location.accuracy, homepoint.location.provider.toString(), homepoint.location.time)
|
||||||
|
overlayItem.setMarker(newMarker)
|
||||||
|
overlayItems.add(overlayItem)
|
||||||
|
homepoints_overlay_folder.add(createOverlay(context, overlayItems))
|
||||||
|
|
||||||
|
val p = Polygon()
|
||||||
|
p.points = Polygon.pointsAsCircle(GeoPoint(homepoint.location.latitude, homepoint.location.longitude), homepoint.location.accuracy.toDouble())
|
||||||
|
p.fillPaint.color = Color.argb(64, 255, 193, 7)
|
||||||
|
p.outlinePaint.color = Color.argb(0, 0, 0, 0)
|
||||||
|
homepoints_overlay_folder.add(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay current track on map */
|
||||||
|
fun overlayCurrentTrack(track: Track, trackingState: Int) {
|
||||||
|
if (currentTrackOverlay != null) {
|
||||||
|
mapView.overlays.remove(currentTrackOverlay)
|
||||||
|
}
|
||||||
|
if (currentTrackSpecialMarkerOverlay != null) {
|
||||||
|
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
||||||
|
}
|
||||||
|
if (track.trkpts.isNotEmpty()) {
|
||||||
|
createTrackOverlay(requireContext(), mapView, track, trackingState)
|
||||||
|
createSpecialMakersTrackOverlay(requireContext(), mapView, track, trackingState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggles state of main button and additional buttons (save & resume) */
|
||||||
|
fun updateMainButton(trackingState: Int)
|
||||||
|
{
|
||||||
|
when (trackingState) {
|
||||||
|
Keys.STATE_TRACKING_STOPPED -> {
|
||||||
|
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
|
||||||
|
mainButton.text = requireContext().getString(R.string.button_start)
|
||||||
|
mainButton.contentDescription = requireContext().getString(R.string.descr_button_start)
|
||||||
|
currentLocationButton.isVisible = true
|
||||||
|
}
|
||||||
|
Keys.STATE_TRACKING_ACTIVE -> {
|
||||||
|
mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp)
|
||||||
|
mainButton.text = requireContext().getString(R.string.button_pause)
|
||||||
|
mainButton.contentDescription = requireContext().getString(R.string.descr_button_pause)
|
||||||
|
currentLocationButton.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
||||||
|
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
||||||
|
// CASE: Location permission not granted
|
||||||
|
locationErrorBar.setText(R.string.snackbar_message_location_permission_denied)
|
||||||
|
if (!locationErrorBar.isShown) locationErrorBar.show()
|
||||||
|
} else if (!gpsProviderActive && !networkProviderActive) {
|
||||||
|
// CASE: Location setting is off
|
||||||
|
locationErrorBar.setText(R.string.snackbar_message_location_offline)
|
||||||
|
if (!locationErrorBar.isShown) locationErrorBar.show()
|
||||||
|
} else {
|
||||||
|
if (locationErrorBar.isShown) locationErrorBar.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* End of declaration
|
* End of declaration
|
||||||
*/
|
*/
|
||||||
|
@ -231,7 +474,7 @@ class MapFragment : Fragment()
|
||||||
trackerService = binder.service
|
trackerService = binder.service
|
||||||
// get state of tracking and update button if necessary
|
// get state of tracking and update button if necessary
|
||||||
trackingState = trackerService.trackingState
|
trackingState = trackerService.trackingState
|
||||||
layout.updateMainButton(trackingState)
|
updateMainButton(trackingState)
|
||||||
// register listener for changes in shared preferences
|
// register listener for changes in shared preferences
|
||||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
// start listening for location updates
|
// start listening for location updates
|
||||||
|
@ -259,15 +502,15 @@ class MapFragment : Fragment()
|
||||||
networkProviderActive = trackerService.networkProviderActive
|
networkProviderActive = trackerService.networkProviderActive
|
||||||
trackingState = trackerService.trackingState
|
trackingState = trackerService.trackingState
|
||||||
// update location and track
|
// update location and track
|
||||||
layout.markCurrentPosition(currentBestLocation, trackingState)
|
markCurrentPosition(currentBestLocation, trackingState)
|
||||||
layout.overlayCurrentTrack(track, trackingState)
|
overlayCurrentTrack(track, trackingState)
|
||||||
// center map, if it had not been dragged/zoomed before
|
// center map, if it had not been dragged/zoomed before
|
||||||
if (!layout.userInteraction)
|
if (!userInteraction)
|
||||||
{
|
{
|
||||||
layout.centerMap(currentBestLocation, true)
|
centerMap(currentBestLocation, true)
|
||||||
}
|
}
|
||||||
// show error snackbar if necessary
|
// show error snackbar if necessary
|
||||||
layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
toggleLocationErrorBar(gpsProviderActive, networkProviderActive)
|
||||||
// use the handler to start runnable again after specified delay
|
// use the handler to start runnable again after specified delay
|
||||||
handler.postDelayed(this, Keys.REQUEST_CURRENT_LOCATION_INTERVAL)
|
handler.postDelayed(this, Keys.REQUEST_CURRENT_LOCATION_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
/*
|
|
||||||
* MapFragmentLayoutHolder.kt
|
|
||||||
* Implements the MapFragmentLayoutHolder class
|
|
||||||
* A MapFragmentLayoutHolder hold references to the main views of a map 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.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.location.Location
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import org.osmdroid.api.IMapController
|
|
||||||
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.Polygon
|
|
||||||
import org.osmdroid.views.overlay.TilesOverlay
|
|
||||||
import org.osmdroid.views.overlay.compass.CompassOverlay
|
|
||||||
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
|
||||||
import org.y20k.trackbook.Homepoint
|
|
||||||
import org.y20k.trackbook.Keys
|
|
||||||
import org.y20k.trackbook.R
|
|
||||||
import org.y20k.trackbook.Trackbook
|
|
||||||
import org.y20k.trackbook.Track
|
|
||||||
import org.y20k.trackbook.helpers.*
|
|
||||||
|
|
||||||
/*
|
|
||||||
* MapFragmentLayoutHolder class
|
|
||||||
*/
|
|
||||||
data class MapFragmentLayoutHolder(
|
|
||||||
private var context: Context,
|
|
||||||
private var inflater: LayoutInflater,
|
|
||||||
private var container: ViewGroup?,
|
|
||||||
private var statusBarHeight: Int,
|
|
||||||
private val startLocation: Location,
|
|
||||||
private val trackingState: Int
|
|
||||||
)
|
|
||||||
{
|
|
||||||
val rootView: View
|
|
||||||
var userInteraction: Boolean = false
|
|
||||||
val currentLocationButton: FloatingActionButton
|
|
||||||
val mainButton: ExtendedFloatingActionButton
|
|
||||||
private val mapView: MapView
|
|
||||||
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
|
||||||
private var current_location_radius: Polygon = Polygon()
|
|
||||||
private var currentTrackOverlay: SimpleFastPointOverlay?
|
|
||||||
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
|
|
||||||
private var locationErrorBar: Snackbar
|
|
||||||
private var controller: IMapController
|
|
||||||
private var zoomLevel: Double
|
|
||||||
|
|
||||||
init
|
|
||||||
{
|
|
||||||
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.init")
|
|
||||||
// find views
|
|
||||||
rootView = inflater.inflate(R.layout.fragment_map, container, false)
|
|
||||||
mapView = rootView.findViewById(R.id.map)
|
|
||||||
currentLocationButton = rootView.findViewById(R.id.location_button)
|
|
||||||
mainButton = rootView.findViewById(R.id.main_button)
|
|
||||||
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
|
|
||||||
|
|
||||||
// basic map setup
|
|
||||||
controller = mapView.controller
|
|
||||||
mapView.isTilesScaledToDpi = true
|
|
||||||
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
|
||||||
mapView.setMultiTouchControls(true)
|
|
||||||
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
|
||||||
zoomLevel = PreferencesHelper.loadZoomLevel()
|
|
||||||
controller.setZoom(zoomLevel)
|
|
||||||
|
|
||||||
// set dark map tiles, if necessary
|
|
||||||
if (AppThemeHelper.isDarkModeOn(context as Activity)) {
|
|
||||||
mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// store Density Scaling Factor
|
|
||||||
val densityScalingFactor: Float = UiHelper.getDensityScalingFactor(context)
|
|
||||||
|
|
||||||
// add compass to map
|
|
||||||
val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
|
|
||||||
compassOverlay.enableCompass()
|
|
||||||
// compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / densityScalingFactor)) // TODO uncomment when transparent status bar is re-implemented
|
|
||||||
val screen_width = Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
||||||
compassOverlay.setCompassCenter((screen_width / densityScalingFactor) - 36f, 36f)
|
|
||||||
mapView.overlays.add(compassOverlay)
|
|
||||||
|
|
||||||
val app: Trackbook = (context.applicationContext as Trackbook)
|
|
||||||
app.load_homepoints()
|
|
||||||
createHomepointOverlays(context, mapView, app.homepoints)
|
|
||||||
|
|
||||||
// add my location overlay
|
|
||||||
currentPositionOverlay = createOverlay(context, ArrayList<OverlayItem>())
|
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
|
||||||
centerMap(startLocation)
|
|
||||||
|
|
||||||
// initialize track overlays
|
|
||||||
currentTrackOverlay = null
|
|
||||||
currentTrackSpecialMarkerOverlay = null
|
|
||||||
|
|
||||||
// initialize main button state
|
|
||||||
updateMainButton(trackingState)
|
|
||||||
|
|
||||||
// listen for user interaction
|
|
||||||
addInteractionListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Listen for user interaction */
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
private fun addInteractionListener() {
|
|
||||||
mapView.setOnTouchListener { v, event ->
|
|
||||||
userInteraction = true
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set map center */
|
|
||||||
fun centerMap(location: Location, animated: Boolean = false) {
|
|
||||||
val position = GeoPoint(location.latitude, location.longitude)
|
|
||||||
when (animated) {
|
|
||||||
true -> controller.animateTo(position)
|
|
||||||
false -> controller.setCenter(position)
|
|
||||||
}
|
|
||||||
userInteraction = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save current best location and state of map to shared preferences */
|
|
||||||
fun saveState(currentBestLocation: Location) {
|
|
||||||
PreferencesHelper.saveCurrentBestLocation(currentBestLocation)
|
|
||||||
PreferencesHelper.saveZoomLevel(mapView.zoomLevelDouble)
|
|
||||||
// reset user interaction state
|
|
||||||
userInteraction = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mark current position on map */
|
|
||||||
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED)
|
|
||||||
{
|
|
||||||
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.markCurrentPosition")
|
|
||||||
val locationIsOld: Boolean = !(isRecentEnough(location))
|
|
||||||
|
|
||||||
// create marker
|
|
||||||
val newMarker: Drawable
|
|
||||||
val fillcolor: Int
|
|
||||||
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
|
|
||||||
{
|
|
||||||
fillcolor = Color.argb(64, 220, 61, 51)
|
|
||||||
if (locationIsOld)
|
|
||||||
{
|
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_black_24dp)!!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_24dp)!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fillcolor = Color.argb(64, 60, 152, 219)
|
|
||||||
if(locationIsOld)
|
|
||||||
{
|
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_black_24dp)!!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_24dp)!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add marker to list of overlay items
|
|
||||||
val overlayItem: OverlayItem = createOverlayItem(context, location.latitude, location.longitude, location.accuracy, location.provider.toString(), location.time)
|
|
||||||
overlayItem.setMarker(newMarker)
|
|
||||||
currentPositionOverlay.removeAllItems()
|
|
||||||
currentPositionOverlay.addItem(overlayItem)
|
|
||||||
|
|
||||||
if (current_location_radius in mapView.overlays)
|
|
||||||
{
|
|
||||||
mapView.overlays.remove(current_location_radius)
|
|
||||||
}
|
|
||||||
current_location_radius = Polygon()
|
|
||||||
current_location_radius.points = Polygon.pointsAsCircle(GeoPoint(location.latitude, location.longitude), location.accuracy.toDouble())
|
|
||||||
current_location_radius.fillPaint.color = fillcolor
|
|
||||||
current_location_radius.outlinePaint.color = Color.argb(0, 0, 0, 0)
|
|
||||||
mapView.overlays.add(current_location_radius)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createHomepointOverlays(context: Context, map_view: MapView, homepoints: List<Homepoint>)
|
|
||||||
{
|
|
||||||
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.createHomepointOverlays")
|
|
||||||
val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>()
|
|
||||||
|
|
||||||
val newMarker: Drawable = ContextCompat.getDrawable(context, R.drawable.ic_homepoint_24dp)!!
|
|
||||||
|
|
||||||
for (homepoint in homepoints)
|
|
||||||
{
|
|
||||||
val overlayItem: OverlayItem = createOverlayItem(context, homepoint.location.latitude, homepoint.location.longitude, homepoint.location.accuracy, homepoint.location.provider.toString(), homepoint.location.time)
|
|
||||||
overlayItem.setMarker(newMarker)
|
|
||||||
overlayItems.add(overlayItem)
|
|
||||||
map_view.overlays.add(createOverlay(context, overlayItems))
|
|
||||||
|
|
||||||
val p = Polygon()
|
|
||||||
p.points = Polygon.pointsAsCircle(GeoPoint(homepoint.location.latitude, homepoint.location.longitude), homepoint.location.accuracy.toDouble())
|
|
||||||
p.fillPaint.color = Color.argb(64, 255, 193, 7)
|
|
||||||
p.outlinePaint.color = Color.argb(0, 0, 0, 0)
|
|
||||||
map_view.overlays.add(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overlay current track on map */
|
|
||||||
fun overlayCurrentTrack(track: Track, trackingState: Int) {
|
|
||||||
if (currentTrackOverlay != null) {
|
|
||||||
mapView.overlays.remove(currentTrackOverlay)
|
|
||||||
}
|
|
||||||
if (currentTrackSpecialMarkerOverlay != null) {
|
|
||||||
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
|
||||||
}
|
|
||||||
if (track.trkpts.isNotEmpty()) {
|
|
||||||
createTrackOverlay(context, mapView, track, trackingState)
|
|
||||||
createSpecialMakersTrackOverlay(context, mapView, track, trackingState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggles state of main button and additional buttons (save & resume) */
|
|
||||||
fun updateMainButton(trackingState: Int)
|
|
||||||
{
|
|
||||||
when (trackingState) {
|
|
||||||
Keys.STATE_TRACKING_STOPPED -> {
|
|
||||||
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
|
|
||||||
mainButton.text = context.getString(R.string.button_start)
|
|
||||||
mainButton.contentDescription = context.getString(R.string.descr_button_start)
|
|
||||||
currentLocationButton.isVisible = true
|
|
||||||
}
|
|
||||||
Keys.STATE_TRACKING_ACTIVE -> {
|
|
||||||
mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp)
|
|
||||||
mainButton.text = context.getString(R.string.button_pause)
|
|
||||||
mainButton.contentDescription = context.getString(R.string.descr_button_pause)
|
|
||||||
currentLocationButton.isVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toggles content and visibility of the location error snackbar */
|
|
||||||
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
|
||||||
// CASE: Location permission not granted
|
|
||||||
locationErrorBar.setText(R.string.snackbar_message_location_permission_denied)
|
|
||||||
if (!locationErrorBar.isShown) locationErrorBar.show()
|
|
||||||
} else if (!gpsProviderActive && !networkProviderActive) {
|
|
||||||
// CASE: Location setting is off
|
|
||||||
locationErrorBar.setText(R.string.snackbar_message_location_offline)
|
|
||||||
if (!locationErrorBar.isShown) locationErrorBar.show()
|
|
||||||
} else {
|
|
||||||
if (locationErrorBar.isShown) locationErrorBar.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue