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 | ||||
| 
 | ||||
| import android.Manifest | ||||
| import android.app.Activity | ||||
| import android.content.* | ||||
| 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.os.* | ||||
| import android.util.Log | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.view.WindowManager | ||||
| import androidx.activity.result.contract.ActivityResultContracts.RequestPermission | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.core.view.isVisible | ||||
| 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.ui.MapFragmentLayoutHolder | ||||
| 
 | ||||
| /* | ||||
|  * MapFragment class | ||||
|  | @ -48,11 +67,25 @@ class MapFragment : Fragment() | |||
|     private var networkProviderActive: Boolean = false | ||||
|     private lateinit var track: Track | ||||
|     private lateinit var currentBestLocation: Location | ||||
|     private lateinit var layout: MapFragmentLayoutHolder | ||||
|     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 */ | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|     override fun onCreate(savedInstanceState: Bundle?) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onCreate") | ||||
|         super.onCreate(savedInstanceState) | ||||
|         // TODO make only MapFragment's status bar transparent - see: | ||||
|         // https://gist.github.com/Dvik/a3de88d39da9d1d6d175025a56c5e797#file-viewextension-kt and | ||||
|  | @ -61,28 +94,84 @@ class MapFragment : Fragment() | |||
|         currentBestLocation = getLastKnownLocation(activity as Context) | ||||
|         // get saved tracking state | ||||
|         trackingState = PreferencesHelper.loadTrackingState() | ||||
|         requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onStop from Fragment */ | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { | ||||
|         // initialize layout | ||||
|         val statusBarHeight: Int = UiHelper.getStatusBarHeight(activity as Context) | ||||
|         layout = MapFragmentLayoutHolder(activity as Context, inflater, container, statusBarHeight, currentBestLocation, trackingState) | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onCreateView") | ||||
|         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 | ||||
|         layout.currentLocationButton.setOnClickListener { | ||||
|             layout.centerMap(currentBestLocation, animated = true) | ||||
|         currentLocationButton.setOnClickListener { | ||||
|             centerMap(currentBestLocation, animated = true) | ||||
|         } | ||||
|         layout.mainButton.setOnClickListener { | ||||
|         mainButton.setOnClickListener { | ||||
|             handleTrackingManagementMenu() | ||||
|         } | ||||
| 
 | ||||
|         return layout.rootView | ||||
|         requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||||
|         return rootView | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onStart from Fragment */ | ||||
|     override fun onStart() { | ||||
|     override fun onStart() | ||||
|     { | ||||
|         super.onStart() | ||||
|         // request location permission if 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 */ | ||||
|     override fun onResume() { | ||||
|     override fun onResume() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onResume") | ||||
|         super.onResume() | ||||
| //        if (bound) { | ||||
| //            trackerService.addGpsLocationListener() | ||||
| //            trackerService.addNetworkLocationListener() | ||||
| //        } | ||||
|         requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||||
|         // if (bound) { | ||||
|         //     trackerService.addGpsLocationListener() | ||||
|         //     trackerService.addNetworkLocationListener() | ||||
|         // } | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onPause from Fragment */ | ||||
|     override fun onPause() { | ||||
|     override fun onPause() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onPause") | ||||
|         super.onPause() | ||||
|         layout.saveState(currentBestLocation) | ||||
|         saveBestLocationState(currentBestLocation) | ||||
|         if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) { | ||||
|             trackerService.removeGpsLocationListener() | ||||
|             trackerService.removeNetworkLocationListener() | ||||
|             trackerService.trackbook.database.commit() | ||||
|         } | ||||
|         requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onStop from Fragment */ | ||||
|     override fun onStop() { | ||||
|     override fun onStop() | ||||
|     { | ||||
|         super.onStop() | ||||
|         // unbind from TrackerService | ||||
|         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 */ | ||||
|     private val requestLocationPermissionLauncher = registerForActivityResult(RequestPermission()) { isGranted: Boolean -> | ||||
|         if (isGranted) { | ||||
|  | @ -134,26 +237,24 @@ class MapFragment : Fragment() | |||
|             // permission denied - unbind service | ||||
|             activity?.unbindService(connection) | ||||
|         } | ||||
|         layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive) | ||||
|         toggleLocationErrorBar(gpsProviderActive, networkProviderActive) | ||||
|     } | ||||
| 
 | ||||
|     /* Register the permission launcher for starting the tracking service */ | ||||
|     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 | ||||
|         startTrackerService() | ||||
|         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 */ | ||||
|     private fun startTracking() { | ||||
|         // request activity recognition permission on Android Q+ if denied | ||||
|  | @ -211,11 +312,153 @@ class MapFragment : Fragment() | |||
|                 if (activity != null) | ||||
|                 { | ||||
|                     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 | ||||
|      */ | ||||
|  | @ -231,7 +474,7 @@ class MapFragment : Fragment() | |||
|             trackerService = binder.service | ||||
|             // get state of tracking and update button if necessary | ||||
|             trackingState = trackerService.trackingState | ||||
|             layout.updateMainButton(trackingState) | ||||
|             updateMainButton(trackingState) | ||||
|             // register listener for changes in shared preferences | ||||
|             PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) | ||||
|             // start listening for location updates | ||||
|  | @ -259,15 +502,15 @@ class MapFragment : Fragment() | |||
|             networkProviderActive = trackerService.networkProviderActive | ||||
|             trackingState = trackerService.trackingState | ||||
|             // update location and track | ||||
|             layout.markCurrentPosition(currentBestLocation, trackingState) | ||||
|             layout.overlayCurrentTrack(track, trackingState) | ||||
|             markCurrentPosition(currentBestLocation, trackingState) | ||||
|             overlayCurrentTrack(track, trackingState) | ||||
|             // 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 | ||||
|             layout.toggleLocationErrorBar(gpsProviderActive, networkProviderActive) | ||||
|             toggleLocationErrorBar(gpsProviderActive, networkProviderActive) | ||||
|             // use the handler to start runnable again after specified delay | ||||
|             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