checkpoint
This commit is contained in:
		
							parent
							
								
									5b68b33f1d
								
							
						
					
					
						commit
						06ca0fd2b3
					
				
					 12 changed files with 324 additions and 260 deletions
				
			
		|  | @ -53,6 +53,14 @@ class Database(val trackbook: Trackbook) | |||
|         this.connection.endTransaction() | ||||
|     } | ||||
| 
 | ||||
|     fun delete_trkpt(device_id: String, time: Long) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Database.delete_trkpt") | ||||
|         begin_transaction() | ||||
|         connection.delete("trkpt", "device_id = ? AND time = ?", arrayOf(device_id, time.toString())) | ||||
|         commit() | ||||
|     } | ||||
| 
 | ||||
|     fun insert_trkpt(trkpt: Trkpt) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Database.insert_trkpt") | ||||
|  | @ -60,7 +68,7 @@ class Database(val trackbook: Trackbook) | |||
|             put("device_id", trkpt.device_id) | ||||
|             put("lat", trkpt.latitude) | ||||
|             put("lon", trkpt.longitude) | ||||
|             put("time", GregorianCalendar.getInstance().time.time) | ||||
|             put("time", trkpt.time) | ||||
|             put("accuracy", trkpt.accuracy) | ||||
|             put("sat", trkpt.numberSatellites) | ||||
|             put("ele", trkpt.altitude) | ||||
|  | @ -108,8 +116,9 @@ class Database(val trackbook: Trackbook) | |||
|     { | ||||
|         begin_transaction() | ||||
|         this.connection.execSQL("CREATE TABLE IF NOT EXISTS meta(name TEXT PRIMARY KEY, value TEXT)") | ||||
|         this.connection.execSQL("CREATE TABLE IF NOT EXISTS trkpt(lat REAL NOT NULL, lon REAL NOT NULL, time INTEGER NOT NULL, accuracy REAL, device_id INTEGER NOT NULL, ele INTEGER, sat INTEGER, PRIMARY KEY(lat, lon, time, device_id))") | ||||
|         this.connection.execSQL("CREATE TABLE IF NOT EXISTS trkpt(lat REAL NOT NULL, lon REAL NOT NULL, time INTEGER NOT NULL, accuracy REAL, device_id INTEGER NOT NULL, ele INTEGER, sat INTEGER, PRIMARY KEY(device_id, time))") | ||||
|         this.connection.execSQL("CREATE TABLE IF NOT EXISTS homepoints(id INTEGER PRIMARY KEY, lat REAL NOT NULL, lon REAL NOT NULL, radius REAL NOT NULL, name TEXT)") | ||||
|         this.connection.execSQL("CREATE INDEX IF NOT EXISTS index_trkpt_device_id_time on trkpt(device_id, time)") | ||||
|         // The pragmas don't seem to execute unless you call moveToNext. | ||||
|         var cursor: Cursor | ||||
|         cursor = this.connection.rawQuery("PRAGMA journal_mode = DELETE", null) | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| package org.y20k.trackbook | ||||
| 
 | ||||
| import android.graphics.Color | ||||
| import java.util.* | ||||
| 
 | ||||
| /* | ||||
|  | @ -109,4 +110,7 @@ object Keys { | |||
|     // notification | ||||
|     const val TRACKER_SERVICE_NOTIFICATION_ID: Int = 1 | ||||
|     const val NOTIFICATION_CHANNEL_RECORDING: String = "notificationChannelIdRecordingChannel" | ||||
| 
 | ||||
|     const val POLYLINE_THICKNESS = 4F | ||||
|     val POLYLINE_COLOR = Color.argb(255, 255, 0, 255) | ||||
| } | ||||
|  |  | |||
|  | @ -40,8 +40,6 @@ import androidx.fragment.app.Fragment | |||
| 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.IGeoPoint | ||||
| import org.osmdroid.api.IMapController | ||||
| import org.osmdroid.events.MapEventsReceiver | ||||
| import org.osmdroid.tileprovider.tilesource.TileSourceFactory | ||||
| import org.osmdroid.util.GeoPoint | ||||
|  | @ -51,46 +49,46 @@ import org.osmdroid.views.overlay.MapEventsOverlay | |||
| import org.osmdroid.views.overlay.Overlay | ||||
| import org.osmdroid.views.overlay.OverlayItem | ||||
| import org.osmdroid.views.overlay.Polygon | ||||
| import org.osmdroid.views.overlay.Polyline | ||||
| 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.* | ||||
| 
 | ||||
| /* | ||||
|  * MapFragment class | ||||
|  */ | ||||
| class MapFragment : Fragment() | ||||
| { | ||||
|     /* Main class variables */ | ||||
|     private lateinit var trackbook: Trackbook | ||||
| 
 | ||||
|     private var bound: Boolean = false | ||||
|     private val handler: Handler = Handler(Looper.getMainLooper()) | ||||
|     val handler: Handler = Handler(Looper.getMainLooper()) | ||||
|     private var trackingState: Int = Keys.STATE_TRACKING_STOPPED | ||||
|     private var gpsProviderActive: Boolean = false | ||||
|     private var networkProviderActive: Boolean = false | ||||
|     private lateinit var currentBestLocation: Location | ||||
|     private lateinit var trackerService: TrackerService | ||||
| 
 | ||||
|     private lateinit var trackbook: Trackbook | ||||
|     lateinit var rootView: View | ||||
|     var continuous_auto_center: Boolean = true | ||||
|     lateinit var currentLocationButton: FloatingActionButton | ||||
|     private lateinit var trackerService: TrackerService | ||||
|     private lateinit var database_changed_listener: DatabaseChangedListener | ||||
| 
 | ||||
|     var thismapfragment: MapFragment? = null | ||||
|     lateinit var rootView: View | ||||
|     private lateinit var mapView: MapView | ||||
|     lateinit var mainButton: ExtendedFloatingActionButton | ||||
| 
 | ||||
|     lateinit var zoom_in_button: FloatingActionButton | ||||
|     lateinit var zoom_out_button: FloatingActionButton | ||||
|     lateinit var mainButton: ExtendedFloatingActionButton | ||||
|     private lateinit var mapView: MapView | ||||
|     lateinit var currentLocationButton: FloatingActionButton | ||||
|     private var current_track_overlay: Polyline? = null | ||||
|     private var current_position_overlays = ArrayList<Overlay>() | ||||
|     private var currentTrackOverlay: SimpleFastPointOverlay? = null | ||||
|     private lateinit var locationErrorBar: Snackbar | ||||
|     private var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL | ||||
|     private var homepoints_overlays = ArrayList<Overlay>() | ||||
|     private lateinit var database_changed_listener: DatabaseChangedListener | ||||
|     private lateinit var locationErrorBar: Snackbar | ||||
| 
 | ||||
|     /* Overrides onCreate from Fragment */ | ||||
|     override fun onCreate(savedInstanceState: Bundle?) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onCreate") | ||||
|         super.onCreate(savedInstanceState) | ||||
|         thismapfragment = this | ||||
|         this.trackbook = (requireContext().applicationContext as Trackbook) | ||||
|         database_changed_listener = object: DatabaseChangedListener | ||||
|         { | ||||
|  | @ -99,7 +97,7 @@ class MapFragment : Fragment() | |||
|                 Log.i("VOUSSOIR", "MapFragment database_ready_changed to ${trackbook.database.ready}") | ||||
|                 if (trackbook.database.ready) | ||||
|                 { | ||||
|                     create_homepoint_overlays(requireContext(), mapView, trackbook.homepoints) | ||||
|                     create_homepoint_overlays() | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|  | @ -116,7 +114,7 @@ class MapFragment : Fragment() | |||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onCreateView") | ||||
|         // find views | ||||
| 
 | ||||
|         rootView = inflater.inflate(R.layout.fragment_map, container, false) | ||||
|         mapView = rootView.findViewById(R.id.map) | ||||
|         currentLocationButton = rootView.findViewById(R.id.location_button) | ||||
|  | @ -130,8 +128,6 @@ class MapFragment : Fragment() | |||
|             true | ||||
|         } | ||||
|         mapView.isLongClickable = true | ||||
| 
 | ||||
|         // basic map setup | ||||
|         mapView.isTilesScaledToDpi = true | ||||
|         mapView.isVerticalMapRepetitionEnabled = false | ||||
|         mapView.setTileSource(TileSourceFactory.MAPNIK) | ||||
|  | @ -145,10 +141,8 @@ class MapFragment : Fragment() | |||
|         } | ||||
| 
 | ||||
|         val densityScalingFactor: Float = UiHelper.getDensityScalingFactor(requireContext()) | ||||
| 
 | ||||
|         val compassOverlay = CompassOverlay(requireContext(), InternalCompassOrientationProvider(requireContext()), 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) | ||||
|  | @ -187,7 +181,7 @@ class MapFragment : Fragment() | |||
|                         radius=radius, | ||||
|                     ) | ||||
|                     trackbook.load_homepoints() | ||||
|                     create_homepoint_overlays(requireContext(), mapView, trackbook.homepoints) | ||||
|                     create_homepoint_overlays() | ||||
|                     dialog.dismiss() | ||||
|                 } | ||||
| 
 | ||||
|  | @ -198,28 +192,34 @@ class MapFragment : Fragment() | |||
|         mapView.overlays.add(MapEventsOverlay(receiver)) | ||||
| 
 | ||||
|         trackbook.load_homepoints() | ||||
|         create_homepoint_overlays(requireContext(), mapView, trackbook.homepoints) | ||||
|         create_homepoint_overlays() | ||||
|         if (database_changed_listener !in trackbook.database_changed_listeners) | ||||
|         { | ||||
|             trackbook.database_changed_listeners.add(database_changed_listener) | ||||
|         } | ||||
| 
 | ||||
|         create_current_position_overlays(currentBestLocation, trackingState) | ||||
| 
 | ||||
|         centerMap(currentBestLocation) | ||||
| 
 | ||||
|         // initialize track overlays | ||||
|         currentTrackOverlay = null | ||||
| 
 | ||||
|         // initialize main button state | ||||
|         update_main_button() | ||||
|         current_track_overlay = null | ||||
| 
 | ||||
|         mapView.setOnTouchListener { v, event -> | ||||
|             continuous_auto_center = false | ||||
|             false | ||||
|         } | ||||
| 
 | ||||
|         // set up buttons | ||||
|         update_main_button() | ||||
|         mainButton.setOnClickListener { | ||||
|             handleTrackingManagementMenu() | ||||
|             if (trackingState == Keys.STATE_TRACKING_ACTIVE) | ||||
|             { | ||||
|                 trackerService.stopTracking() | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 startTracking() | ||||
|             } | ||||
|             handler.post(location_update_redraw) | ||||
|         } | ||||
|         currentLocationButton.setOnClickListener { | ||||
|             centerMap(currentBestLocation, animated=true) | ||||
|  | @ -238,6 +238,7 @@ class MapFragment : Fragment() | |||
|     /* Overrides onStart from Fragment */ | ||||
|     override fun onStart() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onStart") | ||||
|         super.onStart() | ||||
|         // request location permission if denied | ||||
|         if (ContextCompat.checkSelfPermission(activity as Context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) | ||||
|  | @ -265,6 +266,10 @@ class MapFragment : Fragment() | |||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onPause") | ||||
|         super.onPause() | ||||
|         if (::trackerService.isInitialized) | ||||
|         { | ||||
|             trackerService.mapfragment = null | ||||
|         } | ||||
|         saveBestLocationState(currentBestLocation) | ||||
|         if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) { | ||||
|             trackerService.removeGpsLocationListener() | ||||
|  | @ -278,6 +283,10 @@ class MapFragment : Fragment() | |||
|     override fun onStop() | ||||
|     { | ||||
|         super.onStop() | ||||
|         if (::trackerService.isInitialized) | ||||
|         { | ||||
|             trackerService.mapfragment = null | ||||
|         } | ||||
|         // unbind from TrackerService | ||||
|         if (bound) | ||||
|         { | ||||
|  | @ -290,6 +299,7 @@ class MapFragment : Fragment() | |||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragment.onDestroy") | ||||
|         super.onDestroy() | ||||
|         trackerService.mapfragment = null | ||||
|         requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||||
| 
 | ||||
|         if (database_changed_listener in trackbook.database_changed_listeners) | ||||
|  | @ -314,14 +324,17 @@ class MapFragment : Fragment() | |||
|         toggleLocationErrorBar(gpsProviderActive, networkProviderActive) | ||||
|     } | ||||
| 
 | ||||
|     /* Start recording waypoints */ | ||||
|     private fun startTracking() { | ||||
|     private fun startTracking() | ||||
|     { | ||||
|         // start service via intent so that it keeps running after unbind | ||||
|         val intent = Intent(activity, TrackerService::class.java) | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) | ||||
|         { | ||||
|             // ... start service in foreground to prevent it being killed on Oreo | ||||
|             activity?.startForegroundService(intent) | ||||
|         } else { | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             activity?.startService(intent) | ||||
|         } | ||||
|         trackerService.startTracking() | ||||
|  | @ -334,16 +347,9 @@ class MapFragment : Fragment() | |||
|         bound = false | ||||
|         // unregister listener for changes in shared preferences | ||||
|         PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener) | ||||
|         // stop receiving location updates | ||||
|         handler.removeCallbacks(periodicLocationRequestRunnable) | ||||
|     } | ||||
| 
 | ||||
|     /* Starts / pauses tracking and toggles the recording sub menu_bottom_navigation */ | ||||
|     private fun handleTrackingManagementMenu() | ||||
|     { | ||||
|         when (trackingState) { | ||||
|             Keys.STATE_TRACKING_ACTIVE -> trackerService.stopTracking() | ||||
|             Keys.STATE_TRACKING_STOPPED -> startTracking() | ||||
|         if (::trackerService.isInitialized) | ||||
|         { | ||||
|             trackerService.mapfragment = null | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -374,7 +380,8 @@ class MapFragment : Fragment() | |||
|         continuous_auto_center = true | ||||
|     } | ||||
| 
 | ||||
|     fun saveBestLocationState(currentBestLocation: Location) { | ||||
|     fun saveBestLocationState(currentBestLocation: Location) | ||||
|     { | ||||
|         PreferencesHelper.saveCurrentBestLocation(currentBestLocation) | ||||
|         PreferencesHelper.saveZoomLevel(mapView.zoomLevelDouble) | ||||
|         continuous_auto_center = true | ||||
|  | @ -395,38 +402,26 @@ class MapFragment : Fragment() | |||
|     /* Mark current position on map */ | ||||
|     fun create_current_position_overlays(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED) | ||||
|     { | ||||
|         // Log.i("VOUSSOIR", "MapFragmentLayoutHolder.markCurrentPosition") | ||||
| 
 | ||||
|         clear_current_position_overlays() | ||||
| 
 | ||||
|         val locationIsOld: Boolean = !(isRecentEnough(location)) | ||||
| 
 | ||||
|         // create marker | ||||
|         val newMarker: Drawable | ||||
|         val fillcolor: Int | ||||
|         if (trackingState == Keys.STATE_TRACKING_ACTIVE) | ||||
|         if (locationIsOld) | ||||
|         { | ||||
|             fillcolor = Color.argb(64, 0, 0, 0) | ||||
|             newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_black_24dp)!! | ||||
|         } | ||||
|         else 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)!! | ||||
|             } | ||||
|             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)!! | ||||
|             } | ||||
|             newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_blue_24dp)!! | ||||
|         } | ||||
| 
 | ||||
|         val current_location_radius = Polygon() | ||||
|  | @ -447,6 +442,21 @@ class MapFragment : Fragment() | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun clear_track_overlay() | ||||
|     { | ||||
|         mapView.overlays.remove(current_track_overlay) | ||||
|     } | ||||
| 
 | ||||
|     fun create_track_overlay() | ||||
|     { | ||||
|         clear_track_overlay() | ||||
|         val pl = Polyline(mapView) | ||||
|         pl.outlinePaint.strokeWidth = Keys.POLYLINE_THICKNESS | ||||
|         pl.outlinePaint.color = Keys.POLYLINE_COLOR | ||||
|         mapView.overlays.add(pl) | ||||
|         current_track_overlay = pl | ||||
|     } | ||||
| 
 | ||||
|     fun clear_homepoint_overlays() | ||||
|     { | ||||
|         for (ov in homepoints_overlays) | ||||
|  | @ -459,15 +469,16 @@ class MapFragment : Fragment() | |||
|         homepoints_overlays.clear() | ||||
|     } | ||||
| 
 | ||||
|     fun create_homepoint_overlays(context: Context, map_view: MapView, homepoints: List<Homepoint>) | ||||
|     fun create_homepoint_overlays() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "MapFragmentLayoutHolder.createHomepointOverlays") | ||||
| 
 | ||||
|         val context = requireContext() | ||||
|         val newMarker: Drawable = ContextCompat.getDrawable(context, R.drawable.ic_homepoint_24dp)!! | ||||
| 
 | ||||
|         clear_homepoint_overlays() | ||||
| 
 | ||||
|         for (homepoint in homepoints) | ||||
|         for (homepoint in trackbook.homepoints) | ||||
|         { | ||||
|             val p = Polygon() | ||||
|             p.points = Polygon.pointsAsCircle(GeoPoint(homepoint.location.latitude, homepoint.location.longitude), homepoint.location.accuracy.toDouble()) | ||||
|  | @ -476,7 +487,14 @@ class MapFragment : Fragment() | |||
|             homepoints_overlays.add(p) | ||||
| 
 | ||||
|             val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>() | ||||
|             val overlayItem: OverlayItem = createOverlayItem(context, homepoint.location.latitude, homepoint.location.longitude, homepoint.location.accuracy, homepoint.location.provider.toString(), homepoint.location.time) | ||||
|             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) | ||||
|             val homepoint_overlay = ItemizedIconOverlay<OverlayItem>(context, overlayItems, | ||||
|  | @ -504,14 +522,14 @@ class MapFragment : Fragment() | |||
|                         delete_button.setOnClickListener { | ||||
|                             trackbook.database.delete_homepoint(homepoint.id) | ||||
|                             trackbook.load_homepoints() | ||||
|                             create_homepoint_overlays(requireContext(), mapView, trackbook.homepoints) | ||||
|                             create_homepoint_overlays() | ||||
|                             dialog.dismiss() | ||||
|                         } | ||||
|                         save_button.setOnClickListener { | ||||
|                             val radius = radius_input.text.toString().toDoubleOrNull() ?: 25.0 | ||||
|                             trackbook.database.update_homepoint(homepoint.id, name=name_input.text.toString(), radius=radius) | ||||
|                             trackbook.load_homepoints() | ||||
|                             create_homepoint_overlays(requireContext(), mapView, trackbook.homepoints) | ||||
|                             create_homepoint_overlays() | ||||
|                             dialog.dismiss() | ||||
|                         } | ||||
| 
 | ||||
|  | @ -529,19 +547,6 @@ class MapFragment : Fragment() | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Overlay current track on map */ | ||||
|     fun create_current_track_overlay(geopoints: MutableList<IGeoPoint>, trackingState: Int) | ||||
|     { | ||||
|         if (currentTrackOverlay != null) | ||||
|         { | ||||
|             mapView.overlays.remove(currentTrackOverlay) | ||||
|         } | ||||
|         if (geopoints.isNotEmpty()) | ||||
|         { | ||||
|             currentTrackOverlay = createTrackOverlay(requireContext(), mapView, geopoints, trackingState) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun update_main_button() | ||||
|     { | ||||
|         mainButton.isEnabled = trackbook.database.ready | ||||
|  | @ -592,14 +597,13 @@ class MapFragment : Fragment() | |||
|             // get reference to tracker service | ||||
|             val binder = service as TrackerService.LocalBinder | ||||
|             trackerService = binder.service | ||||
|             trackerService.mapfragment = thismapfragment | ||||
|             // get state of tracking and update button if necessary | ||||
|             trackingState = trackerService.trackingState | ||||
|             update_main_button() | ||||
|             // register listener for changes in shared preferences | ||||
|             PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) | ||||
|             // start listening for location updates | ||||
|             handler.removeCallbacks(periodicLocationRequestRunnable) | ||||
|             handler.postDelayed(periodicLocationRequestRunnable, 0) | ||||
|         } | ||||
|         override fun onServiceDisconnected(arg0: ComponentName) | ||||
|         { | ||||
|  | @ -608,22 +612,27 @@ class MapFragment : Fragment() | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private val periodicLocationRequestRunnable: Runnable = object : Runnable { | ||||
|     val location_update_redraw: Runnable = object : Runnable | ||||
|     { | ||||
|         override fun run() | ||||
|         { | ||||
|             Log.i("VOUSSOIR", "MapFragment.location_update_redraw") | ||||
|             currentBestLocation = trackerService.currentBestLocation | ||||
|             gpsProviderActive = trackerService.gpsProviderActive | ||||
|             networkProviderActive = trackerService.networkProviderActive | ||||
|             trackingState = trackerService.trackingState | ||||
|             // update location and track | ||||
| 
 | ||||
|             create_current_position_overlays(currentBestLocation, trackingState) | ||||
|             create_current_track_overlay(trackerService.recent_trackpoints_for_mapview, trackingState) | ||||
|             // center map, if it had not been dragged/zoomed before | ||||
|             if (current_track_overlay == null) | ||||
|             { | ||||
|                 create_track_overlay() | ||||
|             } | ||||
|             current_track_overlay!!.setPoints(trackerService.recent_trackpoints_for_mapview) | ||||
| 
 | ||||
|             if (continuous_auto_center) | ||||
|             { | ||||
|                 centerMap(currentBestLocation, animated=false) | ||||
|             } | ||||
|             handler.postDelayed(this, Keys.REQUEST_CURRENT_LOCATION_INTERVAL) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -18,12 +18,13 @@ package org.y20k.trackbook | |||
| 
 | ||||
| import android.content.Context | ||||
| import android.database.Cursor | ||||
| import android.database.DatabaseUtils.dumpCursorToString | ||||
| import android.net.Uri | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import android.util.Log | ||||
| import android.widget.Toast | ||||
| import org.y20k.trackbook.helpers.iso8601_format | ||||
| import org.y20k.trackbook.helpers.iso8601 | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| 
 | ||||
|  | @ -33,7 +34,7 @@ data class Track ( | |||
|     var start_time: Date, | ||||
|     var end_time: Date, | ||||
|     var name: String = "", | ||||
|     val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(), | ||||
|     val trkpts: ArrayList<Trkpt> = ArrayList<Trkpt>(), | ||||
|     var view_latitude: Double = Keys.DEFAULT_LATITUDE, | ||||
|     var view_longitude: Double = Keys.DEFAULT_LONGITUDE, | ||||
|     var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION, | ||||
|  | @ -73,7 +74,7 @@ data class Track ( | |||
|         > | ||||
|         """.trimIndent()) | ||||
|         write("\t<metadata>") | ||||
|         write("\t\t<name>Trackbook Recording: ${this.name}</name>") | ||||
|         write("\t\t<name>${this.name}</name>") | ||||
|         write("\t\t<device>${this.device_id}</device>") | ||||
|         write("\t</metadata>") | ||||
| 
 | ||||
|  | @ -81,7 +82,6 @@ data class Track ( | |||
|         val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US) | ||||
|         dateFormat.timeZone = TimeZone.getTimeZone("UTC") | ||||
|         write("\t<trk>") | ||||
|         write("\t\t<name>${this.name}</name>") | ||||
|         write("\t\t<trkseg>") | ||||
| 
 | ||||
|         var previous: Trkpt? = null | ||||
|  | @ -94,7 +94,8 @@ data class Track ( | |||
|             } | ||||
|             write("\t\t\t<trkpt lat=\"${trkpt.latitude}\" lon=\"${trkpt.longitude}\">") | ||||
|             write("\t\t\t\t<ele>${trkpt.altitude}</ele>") | ||||
|             write("\t\t\t\t<time>${iso8601_format.format(trkpt.time)}</time>") | ||||
|             write("\t\t\t\t<time>${iso8601(trkpt.time)}</time>") | ||||
|             write("\t\t\t\t<unix>${trkpt.time}</unix>") | ||||
|             write("\t\t\t\t<sat>${trkpt.numberSatellites}</sat>") | ||||
|             write("\t\t\t</trkpt>") | ||||
|             previous = trkpt | ||||
|  | @ -170,11 +171,11 @@ data class Track ( | |||
| 
 | ||||
|     fun trkpt_generator() = iterator<Trkpt> | ||||
|     { | ||||
|         val cursor: Cursor = database.connection.rawQuery( | ||||
|         var cursor: Cursor = database.connection.rawQuery( | ||||
|             "SELECT lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND time > ? AND time < ? ORDER BY time ASC", | ||||
|             arrayOf(device_id, start_time.time.toString(), end_time.time.toString()) | ||||
|         ) | ||||
|         Log.i("VOUSSOIR", "Track.trkpt_generator: Querying points between ${start_time} -- ${end_time}") | ||||
|         Log.i("VOUSSOIR", "Track.trkpt_generator: Querying points between ${start_time} -- ${end_time}, ${cursor.count} results") | ||||
|         val COLUMN_LAT = cursor.getColumnIndex("lat") | ||||
|         val COLUMN_LON = cursor.getColumnIndex("lon") | ||||
|         val COLUMN_ELE = cursor.getColumnIndex("ele") | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import YesNoDialog | |||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.graphics.Paint | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.os.Handler | ||||
|  | @ -48,32 +49,38 @@ 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.Polyline | ||||
| import org.osmdroid.views.overlay.TilesOverlay | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme | ||||
| 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.UiHelper | ||||
| import org.y20k.trackbook.helpers.create_start_end_markers | ||||
| import org.y20k.trackbook.helpers.iso8601_format | ||||
| import org.y20k.trackbook.helpers.iso8601 | ||||
| import org.y20k.trackbook.helpers.iso8601_parse | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| 
 | ||||
| class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | ||||
| { | ||||
|     private lateinit var trackbook: Trackbook | ||||
| 
 | ||||
|     lateinit var rootView: View | ||||
|     lateinit var save_track_button: ImageButton | ||||
|     lateinit var deleteButton: ImageButton | ||||
|     lateinit var zoom_in_button: FloatingActionButton | ||||
|     lateinit var zoom_out_button: FloatingActionButton | ||||
|     lateinit var trackNameView: MaterialTextView | ||||
|     lateinit var selected_trkpt_info: 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 | ||||
|     lateinit var delete_selected_trkpt_button: ImageButton | ||||
|     var track_query_start_time_previous: Int = 0 | ||||
|     var track_query_end_time_previous: Int = 0 | ||||
|     private lateinit var mapView: MapView | ||||
|  | @ -95,22 +102,25 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     private lateinit var negativeElevationView: MaterialTextView | ||||
|     private lateinit var elevationDataViews: Group | ||||
|     private lateinit var track: Track | ||||
|     private lateinit var track_segment_overlays: ArrayDeque<Polyline> | ||||
|     private var track_geopoints: MutableList<IGeoPoint> = mutableListOf() | ||||
|     private var track_points_overlay: SimpleFastPointOverlay? = null | ||||
|     // private lateinit var trkpt_infowindow: InfoWindow | ||||
|     private var useImperialUnits: Boolean = false | ||||
|     private val handler: Handler = Handler(Looper.getMainLooper()) | ||||
|     private var special_points_overlay: ItemizedIconOverlay<OverlayItem>? = null | ||||
|     private var track_overlay: SimpleFastPointOverlay? = null | ||||
|     val RERENDER_DELAY: Long = 1000 | ||||
| 
 | ||||
|     /* Overrides onCreateView from Fragment */ | ||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View | ||||
|     { | ||||
|         this.trackbook = (requireContext().applicationContext as Trackbook) | ||||
|         val database: Database = (requireActivity().applicationContext as Trackbook).database | ||||
|         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)!!), | ||||
|             start_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!), | ||||
|             end_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!), | ||||
|         ) | ||||
|         track.load_trkpts() | ||||
|         rootView = inflater.inflate(R.layout.fragment_track, container, false) | ||||
|  | @ -131,6 +141,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|         controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude)) | ||||
|         controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL) | ||||
| 
 | ||||
|         // trkpt_infowindow = MarkerInfoWindow(R.layout.trkpt_infowindow, mapView) | ||||
| 
 | ||||
|         statisticsSheet = rootView.findViewById(R.id.statistics_sheet) | ||||
|         statisticsView = rootView.findViewById(R.id.statistics_view) | ||||
|         distanceView = rootView.findViewById(R.id.statistics_data_distance) | ||||
|  | @ -227,6 +239,27 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         selected_trkpt_info = rootView.findViewById(R.id.selected_trkpt_info) | ||||
|         delete_selected_trkpt_button = rootView.findViewById(R.id.delete_selected_trkpt_button) | ||||
|         delete_selected_trkpt_button.setOnClickListener { | ||||
|             Log.i("VOUSSOIR", "delete selected trkpt button.") | ||||
|             if (track_points_overlay != null) | ||||
|             { | ||||
|                 val selected = (track_geopoints[track_points_overlay!!.selectedPoint] as Trkpt) | ||||
|                 track_geopoints.remove(selected) | ||||
|                 track_points_overlay!!.selectedPoint = null | ||||
|                 Log.i("VOUSSOIR", selected.rendered_by_polyline?.actualPoints?.size.toString()) | ||||
|                 selected.rendered_by_polyline?.actualPoints?.remove(selected) | ||||
|                 Log.i("VOUSSOIR", selected.rendered_by_polyline?.actualPoints?.size.toString()) | ||||
|                 selected.rendered_by_polyline?.setPoints(ArrayList(selected.rendered_by_polyline?.actualPoints)) | ||||
|                 Log.i("VOUSSOIR", selected.rendered_by_polyline?.actualPoints?.size.toString()) | ||||
|                 trackbook.database.delete_trkpt(selected.device_id, selected.time) | ||||
|                 delete_selected_trkpt_button.visibility = View.GONE | ||||
|                 selected_trkpt_info.text = "" | ||||
|                 mapView.invalidate() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         save_track_button.setOnClickListener { | ||||
|             openSaveGpxDialog() | ||||
|         } | ||||
|  | @ -251,6 +284,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|         statisticsSheetBehavior = BottomSheetBehavior.from<View>(statisticsSheet) | ||||
|         statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED | ||||
| 
 | ||||
|         track_segment_overlays = ArrayDeque<Polyline>(10) | ||||
|         render_track() | ||||
| 
 | ||||
|         return rootView | ||||
|  | @ -265,25 +299,81 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     fun render_track() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "TrackFragment.render_track") | ||||
|         if (special_points_overlay != null) | ||||
|         mapView.overlays.clear() | ||||
|         track_segment_overlays.clear() | ||||
|         delete_selected_trkpt_button.visibility = View.GONE | ||||
| 
 | ||||
|         setupStatisticsViews() | ||||
| 
 | ||||
|         if (track.trkpts.isEmpty()) | ||||
|         { | ||||
|             mapView.overlays.remove(special_points_overlay) | ||||
|             return | ||||
|         } | ||||
|         if (track_overlay != null) | ||||
|         { | ||||
|             mapView.overlays.remove(track_overlay) | ||||
|         } | ||||
|         val geopoints: MutableList<IGeoPoint> = mutableListOf() | ||||
|         Log.i("VOUSSOIR", "MapOverlayHelper.createTrackOverlay") | ||||
|         track_geopoints = mutableListOf() | ||||
|         for (trkpt in track.trkpts) | ||||
|         { | ||||
|             geopoints.add(trkpt) | ||||
|             track_geopoints.add(trkpt) | ||||
|         } | ||||
|         if (track.trkpts.isNotEmpty()) | ||||
| 
 | ||||
|         var pl = new_track_segment_overlay() | ||||
|         var previous_time: Long = 0 | ||||
|         for (trkpt in track.trkpts) | ||||
|         { | ||||
|             track_overlay = createTrackOverlay(requireContext(), mapView, geopoints, Keys.STATE_TRACKING_STOPPED) | ||||
|             special_points_overlay = create_start_end_markers(requireContext(), mapView, track.trkpts) | ||||
|             if (previous_time > 0 && (trkpt.time - previous_time) > Keys.STOP_OVER_THRESHOLD) | ||||
|             { | ||||
|                 pl = new_track_segment_overlay() | ||||
|             } | ||||
|             pl.addPoint(trkpt) | ||||
|             trkpt.rendered_by_polyline = pl | ||||
|             previous_time = trkpt.time | ||||
|         } | ||||
|         setupStatisticsViews() | ||||
| 
 | ||||
|         for (pl in track_segment_overlays) | ||||
|         { | ||||
|             create_start_end_markers(requireContext(), mapView, pl.actualPoints.first() as Trkpt, pl.actualPoints.last() as Trkpt) | ||||
|         } | ||||
| 
 | ||||
|         val pointTheme = SimplePointTheme(track_geopoints, false) | ||||
|         val style = Paint() | ||||
|         style.style = Paint.Style.FILL | ||||
|         style.color = Keys.POLYLINE_COLOR | ||||
|         style.flags = Paint.ANTI_ALIAS_FLAG | ||||
|         val overlayOptions: SimpleFastPointOverlayOptions = SimpleFastPointOverlayOptions.getDefaultStyle() | ||||
|             .setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MEDIUM_OPTIMIZATION) | ||||
|             .setSymbol(SimpleFastPointOverlayOptions.Shape.CIRCLE) | ||||
|             .setPointStyle(style) | ||||
|             .setRadius(((Keys.POLYLINE_THICKNESS + 1 ) / 2) * UiHelper.getDensityScalingFactor(requireContext())) | ||||
|             .setIsClickable(true) | ||||
|             .setCellSize(12) | ||||
|         track_points_overlay = SimpleFastPointOverlay(pointTheme, overlayOptions) | ||||
|         mapView.overlays.add(track_points_overlay) | ||||
| 
 | ||||
|         track_points_overlay!!.setOnClickListener(object : SimpleFastPointOverlay.OnClickListener { | ||||
|             override fun onClick(points: SimpleFastPointOverlay.PointAdapter?, point: Int?) | ||||
|             { | ||||
|                 if (points == null || point == null || point == 0) | ||||
|                 { | ||||
|                     return | ||||
|                 } | ||||
|                 val trkpt = (points[point]) as Trkpt | ||||
|                 Log.i("VOUSSOIR", "Clicked ${trkpt.device_id} ${trkpt.time}") | ||||
|                 selected_trkpt_info.text = "${trkpt.time}\n${iso8601(trkpt.time)}\n${trkpt.latitude}\n${trkpt.longitude}" | ||||
|                 delete_selected_trkpt_button.visibility = View.VISIBLE | ||||
|                 return | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fun new_track_segment_overlay(): Polyline | ||||
|     { | ||||
|         var pl = Polyline(mapView) | ||||
|         pl.outlinePaint.strokeWidth = Keys.POLYLINE_THICKNESS | ||||
|         pl.outlinePaint.color = Keys.POLYLINE_COLOR | ||||
|         pl.infoWindow = null | ||||
|         track_segment_overlays.add(pl) | ||||
|         mapView.overlays.add(pl) | ||||
|         return pl | ||||
|     } | ||||
| 
 | ||||
|     fun get_datetime(datepicker: DatePicker, timepicker: TimePicker, seconds: Int): Date | ||||
|  | @ -348,7 +438,16 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     /* Overrides onZoom from MapListener */ | ||||
|     override fun onZoom(event: ZoomEvent?): Boolean | ||||
|     { | ||||
|         return (event != null) | ||||
|         if (event == null) | ||||
|         { | ||||
|             return false | ||||
|         } | ||||
|         if (track_points_overlay == null) | ||||
|         { | ||||
|             return false | ||||
|         } | ||||
|         track_points_overlay!!.isEnabled = event.zoomLevel >= 16 | ||||
|         return true | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onScroll from MapListener */ | ||||
|  |  | |||
|  | @ -30,9 +30,7 @@ import android.Manifest | |||
| import android.os.* | ||||
| import android.util.Log | ||||
| import androidx.core.content.ContextCompat | ||||
| import org.osmdroid.api.IGeoPoint | ||||
| import org.osmdroid.util.GeoPoint | ||||
| import org.osmdroid.views.overlay.simplefastpoint.LabelledGeoPoint | ||||
| import java.util.* | ||||
| import org.y20k.trackbook.helpers.* | ||||
| 
 | ||||
|  | @ -55,8 +53,8 @@ class TrackerService: Service() | |||
|     var location_min_time_ms: Long = 0 | ||||
|     private val RECENT_TRKPT_COUNT = 7200 | ||||
|     lateinit var recent_trkpts: Deque<Trkpt> | ||||
|     lateinit var recent_displacement_trkpts: Deque<Trkpt> | ||||
|     var recent_trackpoints_for_mapview: MutableList<IGeoPoint> = mutableListOf() | ||||
|     lateinit var recent_displacement_locations: Deque<Location> | ||||
|     var recent_trackpoints_for_mapview: MutableList<GeoPoint> = mutableListOf() | ||||
|     var gpsLocationListenerRegistered: Boolean = false | ||||
|     var networkLocationListenerRegistered: Boolean = false | ||||
|     var bound: Boolean = false | ||||
|  | @ -67,6 +65,7 @@ class TrackerService: Service() | |||
|     private lateinit var notificationHelper: NotificationHelper | ||||
|     private lateinit var gpsLocationListener: LocationListener | ||||
|     private lateinit var networkLocationListener: LocationListener | ||||
|     var mapfragment: MapFragment? = null | ||||
| 
 | ||||
|     private fun addGpsLocationListener() | ||||
|     { | ||||
|  | @ -160,6 +159,11 @@ class TrackerService: Service() | |||
| 
 | ||||
|                 currentBestLocation = location | ||||
| 
 | ||||
|                 if (mapfragment != null) | ||||
|                 { | ||||
|                     mapfragment!!.handler.postDelayed(mapfragment!!.location_update_redraw, 0) | ||||
|                 } | ||||
| 
 | ||||
|                 if (trackingState != Keys.STATE_TRACKING_ACTIVE) | ||||
|                 { | ||||
|                     return | ||||
|  | @ -195,7 +199,7 @@ class TrackerService: Service() | |||
|                         return | ||||
|                     } | ||||
|                 } | ||||
|                 if (! (recent_displacement_trkpts.isEmpty() || isDifferentEnough(recent_displacement_trkpts.first().toLocation(), location, omitRests))) | ||||
|                 if (! (recent_displacement_locations.isEmpty() || isDifferentEnough(recent_displacement_locations.first(), location, omitRests))) | ||||
|                 { | ||||
|                     Log.i("VOUSSOIR", "Omitting due to too close to previous.") | ||||
|                     return | ||||
|  | @ -215,10 +219,10 @@ class TrackerService: Service() | |||
|                     recent_trackpoints_for_mapview.removeFirst() | ||||
|                 } | ||||
| 
 | ||||
|                 recent_displacement_trkpts.add(trkpt) | ||||
|                 while (recent_displacement_trkpts.size > 5) | ||||
|                 recent_displacement_locations.add(location) | ||||
|                 while (recent_displacement_locations.size > 5) | ||||
|                 { | ||||
|                     recent_displacement_trkpts.removeFirst() | ||||
|                     recent_displacement_locations.removeFirst() | ||||
|                 } | ||||
| 
 | ||||
|                 if (location.time - lastCommit > Keys.COMMIT_INTERVAL) | ||||
|  | @ -275,7 +279,7 @@ class TrackerService: Service() | |||
|         trackbook = (applicationContext as Trackbook) | ||||
|         trackbook.load_homepoints() | ||||
|         recent_trkpts = ArrayDeque<Trkpt>(RECENT_TRKPT_COUNT) | ||||
|         recent_displacement_trkpts = ArrayDeque<Trkpt>(5) | ||||
|         recent_displacement_locations = ArrayDeque<Location>(5) | ||||
|         recent_trackpoints_for_mapview = mutableListOf() | ||||
|         use_gps_location = PreferencesHelper.load_location_gps() | ||||
|         use_network_location = PreferencesHelper.load_location_network() | ||||
|  | @ -392,7 +396,7 @@ class TrackerService: Service() | |||
|         addNetworkLocationListener() | ||||
|         trackingState = Keys.STATE_TRACKING_ACTIVE | ||||
|         PreferencesHelper.saveTrackingState(trackingState) | ||||
|         recent_displacement_trkpts.clear() | ||||
|         recent_displacement_locations.clear() | ||||
|         startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification()) | ||||
|     } | ||||
| 
 | ||||
|  | @ -402,7 +406,7 @@ class TrackerService: Service() | |||
| 
 | ||||
|         trackingState = Keys.STATE_TRACKING_STOPPED | ||||
|         PreferencesHelper.saveTrackingState(trackingState) | ||||
|         recent_displacement_trkpts.clear() | ||||
|         recent_displacement_locations.clear() | ||||
|         displayNotification() | ||||
|         stopForeground(STOP_FOREGROUND_DETACH) | ||||
|     } | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ import androidx.recyclerview.widget.RecyclerView | |||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.Dispatchers.Main | ||||
| import org.y20k.trackbook.helpers.UiHelper | ||||
| import org.y20k.trackbook.helpers.iso8601_format | ||||
| import org.y20k.trackbook.helpers.iso8601 | ||||
| import org.y20k.trackbook.tracklist.TracklistAdapter | ||||
| 
 | ||||
| /* | ||||
|  | @ -46,7 +46,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|     private lateinit var trackElementList: RecyclerView | ||||
|     private lateinit var tracklistOnboarding: ConstraintLayout | ||||
| 
 | ||||
|     /* Overrides onCreateView from Fragment */ | ||||
|     /* Overrides onCreate from Fragment */ | ||||
|     override fun onCreate(savedInstanceState: Bundle?) | ||||
|     { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | @ -67,18 +67,6 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|         trackElementList.itemAnimator = DefaultItemAnimator() | ||||
|         trackElementList.adapter = tracklistAdapter | ||||
| 
 | ||||
|         // enable swipe to delete | ||||
|         val swipeHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) { | ||||
|             override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { | ||||
|                 // ask user | ||||
|                 val adapterPosition: Int = viewHolder.adapterPosition // first position in list is reserved for statistics | ||||
|                 val dialogMessage: String = "${getString(R.string.dialog_yes_no_message_delete_recording)}\n\n- ${tracklistAdapter.getTrackName(adapterPosition)}" | ||||
|                 YesNoDialog(this@TracklistFragment as YesNoDialog.YesNoDialogListener).show(context = activity as Context, type = Keys.DIALOG_DELETE_TRACK, messageString = dialogMessage, yesButton = R.string.dialog_yes_no_positive_button_delete_recording, payload = adapterPosition) | ||||
|             } | ||||
|         } | ||||
|         val itemTouchHelper = ItemTouchHelper(swipeHandler) | ||||
|         itemTouchHelper.attachToRecyclerView(trackElementList) | ||||
| 
 | ||||
|         // toggle onboarding layout | ||||
|         toggleOnboardingLayout() | ||||
| 
 | ||||
|  | @ -90,40 +78,15 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|         val bundle: Bundle = bundleOf( | ||||
|             Keys.ARG_TRACK_TITLE to track.name, | ||||
|             Keys.ARG_TRACK_DEVICE_ID to track.device_id, | ||||
|             Keys.ARG_TRACK_START_TIME to iso8601_format.format(track.start_time), | ||||
|             Keys.ARG_TRACK_STOP_TIME to iso8601_format.format(track.end_time), | ||||
|             Keys.ARG_TRACK_START_TIME to iso8601(track.start_time), | ||||
|             Keys.ARG_TRACK_STOP_TIME to iso8601(track.end_time), | ||||
|         ) | ||||
|         findNavController().navigate(R.id.fragment_track, bundle) | ||||
|     } | ||||
| 
 | ||||
|     /* Overrides onYesNoDialog from YesNoDialogListener */ | ||||
|     override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) | ||||
|     { | ||||
|         when (type) | ||||
|         { | ||||
|             Keys.DIALOG_DELETE_TRACK -> | ||||
|             { | ||||
|                 when (dialogResult) { | ||||
|                     // user tapped remove track | ||||
|                     true -> | ||||
|                     { | ||||
|                         tracklistAdapter.delete_track_at_position(activity as Context, payload) | ||||
|                         toggleOnboardingLayout() | ||||
|                     } | ||||
|                     // user tapped cancel | ||||
|                     false -> | ||||
|                     { | ||||
|                         // The user slid the track over to the side and turned it red, we have to | ||||
|                         // bring it back. | ||||
|                         tracklistAdapter.notifyItemChanged(payload) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // toggle onboarding layout | ||||
|     private fun toggleOnboardingLayout() { | ||||
|     private fun toggleOnboardingLayout() | ||||
|     { | ||||
|         when (tracklistAdapter.isEmpty()) { | ||||
|             true -> { | ||||
|                 // show onboarding layout | ||||
|  | @ -143,11 +106,13 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|      */ | ||||
|     inner class CustomLinearLayoutManager(context: Context): LinearLayoutManager(context, VERTICAL, false) | ||||
|     { | ||||
|         override fun supportsPredictiveItemAnimations(): Boolean { | ||||
|         override fun supportsPredictiveItemAnimations(): Boolean | ||||
|         { | ||||
|             return true | ||||
|         } | ||||
| 
 | ||||
|         override fun onLayoutCompleted(state: RecyclerView.State?) { | ||||
|         override fun onLayoutCompleted(state: RecyclerView.State?) | ||||
|         { | ||||
|             super.onLayoutCompleted(state) | ||||
|             // handle delete request from TrackFragment - after layout calculations are complete | ||||
|             val deleteTrackId: Long = arguments?.getLong(Keys.ARG_TRACK_ID, -1L) ?: -1L | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ package org.y20k.trackbook | |||
| import android.location.Location | ||||
| import org.osmdroid.api.IGeoPoint | ||||
| import org.osmdroid.util.GeoPoint | ||||
| import org.osmdroid.views.overlay.Polyline | ||||
| import org.y20k.trackbook.helpers.getNumberOfSatellites | ||||
| 
 | ||||
| class Trkpt( | ||||
|  | @ -30,6 +31,7 @@ class Trkpt( | |||
|     val accuracy: Float, | ||||
|     val time: Long, | ||||
|     val numberSatellites: Int = 0, | ||||
|     var rendered_by_polyline: Polyline? = null | ||||
| ) : GeoPoint(latitude, longitude, altitude) | ||||
| { | ||||
|     constructor(device_id: String, location: Location) : this( | ||||
|  |  | |||
|  | @ -15,12 +15,25 @@ import java.text.SimpleDateFormat | |||
| import java.util.* | ||||
| import kotlin.random.Random.Default.nextBits | ||||
| 
 | ||||
| val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US) | ||||
| private val RNG = SecureRandom() | ||||
| 
 | ||||
| fun iso8601(timestamp: Long): String | ||||
| { | ||||
|     val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | ||||
|     iso8601_format.timeZone = TimeZone.getTimeZone("UTC") | ||||
|     return iso8601_format.format(timestamp) | ||||
| } | ||||
| 
 | ||||
| fun iso8601(datetime: Date): String | ||||
| { | ||||
|     return iso8601_format.format(datetime) | ||||
|     return iso8601(datetime.time) | ||||
| } | ||||
| 
 | ||||
| fun iso8601_parse(datetime: String): Date | ||||
| { | ||||
|     val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | ||||
|     iso8601_format.timeZone = TimeZone.getTimeZone("UTC") | ||||
|     return iso8601_format.parse(datetime) | ||||
| } | ||||
| 
 | ||||
| fun random_int(): Int | ||||
|  |  | |||
|  | @ -83,15 +83,4 @@ object DateTimeHelper { | |||
|     fun convertToReadableDateAndTime(date: Date, dateStyle: Int = DateFormat.SHORT, timeStyle: Int = DateFormat.SHORT): String { | ||||
|         return "${DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)} ${DateFormat.getTimeInstance(timeStyle, Locale.getDefault()).format(date)}" | ||||
|     } | ||||
| 
 | ||||
|     /* Calculates time difference between two locations */ | ||||
|     fun calculateTimeDistance(previousLocation: Location?, location: Location): Long  { | ||||
|         var timeDifference: Long = 0L | ||||
|         // two data points needed to calculate time difference | ||||
|         if (previousLocation != null) { | ||||
|             // get time difference | ||||
|             timeDifference = location.time - previousLocation.time | ||||
|         } | ||||
|         return timeDifference | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -17,81 +17,27 @@ | |||
| package org.y20k.trackbook.helpers | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.graphics.Paint | ||||
| import android.util.Log | ||||
| import android.widget.Toast | ||||
| import androidx.core.content.ContextCompat | ||||
| import org.osmdroid.api.IGeoPoint | ||||
| 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.simplefastpoint.LabelledGeoPoint | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme | ||||
| import org.y20k.trackbook.Keys | ||||
| import org.y20k.trackbook.R | ||||
| import org.y20k.trackbook.Trkpt | ||||
| import java.text.DecimalFormat | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| 
 | ||||
| fun createTrackOverlay(context: Context, map_view: MapView, geopoints: MutableList<IGeoPoint>, trackingState: Int): SimpleFastPointOverlay | ||||
| { | ||||
|     Log.i("VOUSSOIR", "MapOverlayHelper.createTrackOverlay") | ||||
|     val pointTheme = SimplePointTheme(geopoints, false) | ||||
|     val style = Paint() | ||||
|     style.style = Paint.Style.FILL | ||||
|     style.color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red) else context.getColor(R.color.default_blue) | ||||
|     style.flags = Paint.ANTI_ALIAS_FLAG | ||||
|     val overlayOptions: SimpleFastPointOverlayOptions = SimpleFastPointOverlayOptions.getDefaultStyle() | ||||
|         .setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MAXIMUM_OPTIMIZATION) | ||||
|         .setSymbol(SimpleFastPointOverlayOptions.Shape.CIRCLE) | ||||
|         .setPointStyle(style) | ||||
|         .setRadius(6F * UiHelper.getDensityScalingFactor(context)) // radius is set in px - scaling factor makes that display density independent (= dp) | ||||
|         .setIsClickable(true) | ||||
|         .setCellSize(12) // Sets the grid cell size used for indexing, in pixels. Larger cells result in faster rendering speed, but worse fidelity. Default is 10 pixels, for large datasets (>10k points), use 15. | ||||
|     var overlay = SimpleFastPointOverlay(pointTheme, overlayOptions) | ||||
| 
 | ||||
|     overlay.setOnClickListener(object : SimpleFastPointOverlay.OnClickListener { | ||||
|         override fun onClick(points: SimpleFastPointOverlay.PointAdapter?, point: Int?) | ||||
|         { | ||||
|             if (points == null || point == null || point == 0) | ||||
|             { | ||||
|                 return | ||||
|             } | ||||
|             val trkpt = (points[point]) as Trkpt | ||||
|             Log.i("VOUSSOIR", "Clicked ${trkpt.device_id} ${trkpt.time}") | ||||
|             // trackpoints.remove(points[point]) | ||||
|             // map_view.overlays.remove(overlay) | ||||
|             // overlay = SimpleFastPointOverlay(pointTheme, overlayOptions) | ||||
|             // overlay.setOnClickListener(this) | ||||
|             // map_view.overlays.add(overlay) | ||||
|             // map_view.postInvalidate() | ||||
|             return | ||||
|         } | ||||
|     }) | ||||
| 
 | ||||
|     map_view.overlays.add(overlay) | ||||
|     return overlay | ||||
| } | ||||
| 
 | ||||
| fun create_start_end_markers(context: Context, map_view: MapView, trkpts: Collection<Trkpt>): ItemizedIconOverlay<OverlayItem>? | ||||
| fun create_start_end_markers(context: Context, map_view: MapView, startpoint: Trkpt, endpoint: Trkpt): ItemizedIconOverlay<OverlayItem>? | ||||
| { | ||||
|     Log.i("VOUSSOIR", "MapOverlayHelper.create_start_end_markers") | ||||
|     if (trkpts.size == 0) | ||||
|     { | ||||
|         return null | ||||
|     } | ||||
| 
 | ||||
|     val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>() | ||||
|     val startpoint = trkpts.first() | ||||
|     val endpoint = trkpts.last() | ||||
|     val startmarker: OverlayItem = createOverlayItem(context, startpoint.latitude, startpoint.longitude, startpoint.accuracy, startpoint.provider, startpoint.time) | ||||
|     startmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!) | ||||
|     overlayItems.add(startmarker) | ||||
|     if (trkpts.size > 1) | ||||
|     if (startpoint != endpoint) | ||||
|     { | ||||
|         val endmarker: OverlayItem = createOverlayItem(context, endpoint.latitude, endpoint.longitude, endpoint.accuracy, endpoint.provider, endpoint.time) | ||||
|         endmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!) | ||||
|  |  | |||
|  | @ -12,19 +12,42 @@ | |||
|         android:layout_height="match_parent" | ||||
|         > | ||||
| 
 | ||||
|         <TextView | ||||
|             android:id="@+id/selected_trkpt_info" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginEnd="8dp" | ||||
|             android:gravity="right" | ||||
|             android:text="time\nlat\nlong" | ||||
|             app:fontFamily="monospace" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|         <ImageButton | ||||
|             android:id="@+id/delete_selected_trkpt_button" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:contentDescription="Delete selected trackpoint" | ||||
|             android:src="@drawable/ic_delete_24dp" | ||||
|             android:visibility="gone" | ||||
|             app:backgroundTint="@color/default_transparent" | ||||
|             app:layout_constraintBottom_toTopOf="@+id/map" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:srcCompat="@drawable/ic_delete_24dp" /> | ||||
| 
 | ||||
| 
 | ||||
|         <DatePicker | ||||
|             android:id="@+id/track_query_start_date" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="130dp" | ||||
|             android:translationX="-45dp" | ||||
|             android:translationY="-30dp" | ||||
|             android:calendarViewShown="false" | ||||
|             android:datePickerMode="spinner" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             android:scaleX="0.6" | ||||
|             android:scaleY="0.6" | ||||
|             android:translationX="-45dp" | ||||
|             android:translationY="-30dp" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             /> | ||||
|             app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|         <TimePicker | ||||
|             android:id="@+id/track_query_start_time" | ||||
|  | @ -35,8 +58,8 @@ | |||
|             android:timePickerMode="spinner" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             app:layout_constraintStart_toEndOf="@+id/track_query_start_date" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             android:scaleX="0.6" | ||||
|             android:scaleY="0.6" | ||||
|             /> | ||||
| 
 | ||||
|         <DatePicker | ||||
|  | @ -49,8 +72,8 @@ | |||
|             android:calendarViewShown="false" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/track_query_start_date" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             android:scaleX="0.6" | ||||
|             android:scaleY="0.6" | ||||
|             /> | ||||
| 
 | ||||
|         <TimePicker | ||||
|  | @ -62,8 +85,8 @@ | |||
|             android:timePickerMode="spinner" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/track_query_start_time" | ||||
|             app:layout_constraintStart_toEndOf="@+id/track_query_start_date" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             android:scaleX="0.6" | ||||
|             android:scaleY="0.6" | ||||
|             /> | ||||
| 
 | ||||
|         <org.osmdroid.views.MapView | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue