checkpoint

This commit is contained in:
voussoir 2023-03-09 20:24:06 -08:00
parent 2568af3bb1
commit df77b089ac
36 changed files with 406 additions and 804 deletions

View file

@ -1,6 +1,4 @@
package org.y20k.trackbook.core
import android.content.Context
package org.y20k.trackbook
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase.openOrCreateDatabase
import android.util.Log
@ -11,6 +9,7 @@ class Database()
var ready: Boolean = false
lateinit var file: File
lateinit var connection: SQLiteDatabase
fun close()
{
this.connection.close()
@ -19,6 +18,7 @@ class Database()
fun connect(file: File)
{
Log.i("VOUSSOIR", "Connecting to database " + file.absolutePath)
this.file = file
this.connection = openOrCreateDatabase(file, null)
this.initialize_tables()
@ -44,7 +44,7 @@ class Database()
{
this.connection.beginTransaction()
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 TEXT NOT NULL, accuracy REAL, device_id INTEGER NOT NULL, ele INTEGER, sat INTEGER, star 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, star INTEGER, PRIMARY KEY(lat, lon, time, device_id))")
this.connection.execSQL("CREATE TABLE IF NOT EXISTS homepoints(lat REAL NOT NULL, lon REAL NOT NULL, radius REAL NOT NULL, name TEXT, PRIMARY KEY(lat, lon))")
this.connection.setTransactionSuccessful()
this.connection.endTransaction()

View file

@ -1,5 +1,4 @@
package org.y20k.trackbook.core
package org.y20k.trackbook
import android.location.Location
import java.util.*
@ -17,4 +16,4 @@ class Homepoint(val latitude: Double, val longitude: Double, val radius: Double,
location.time = GregorianCalendar.getInstance().time.time
return location
}
}
}

View file

@ -14,12 +14,10 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import java.util.*
/*
* Keys object
*/
@ -116,7 +114,7 @@ object Keys {
const val COMMIT_INTERVAL: Int = 30
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 13
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 60_000_000_000L // one minute in nanoseconds
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 5_000_000_000L // 5s in nanoseconds
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD = 10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded

View file

@ -14,19 +14,21 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
@ -35,55 +37,22 @@ import org.y20k.trackbook.helpers.AppThemeHelper
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.PreferencesHelper
private const val REQUEST_EXTERNAL_STORAGE = 1
private val PERMISSIONS_STORAGE = arrayOf<String>(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
/**
* Checks if the app has permission to write to device storage
*
* If the app does not has permission then the user will be prompted to grant permissions
*
* @param activity
*/
fun verifyStoragePermissions(activity: Activity?)
class MainActivity: AppCompatActivity()
{
// Check if we have write permission
val permission = ActivityCompat.checkSelfPermission(activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED)
{
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
)
}
}
/*
* MainActivity class
*/
class MainActivity : AppCompatActivity() {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(MainActivity::class.java)
/* Main class variables */
lateinit var trackbook: Trackbook
private lateinit var navHostFragment: NavHostFragment
private lateinit var bottomNavigationView: BottomNavigationView
/* Overrides onCreate from AppCompatActivity */
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?)
{
trackbook = (applicationContext as Trackbook)
super.onCreate(savedInstanceState)
verifyStoragePermissions(this)
request_permissions(this)
// todo: remove after testing finished
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectNonSdkApiUsage()
@ -109,8 +78,10 @@ class MainActivity : AppCompatActivity() {
// listen for navigation changes
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.fragment_track -> {
when (destination.id)
{
R.id.fragment_track ->
{
runOnUiThread {
run {
// mark menu item "Tracks" as checked
@ -118,7 +89,8 @@ class MainActivity : AppCompatActivity() {
}
}
}
else -> {
else ->
{
// do nothing
}
}
@ -128,26 +100,62 @@ class MainActivity : AppCompatActivity() {
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
}
private fun request_permissions(activity: Activity)
{
Log.i("VOUSSOIR", "MainActivity requests permissions.")
val permissions_wanted = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
)
val permissions_needed = ArrayList<String>()
for (permission in permissions_wanted)
{
if (ContextCompat.checkSelfPermission(applicationContext, permission) != PackageManager.PERMISSION_GRANTED)
{
Log.i("VOUSSOIR", "We need " + permission)
permissions_needed.add(permission)
}
}
val result = requestPermissions(permissions_wanted, 1);
Log.i("VOUSSOIR", "Permissions result " + result)
}
/* Overrides onDestroy from AppCompatActivity */
override fun onDestroy() {
override fun onDestroy()
{
super.onDestroy()
// unregister listener for changes in shared preferences
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
}
/*
* Defines the listener for changes in shared preferences
*/
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
when (key) {
Keys.PREF_THEME_SELECTION -> {
when (key)
{
Keys.PREF_THEME_SELECTION ->
{
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
}
Keys.PREF_DEVICE_ID ->
{
Log.i("VOUSSOIR", "MainActivity: device_id has changed.")
trackbook.load_database()
}
}
}
/*
* End of declaration
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Log.i("VOUSSOIR", "MainActivity tries to load the database.")
trackbook.load_database()
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import android.Manifest
@ -25,17 +24,18 @@ import android.os.*
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.fragment.app.Fragment
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.Track
import org.y20k.trackbook.helpers.*
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
/*
* MapFragment class
*/
class MapFragment : Fragment(), MapOverlayHelper.MarkerListener
class MapFragment : Fragment()
{
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(MapFragment::class.java)
@ -61,13 +61,14 @@ class MapFragment : Fragment(), MapOverlayHelper.MarkerListener
currentBestLocation = LocationHelper.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, this as MapOverlayHelper.MarkerListener, inflater, container, statusBarHeight, currentBestLocation, trackingState)
layout = MapFragmentLayoutHolder(activity as Context, inflater, container, statusBarHeight, currentBestLocation, trackingState)
// set up buttons
layout.currentLocationButton.setOnClickListener {

View file

@ -14,33 +14,21 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import YesNoDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.preference.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import org.y20k.trackbook.helpers.AppThemeHelper
import org.y20k.trackbook.helpers.FileHelper
import org.y20k.trackbook.helpers.LengthUnitHelper
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.PreferencesHelper
import org.y20k.trackbook.helpers.random_int
import kotlin.random.Random
import org.y20k.trackbook.helpers.random_device_id
/*
* SettingsFragment class
@ -50,7 +38,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(SettingsFragment::class.java)
/* Overrides onViewCreated from PreferenceFragmentCompat */
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -58,7 +45,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
view.setBackgroundColor(resources.getColor(R.color.app_window_background, null))
}
/* Overrides onCreatePreferences from PreferenceFragmentCompat */
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -130,7 +116,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceDeviceID.setIcon(R.drawable.ic_smartphone_24dp)
preferenceDeviceID.key = Keys.PREF_DEVICE_ID
preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + PreferencesHelper.load_device_id()
preferenceDeviceID.setDefaultValue(random_int().toString())
preferenceDeviceID.setDefaultValue(random_device_id())
preferenceCategoryGeneral.contains(preferenceDeviceID)
screen.addPreference(preferenceDeviceID)
@ -142,7 +128,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val preferenceAppVersion: Preference = Preference(context)
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
preferenceAppVersion.summary = "${getString(R.string.pref_app_version_summary)} ${BuildConfig.VERSION_NAME}"
preferenceAppVersion.summary = getString(R.string.pref_app_version_summary)
preferenceAppVersion.setOnPreferenceClickListener {
// copy to clipboard
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
@ -158,7 +144,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceScreen = screen
}
/* Overrides onYesNoDialog from YesNoDialogListener */
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
when (type) {

View file

@ -14,48 +14,37 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.core
package org.y20k.trackbook
import android.content.Context
import android.database.Cursor
import android.location.Location
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.net.toUri
import org.y20k.trackbook.Keys
import org.y20k.trackbook.helpers.DateTimeHelper
import org.y20k.trackbook.helpers.LocationHelper
import org.y20k.trackbook.helpers.iso8601
import org.y20k.trackbook.helpers.iso8601_format
import java.io.File
import java.net.URI
import java.text.SimpleDateFormat
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/*
* Track data class
*/
data class Track (
val database: Database,
val device_id: String,
val start_time: Date,
val stop_time: Date,
var name: String = "",
var dequelimit: Int = 7200,
val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(),
var view_latitude: Double = Keys.DEFAULT_LATITUDE,
var view_longitude: Double = Keys.DEFAULT_LONGITUDE,
var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(dequelimit),
var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
)
{
fun delete()
{
Log.i("VOUSSOIR", "Track.delete.")
}
suspend fun delete_suspended(context: Context)
@ -65,12 +54,6 @@ data class Track (
}
}
fun get_export_gpx_file(context: Context): File
{
val basename: String = DateTimeHelper.convertToSortableDateString(this.start_time) + " " + DateTimeHelper.convertToSortableDateString(this.start_time) + Keys.GPX_FILE_EXTENSION
return File(File("/storage/emulated/0/Syncthing/GPX"), basename)
}
fun export_gpx(context: Context, fileuri: Uri): Uri?
{
if (! database.ready)
@ -153,7 +136,8 @@ data class Track (
stats.min_altitude = trkpt.altitude
continue
}
stats.distance += LocationHelper.calculateDistance(previous.toLocation(), trkpt.toLocation())
stats.distance += previous.toLocation().distanceTo(trkpt.toLocation())
Log.i("VOUSSOIR", previous.toLocation().distanceTo(trkpt.toLocation()).toString())
val ascentdiff = trkpt.altitude - previous.altitude
if (ascentdiff > 0)
{
@ -171,28 +155,23 @@ data class Track (
{
stats.min_altitude = trkpt.altitude
}
previous = trkpt
last = trkpt
}
if (first == null || last == null)
{
return stats
}
stats.duration = last.time.time - first.time.time
stats.duration = last.time - first.time
stats.velocity = stats.distance / stats.duration
return stats
}
fun trkpt_generator() = iterator<Trkpt>
{
val cursor: Cursor = database.connection.query(
"trkpt",
arrayOf("lat", "lon", "time", "ele", "accuracy", "sat"),
"device_id = ? AND time > ? AND time < ?",
arrayOf(device_id, iso8601(start_time), iso8601(stop_time)),
null,
null,
"time ASC",
null,
val 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(), stop_time.time.toString())
)
val COLUMN_LAT = cursor.getColumnIndex("lat")
val COLUMN_LON = cursor.getColumnIndex("lon")
@ -204,14 +183,13 @@ data class Track (
{
while (cursor.moveToNext())
{
val trkpt: Trkpt = Trkpt(
val trkpt = Trkpt(
provider="",
latitude=cursor.getDouble(COLUMN_LAT),
longitude=cursor.getDouble(COLUMN_LON),
altitude=cursor.getDouble(COLUMN_ELE),
accuracy=cursor.getFloat(COLUMN_ACCURACY),
time=iso8601_format.parse(cursor.getString(COLUMN_TIME)),
distanceToStartingPoint=0F,
time=cursor.getLong(COLUMN_TIME),
numberSatellites=cursor.getInt(COLUMN_SAT),
)
yield(trkpt)

View file

@ -14,15 +14,12 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import YesNoDialog
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
@ -32,23 +29,18 @@ import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.fragment.app.Fragment
import org.y20k.trackbook.core.Database
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.dialogs.RenameTrackDialog
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.MapOverlayHelper
import org.y20k.trackbook.helpers.TrackHelper
import org.y20k.trackbook.helpers.iso8601_format
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
import java.text.SimpleDateFormat
import java.util.*
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener
{
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
/* Main class variables */
private lateinit var layout: TrackFragmentLayoutHolder
@ -58,12 +50,13 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
val database: Database = (requireActivity().applicationContext as Trackbook).database
val track: Track = Track(
database=database,
name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""),
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
start_time= iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!),
stop_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!),
)
track.load_trkpts()
layout = TrackFragmentLayoutHolder(activity as Context, this as MapOverlayHelper.MarkerListener, inflater, container, track)
layout = TrackFragmentLayoutHolder(activity as Context, inflater, container, track)
// set up share button
layout.shareButton.setOnClickListener {
@ -101,11 +94,9 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
layout.saveViewStateToTrack()
}
/* Register the ActivityResultLauncher for saving GPX */
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
private fun requestSaveGpxResult(result: ActivityResult)
{
if (result.resultCode != Activity.RESULT_OK || result.data == null)
@ -149,19 +140,11 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
}
}
/* Overrides onMarkerTapped from MarkerListener */
override fun onMarkerTapped(latitude: Double, longitude: Double)
{
super.onMarkerTapped(latitude, longitude)
TrackHelper.toggle_waypoint_starred(activity as Context, layout.track, latitude, longitude)
layout.updateTrackOverlay()
}
/* Opens up a file picker to select the save location */
private fun openSaveGpxDialog()
{
val context = this.activity as Context
val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(layout.track.start_time) + Keys.GPX_FILE_EXTENSION
val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(layout.track.start_time) + " " + layout.track.device_id + Keys.GPX_FILE_EXTENSION
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = Keys.MIME_TYPE_GPX

View file

@ -14,27 +14,21 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.Manifest
import android.app.Activity
import android.app.Application
import android.content.pm.PackageManager
import android.database.Cursor
import android.util.Log
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.material.color.DynamicColors
import org.y20k.trackbook.core.Database
import org.y20k.trackbook.core.Homepoint
import org.y20k.trackbook.core.Trkpt
import org.y20k.trackbook.helpers.AppThemeHelper
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.PreferencesHelper
import org.y20k.trackbook.helpers.PreferencesHelper.initPreferences
import org.y20k.trackbook.helpers.iso8601
import org.y20k.trackbook.helpers.iso8601_format
import java.io.File
/*
@ -54,17 +48,25 @@ class Trackbook(): Application() {
// set Dark / Light theme state
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_COARSE_LOCATION)
ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_FINE_LOCATION)
Log.i("VOUSSOIR", "Device ID = ${PreferencesHelper.load_device_id()}")
if (ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
this.database.connect(File("/storage/emulated/0/Syncthing/GPX/trkpt_${PreferencesHelper.load_device_id()}.db"))
}
}
fun load_database()
{
Log.i("VOUSSOIR", "Trackbook.load_database")
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
this.database.connect(File("/storage/emulated/0/Syncthing/GPX/trkpt_${PreferencesHelper.load_device_id()}.db"))
this.load_homepoints()
}
else
{
this.database.ready = false
}
}
fun load_homepoints()
{
Log.i("VOUSSOIR", "Trackbook.load_homepoints")
this.homepoints.clear()
homepoint_generator().forEach { homepoint -> this.homepoints.add(homepoint) }
}
@ -73,18 +75,14 @@ class Trackbook(): Application() {
{
if (! database.ready)
{
Log.i("VOUSSOIR", "Trackbook.homepoint_generator: database is not ready.")
return@iterator
}
val cursor: Cursor = database.connection.query(
"homepoints",
arrayOf("lat", "lon", "radius", "name"),
null,
null,
null,
null,
null,
null,
val cursor: Cursor = database.connection.rawQuery(
"SELECT lat, lon, radius, name FROM homepoints",
arrayOf()
)
Log.i("VOUSSOIR", "Trackbook.homepoint_generator: Got ${cursor.count} homepoints.")
val COLUMN_LAT = cursor.getColumnIndex("lat")
val COLUMN_LON = cursor.getColumnIndex("lon")
val COLUMN_RADIUS = cursor.getColumnIndex("radius")

View file

@ -37,11 +37,7 @@ import android.util.Log
import androidx.core.content.ContextCompat
import java.util.*
import kotlinx.coroutines.Runnable
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.core.Database
import org.y20k.trackbook.core.Trkpt
import org.y20k.trackbook.helpers.*
import java.text.SimpleDateFormat
/*
* TrackerService class
@ -58,7 +54,7 @@ class TrackerService: Service(), SensorEventListener
var useImperial: Boolean = false
var gpsOnly: Boolean = false
var omitRests: Boolean = true
var device_id: String = random_int().toString()
var device_id: String = random_device_id()
var recording_started: Date = GregorianCalendar.getInstance().time
var commitInterval: Int = Keys.COMMIT_INTERVAL
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
@ -150,21 +146,11 @@ class TrackerService: Service(), SensorEventListener
LogHelper.v(TAG, "Added Network location listener.")
}
fun clearTrack()
{
trackingState = Keys.STATE_TRACKING_STOPPED
PreferencesHelper.saveTrackingState(trackingState)
track.delete()
track = Track(trackbook.database, device_id, start_time=GregorianCalendar.getInstance().time, stop_time=Date(GregorianCalendar.getInstance().time.time + 86400))
stopForeground(STOP_FOREGROUND_REMOVE)
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
}
private fun createLocationListener(): LocationListener
{
return object : LocationListener {
override fun onLocationChanged(location: Location) {
// update currentBestLocation if a better location is available
override fun onLocationChanged(location: Location)
{
if (LocationHelper.isBetterLocation(location, currentBestLocation)) {
currentBestLocation = location
}
@ -173,26 +159,16 @@ class TrackerService: Service(), SensorEventListener
{
LogHelper.v(TAG, "onProviderEnabled $provider")
when (provider) {
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(
locationManager
)
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
LocationHelper.isNetworkEnabled(
locationManager
)
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
}
}
override fun onProviderDisabled(provider: String)
{
LogHelper.v(TAG, "onProviderDisabled $provider")
when (provider) {
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(
locationManager
)
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
LocationHelper.isNetworkEnabled(
locationManager
)
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
}
}
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
@ -278,7 +254,7 @@ class TrackerService: Service(), SensorEventListener
/* Overrides onSensorChanged from SensorEventListener */
override fun onSensorChanged(sensorEvent: SensorEvent?) {
var steps: Float = 0f
var steps = 0f
if (sensorEvent != null) {
if (stepCountOffset == 0f) {
// store steps previously recorded by the system
@ -454,10 +430,10 @@ class TrackerService: Service(), SensorEventListener
}
for (homepoint in trackbook.homepoints)
{
if (LocationHelper.calculateDistance(homepoint.location, location) < homepoint.radius)
if (homepoint.location.distanceTo(location) < homepoint.radius)
{
Log.i("VOUSSOIR", "Omitting due to homepoint ${homepoint}.")
return false;
Log.i("VOUSSOIR", "Omitting due to homepoint ${homepoint.name}.")
return false
}
}
if (track.trkpts.isEmpty())
@ -478,16 +454,15 @@ class TrackerService: Service(), SensorEventListener
{
override fun run() {
val now: Date = GregorianCalendar.getInstance().time
val nowstr: String = iso8601(now)
val trkpt: Trkpt = Trkpt(location=currentBestLocation)
Log.i("VOUSSOIR", "Processing point ${currentBestLocation.latitude}, ${currentBestLocation.longitude} ${nowstr}.")
Log.i("VOUSSOIR", "Processing point ${currentBestLocation.latitude}, ${currentBestLocation.longitude} ${now.time}.")
if (should_keep_point((currentBestLocation)))
{
val values = ContentValues().apply {
put("device_id", device_id)
put("lat", trkpt.latitude)
put("lon", trkpt.longitude)
put("time", nowstr)
put("time", now.time)
put("accuracy", trkpt.accuracy)
put("sat", trkpt.numberSatellites)
put("ele", trkpt.altitude)
@ -499,7 +474,7 @@ class TrackerService: Service(), SensorEventListener
}
trackbook.database.connection.insert("trkpt", null, values)
track.trkpts.add(trkpt)
if (track.trkpts.size > track.dequelimit)
while (track.trkpts.size > 7200)
{
track.trkpts.removeFirst()
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import android.content.Intent
@ -26,7 +25,6 @@ import android.service.quicksettings.TileService
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.PreferencesHelper
/*
* TrackingToggleTileService class
*/
@ -35,13 +33,11 @@ class TrackingToggleTileService: TileService() {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
/* Main class variables */
private var bound: Boolean = false
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
private lateinit var trackerService: TrackerService
/* Overrides onTileAdded from TileService */
override fun onTileAdded() {
super.onTileAdded()
@ -56,7 +52,6 @@ class TrackingToggleTileService: TileService() {
super.onTileRemoved()
}
/* Overrides onStartListening from TileService (tile becomes visible) */
override fun onStartListening() {
super.onStartListening()
@ -68,7 +63,6 @@ class TrackingToggleTileService: TileService() {
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onClick from TileService */
override fun onClick() {
super.onClick()
@ -78,7 +72,6 @@ class TrackingToggleTileService: TileService() {
}
}
/* Overrides onStopListening from TileService (tile no longer visible) */
override fun onStopListening() {
super.onStopListening()
@ -86,13 +79,11 @@ class TrackingToggleTileService: TileService() {
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onDestroy from Service */
override fun onDestroy() {
super.onDestroy()
}
/* Update quick settings tile */
private fun updateTile() {
val tile: Tile = qsTile
@ -112,7 +103,6 @@ class TrackingToggleTileService: TileService() {
tile.updateTile()
}
/* Start tracking */
private fun startTracking() {
val intent = Intent(application, TrackerService::class.java)
@ -125,7 +115,6 @@ class TrackingToggleTileService: TileService() {
}
}
/* Stop tracking */
private fun stopTracking() {
val intent = Intent(application, TrackerService::class.java)
@ -133,7 +122,6 @@ class TrackingToggleTileService: TileService() {
application.startService(intent)
}
/*
* Defines the listener for changes in shared preferences
*/
@ -151,5 +139,4 @@ class TrackingToggleTileService: TileService() {
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook
import YesNoDialog
@ -34,13 +33,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.UiHelper
import org.y20k.trackbook.helpers.iso8601_format
import org.y20k.trackbook.tracklist.TracklistAdapter
/*
* TracklistFragment class
*/

View file

@ -14,33 +14,26 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.core
package org.y20k.trackbook
import android.location.Location
import android.os.Parcelable
import androidx.annotation.Keep
import com.google.gson.annotations.Expose
import kotlinx.parcelize.Parcelize
import org.y20k.trackbook.helpers.LocationHelper
import java.util.*
/*
* WayPoint data class
*/
@Keep
@Parcelize
data class Trkpt(@Expose val provider: String,
@Expose val latitude: Double,
@Expose val longitude: Double,
@Expose val altitude: Double,
@Expose val accuracy: Float,
@Expose val time: Date,
@Expose val distanceToStartingPoint: Float = 0f,
@Expose val numberSatellites: Int = 0,
@Expose var isStopOver: Boolean = false,
@Expose var starred: Boolean = false): Parcelable {
data class Trkpt(
val provider: String,
val latitude: Double,
val longitude: Double,
val altitude: Double,
val accuracy: Float,
val time: Long,
val numberSatellites: Int = 0,
var starred: Boolean = false
)
{
/* Constructor using just Location */
constructor(location: Location) : this (
provider=location.provider.toString(),
@ -48,19 +41,18 @@ data class Trkpt(@Expose val provider: String,
longitude=location.longitude,
altitude=location.altitude,
accuracy=location.accuracy,
time=Date(location.time),
distanceToStartingPoint=0F,
time=location.time,
numberSatellites=LocationHelper.getNumberOfSatellites(location),
)
/* Converts WayPoint into Location */
fun toLocation(): Location {
val location: Location = Location(provider)
val location = Location(provider)
location.latitude = latitude
location.longitude = longitude
location.altitude = altitude
location.accuracy = accuracy
location.time = this.time.time
location.time = this.time
return location
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.dialogs
import android.content.Context
@ -29,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.y20k.trackbook.R
import org.y20k.trackbook.helpers.LogHelper
/*
* ErrorDialog object
*/
@ -38,7 +36,6 @@ object ErrorDialog {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(ErrorDialog::class.java)
/* Construct and show dialog */
fun show(context: Context, errorTitle: Int, errorMessage: Int, errorDetails: String = String()) {
// prepare dialog builder

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.dialogs
import android.content.Context
@ -26,7 +25,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.y20k.trackbook.R
import org.y20k.trackbook.helpers.LogHelper
/*
* RenameTrackDialog class
*/
@ -41,7 +39,6 @@ class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) {
/* Define log tag */
private val TAG = LogHelper.makeLogTag(RenameTrackDialog::class.java.simpleName)
/* Construct and show dialog */
fun show(context: Context, trackName: String) {
// prepare dialog builder

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
import android.content.Context
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.y20k.trackbook.Keys
@ -32,11 +31,9 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
}
}
/* Define log tag */
private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName)
/* Construct and show dialog - variant: message from string */
fun show(context: Context,
type: Int,
@ -50,7 +47,6 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
show(context, type, title, context.getString(message), yesButton, noButton, payload, payloadString)
}
/* Construct and show dialog */
fun show(context: Context,
type: Int,
@ -70,7 +66,6 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
builder.setTitle(context.getString(title))
}
// add yes button
builder.setPositiveButton(yesButton) { _, _ ->
// listen for click on yes button

View file

@ -14,16 +14,12 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.extensions
import android.content.SharedPreferences
/* Puts a Double value in SharedPreferences */
fun SharedPreferences.Editor.putDouble(key: String, double: Double) = putLong(key, java.lang.Double.doubleToRawLongBits(double))
/* gets a Double value from SharedPreferences */
fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default)))

View file

@ -1,22 +1,24 @@
package org.y20k.trackbook.helpers
import java.lang.Math.abs
import java.security.SecureRandom
import java.text.SimpleDateFormat
import java.util.*
import kotlin.random.Random
val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
private val RNG = SecureRandom()
fun iso8601(datetime: Date): String
{
return iso8601_format.format(datetime)
}
fun random_long(): Long
{
return (Random.nextBits(31).toLong() shl 32) + Random.nextBits(32)
}
fun random_int(): Int
{
return Random.nextBits(31)
return abs(RNG.nextInt())
}
fun random_device_id(): String
{
return "myphone" + random_int()
}

View file

@ -14,18 +14,14 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import org.y20k.trackbook.Keys
import org.y20k.trackbook.R
/*
* AppThemeHelper object
*/
@ -34,7 +30,6 @@ object AppThemeHelper {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(AppThemeHelper::class.java)
/* Sets app theme */
fun setTheme(nightModeState: String) {
when (nightModeState) {
@ -67,14 +62,12 @@ object AppThemeHelper {
}
}
/* Return weather Night Mode is on, or not */
fun isDarkModeOn(context: Context): Boolean {
val nightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightMode == Configuration.UI_MODE_NIGHT_YES
}
/* Returns a readable String for currently selected App Theme */
fun getCurrentTheme(context: Context): String {
return when (PreferencesHelper.loadThemeSelection()) {
@ -83,22 +76,4 @@ object AppThemeHelper {
else -> context.getString(R.string.pref_theme_selection_mode_device_default)
}
}
/* Displays the default status bar */
private fun displayDefaultStatusBar(activity: Activity) {
val decorView = activity.window.decorView
decorView.systemUiVisibility = 0
}
/* Displays the light (inverted) status bar */
private fun displayLightStatusBar(activity: Activity) {
val decorView = activity.window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.content.Context
@ -26,7 +25,6 @@ import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
/*
* DateTimeHelper object
*/
@ -81,26 +79,22 @@ object DateTimeHelper {
return timeString
}
/* Create sortable string for date - used for filenames */
fun convertToSortableDateString(date: Date): String {
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US)
return dateFormat.format(date)
}
/* Creates a readable string for date - used in the UI */
fun convertToReadableDate(date: Date, dateStyle: Int = DateFormat.LONG): String {
return DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)
}
/* Creates a readable string date and time - used in the UI */
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
@ -112,5 +106,4 @@ object DateTimeHelper {
return timeDifference
}
}

View file

@ -1,98 +0,0 @@
/*
* FileHelper.kt
* Implements the FileHelper object
* A FileHelper provides helper methods for reading and writing files from and to device storage
*
* 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.helpers
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import androidx.core.net.toUri
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.io.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import org.y20k.trackbook.Keys
import org.y20k.trackbook.core.Track
/*
* FileHelper object
*/
object FileHelper {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(FileHelper::class.java)
/* Suspend function: Wrapper for copyFile */
suspend fun saveCopyOfFileSuspended(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
return suspendCoroutine { cont ->
cont.resume(copyFile(context, originalFileUri, targetFileUri, deleteOriginal))
}
}
/* Copies file to specified target */
fun copyFile(context: Context, originalFileUri: Uri, targetFileUri: Uri, deleteOriginal: Boolean = false) {
val inputStream = context.contentResolver.openInputStream(originalFileUri)
val outputStream = context.contentResolver.openOutputStream(targetFileUri)
if (outputStream != null) {
inputStream?.copyTo(outputStream)
}
if (deleteOriginal) {
context.contentResolver.delete(originalFileUri, null, null)
}
}
fun getCustomGson(): Gson
{
val gsonBuilder = GsonBuilder()
gsonBuilder.setDateFormat("yyyy-MM-dd-HH-mm-ss")
gsonBuilder.excludeFieldsWithoutExposeAnnotation()
return gsonBuilder.create()
}
/* Reads InputStream from file uri and returns it as String */
fun readTextFile(context: Context, file: File): String {
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
// https://developer.android.com/training/secure-file-sharing/retrieve-info
if (!file.exists()) {
return String()
}
// read until last line reached
val stream: InputStream = file.inputStream()
val reader: BufferedReader = BufferedReader(InputStreamReader(stream))
val builder: StringBuilder = StringBuilder()
reader.forEachLine {
builder.append(it)
builder.append("\n") }
stream.close()
return builder.toString()
}
/* Writes given text to file on storage */
fun write_text_file_noblank(text: String, file: File)
{
if (text.isNotEmpty()) {
file.writeText(text)
} else {
LogHelper.w(TAG, "Writing text file ${file.toUri()} failed. Empty text string was provided.")
}
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import java.math.BigDecimal
@ -22,19 +21,16 @@ import java.math.RoundingMode
import java.text.NumberFormat
import java.util.*
/*
* LengthUnitHelper object
*/
object LengthUnitHelper {
/* Converts for the given unit system a distance value to a readable string */
fun convertDistanceToString(distance: Float, useImperial: Boolean = false): String {
return convertDistanceToString(distance.toDouble(), useImperial)
}
/* Converts for the given unit system a distance value to a readable string */
fun convertDistanceToString(distance: Double, useImperial: Boolean = false): String {
val readableDistance: Double
@ -85,7 +81,6 @@ object LengthUnitHelper {
return "${numberFormat.format(readableDistance)} $unit"
}
/* Determines which unit system the device is using (metric or imperial) */
fun useImperialUnits(): Boolean {
// America (US), Liberia (LR), Myanmar(MM) use the imperial system
@ -94,7 +89,6 @@ object LengthUnitHelper {
return imperialSystemCountries.contains(countryCode)
}
/* Converts for the given unit System distance and duration values to a readable velocity string */
fun convertToVelocityString(velocity: Double, useImperialUnits: Boolean = false) : String {
var speed: String = "0"
@ -114,7 +108,6 @@ object LengthUnitHelper {
}
}
/* Coverts meters per second to either km/h or mph */
fun convertMetersPerSecond(metersPerSecond: Double, useImperial: Boolean = false): Double {
if (useImperial) {
@ -126,5 +119,4 @@ object LengthUnitHelper {
}
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.Manifest
@ -26,11 +25,8 @@ import android.os.Bundle
import android.os.SystemClock
import androidx.core.content.ContextCompat
import org.y20k.trackbook.Keys
import org.y20k.trackbook.core.Track
import java.util.*
import kotlin.math.pow
/*
* Keys object
*/
@ -39,7 +35,6 @@ object LocationHelper {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(LocationHelper::class.java)
/* Get default location */
fun getDefaultLocation(): Location {
val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
@ -68,9 +63,9 @@ object LocationHelper {
return lastKnownLocation
}
/* Determines whether one location reading is better than the current location fix */
fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean {
fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean
{
// Credit: https://developer.android.com/guide/topics/location/strategies.html#BestEstimate
if (currentBestLocation == null) {
@ -110,15 +105,18 @@ object LocationHelper {
}
/* Checks if GPS location provider is available and enabled */
fun isGpsEnabled(locationManager: LocationManager): Boolean {
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
fun isGpsEnabled(locationManager: LocationManager): Boolean
{
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER))
{
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
} else {
}
else
{
return false
}
}
/* Checks if Network location provider is available and enabled */
fun isNetworkEnabled(locationManager: LocationManager): Boolean {
if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
@ -129,14 +127,12 @@ object LocationHelper {
}
/* Checks if given location is new */
fun isRecentEnough(location: Location): Boolean {
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
return locationAge < Keys.DEFAULT_THRESHOLD_LOCATION_AGE
}
/* Checks if given location is accurate */
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
val isAccurate: Boolean
@ -166,7 +162,7 @@ object LocationHelper {
val accuracy: Float = if (location.accuracy != 0.0f) location.accuracy else Keys.DEFAULT_THRESHOLD_DISTANCE
val previousAccuracy: Float = if (previousLocation.accuracy != 0.0f) previousLocation.accuracy else Keys.DEFAULT_THRESHOLD_DISTANCE
val accuracyDelta: Double = Math.sqrt((accuracy.pow(2) + previousAccuracy.pow(2)).toDouble())
val distance: Float = calculateDistance(previousLocation, location)
val distance: Float = previousLocation.distanceTo(location)
// With 1*accuracyDelta we have 68% confidence that the points are
// different. We can multiply this number to increase confidence but
@ -174,18 +170,6 @@ object LocationHelper {
return distance > accuracyDelta
}
/* Calculates distance in meters between two locations */
fun calculateDistance(previousLocation: Location?, location: Location): Float {
var distance: Float = 0f
// two data points needed to calculate distance
if (previousLocation != null) {
// add up distance
distance = previousLocation.distanceTo(location)
}
return distance
}
/* Get number of satellites from Location extras */
fun getNumberOfSatellites(location: Location): Int {
val numberOfSatellites: Int
@ -198,5 +182,4 @@ object LocationHelper {
return numberOfSatellites
}
}

View file

@ -14,13 +14,11 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.util.Log
import org.y20k.trackbook.BuildConfig
/*
* LogHelper object
*/

View file

@ -14,227 +14,152 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.location.Location
import android.os.Vibrator
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.Polygon
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.Homepoint
import org.y20k.trackbook.Keys
import org.y20k.trackbook.R
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.core.Trkpt
import org.y20k.trackbook.Track
import org.y20k.trackbook.Trkpt
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
/*
* MapOverlayHelper class
*/
class MapOverlayHelper (private var markerListener: MarkerListener) {
/* Interface used to communicate back to activity/fragment */
interface MarkerListener {
fun onMarkerTapped(latitude: Double, longitude: Double) {
}
}
/* Define log tag */
private val TAG = MapOverlayHelper::class.java.simpleName
//private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
/* Creates icon overlay for current position (used in MapFragment) */
fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay<OverlayItem>
{
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
val locationIsOld: Boolean = !(LocationHelper.isRecentEnough(location))
// create marker
val newMarker: Drawable
when (trackingState) {
// CASE: Tracking active
Keys.STATE_TRACKING_ACTIVE -> {
when (locationIsOld) {
true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_grey_24dp)!!
false -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_red_24dp)!!
}
}
// CASE. Tracking is NOT active
else -> {
when (locationIsOld) {
true -> newMarker = ContextCompat.getDrawable(context, R.drawable.ic_marker_location_blue_grey_24dp)!!
false -> 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)
overlayItems.add(overlayItem)
// create and return overlay for current position
return createOverlay(context, overlayItems, enableStarring = false)
}
/* Creates icon overlay for current position (used in MapFragment) */
fun createHomepointOverlay(context: Context, location: Location): ItemizedIconOverlay<OverlayItem>
{
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
// create marker
val newMarker: Drawable = ContextCompat.getDrawable(context, R.drawable.ic_homepoint_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)
overlayItems.add(overlayItem)
// create and return overlay for current position
return createOverlay(context, overlayItems, enableStarring = false)
}
/* Creates icon overlay for track */
fun createTrackOverlay(context: Context, track: Track, trackingState: Int): SimpleFastPointOverlay
{
// get marker color
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red)
else context.getColor(R.color.default_blue)
// gather points for overlay
val points: MutableList<IGeoPoint> = mutableListOf()
track.trkpts.forEach { wayPoint ->
val label: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(wayPoint.time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(wayPoint.accuracy)} (${wayPoint.provider})"
// only add normal points
if (!wayPoint.starred && !wayPoint.isStopOver) {
points.add(LabelledGeoPoint(wayPoint.latitude, wayPoint.longitude, wayPoint.altitude, label))
}
}
val pointTheme: SimplePointTheme = SimplePointTheme(points, false)
// set styling for overlay
val style: Paint = Paint()
style.style = Paint.Style.FILL
style.color = color
style.flags = Paint.ANTI_ALIAS_FLAG
val scalingFactor: Float = UiHelper.getDensityScalingFactor(context)
val overlayOptions: SimpleFastPointOverlayOptions = SimpleFastPointOverlayOptions.getDefaultStyle()
.setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MAXIMUM_OPTIMIZATION)
.setSymbol(SimpleFastPointOverlayOptions.Shape.CIRCLE)
.setPointStyle(style)
.setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp)
.setIsClickable(true)
// .setCellSize(15) // 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.
// create and return overlay
val overlay: SimpleFastPointOverlay = SimpleFastPointOverlay(pointTheme, overlayOptions)
overlay.setOnClickListener(object : SimpleFastPointOverlay.OnClickListener {
override fun onClick(points: SimpleFastPointOverlay.PointAdapter?, point: Int?) {
if (points != null && point != null) {
val markerPoint: IGeoPoint = points.get(point)
markerListener.onMarkerTapped(markerPoint.latitude, markerPoint.longitude)
}
}
})
return overlay
}
/* Creates overlay containing start, stop, stopover and starred markers for track */
fun createSpecialMakersTrackOverlay(context: Context, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false): ItemizedIconOverlay<OverlayItem>
{
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
val maxIndex: Int = track.trkpts.size - 1
track.trkpts.forEachIndexed { index: Int, trkpt: Trkpt ->
var overlayItem: OverlayItem? = null
if (!trackingActive && index == 0 && displayStartEndMarker && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_starred_blue_48dp)!!)
}
else if (!trackingActive && index == 0 && displayStartEndMarker && !trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!)
}
else if (!trackingActive && index == maxIndex && displayStartEndMarker && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_starred_blue_48dp)!!)
}
else if (!trackingActive && index == maxIndex && displayStartEndMarker && !trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!)
}
else if (!trackingActive && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
}
else if (trackingActive && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!)
}
else if (trkpt.isStopOver)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_location_grey_24dp)!!)
}
// add overlay item, if it was created
if (overlayItem != null)
{
overlayItems.add(overlayItem)
}
}
// create and return overlay for current position
return createOverlay(context, overlayItems, enableStarring = true)
}
/* Creates a marker overlay item */
private fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem {
val title: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}"
//val description: String = "${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
val description: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
val position: GeoPoint = GeoPoint(latitude, longitude)
val item: OverlayItem = OverlayItem(title, description, position)
item.markerHotspot = OverlayItem.HotspotPlace.CENTER
return item
}
/* Creates an overlay */
private fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>, enableStarring: Boolean): ItemizedIconOverlay<OverlayItem> {
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
if (enableStarring) {
markerListener.onMarkerTapped(item.point.latitude, item.point.longitude)
return true
} else {
return false
}
}
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
v.vibrate(50)
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
return true
}
})
}
/* Creates icon overlay for current position (used in MapFragment) */
fun createMyLocationOverlay(context: Context, map_view: MapView, currentPositionOverlay: ItemizedIconOverlay<OverlayItem>, location: Location, trackingState: Int)
{
}
/* Creates icon overlay for track */
fun createTrackOverlay(context: Context, map_view: MapView, track: Track, trackingState: Int)
{
// get marker color
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red) else context.getColor(R.color.default_blue)
// gather points for overlay
val points: MutableList<IGeoPoint> = mutableListOf()
track.trkpts.forEach { wayPoint ->
val label: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(wayPoint.time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(wayPoint.accuracy)} (${wayPoint.provider})"
// only add normal points
if (!wayPoint.starred)
{
points.add(LabelledGeoPoint(wayPoint.latitude, wayPoint.longitude, wayPoint.altitude, label))
}
}
val pointTheme: SimplePointTheme = SimplePointTheme(points, false)
// set styling for overlay
val style: Paint = Paint()
style.style = Paint.Style.FILL
style.color = color
style.flags = Paint.ANTI_ALIAS_FLAG
val scalingFactor: Float = UiHelper.getDensityScalingFactor(context)
val overlayOptions: SimpleFastPointOverlayOptions = SimpleFastPointOverlayOptions.getDefaultStyle()
.setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MAXIMUM_OPTIMIZATION)
.setSymbol(SimpleFastPointOverlayOptions.Shape.CIRCLE)
.setPointStyle(style)
.setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp)
.setIsClickable(true)
.setCellSize(15) // 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.
val overlay = SimpleFastPointOverlay(pointTheme, overlayOptions)
map_view.overlays.add(overlay)
}
/* Creates overlay containing start, stop, stopover and starred markers for track */
fun createSpecialMakersTrackOverlay(context: Context, map_view: MapView, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false)
{
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
val maxIndex: Int = track.trkpts.size - 1
track.trkpts.forEachIndexed { index: Int, trkpt: Trkpt ->
var overlayItem: OverlayItem? = null
if (!trackingActive && index == 0 && displayStartEndMarker && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_starred_blue_48dp)!!)
}
else if (!trackingActive && index == 0 && displayStartEndMarker && !trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!)
}
else if (!trackingActive && index == maxIndex && displayStartEndMarker && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_starred_blue_48dp)!!)
}
else if (!trackingActive && index == maxIndex && displayStartEndMarker && !trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!)
}
else if (!trackingActive && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
}
else if (trackingActive && trkpt.starred)
{
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!)
}
if (overlayItem != null)
{
overlayItems.add(overlayItem)
}
}
map_view.overlays.add(createOverlay(context, overlayItems))
}
fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem
{
val title: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}"
//val description: String = "${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
val description: String = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
val position: GeoPoint = GeoPoint(latitude, longitude)
val item: OverlayItem = OverlayItem(title, description, position)
item.markerHotspot = OverlayItem.HotspotPlace.CENTER
return item
}
fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>): ItemizedIconOverlay<OverlayItem>
{
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
return false
}
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
v.vibrate(50)
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
return true
}
}
)
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.app.*
@ -30,7 +29,6 @@ import org.y20k.trackbook.MainActivity
import org.y20k.trackbook.R
import org.y20k.trackbook.TrackerService
/*
* NotificationHelper class
*/
@ -39,11 +37,9 @@ class NotificationHelper(private val trackerService: TrackerService) {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(NotificationHelper::class.java)
/* Main class variables */
private val notificationManager: NotificationManager = trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
/* Creates notification */
fun createNotification(trackingState: Int, timestamp: String): Notification {

View file

@ -46,16 +46,23 @@ object PreferencesHelper {
fun load_device_id(): String
{
val v = sharedPreferences.getString(Keys.PREF_DEVICE_ID, random_int().toString()).toString();
Log.i("VOUSSOIR", "Loaded device_id ${v}.")
val fallback = random_device_id()
val v = sharedPreferences.getString(Keys.PREF_DEVICE_ID, fallback).toString()
if (v == fallback)
{
sharedPreferences.edit { putString(Keys.PREF_DEVICE_ID, fallback) }
}
Log.i("VOUSSOIR", "PreferencesHelper.load_device_id: Got ${v}.")
return v
}
fun loadZoomLevel(): Double {
fun loadZoomLevel(): Double
{
return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL)
}
fun saveZoomLevel(zoomLevel: Double) {
fun saveZoomLevel(zoomLevel: Double)
{
sharedPreferences.edit { putDouble(Keys.PREF_MAP_ZOOM_LEVEL, zoomLevel) }
}

View file

@ -1,47 +0,0 @@
/*
* TrackHelper.kt
* Implements the TrackHelper object
* A TrackHelper offers helper methods for dealing with track objects
*
* 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.helpers
import android.content.Context
import android.widget.Toast
import org.y20k.trackbook.R
import org.y20k.trackbook.core.Track
/*
* TrackHelper object
*/
object TrackHelper {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(TrackHelper::class.java)
/* Adds given locatiom as waypoint to track */
/* Toggles starred flag for given position */
fun toggle_waypoint_starred(context: Context, track: Track, latitude: Double, longitude: Double)
{
track.trkpts.forEach { waypoint ->
if (waypoint.latitude == latitude && waypoint.longitude == longitude) {
waypoint.starred = !waypoint.starred
when (waypoint.starred) {
true -> Toast.makeText(context, R.string.toast_message_poi_added, Toast.LENGTH_LONG).show()
false -> Toast.makeText(context, R.string.toast_message_poi_removed, Toast.LENGTH_LONG).show()
}
}
}
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.helpers
import android.content.Context
@ -31,7 +30,6 @@ import androidx.recyclerview.widget.RecyclerView
import org.y20k.trackbook.R
import org.y20k.trackbook.tracklist.TracklistAdapter
/*
* UiHelper object
*/
@ -40,7 +38,6 @@ object UiHelper {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(UiHelper::class.java)
/* Sets layout margins for given view in DP */
fun setViewMargins(context: Context, view: View, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
val scalingFactor: Float = context.resources.displayMetrics.density
@ -55,7 +52,6 @@ object UiHelper {
}
}
/* Sets layout margins for given view in percent */
fun setViewMarginsPercentage(context: Context, view: View, height: Int, width: Int, left: Int = 0, right: Int = 0, top: Int= 0, bottom: Int = 0) {
val l: Int = ((width / 100.0f) * left).toInt()
@ -65,7 +61,6 @@ object UiHelper {
setViewMargins(context, view, l, r, t, b)
}
/* Get the height of the system's top status bar */
fun getStatusBarHeight(context: Context): Int {
var result: Int = 0
@ -76,14 +71,12 @@ object UiHelper {
return result
}
/* Get scaling factor from display density */
fun getDensityScalingFactor(context: Context): Float {
return context.resources.displayMetrics.density
}
/*
* Inner class: Callback that detects a left swipe
* Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt

View file

@ -14,25 +14,22 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.tracklist
import android.content.Context
import android.database.Cursor
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import org.y20k.trackbook.Keys
import org.y20k.trackbook.R
import org.y20k.trackbook.core.Database
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.Database
import org.y20k.trackbook.Track
import org.y20k.trackbook.helpers.*
import java.text.DateFormat
import java.text.SimpleDateFormat
@ -65,27 +62,25 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
{
return
}
val cursor: Cursor = database.connection.query(
"trkpt",
arrayOf("distinct strftime('%Y-%m-%d', time)", "device_id"),
null,
null,
null,
null,
"time DESC",
null,
val cursor: Cursor = database.connection.rawQuery(
"SELECT distinct(date(time/1000, 'unixepoch', 'localtime')) as thedate, device_id FROM trkpt ORDER BY thedate DESC",
arrayOf()
)
try
{
val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
while (cursor.moveToNext())
{
val start_time: Date? = df.parse(cursor.getString(0) + "T00:00:00.000")
val stop_time: Date? = df.parse(cursor.getString(0) + "T23:59:59.999")
Log.i("VOUSSOIR", "TracklistAdapter prep track ${cursor.getString(0)}")
val trackdate = cursor.getString(0)
val device_id = cursor.getString(1)
val start_time: Date? = df.parse(trackdate + "T00:00:00.000")
val stop_time: Date? = df.parse(trackdate + "T23:59:59.999")
Log.i("VOUSSOIR", "TracklistAdapter prep track ${trackdate}")
if (start_time != null && stop_time != null)
{
tracks.add(Track(database=database, device_id=cursor.getString(1), start_time=start_time, stop_time=stop_time))
val track = Track(database=database, device_id=device_id, start_time=start_time, stop_time=stop_time)
track.name = "$trackdate $device_id"
tracks.add(track)
}
}
}
@ -95,7 +90,6 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
}
}
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
{
@ -103,21 +97,18 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
return ElementTrackViewHolder(v)
}
/* Overrides getItemViewType */
override fun getItemViewType(position: Int): Int
{
return Keys.VIEW_TYPE_TRACK
}
/* Overrides getItemCount from RecyclerView.Adapter */
override fun getItemCount(): Int
{
return tracks.size
}
/* Overrides onBindViewHolder from RecyclerView.Adapter */
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
{
@ -130,7 +121,6 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
}
}
/* Get track name for given position */
fun getTrackName(positionInRecyclerView: Int): String
{
@ -188,7 +178,6 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
// return trackDataString
}
/*
* Inner class: ViewHolder for a track element
*/

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.ui
import android.Manifest
@ -23,53 +22,57 @@ 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.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
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 com.google.android.material.textview.MaterialTextView
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.core.Track
import org.y20k.trackbook.Track
import org.y20k.trackbook.helpers.*
/*
* MapFragmentLayoutHolder class
*/
data class MapFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var container: ViewGroup?, private var statusBarHeight: Int, private val startLocation: Location, private val trackingState: Int) {
/* Define log tag */
private val TAG: String = LogHelper.makeLogTag(MapFragmentLayoutHolder::class.java)
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
)
{
/* Main class variables */
val rootView: View
var userInteraction: Boolean = false
val currentLocationButton: FloatingActionButton
val mainButton: ExtendedFloatingActionButton
private val mapView: MapView
private val homepoint_overlays: ArrayList<ItemizedIconOverlay<OverlayItem>> = ArrayList()
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
private var current_location_radius: Polygon = Polygon()
private var currentTrackOverlay: SimpleFastPointOverlay?
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
private val useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
@ -77,8 +80,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
private var controller: IMapController
private var zoomLevel: Double
/* Init block */
init {
// find views
rootView = inflater.inflate(R.layout.fragment_map, container, false)
@ -113,10 +114,11 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
mapView.overlays.add(compassOverlay)
val app: Trackbook = (context.applicationContext as Trackbook)
app.homepoint_generator().forEach { homepoint -> mapView.overlays.add(MapOverlayHelper(markerListener).createHomepointOverlay(context, homepoint.location))}
app.load_homepoints()
createHomepointOverlays(context, mapView, app.homepoints)
// add my location overlay
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, startLocation, trackingState)
currentPositionOverlay = createOverlay(context, ArrayList<OverlayItem>())
mapView.overlays.add(currentPositionOverlay)
centerMap(startLocation)
@ -131,7 +133,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
addInteractionListener()
}
/* Listen for user interaction */
@SuppressLint("ClickableViewAccessibility")
private fun addInteractionListener() {
@ -141,7 +142,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
}
}
/* Set map center */
fun centerMap(location: Location, animated: Boolean = false) {
val position = GeoPoint(location.latitude, location.longitude)
@ -152,7 +152,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
userInteraction = false
}
/* Save current best location and state of map to shared preferences */
fun saveState(currentBestLocation: Location) {
PreferencesHelper.saveCurrentBestLocation(currentBestLocation)
@ -161,14 +160,78 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
userInteraction = false
}
/* Mark current position on map */
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED) {
mapView.overlays.remove(currentPositionOverlay)
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, location, trackingState)
mapView.overlays.add(currentPositionOverlay)
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED)
{
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.markCurrentPosition")
val locationIsOld: Boolean = !(LocationHelper.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(128, 255, 193, 7)
map_view.overlays.add(p)
}
}
/* Overlay current track on map */
fun overlayCurrentTrack(track: Track, trackingState: Int) {
@ -179,11 +242,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
}
if (track.trkpts.isNotEmpty()) {
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
currentTrackOverlay = mapOverlayHelper.createTrackOverlay(context, track, trackingState)
currentTrackSpecialMarkerOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, trackingState)
mapView.overlays.add(currentTrackSpecialMarkerOverlay)
mapView.overlays.add(currentTrackOverlay)
createTrackOverlay(context, mapView, track, trackingState)
createSpecialMakersTrackOverlay(context, mapView, track, trackingState)
}
}
@ -206,7 +266,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
}
}
/* 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) {
@ -221,5 +280,4 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
if (locationErrorBar.isShown) locationErrorBar.dismiss()
}
}
}

View file

@ -14,7 +14,6 @@
* https://github.com/osmdroid/osmdroid
*/
package org.y20k.trackbook.ui
import android.app.Activity
@ -46,19 +45,17 @@ import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
import org.y20k.trackbook.Keys
import org.y20k.trackbook.R
import org.y20k.trackbook.core.Track
import org.y20k.trackbook.core.TrackStatistics
import org.y20k.trackbook.Track
import org.y20k.trackbook.TrackStatistics
import org.y20k.trackbook.helpers.*
import kotlin.math.roundToInt
/*
* TrackFragmentLayoutHolder class
*/
//data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var statusBarHeight: Int, private var container: ViewGroup?, var track: Track): MapListener { TODO REMOVE
data class TrackFragmentLayoutHolder(
private var context: Context,
private var markerListener: MapOverlayHelper.MarkerListener,
private var inflater: LayoutInflater,
private var container: ViewGroup?,
var track: Track
@ -71,8 +68,6 @@ data class TrackFragmentLayoutHolder(
val editButton: ImageButton
val trackNameView: MaterialTextView
private val mapView: MapView
private var trackSpecialMarkersOverlay: ItemizedIconOverlay<OverlayItem>?
private var trackOverlay: SimpleFastPointOverlay?
private var controller: IMapController
//private var zoomLevel: Double
private val statisticsSheetBehavior: BottomSheetBehavior<View>
@ -96,7 +91,6 @@ data class TrackFragmentLayoutHolder(
private val trackManagementViews: Group
private val useImperialUnits: Boolean
/* Init block */
init {
// find views
@ -152,13 +146,9 @@ data class TrackFragmentLayoutHolder(
// compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / UiHelper.getDensityScalingFactor(context))) TODO REMOVE
mapView.overlays.add(compassOverlay)
// create map overlay
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED)
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
if (track.trkpts.isNotEmpty()) {
mapView.overlays.add(trackSpecialMarkersOverlay)
mapView.overlays.add(trackOverlay)
createSpecialMakersTrackOverlay(context, mapView, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
createTrackOverlay(context, mapView, track, Keys.STATE_TRACKING_STOPPED)
}
// set up and show statistics sheet
@ -168,25 +158,6 @@ data class TrackFragmentLayoutHolder(
setupStatisticsViews()
}
/* Updates map overlay */
fun updateTrackOverlay()
{
if (trackOverlay != null) {
mapView.overlays.remove(trackOverlay)
}
if (trackSpecialMarkersOverlay != null) {
mapView.overlays.remove(trackSpecialMarkersOverlay)
}
if (track.trkpts.isNotEmpty()) {
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
trackOverlay = mapOverlayHelper.createTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED)
trackSpecialMarkersOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
mapView.overlays.add(trackOverlay)
mapView.overlays.add(trackSpecialMarkersOverlay)
}
}
/* Saves zoom level and center of this map */
fun saveViewStateToTrack()
{
@ -195,7 +166,6 @@ data class TrackFragmentLayoutHolder(
}
}
/* Sets up the statistics sheet */
private fun setupStatisticsViews()
{
@ -225,7 +195,6 @@ data class TrackFragmentLayoutHolder(
}
}
/* Shows/hides the statistics sheet */
private fun toggleStatisticsSheetVisibility() {
when (statisticsSheetBehavior.state) {
@ -263,7 +232,6 @@ data class TrackFragmentLayoutHolder(
}
}
/* Overrides onZoom from MapListener */
override fun onZoom(event: ZoomEvent?): Boolean {
if (event == null) {
@ -274,7 +242,6 @@ data class TrackFragmentLayoutHolder(
}
}
/* Overrides onScroll from MapListener */
override fun onScroll(event: ScrollEvent?): Boolean {
if (event == null) {

View file

@ -4,10 +4,10 @@
android:viewportWidth="96.0"
android:width="24dp">
<path
android:fillAlpha="0.33"
android:fillAlpha="0.0"
android:fillColor="@color/default_red"
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path
android:fillColor="@color/default_neutral_medium_light"
android:fillColor="#ff000000"
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
</vector>

View file

@ -3,10 +3,6 @@
android:viewportHeight="96.0"
android:viewportWidth="96.0"
android:width="24dp">
<path
android:fillAlpha="0.33"
android:fillColor="@color/default_blue"
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path
android:fillColor="@color/default_blue"
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>

View file

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="96.0"
android:viewportWidth="96.0"
android:width="24dp">
<path
android:fillAlpha="0.33"
android:fillColor="@color/default_blue"
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path
android:fillColor="@color/default_neutral_medium_light"
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
</vector>

View file

@ -4,7 +4,7 @@
android:viewportWidth="96.0"
android:width="24dp">
<path
android:fillAlpha="0.33"
android:fillAlpha="0.0"
android:fillColor="@color/default_red"
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path