checkpoint
parent
2568af3bb1
commit
df77b089ac
|
@ -1,6 +1,4 @@
|
||||||
package org.y20k.trackbook.core
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteDatabase.openOrCreateDatabase
|
import android.database.sqlite.SQLiteDatabase.openOrCreateDatabase
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -11,6 +9,7 @@ class Database()
|
||||||
var ready: Boolean = false
|
var ready: Boolean = false
|
||||||
lateinit var file: File
|
lateinit var file: File
|
||||||
lateinit var connection: SQLiteDatabase
|
lateinit var connection: SQLiteDatabase
|
||||||
|
|
||||||
fun close()
|
fun close()
|
||||||
{
|
{
|
||||||
this.connection.close()
|
this.connection.close()
|
||||||
|
@ -19,6 +18,7 @@ class Database()
|
||||||
|
|
||||||
fun connect(file: File)
|
fun connect(file: File)
|
||||||
{
|
{
|
||||||
|
Log.i("VOUSSOIR", "Connecting to database " + file.absolutePath)
|
||||||
this.file = file
|
this.file = file
|
||||||
this.connection = openOrCreateDatabase(file, null)
|
this.connection = openOrCreateDatabase(file, null)
|
||||||
this.initialize_tables()
|
this.initialize_tables()
|
||||||
|
@ -44,7 +44,7 @@ class Database()
|
||||||
{
|
{
|
||||||
this.connection.beginTransaction()
|
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 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.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.setTransactionSuccessful()
|
||||||
this.connection.endTransaction()
|
this.connection.endTransaction()
|
|
@ -1,5 +1,4 @@
|
||||||
package org.y20k.trackbook.core
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
|
@ -14,12 +14,10 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Keys object
|
* Keys object
|
||||||
*/
|
*/
|
||||||
|
@ -116,7 +114,7 @@ object Keys {
|
||||||
const val COMMIT_INTERVAL: Int = 30
|
const val COMMIT_INTERVAL: Int = 30
|
||||||
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 13
|
const val DEFAULT_ALTITUDE_SMOOTHING_VALUE: Int = 13
|
||||||
const val DEFAULT_THRESHOLD_LOCATION_ACCURACY: Int = 30 // 30 meters
|
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_THRESHOLD_DISTANCE: Float = 15f // 15 meters
|
||||||
const val DEFAULT_ZOOM_LEVEL: Double = 16.0
|
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
|
const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD = 10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded
|
||||||
|
|
|
@ -14,19 +14,21 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.VmPolicy
|
import android.os.StrictMode.VmPolicy
|
||||||
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
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.LogHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
|
|
||||||
private const val REQUEST_EXTERNAL_STORAGE = 1
|
class MainActivity: AppCompatActivity()
|
||||||
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?)
|
|
||||||
{
|
{
|
||||||
// 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 */
|
/* Main class variables */
|
||||||
|
lateinit var trackbook: Trackbook
|
||||||
private lateinit var navHostFragment: NavHostFragment
|
private lateinit var navHostFragment: NavHostFragment
|
||||||
private lateinit var bottomNavigationView: BottomNavigationView
|
private lateinit var bottomNavigationView: BottomNavigationView
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreate from AppCompatActivity */
|
/* Overrides onCreate from AppCompatActivity */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?)
|
||||||
|
{
|
||||||
|
trackbook = (applicationContext as Trackbook)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
verifyStoragePermissions(this)
|
request_permissions(this)
|
||||||
// todo: remove after testing finished
|
// 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(
|
StrictMode.setVmPolicy(
|
||||||
VmPolicy.Builder()
|
VmPolicy.Builder()
|
||||||
.detectNonSdkApiUsage()
|
.detectNonSdkApiUsage()
|
||||||
|
@ -109,8 +78,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
// listen for navigation changes
|
// listen for navigation changes
|
||||||
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
|
navHostFragment.navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||||
when (destination.id) {
|
when (destination.id)
|
||||||
R.id.fragment_track -> {
|
{
|
||||||
|
R.id.fragment_track ->
|
||||||
|
{
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
run {
|
run {
|
||||||
// mark menu item "Tracks" as checked
|
// mark menu item "Tracks" as checked
|
||||||
|
@ -118,7 +89,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else ->
|
||||||
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,26 +100,62 @@ class MainActivity : AppCompatActivity() {
|
||||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
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 */
|
/* Overrides onDestroy from AppCompatActivity */
|
||||||
override fun onDestroy() {
|
override fun onDestroy()
|
||||||
|
{
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
// unregister listener for changes in shared preferences
|
// unregister listener for changes in shared preferences
|
||||||
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||||
when (key) {
|
when (key)
|
||||||
Keys.PREF_THEME_SELECTION -> {
|
{
|
||||||
|
Keys.PREF_THEME_SELECTION ->
|
||||||
|
{
|
||||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
@ -25,17 +24,18 @@ import android.os.*
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
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.helpers.*
|
||||||
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
import org.y20k.trackbook.ui.MapFragmentLayoutHolder
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MapFragment class
|
* MapFragment class
|
||||||
*/
|
*/
|
||||||
class MapFragment : Fragment(), MapOverlayHelper.MarkerListener
|
class MapFragment : Fragment()
|
||||||
{
|
{
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(MapFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(MapFragment::class.java)
|
||||||
|
@ -61,13 +61,14 @@ class MapFragment : Fragment(), MapOverlayHelper.MarkerListener
|
||||||
currentBestLocation = LocationHelper.getLastKnownLocation(activity as Context)
|
currentBestLocation = LocationHelper.getLastKnownLocation(activity as Context)
|
||||||
// get saved tracking state
|
// get saved tracking state
|
||||||
trackingState = PreferencesHelper.loadTrackingState()
|
trackingState = PreferencesHelper.loadTrackingState()
|
||||||
|
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides onStop from Fragment */
|
/* Overrides onStop from Fragment */
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
// initialize layout
|
// initialize layout
|
||||||
val statusBarHeight: Int = UiHelper.getStatusBarHeight(activity as Context)
|
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
|
// set up buttons
|
||||||
layout.currentLocationButton.setOnClickListener {
|
layout.currentLocationButton.setOnClickListener {
|
||||||
|
|
|
@ -14,33 +14,21 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
|
|
||||||
import YesNoDialog
|
import YesNoDialog
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.*
|
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.AppThemeHelper
|
||||||
import org.y20k.trackbook.helpers.FileHelper
|
|
||||||
import org.y20k.trackbook.helpers.LengthUnitHelper
|
import org.y20k.trackbook.helpers.LengthUnitHelper
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
import org.y20k.trackbook.helpers.random_int
|
import org.y20k.trackbook.helpers.random_device_id
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SettingsFragment class
|
* SettingsFragment class
|
||||||
|
@ -50,7 +38,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(SettingsFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(SettingsFragment::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onViewCreated from PreferenceFragmentCompat */
|
/* Overrides onViewCreated from PreferenceFragmentCompat */
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -58,7 +45,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
view.setBackgroundColor(resources.getColor(R.color.app_window_background, null))
|
view.setBackgroundColor(resources.getColor(R.color.app_window_background, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onCreatePreferences from PreferenceFragmentCompat */
|
/* Overrides onCreatePreferences from PreferenceFragmentCompat */
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
|
||||||
|
@ -130,7 +116,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceDeviceID.setIcon(R.drawable.ic_smartphone_24dp)
|
preferenceDeviceID.setIcon(R.drawable.ic_smartphone_24dp)
|
||||||
preferenceDeviceID.key = Keys.PREF_DEVICE_ID
|
preferenceDeviceID.key = Keys.PREF_DEVICE_ID
|
||||||
preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + PreferencesHelper.load_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)
|
preferenceCategoryGeneral.contains(preferenceDeviceID)
|
||||||
screen.addPreference(preferenceDeviceID)
|
screen.addPreference(preferenceDeviceID)
|
||||||
|
|
||||||
|
@ -142,7 +128,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
val preferenceAppVersion: Preference = Preference(context)
|
val preferenceAppVersion: Preference = Preference(context)
|
||||||
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
preferenceAppVersion.title = getString(R.string.pref_app_version_title)
|
||||||
preferenceAppVersion.setIcon(R.drawable.ic_info_24dp)
|
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 {
|
preferenceAppVersion.setOnPreferenceClickListener {
|
||||||
// copy to clipboard
|
// copy to clipboard
|
||||||
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
val clip: ClipData = ClipData.newPlainText("simple text", preferenceAppVersion.summary)
|
||||||
|
@ -158,7 +144,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||||
preferenceScreen = screen
|
preferenceScreen = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) {
|
||||||
when (type) {
|
when (type) {
|
||||||
|
|
|
@ -14,48 +14,37 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.y20k.trackbook.core
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.location.Location
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
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 org.y20k.trackbook.helpers.iso8601_format
|
||||||
import java.io.File
|
|
||||||
import java.net.URI
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
/*
|
|
||||||
* Track data class
|
|
||||||
*/
|
|
||||||
data class Track (
|
data class Track (
|
||||||
val database: Database,
|
val database: Database,
|
||||||
val device_id: String,
|
val device_id: String,
|
||||||
val start_time: Date,
|
val start_time: Date,
|
||||||
val stop_time: Date,
|
val stop_time: Date,
|
||||||
var name: String = "",
|
var name: String = "",
|
||||||
var dequelimit: Int = 7200,
|
val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(),
|
||||||
var view_latitude: Double = Keys.DEFAULT_LATITUDE,
|
var view_latitude: Double = Keys.DEFAULT_LATITUDE,
|
||||||
var view_longitude: Double = Keys.DEFAULT_LONGITUDE,
|
var view_longitude: Double = Keys.DEFAULT_LONGITUDE,
|
||||||
var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION,
|
||||||
val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(dequelimit),
|
|
||||||
var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
var zoomLevel: Double = Keys.DEFAULT_ZOOM_LEVEL,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
fun delete()
|
fun delete()
|
||||||
{
|
{
|
||||||
|
Log.i("VOUSSOIR", "Track.delete.")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete_suspended(context: Context)
|
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?
|
fun export_gpx(context: Context, fileuri: Uri): Uri?
|
||||||
{
|
{
|
||||||
if (! database.ready)
|
if (! database.ready)
|
||||||
|
@ -153,7 +136,8 @@ data class Track (
|
||||||
stats.min_altitude = trkpt.altitude
|
stats.min_altitude = trkpt.altitude
|
||||||
continue
|
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
|
val ascentdiff = trkpt.altitude - previous.altitude
|
||||||
if (ascentdiff > 0)
|
if (ascentdiff > 0)
|
||||||
{
|
{
|
||||||
|
@ -171,28 +155,23 @@ data class Track (
|
||||||
{
|
{
|
||||||
stats.min_altitude = trkpt.altitude
|
stats.min_altitude = trkpt.altitude
|
||||||
}
|
}
|
||||||
|
previous = trkpt
|
||||||
last = trkpt
|
last = trkpt
|
||||||
}
|
}
|
||||||
if (first == null || last == null)
|
if (first == null || last == null)
|
||||||
{
|
{
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
stats.duration = last.time.time - first.time.time
|
stats.duration = last.time - first.time
|
||||||
stats.velocity = stats.distance / stats.duration
|
stats.velocity = stats.distance / stats.duration
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trkpt_generator() = iterator<Trkpt>
|
fun trkpt_generator() = iterator<Trkpt>
|
||||||
{
|
{
|
||||||
val cursor: Cursor = database.connection.query(
|
val cursor: Cursor = database.connection.rawQuery(
|
||||||
"trkpt",
|
"SELECT lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND time > ? AND time < ? ORDER BY time ASC",
|
||||||
arrayOf("lat", "lon", "time", "ele", "accuracy", "sat"),
|
arrayOf(device_id, start_time.time.toString(), stop_time.time.toString())
|
||||||
"device_id = ? AND time > ? AND time < ?",
|
|
||||||
arrayOf(device_id, iso8601(start_time), iso8601(stop_time)),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"time ASC",
|
|
||||||
null,
|
|
||||||
)
|
)
|
||||||
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
||||||
val COLUMN_LON = cursor.getColumnIndex("lon")
|
val COLUMN_LON = cursor.getColumnIndex("lon")
|
||||||
|
@ -204,14 +183,13 @@ data class Track (
|
||||||
{
|
{
|
||||||
while (cursor.moveToNext())
|
while (cursor.moveToNext())
|
||||||
{
|
{
|
||||||
val trkpt: Trkpt = Trkpt(
|
val trkpt = Trkpt(
|
||||||
provider="",
|
provider="",
|
||||||
latitude=cursor.getDouble(COLUMN_LAT),
|
latitude=cursor.getDouble(COLUMN_LAT),
|
||||||
longitude=cursor.getDouble(COLUMN_LON),
|
longitude=cursor.getDouble(COLUMN_LON),
|
||||||
altitude=cursor.getDouble(COLUMN_ELE),
|
altitude=cursor.getDouble(COLUMN_ELE),
|
||||||
accuracy=cursor.getFloat(COLUMN_ACCURACY),
|
accuracy=cursor.getFloat(COLUMN_ACCURACY),
|
||||||
time=iso8601_format.parse(cursor.getString(COLUMN_TIME)),
|
time=cursor.getLong(COLUMN_TIME),
|
||||||
distanceToStartingPoint=0F,
|
|
||||||
numberSatellites=cursor.getInt(COLUMN_SAT),
|
numberSatellites=cursor.getInt(COLUMN_SAT),
|
||||||
)
|
)
|
||||||
yield(trkpt)
|
yield(trkpt)
|
|
@ -14,15 +14,12 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
|
|
||||||
import YesNoDialog
|
import YesNoDialog
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -32,23 +29,18 @@ import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.fragment.app.Fragment
|
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.dialogs.RenameTrackDialog
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
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.helpers.iso8601_format
|
||||||
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
import org.y20k.trackbook.ui.TrackFragmentLayoutHolder
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener, MapOverlayHelper.MarkerListener {
|
class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDialog.YesNoDialogListener
|
||||||
|
{
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackFragment::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private lateinit var layout: TrackFragmentLayoutHolder
|
private lateinit var layout: TrackFragmentLayoutHolder
|
||||||
|
|
||||||
|
@ -58,12 +50,13 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
val database: Database = (requireActivity().applicationContext as Trackbook).database
|
val database: Database = (requireActivity().applicationContext as Trackbook).database
|
||||||
val track: Track = Track(
|
val track: Track = Track(
|
||||||
database=database,
|
database=database,
|
||||||
|
name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""),
|
||||||
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
||||||
start_time= iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!),
|
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)!!),
|
stop_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!),
|
||||||
)
|
)
|
||||||
track.load_trkpts()
|
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
|
// set up share button
|
||||||
layout.shareButton.setOnClickListener {
|
layout.shareButton.setOnClickListener {
|
||||||
|
@ -101,11 +94,9 @@ class TrackFragment : Fragment(), RenameTrackDialog.RenameTrackListener, YesNoDi
|
||||||
layout.saveViewStateToTrack()
|
layout.saveViewStateToTrack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Register the ActivityResultLauncher for saving GPX */
|
/* Register the ActivityResultLauncher for saving GPX */
|
||||||
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
|
private val requestSaveGpxLauncher = registerForActivityResult(StartActivityForResult(), this::requestSaveGpxResult)
|
||||||
|
|
||||||
|
|
||||||
private fun requestSaveGpxResult(result: ActivityResult)
|
private fun requestSaveGpxResult(result: ActivityResult)
|
||||||
{
|
{
|
||||||
if (result.resultCode != Activity.RESULT_OK || result.data == null)
|
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 */
|
/* Opens up a file picker to select the save location */
|
||||||
private fun openSaveGpxDialog()
|
private fun openSaveGpxDialog()
|
||||||
{
|
{
|
||||||
val context = this.activity as Context
|
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 {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = Keys.MIME_TYPE_GPX
|
type = Keys.MIME_TYPE_GPX
|
||||||
|
|
|
@ -14,27 +14,21 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.color.DynamicColors
|
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.AppThemeHelper
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper.initPreferences
|
import org.y20k.trackbook.helpers.PreferencesHelper.initPreferences
|
||||||
import org.y20k.trackbook.helpers.iso8601
|
|
||||||
import org.y20k.trackbook.helpers.iso8601_format
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -54,17 +48,25 @@ class Trackbook(): Application() {
|
||||||
// set Dark / Light theme state
|
// set Dark / Light theme state
|
||||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
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()}")
|
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()
|
fun load_homepoints()
|
||||||
{
|
{
|
||||||
|
Log.i("VOUSSOIR", "Trackbook.load_homepoints")
|
||||||
this.homepoints.clear()
|
this.homepoints.clear()
|
||||||
homepoint_generator().forEach { homepoint -> this.homepoints.add(homepoint) }
|
homepoint_generator().forEach { homepoint -> this.homepoints.add(homepoint) }
|
||||||
}
|
}
|
||||||
|
@ -73,18 +75,14 @@ class Trackbook(): Application() {
|
||||||
{
|
{
|
||||||
if (! database.ready)
|
if (! database.ready)
|
||||||
{
|
{
|
||||||
|
Log.i("VOUSSOIR", "Trackbook.homepoint_generator: database is not ready.")
|
||||||
return@iterator
|
return@iterator
|
||||||
}
|
}
|
||||||
val cursor: Cursor = database.connection.query(
|
val cursor: Cursor = database.connection.rawQuery(
|
||||||
"homepoints",
|
"SELECT lat, lon, radius, name FROM homepoints",
|
||||||
arrayOf("lat", "lon", "radius", "name"),
|
arrayOf()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
)
|
)
|
||||||
|
Log.i("VOUSSOIR", "Trackbook.homepoint_generator: Got ${cursor.count} homepoints.")
|
||||||
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
val COLUMN_LAT = cursor.getColumnIndex("lat")
|
||||||
val COLUMN_LON = cursor.getColumnIndex("lon")
|
val COLUMN_LON = cursor.getColumnIndex("lon")
|
||||||
val COLUMN_RADIUS = cursor.getColumnIndex("radius")
|
val COLUMN_RADIUS = cursor.getColumnIndex("radius")
|
||||||
|
|
|
@ -37,11 +37,7 @@ import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlinx.coroutines.Runnable
|
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 org.y20k.trackbook.helpers.*
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TrackerService class
|
* TrackerService class
|
||||||
|
@ -58,7 +54,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
var useImperial: Boolean = false
|
var useImperial: Boolean = false
|
||||||
var gpsOnly: Boolean = false
|
var gpsOnly: Boolean = false
|
||||||
var omitRests: Boolean = true
|
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 recording_started: Date = GregorianCalendar.getInstance().time
|
||||||
var commitInterval: Int = Keys.COMMIT_INTERVAL
|
var commitInterval: Int = Keys.COMMIT_INTERVAL
|
||||||
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
var currentBestLocation: Location = LocationHelper.getDefaultLocation()
|
||||||
|
@ -150,21 +146,11 @@ class TrackerService: Service(), SensorEventListener
|
||||||
LogHelper.v(TAG, "Added Network location listener.")
|
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
|
private fun createLocationListener(): LocationListener
|
||||||
{
|
{
|
||||||
return object : LocationListener {
|
return object : LocationListener {
|
||||||
override fun onLocationChanged(location: Location) {
|
override fun onLocationChanged(location: Location)
|
||||||
// update currentBestLocation if a better location is available
|
{
|
||||||
if (LocationHelper.isBetterLocation(location, currentBestLocation)) {
|
if (LocationHelper.isBetterLocation(location, currentBestLocation)) {
|
||||||
currentBestLocation = location
|
currentBestLocation = location
|
||||||
}
|
}
|
||||||
|
@ -173,26 +159,16 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
LogHelper.v(TAG, "onProviderEnabled $provider")
|
LogHelper.v(TAG, "onProviderEnabled $provider")
|
||||||
when (provider) {
|
when (provider) {
|
||||||
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(
|
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
|
||||||
locationManager
|
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
|
||||||
)
|
|
||||||
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
|
|
||||||
LocationHelper.isNetworkEnabled(
|
|
||||||
locationManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onProviderDisabled(provider: String)
|
override fun onProviderDisabled(provider: String)
|
||||||
{
|
{
|
||||||
LogHelper.v(TAG, "onProviderDisabled $provider")
|
LogHelper.v(TAG, "onProviderDisabled $provider")
|
||||||
when (provider) {
|
when (provider) {
|
||||||
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(
|
LocationManager.GPS_PROVIDER -> gpsProviderActive = LocationHelper.isGpsEnabled(locationManager)
|
||||||
locationManager
|
LocationManager.NETWORK_PROVIDER -> networkProviderActive = LocationHelper.isNetworkEnabled(locationManager)
|
||||||
)
|
|
||||||
LocationManager.NETWORK_PROVIDER -> networkProviderActive =
|
|
||||||
LocationHelper.isNetworkEnabled(
|
|
||||||
locationManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
|
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
|
||||||
|
@ -278,7 +254,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
|
|
||||||
/* Overrides onSensorChanged from SensorEventListener */
|
/* Overrides onSensorChanged from SensorEventListener */
|
||||||
override fun onSensorChanged(sensorEvent: SensorEvent?) {
|
override fun onSensorChanged(sensorEvent: SensorEvent?) {
|
||||||
var steps: Float = 0f
|
var steps = 0f
|
||||||
if (sensorEvent != null) {
|
if (sensorEvent != null) {
|
||||||
if (stepCountOffset == 0f) {
|
if (stepCountOffset == 0f) {
|
||||||
// store steps previously recorded by the system
|
// store steps previously recorded by the system
|
||||||
|
@ -454,10 +430,10 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
for (homepoint in trackbook.homepoints)
|
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}.")
|
Log.i("VOUSSOIR", "Omitting due to homepoint ${homepoint.name}.")
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (track.trkpts.isEmpty())
|
if (track.trkpts.isEmpty())
|
||||||
|
@ -478,16 +454,15 @@ class TrackerService: Service(), SensorEventListener
|
||||||
{
|
{
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val now: Date = GregorianCalendar.getInstance().time
|
val now: Date = GregorianCalendar.getInstance().time
|
||||||
val nowstr: String = iso8601(now)
|
|
||||||
val trkpt: Trkpt = Trkpt(location=currentBestLocation)
|
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)))
|
if (should_keep_point((currentBestLocation)))
|
||||||
{
|
{
|
||||||
val values = ContentValues().apply {
|
val values = ContentValues().apply {
|
||||||
put("device_id", device_id)
|
put("device_id", device_id)
|
||||||
put("lat", trkpt.latitude)
|
put("lat", trkpt.latitude)
|
||||||
put("lon", trkpt.longitude)
|
put("lon", trkpt.longitude)
|
||||||
put("time", nowstr)
|
put("time", now.time)
|
||||||
put("accuracy", trkpt.accuracy)
|
put("accuracy", trkpt.accuracy)
|
||||||
put("sat", trkpt.numberSatellites)
|
put("sat", trkpt.numberSatellites)
|
||||||
put("ele", trkpt.altitude)
|
put("ele", trkpt.altitude)
|
||||||
|
@ -499,7 +474,7 @@ class TrackerService: Service(), SensorEventListener
|
||||||
}
|
}
|
||||||
trackbook.database.connection.insert("trkpt", null, values)
|
trackbook.database.connection.insert("trkpt", null, values)
|
||||||
track.trkpts.add(trkpt)
|
track.trkpts.add(trkpt)
|
||||||
if (track.trkpts.size > track.dequelimit)
|
while (track.trkpts.size > 7200)
|
||||||
{
|
{
|
||||||
track.trkpts.removeFirst()
|
track.trkpts.removeFirst()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -26,7 +25,6 @@ import android.service.quicksettings.TileService
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.PreferencesHelper
|
import org.y20k.trackbook.helpers.PreferencesHelper
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TrackingToggleTileService class
|
* TrackingToggleTileService class
|
||||||
*/
|
*/
|
||||||
|
@ -35,13 +33,11 @@ class TrackingToggleTileService: TileService() {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
|
private val TAG: String = LogHelper.makeLogTag(TrackingToggleTileService::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private var bound: Boolean = false
|
private var bound: Boolean = false
|
||||||
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
|
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
|
||||||
private lateinit var trackerService: TrackerService
|
private lateinit var trackerService: TrackerService
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onTileAdded from TileService */
|
/* Overrides onTileAdded from TileService */
|
||||||
override fun onTileAdded() {
|
override fun onTileAdded() {
|
||||||
super.onTileAdded()
|
super.onTileAdded()
|
||||||
|
@ -56,7 +52,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
super.onTileRemoved()
|
super.onTileRemoved()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStartListening from TileService (tile becomes visible) */
|
/* Overrides onStartListening from TileService (tile becomes visible) */
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
@ -68,7 +63,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onClick from TileService */
|
/* Overrides onClick from TileService */
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
|
@ -78,7 +72,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onStopListening from TileService (tile no longer visible) */
|
/* Overrides onStopListening from TileService (tile no longer visible) */
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
super.onStopListening()
|
super.onStopListening()
|
||||||
|
@ -86,13 +79,11 @@ class TrackingToggleTileService: TileService() {
|
||||||
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onDestroy from Service */
|
/* Overrides onDestroy from Service */
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Update quick settings tile */
|
/* Update quick settings tile */
|
||||||
private fun updateTile() {
|
private fun updateTile() {
|
||||||
val tile: Tile = qsTile
|
val tile: Tile = qsTile
|
||||||
|
@ -112,7 +103,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
tile.updateTile()
|
tile.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Start tracking */
|
/* Start tracking */
|
||||||
private fun startTracking() {
|
private fun startTracking() {
|
||||||
val intent = Intent(application, TrackerService::class.java)
|
val intent = Intent(application, TrackerService::class.java)
|
||||||
|
@ -125,7 +115,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Stop tracking */
|
/* Stop tracking */
|
||||||
private fun stopTracking() {
|
private fun stopTracking() {
|
||||||
val intent = Intent(application, TrackerService::class.java)
|
val intent = Intent(application, TrackerService::class.java)
|
||||||
|
@ -133,7 +122,6 @@ class TrackingToggleTileService: TileService() {
|
||||||
application.startService(intent)
|
application.startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Defines the listener for changes in shared preferences
|
* Defines the listener for changes in shared preferences
|
||||||
*/
|
*/
|
||||||
|
@ -151,5 +139,4 @@ class TrackingToggleTileService: TileService() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook
|
package org.y20k.trackbook
|
||||||
|
|
||||||
import YesNoDialog
|
import YesNoDialog
|
||||||
|
@ -34,13 +33,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import org.y20k.trackbook.core.Track
|
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
import org.y20k.trackbook.helpers.UiHelper
|
import org.y20k.trackbook.helpers.UiHelper
|
||||||
import org.y20k.trackbook.helpers.iso8601_format
|
import org.y20k.trackbook.helpers.iso8601_format
|
||||||
import org.y20k.trackbook.tracklist.TracklistAdapter
|
import org.y20k.trackbook.tracklist.TracklistAdapter
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TracklistFragment class
|
* TracklistFragment class
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,33 +14,26 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.y20k.trackbook
|
||||||
package org.y20k.trackbook.core
|
|
||||||
|
|
||||||
import android.location.Location
|
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 org.y20k.trackbook.helpers.LocationHelper
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WayPoint data class
|
* WayPoint data class
|
||||||
*/
|
*/
|
||||||
@Keep
|
data class Trkpt(
|
||||||
@Parcelize
|
val provider: String,
|
||||||
data class Trkpt(@Expose val provider: String,
|
val latitude: Double,
|
||||||
@Expose val latitude: Double,
|
val longitude: Double,
|
||||||
@Expose val longitude: Double,
|
val altitude: Double,
|
||||||
@Expose val altitude: Double,
|
val accuracy: Float,
|
||||||
@Expose val accuracy: Float,
|
val time: Long,
|
||||||
@Expose val time: Date,
|
val numberSatellites: Int = 0,
|
||||||
@Expose val distanceToStartingPoint: Float = 0f,
|
var starred: Boolean = false
|
||||||
@Expose val numberSatellites: Int = 0,
|
)
|
||||||
@Expose var isStopOver: Boolean = false,
|
{
|
||||||
@Expose var starred: Boolean = false): Parcelable {
|
|
||||||
|
|
||||||
/* Constructor using just Location */
|
/* Constructor using just Location */
|
||||||
constructor(location: Location) : this (
|
constructor(location: Location) : this (
|
||||||
provider=location.provider.toString(),
|
provider=location.provider.toString(),
|
||||||
|
@ -48,19 +41,18 @@ data class Trkpt(@Expose val provider: String,
|
||||||
longitude=location.longitude,
|
longitude=location.longitude,
|
||||||
altitude=location.altitude,
|
altitude=location.altitude,
|
||||||
accuracy=location.accuracy,
|
accuracy=location.accuracy,
|
||||||
time=Date(location.time),
|
time=location.time,
|
||||||
distanceToStartingPoint=0F,
|
|
||||||
numberSatellites=LocationHelper.getNumberOfSatellites(location),
|
numberSatellites=LocationHelper.getNumberOfSatellites(location),
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Converts WayPoint into Location */
|
/* Converts WayPoint into Location */
|
||||||
fun toLocation(): Location {
|
fun toLocation(): Location {
|
||||||
val location: Location = Location(provider)
|
val location = Location(provider)
|
||||||
location.latitude = latitude
|
location.latitude = latitude
|
||||||
location.longitude = longitude
|
location.longitude = longitude
|
||||||
location.altitude = altitude
|
location.altitude = altitude
|
||||||
location.accuracy = accuracy
|
location.accuracy = accuracy
|
||||||
location.time = this.time.time
|
location.time = this.time
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.dialogs
|
package org.y20k.trackbook.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -29,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ErrorDialog object
|
* ErrorDialog object
|
||||||
*/
|
*/
|
||||||
|
@ -38,7 +36,6 @@ object ErrorDialog {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(ErrorDialog::class.java)
|
private val TAG: String = LogHelper.makeLogTag(ErrorDialog::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context, errorTitle: Int, errorMessage: Int, errorDetails: String = String()) {
|
fun show(context: Context, errorTitle: Int, errorMessage: Int, errorDetails: String = String()) {
|
||||||
// prepare dialog builder
|
// prepare dialog builder
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.dialogs
|
package org.y20k.trackbook.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -26,7 +25,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.helpers.LogHelper
|
import org.y20k.trackbook.helpers.LogHelper
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* RenameTrackDialog class
|
* RenameTrackDialog class
|
||||||
*/
|
*/
|
||||||
|
@ -41,7 +39,6 @@ class RenameTrackDialog (private var renameTrackListener: RenameTrackListener) {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG = LogHelper.makeLogTag(RenameTrackDialog::class.java.simpleName)
|
private val TAG = LogHelper.makeLogTag(RenameTrackDialog::class.java.simpleName)
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context, trackName: String) {
|
fun show(context: Context, trackName: String) {
|
||||||
// prepare dialog builder
|
// prepare dialog builder
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
|
@ -32,11 +31,9 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName)
|
private val TAG = LogHelper.makeLogTag(YesNoDialog::class.java.simpleName)
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog - variant: message from string */
|
/* Construct and show dialog - variant: message from string */
|
||||||
fun show(context: Context,
|
fun show(context: Context,
|
||||||
type: Int,
|
type: Int,
|
||||||
|
@ -50,7 +47,6 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
|
||||||
show(context, type, title, context.getString(message), yesButton, noButton, payload, payloadString)
|
show(context, type, title, context.getString(message), yesButton, noButton, payload, payloadString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Construct and show dialog */
|
/* Construct and show dialog */
|
||||||
fun show(context: Context,
|
fun show(context: Context,
|
||||||
type: Int,
|
type: Int,
|
||||||
|
@ -70,7 +66,6 @@ class YesNoDialog (private var yesNoDialogListener: YesNoDialogListener) {
|
||||||
builder.setTitle(context.getString(title))
|
builder.setTitle(context.getString(title))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// add yes button
|
// add yes button
|
||||||
builder.setPositiveButton(yesButton) { _, _ ->
|
builder.setPositiveButton(yesButton) { _, _ ->
|
||||||
// listen for click on yes button
|
// listen for click on yes button
|
||||||
|
|
|
@ -14,16 +14,12 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.extensions
|
package org.y20k.trackbook.extensions
|
||||||
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
|
||||||
/* Puts a Double value in SharedPreferences */
|
/* Puts a Double value in SharedPreferences */
|
||||||
fun SharedPreferences.Editor.putDouble(key: String, double: Double) = putLong(key, java.lang.Double.doubleToRawLongBits(double))
|
fun SharedPreferences.Editor.putDouble(key: String, double: Double) = putLong(key, java.lang.Double.doubleToRawLongBits(double))
|
||||||
|
|
||||||
|
|
||||||
/* gets a Double value from SharedPreferences */
|
/* gets a Double value from SharedPreferences */
|
||||||
fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default)))
|
fun SharedPreferences.getDouble(key: String, default: Double) = java.lang.Double.longBitsToDouble(getLong(key, java.lang.Double.doubleToRawLongBits(default)))
|
|
@ -1,22 +1,24 @@
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
import java.lang.Math.abs
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
|
val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
|
||||||
|
private val RNG = SecureRandom()
|
||||||
|
|
||||||
fun iso8601(datetime: Date): String
|
fun iso8601(datetime: Date): String
|
||||||
{
|
{
|
||||||
return iso8601_format.format(datetime)
|
return iso8601_format.format(datetime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun random_long(): Long
|
|
||||||
{
|
|
||||||
return (Random.nextBits(31).toLong() shl 32) + Random.nextBits(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun random_int(): Int
|
fun random_int(): Int
|
||||||
{
|
{
|
||||||
return Random.nextBits(31)
|
return abs(RNG.nextInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun random_device_id(): String
|
||||||
|
{
|
||||||
|
return "myphone" + random_int()
|
||||||
}
|
}
|
|
@ -14,18 +14,14 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AppThemeHelper object
|
* AppThemeHelper object
|
||||||
*/
|
*/
|
||||||
|
@ -34,7 +30,6 @@ object AppThemeHelper {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(AppThemeHelper::class.java)
|
private val TAG: String = LogHelper.makeLogTag(AppThemeHelper::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Sets app theme */
|
/* Sets app theme */
|
||||||
fun setTheme(nightModeState: String) {
|
fun setTheme(nightModeState: String) {
|
||||||
when (nightModeState) {
|
when (nightModeState) {
|
||||||
|
@ -67,14 +62,12 @@ object AppThemeHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Return weather Night Mode is on, or not */
|
/* Return weather Night Mode is on, or not */
|
||||||
fun isDarkModeOn(context: Context): Boolean {
|
fun isDarkModeOn(context: Context): Boolean {
|
||||||
val nightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
val nightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||||
return nightMode == Configuration.UI_MODE_NIGHT_YES
|
return nightMode == Configuration.UI_MODE_NIGHT_YES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Returns a readable String for currently selected App Theme */
|
/* Returns a readable String for currently selected App Theme */
|
||||||
fun getCurrentTheme(context: Context): String {
|
fun getCurrentTheme(context: Context): String {
|
||||||
return when (PreferencesHelper.loadThemeSelection()) {
|
return when (PreferencesHelper.loadThemeSelection()) {
|
||||||
|
@ -83,22 +76,4 @@ object AppThemeHelper {
|
||||||
else -> context.getString(R.string.pref_theme_selection_mode_device_default)
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -26,7 +25,6 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DateTimeHelper object
|
* DateTimeHelper object
|
||||||
*/
|
*/
|
||||||
|
@ -81,26 +79,22 @@ object DateTimeHelper {
|
||||||
return timeString
|
return timeString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Create sortable string for date - used for filenames */
|
/* Create sortable string for date - used for filenames */
|
||||||
fun convertToSortableDateString(date: Date): String {
|
fun convertToSortableDateString(date: Date): String {
|
||||||
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US)
|
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US)
|
||||||
return dateFormat.format(date)
|
return dateFormat.format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates a readable string for date - used in the UI */
|
/* Creates a readable string for date - used in the UI */
|
||||||
fun convertToReadableDate(date: Date, dateStyle: Int = DateFormat.LONG): String {
|
fun convertToReadableDate(date: Date, dateStyle: Int = DateFormat.LONG): String {
|
||||||
return DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)
|
return DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Creates a readable string date and time - used in the UI */
|
/* Creates a readable string date and time - used in the UI */
|
||||||
fun convertToReadableDateAndTime(date: Date, dateStyle: Int = DateFormat.SHORT, timeStyle: Int = DateFormat.SHORT): String {
|
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)}"
|
return "${DateFormat.getDateInstance(dateStyle, Locale.getDefault()).format(date)} ${DateFormat.getTimeInstance(timeStyle, Locale.getDefault()).format(date)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Calculates time difference between two locations */
|
/* Calculates time difference between two locations */
|
||||||
fun calculateTimeDistance(previousLocation: Location?, location: Location): Long {
|
fun calculateTimeDistance(previousLocation: Location?, location: Location): Long {
|
||||||
var timeDifference: Long = 0L
|
var timeDifference: Long = 0L
|
||||||
|
@ -112,5 +106,4 @@ object DateTimeHelper {
|
||||||
return timeDifference
|
return timeDifference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
@ -22,19 +21,16 @@ import java.math.RoundingMode
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* LengthUnitHelper object
|
* LengthUnitHelper object
|
||||||
*/
|
*/
|
||||||
object LengthUnitHelper {
|
object LengthUnitHelper {
|
||||||
|
|
||||||
|
|
||||||
/* Converts for the given unit system a distance value to a readable string */
|
/* Converts for the given unit system a distance value to a readable string */
|
||||||
fun convertDistanceToString(distance: Float, useImperial: Boolean = false): String {
|
fun convertDistanceToString(distance: Float, useImperial: Boolean = false): String {
|
||||||
return convertDistanceToString(distance.toDouble(), useImperial)
|
return convertDistanceToString(distance.toDouble(), useImperial)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Converts for the given unit system a distance value to a readable string */
|
/* Converts for the given unit system a distance value to a readable string */
|
||||||
fun convertDistanceToString(distance: Double, useImperial: Boolean = false): String {
|
fun convertDistanceToString(distance: Double, useImperial: Boolean = false): String {
|
||||||
val readableDistance: Double
|
val readableDistance: Double
|
||||||
|
@ -85,7 +81,6 @@ object LengthUnitHelper {
|
||||||
return "${numberFormat.format(readableDistance)} $unit"
|
return "${numberFormat.format(readableDistance)} $unit"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Determines which unit system the device is using (metric or imperial) */
|
/* Determines which unit system the device is using (metric or imperial) */
|
||||||
fun useImperialUnits(): Boolean {
|
fun useImperialUnits(): Boolean {
|
||||||
// America (US), Liberia (LR), Myanmar(MM) use the imperial system
|
// America (US), Liberia (LR), Myanmar(MM) use the imperial system
|
||||||
|
@ -94,7 +89,6 @@ object LengthUnitHelper {
|
||||||
return imperialSystemCountries.contains(countryCode)
|
return imperialSystemCountries.contains(countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
/* Converts for the given unit System distance and duration values to a readable velocity string */
|
||||||
fun convertToVelocityString(velocity: Double, useImperialUnits: Boolean = false) : String {
|
fun convertToVelocityString(velocity: Double, useImperialUnits: Boolean = false) : String {
|
||||||
var speed: String = "0"
|
var speed: String = "0"
|
||||||
|
@ -114,7 +108,6 @@ object LengthUnitHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Coverts meters per second to either km/h or mph */
|
/* Coverts meters per second to either km/h or mph */
|
||||||
fun convertMetersPerSecond(metersPerSecond: Double, useImperial: Boolean = false): Double {
|
fun convertMetersPerSecond(metersPerSecond: Double, useImperial: Boolean = false): Double {
|
||||||
if (useImperial) {
|
if (useImperial) {
|
||||||
|
@ -126,5 +119,4 @@ object LengthUnitHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
@ -26,11 +25,8 @@ import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.core.Track
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Keys object
|
* Keys object
|
||||||
*/
|
*/
|
||||||
|
@ -39,7 +35,6 @@ object LocationHelper {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(LocationHelper::class.java)
|
private val TAG: String = LogHelper.makeLogTag(LocationHelper::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Get default location */
|
/* Get default location */
|
||||||
fun getDefaultLocation(): Location {
|
fun getDefaultLocation(): Location {
|
||||||
val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
|
val defaultLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
|
||||||
|
@ -68,9 +63,9 @@ object LocationHelper {
|
||||||
return lastKnownLocation
|
return lastKnownLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Determines whether one location reading is better than the current location fix */
|
/* 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
|
// Credit: https://developer.android.com/guide/topics/location/strategies.html#BestEstimate
|
||||||
|
|
||||||
if (currentBestLocation == null) {
|
if (currentBestLocation == null) {
|
||||||
|
@ -110,15 +105,18 @@ object LocationHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checks if GPS location provider is available and enabled */
|
/* Checks if GPS location provider is available and enabled */
|
||||||
fun isGpsEnabled(locationManager: LocationManager): Boolean {
|
fun isGpsEnabled(locationManager: LocationManager): Boolean
|
||||||
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER)) {
|
{
|
||||||
|
if (locationManager.allProviders.contains(LocationManager.GPS_PROVIDER))
|
||||||
|
{
|
||||||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if Network location provider is available and enabled */
|
/* Checks if Network location provider is available and enabled */
|
||||||
fun isNetworkEnabled(locationManager: LocationManager): Boolean {
|
fun isNetworkEnabled(locationManager: LocationManager): Boolean {
|
||||||
if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
|
if (locationManager.allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
|
||||||
|
@ -129,14 +127,12 @@ object LocationHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Checks if given location is new */
|
/* Checks if given location is new */
|
||||||
fun isRecentEnough(location: Location): Boolean {
|
fun isRecentEnough(location: Location): Boolean {
|
||||||
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
|
val locationAge: Long = SystemClock.elapsedRealtimeNanos() - location.elapsedRealtimeNanos
|
||||||
return locationAge < Keys.DEFAULT_THRESHOLD_LOCATION_AGE
|
return locationAge < Keys.DEFAULT_THRESHOLD_LOCATION_AGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Checks if given location is accurate */
|
/* Checks if given location is accurate */
|
||||||
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
|
fun isAccurateEnough(location: Location, locationAccuracyThreshold: Int): Boolean {
|
||||||
val isAccurate: 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 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 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 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
|
// With 1*accuracyDelta we have 68% confidence that the points are
|
||||||
// different. We can multiply this number to increase confidence but
|
// different. We can multiply this number to increase confidence but
|
||||||
|
@ -174,18 +170,6 @@ object LocationHelper {
|
||||||
return distance > accuracyDelta
|
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 */
|
/* Get number of satellites from Location extras */
|
||||||
fun getNumberOfSatellites(location: Location): Int {
|
fun getNumberOfSatellites(location: Location): Int {
|
||||||
val numberOfSatellites: Int
|
val numberOfSatellites: Int
|
||||||
|
@ -198,5 +182,4 @@ object LocationHelper {
|
||||||
return numberOfSatellites
|
return numberOfSatellites
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,11 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.y20k.trackbook.BuildConfig
|
import org.y20k.trackbook.BuildConfig
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* LogHelper object
|
* LogHelper object
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,113 +14,58 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import org.osmdroid.api.IGeoPoint
|
import org.osmdroid.api.IGeoPoint
|
||||||
import org.osmdroid.util.GeoPoint
|
import org.osmdroid.util.GeoPoint
|
||||||
|
import org.osmdroid.views.MapView
|
||||||
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
||||||
import org.osmdroid.views.overlay.OverlayItem
|
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.LabelledGeoPoint
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme
|
import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme
|
||||||
|
import org.y20k.trackbook.Homepoint
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.Track
|
||||||
import org.y20k.trackbook.core.Trkpt
|
import org.y20k.trackbook.Trkpt
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
//private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
|
|
||||||
/* Creates icon overlay for current position (used in MapFragment) */
|
/* Creates icon overlay for current position (used in MapFragment) */
|
||||||
fun createMyLocationOverlay(context: Context, location: Location, trackingState: Int): ItemizedIconOverlay<OverlayItem>
|
fun createMyLocationOverlay(context: Context, map_view: MapView, currentPositionOverlay: ItemizedIconOverlay<OverlayItem>, location: Location, trackingState: Int)
|
||||||
{
|
{
|
||||||
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 */
|
/* Creates icon overlay for track */
|
||||||
fun createTrackOverlay(context: Context, track: Track, trackingState: Int): SimpleFastPointOverlay
|
fun createTrackOverlay(context: Context, map_view: MapView, track: Track, trackingState: Int)
|
||||||
{
|
{
|
||||||
// get marker color
|
// get marker color
|
||||||
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red)
|
val color = if (trackingState == Keys.STATE_TRACKING_ACTIVE) context.getColor(R.color.default_red) else context.getColor(R.color.default_blue)
|
||||||
else context.getColor(R.color.default_blue)
|
|
||||||
// gather points for overlay
|
// gather points for overlay
|
||||||
val points: MutableList<IGeoPoint> = mutableListOf()
|
val points: MutableList<IGeoPoint> = mutableListOf()
|
||||||
track.trkpts.forEach { wayPoint ->
|
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})"
|
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
|
// only add normal points
|
||||||
if (!wayPoint.starred && !wayPoint.isStopOver) {
|
if (!wayPoint.starred)
|
||||||
|
{
|
||||||
points.add(LabelledGeoPoint(wayPoint.latitude, wayPoint.longitude, wayPoint.altitude, label))
|
points.add(LabelledGeoPoint(wayPoint.latitude, wayPoint.longitude, wayPoint.altitude, label))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,22 +82,13 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
.setPointStyle(style)
|
.setPointStyle(style)
|
||||||
.setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp)
|
.setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp)
|
||||||
.setIsClickable(true)
|
.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.
|
.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(pointTheme, overlayOptions)
|
||||||
val overlay: SimpleFastPointOverlay = SimpleFastPointOverlay(pointTheme, overlayOptions)
|
map_view.overlays.add(overlay)
|
||||||
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 */
|
/* Creates overlay containing start, stop, stopover and starred markers for track */
|
||||||
fun createSpecialMakersTrackOverlay(context: Context, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false): ItemizedIconOverlay<OverlayItem>
|
fun createSpecialMakersTrackOverlay(context: Context, map_view: MapView, track: Track, trackingState: Int, displayStartEndMarker: Boolean = false)
|
||||||
{
|
{
|
||||||
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
|
||||||
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
|
val trackingActive: Boolean = trackingState == Keys.STATE_TRACKING_ACTIVE
|
||||||
|
@ -162,51 +98,46 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
var overlayItem: OverlayItem? = null
|
var overlayItem: OverlayItem? = null
|
||||||
if (!trackingActive && index == 0 && displayStartEndMarker && trkpt.starred)
|
if (!trackingActive && index == 0 && displayStartEndMarker && trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
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)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_starred_blue_48dp)!!)
|
||||||
}
|
}
|
||||||
else if (!trackingActive && index == 0 && displayStartEndMarker && !trkpt.starred)
|
else if (!trackingActive && index == 0 && displayStartEndMarker && !trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
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)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_blue_48dp)!!)
|
||||||
}
|
}
|
||||||
else if (!trackingActive && index == maxIndex && displayStartEndMarker && trkpt.starred)
|
else if (!trackingActive && index == maxIndex && displayStartEndMarker && trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
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)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_starred_blue_48dp)!!)
|
||||||
}
|
}
|
||||||
else if (!trackingActive && index == maxIndex && displayStartEndMarker && !trkpt.starred)
|
else if (!trackingActive && index == maxIndex && displayStartEndMarker && !trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
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)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_blue_48dp)!!)
|
||||||
}
|
}
|
||||||
else if (!trackingActive && trkpt.starred)
|
else if (!trackingActive && trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
|
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_blue_24dp)!!)
|
||||||
}
|
}
|
||||||
else if (trackingActive && trkpt.starred)
|
else if (trackingActive && trkpt.starred)
|
||||||
{
|
{
|
||||||
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time.time)
|
overlayItem = createOverlayItem(context, trkpt.latitude, trkpt.longitude, trkpt.accuracy, trkpt.provider, trkpt.time)
|
||||||
overlayItem.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_star_red_24dp)!!)
|
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)
|
if (overlayItem != null)
|
||||||
{
|
{
|
||||||
overlayItems.add(overlayItem)
|
overlayItems.add(overlayItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create and return overlay for current position
|
map_view.overlays.add(createOverlay(context, overlayItems))
|
||||||
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 {
|
|
||||||
|
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 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_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 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})"
|
||||||
|
@ -216,25 +147,19 @@ class MapOverlayHelper (private var markerListener: MarkerListener) {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Creates an overlay */
|
fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>): ItemizedIconOverlay<OverlayItem>
|
||||||
private fun createOverlay(context: Context, overlayItems: ArrayList<OverlayItem>, enableStarring: Boolean): ItemizedIconOverlay<OverlayItem> {
|
{
|
||||||
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
return ItemizedIconOverlay<OverlayItem>(context, overlayItems,
|
||||||
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
|
object : ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
|
||||||
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
|
override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean {
|
||||||
if (enableStarring) {
|
|
||||||
markerListener.onMarkerTapped(item.point.latitude, item.point.longitude)
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
|
override fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
|
||||||
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
v.vibrate(50)
|
v.vibrate(50)
|
||||||
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, item.snippet, Toast.LENGTH_LONG).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
|
@ -30,7 +29,6 @@ import org.y20k.trackbook.MainActivity
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.TrackerService
|
import org.y20k.trackbook.TrackerService
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NotificationHelper class
|
* NotificationHelper class
|
||||||
*/
|
*/
|
||||||
|
@ -39,11 +37,9 @@ class NotificationHelper(private val trackerService: TrackerService) {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(NotificationHelper::class.java)
|
private val TAG: String = LogHelper.makeLogTag(NotificationHelper::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private val notificationManager: NotificationManager = trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
private val notificationManager: NotificationManager = trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
|
||||||
/* Creates notification */
|
/* Creates notification */
|
||||||
fun createNotification(trackingState: Int, timestamp: String): Notification {
|
fun createNotification(trackingState: Int, timestamp: String): Notification {
|
||||||
|
|
||||||
|
|
|
@ -46,16 +46,23 @@ object PreferencesHelper {
|
||||||
|
|
||||||
fun load_device_id(): String
|
fun load_device_id(): String
|
||||||
{
|
{
|
||||||
val v = sharedPreferences.getString(Keys.PREF_DEVICE_ID, random_int().toString()).toString();
|
val fallback = random_device_id()
|
||||||
Log.i("VOUSSOIR", "Loaded device_id ${v}.")
|
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
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadZoomLevel(): Double {
|
fun loadZoomLevel(): Double
|
||||||
|
{
|
||||||
return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL)
|
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) }
|
sharedPreferences.edit { putDouble(Keys.PREF_MAP_ZOOM_LEVEL, zoomLevel) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers
|
package org.y20k.trackbook.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -31,7 +30,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.tracklist.TracklistAdapter
|
import org.y20k.trackbook.tracklist.TracklistAdapter
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UiHelper object
|
* UiHelper object
|
||||||
*/
|
*/
|
||||||
|
@ -40,7 +38,6 @@ object UiHelper {
|
||||||
/* Define log tag */
|
/* Define log tag */
|
||||||
private val TAG: String = LogHelper.makeLogTag(UiHelper::class.java)
|
private val TAG: String = LogHelper.makeLogTag(UiHelper::class.java)
|
||||||
|
|
||||||
|
|
||||||
/* Sets layout margins for given view in DP */
|
/* 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) {
|
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
|
val scalingFactor: Float = context.resources.displayMetrics.density
|
||||||
|
@ -55,7 +52,6 @@ object UiHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Sets layout margins for given view in percent */
|
/* 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) {
|
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()
|
val l: Int = ((width / 100.0f) * left).toInt()
|
||||||
|
@ -65,7 +61,6 @@ object UiHelper {
|
||||||
setViewMargins(context, view, l, r, t, b)
|
setViewMargins(context, view, l, r, t, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Get the height of the system's top status bar */
|
/* Get the height of the system's top status bar */
|
||||||
fun getStatusBarHeight(context: Context): Int {
|
fun getStatusBarHeight(context: Context): Int {
|
||||||
var result: Int = 0
|
var result: Int = 0
|
||||||
|
@ -76,14 +71,12 @@ object UiHelper {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Get scaling factor from display density */
|
/* Get scaling factor from display density */
|
||||||
fun getDensityScalingFactor(context: Context): Float {
|
fun getDensityScalingFactor(context: Context): Float {
|
||||||
return context.resources.displayMetrics.density
|
return context.resources.displayMetrics.density
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Inner class: Callback that detects a left swipe
|
* 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
|
* Credit: https://github.com/kitek/android-rv-swipe-delete/blob/master/app/src/main/java/pl/kitek/rvswipetodelete/SwipeToDeleteCallback.kt
|
||||||
|
|
|
@ -14,25 +14,22 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.tracklist
|
package org.y20k.trackbook.tracklist
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Database
|
import org.y20k.trackbook.Database
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.Track
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -65,27 +62,25 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val cursor: Cursor = database.connection.query(
|
val cursor: Cursor = database.connection.rawQuery(
|
||||||
"trkpt",
|
"SELECT distinct(date(time/1000, 'unixepoch', 'localtime')) as thedate, device_id FROM trkpt ORDER BY thedate DESC",
|
||||||
arrayOf("distinct strftime('%Y-%m-%d', time)", "device_id"),
|
arrayOf()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"time DESC",
|
|
||||||
null,
|
|
||||||
)
|
)
|
||||||
try
|
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())
|
while (cursor.moveToNext())
|
||||||
{
|
{
|
||||||
val start_time: Date? = df.parse(cursor.getString(0) + "T00:00:00.000")
|
val trackdate = cursor.getString(0)
|
||||||
val stop_time: Date? = df.parse(cursor.getString(0) + "T23:59:59.999")
|
val device_id = cursor.getString(1)
|
||||||
Log.i("VOUSSOIR", "TracklistAdapter prep track ${cursor.getString(0)}")
|
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)
|
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 */
|
/* Overrides onCreateViewHolder from RecyclerView.Adapter */
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
|
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)
|
return ElementTrackViewHolder(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemViewType */
|
/* Overrides getItemViewType */
|
||||||
override fun getItemViewType(position: Int): Int
|
override fun getItemViewType(position: Int): Int
|
||||||
{
|
{
|
||||||
return Keys.VIEW_TYPE_TRACK
|
return Keys.VIEW_TYPE_TRACK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides getItemCount from RecyclerView.Adapter */
|
/* Overrides getItemCount from RecyclerView.Adapter */
|
||||||
override fun getItemCount(): Int
|
override fun getItemCount(): Int
|
||||||
{
|
{
|
||||||
return tracks.size
|
return tracks.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
/* Overrides onBindViewHolder from RecyclerView.Adapter */
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
|
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 */
|
/* Get track name for given position */
|
||||||
fun getTrackName(positionInRecyclerView: Int): String
|
fun getTrackName(positionInRecyclerView: Int): String
|
||||||
{
|
{
|
||||||
|
@ -188,7 +178,6 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
|
||||||
// return trackDataString
|
// return trackDataString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Inner class: ViewHolder for a track element
|
* Inner class: ViewHolder for a track element
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.ui
|
package org.y20k.trackbook.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
@ -23,53 +22,57 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.constraintlayout.widget.Group
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textview.MaterialTextView
|
|
||||||
import org.osmdroid.api.IMapController
|
import org.osmdroid.api.IMapController
|
||||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||||
import org.osmdroid.util.GeoPoint
|
import org.osmdroid.util.GeoPoint
|
||||||
import org.osmdroid.views.MapView
|
import org.osmdroid.views.MapView
|
||||||
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
import org.osmdroid.views.overlay.ItemizedIconOverlay
|
||||||
import org.osmdroid.views.overlay.OverlayItem
|
import org.osmdroid.views.overlay.OverlayItem
|
||||||
|
import org.osmdroid.views.overlay.Polygon
|
||||||
import org.osmdroid.views.overlay.TilesOverlay
|
import org.osmdroid.views.overlay.TilesOverlay
|
||||||
import org.osmdroid.views.overlay.compass.CompassOverlay
|
import org.osmdroid.views.overlay.compass.CompassOverlay
|
||||||
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
|
import org.y20k.trackbook.Homepoint
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.Trackbook
|
import org.y20k.trackbook.Trackbook
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.Track
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MapFragmentLayoutHolder class
|
* 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) {
|
data class MapFragmentLayoutHolder(
|
||||||
|
private var context: Context,
|
||||||
/* Define log tag */
|
private var inflater: LayoutInflater,
|
||||||
private val TAG: String = LogHelper.makeLogTag(MapFragmentLayoutHolder::class.java)
|
private var container: ViewGroup?,
|
||||||
|
private var statusBarHeight: Int,
|
||||||
|
private val startLocation: Location,
|
||||||
|
private val trackingState: Int
|
||||||
|
)
|
||||||
|
{
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
val rootView: View
|
val rootView: View
|
||||||
var userInteraction: Boolean = false
|
var userInteraction: Boolean = false
|
||||||
val currentLocationButton: FloatingActionButton
|
val currentLocationButton: FloatingActionButton
|
||||||
val mainButton: ExtendedFloatingActionButton
|
val mainButton: ExtendedFloatingActionButton
|
||||||
private val mapView: MapView
|
private val mapView: MapView
|
||||||
private val homepoint_overlays: ArrayList<ItemizedIconOverlay<OverlayItem>> = ArrayList()
|
|
||||||
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
private var currentPositionOverlay: ItemizedIconOverlay<OverlayItem>
|
||||||
|
private var current_location_radius: Polygon = Polygon()
|
||||||
private var currentTrackOverlay: SimpleFastPointOverlay?
|
private var currentTrackOverlay: SimpleFastPointOverlay?
|
||||||
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
|
private var currentTrackSpecialMarkerOverlay: ItemizedIconOverlay<OverlayItem>?
|
||||||
private val useImperial: Boolean = PreferencesHelper.loadUseImperialUnits()
|
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 controller: IMapController
|
||||||
private var zoomLevel: Double
|
private var zoomLevel: Double
|
||||||
|
|
||||||
|
|
||||||
/* Init block */
|
|
||||||
init {
|
init {
|
||||||
// find views
|
// find views
|
||||||
rootView = inflater.inflate(R.layout.fragment_map, container, false)
|
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)
|
mapView.overlays.add(compassOverlay)
|
||||||
|
|
||||||
val app: Trackbook = (context.applicationContext as Trackbook)
|
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
|
// add my location overlay
|
||||||
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, startLocation, trackingState)
|
currentPositionOverlay = createOverlay(context, ArrayList<OverlayItem>())
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
mapView.overlays.add(currentPositionOverlay)
|
||||||
centerMap(startLocation)
|
centerMap(startLocation)
|
||||||
|
|
||||||
|
@ -131,7 +133,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
addInteractionListener()
|
addInteractionListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Listen for user interaction */
|
/* Listen for user interaction */
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun addInteractionListener() {
|
private fun addInteractionListener() {
|
||||||
|
@ -141,7 +142,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Set map center */
|
/* Set map center */
|
||||||
fun centerMap(location: Location, animated: Boolean = false) {
|
fun centerMap(location: Location, animated: Boolean = false) {
|
||||||
val position = GeoPoint(location.latitude, location.longitude)
|
val position = GeoPoint(location.latitude, location.longitude)
|
||||||
|
@ -152,7 +152,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
userInteraction = false
|
userInteraction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Save current best location and state of map to shared preferences */
|
/* Save current best location and state of map to shared preferences */
|
||||||
fun saveState(currentBestLocation: Location) {
|
fun saveState(currentBestLocation: Location) {
|
||||||
PreferencesHelper.saveCurrentBestLocation(currentBestLocation)
|
PreferencesHelper.saveCurrentBestLocation(currentBestLocation)
|
||||||
|
@ -161,14 +160,78 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
userInteraction = false
|
userInteraction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Mark current position on map */
|
/* Mark current position on map */
|
||||||
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED) {
|
fun markCurrentPosition(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED)
|
||||||
mapView.overlays.remove(currentPositionOverlay)
|
{
|
||||||
currentPositionOverlay = MapOverlayHelper(markerListener).createMyLocationOverlay(context, location, trackingState)
|
Log.i("VOUSSOIR", "MapFragmentLayoutHolder.markCurrentPosition")
|
||||||
mapView.overlays.add(currentPositionOverlay)
|
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 */
|
/* Overlay current track on map */
|
||||||
fun overlayCurrentTrack(track: Track, trackingState: Int) {
|
fun overlayCurrentTrack(track: Track, trackingState: Int) {
|
||||||
|
@ -179,11 +242,8 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
mapView.overlays.remove(currentTrackSpecialMarkerOverlay)
|
||||||
}
|
}
|
||||||
if (track.trkpts.isNotEmpty()) {
|
if (track.trkpts.isNotEmpty()) {
|
||||||
val mapOverlayHelper: MapOverlayHelper = MapOverlayHelper(markerListener)
|
createTrackOverlay(context, mapView, track, trackingState)
|
||||||
currentTrackOverlay = mapOverlayHelper.createTrackOverlay(context, track, trackingState)
|
createSpecialMakersTrackOverlay(context, mapView, track, trackingState)
|
||||||
currentTrackSpecialMarkerOverlay = mapOverlayHelper.createSpecialMakersTrackOverlay(context, track, trackingState)
|
|
||||||
mapView.overlays.add(currentTrackSpecialMarkerOverlay)
|
|
||||||
mapView.overlays.add(currentTrackOverlay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +266,6 @@ data class MapFragmentLayoutHolder(private var context: Context, private var mar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Toggles content and visibility of the location error snackbar */
|
/* Toggles content and visibility of the location error snackbar */
|
||||||
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
fun toggleLocationErrorBar(gpsProviderActive: Boolean, networkProviderActive: Boolean) {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
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()
|
if (locationErrorBar.isShown) locationErrorBar.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* https://github.com/osmdroid/osmdroid
|
* https://github.com/osmdroid/osmdroid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.y20k.trackbook.ui
|
package org.y20k.trackbook.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
@ -46,19 +45,17 @@ import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
||||||
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
|
||||||
import org.y20k.trackbook.Keys
|
import org.y20k.trackbook.Keys
|
||||||
import org.y20k.trackbook.R
|
import org.y20k.trackbook.R
|
||||||
import org.y20k.trackbook.core.Track
|
import org.y20k.trackbook.Track
|
||||||
import org.y20k.trackbook.core.TrackStatistics
|
import org.y20k.trackbook.TrackStatistics
|
||||||
import org.y20k.trackbook.helpers.*
|
import org.y20k.trackbook.helpers.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TrackFragmentLayoutHolder class
|
* 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 statusBarHeight: Int, private var container: ViewGroup?, var track: Track): MapListener { TODO REMOVE
|
||||||
data class TrackFragmentLayoutHolder(
|
data class TrackFragmentLayoutHolder(
|
||||||
private var context: Context,
|
private var context: Context,
|
||||||
private var markerListener: MapOverlayHelper.MarkerListener,
|
|
||||||
private var inflater: LayoutInflater,
|
private var inflater: LayoutInflater,
|
||||||
private var container: ViewGroup?,
|
private var container: ViewGroup?,
|
||||||
var track: Track
|
var track: Track
|
||||||
|
@ -71,8 +68,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
val editButton: ImageButton
|
val editButton: ImageButton
|
||||||
val trackNameView: MaterialTextView
|
val trackNameView: MaterialTextView
|
||||||
private val mapView: MapView
|
private val mapView: MapView
|
||||||
private var trackSpecialMarkersOverlay: ItemizedIconOverlay<OverlayItem>?
|
|
||||||
private var trackOverlay: SimpleFastPointOverlay?
|
|
||||||
private var controller: IMapController
|
private var controller: IMapController
|
||||||
//private var zoomLevel: Double
|
//private var zoomLevel: Double
|
||||||
private val statisticsSheetBehavior: BottomSheetBehavior<View>
|
private val statisticsSheetBehavior: BottomSheetBehavior<View>
|
||||||
|
@ -96,7 +91,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
private val trackManagementViews: Group
|
private val trackManagementViews: Group
|
||||||
private val useImperialUnits: Boolean
|
private val useImperialUnits: Boolean
|
||||||
|
|
||||||
|
|
||||||
/* Init block */
|
/* Init block */
|
||||||
init {
|
init {
|
||||||
// find views
|
// find views
|
||||||
|
@ -152,13 +146,9 @@ data class TrackFragmentLayoutHolder(
|
||||||
// compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / UiHelper.getDensityScalingFactor(context))) TODO REMOVE
|
// compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / UiHelper.getDensityScalingFactor(context))) TODO REMOVE
|
||||||
mapView.overlays.add(compassOverlay)
|
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()) {
|
if (track.trkpts.isNotEmpty()) {
|
||||||
mapView.overlays.add(trackSpecialMarkersOverlay)
|
createSpecialMakersTrackOverlay(context, mapView, track, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true)
|
||||||
mapView.overlays.add(trackOverlay)
|
createTrackOverlay(context, mapView, track, Keys.STATE_TRACKING_STOPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up and show statistics sheet
|
// set up and show statistics sheet
|
||||||
|
@ -168,25 +158,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
setupStatisticsViews()
|
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 */
|
/* Saves zoom level and center of this map */
|
||||||
fun saveViewStateToTrack()
|
fun saveViewStateToTrack()
|
||||||
{
|
{
|
||||||
|
@ -195,7 +166,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Sets up the statistics sheet */
|
/* Sets up the statistics sheet */
|
||||||
private fun setupStatisticsViews()
|
private fun setupStatisticsViews()
|
||||||
{
|
{
|
||||||
|
@ -225,7 +195,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Shows/hides the statistics sheet */
|
/* Shows/hides the statistics sheet */
|
||||||
private fun toggleStatisticsSheetVisibility() {
|
private fun toggleStatisticsSheetVisibility() {
|
||||||
when (statisticsSheetBehavior.state) {
|
when (statisticsSheetBehavior.state) {
|
||||||
|
@ -263,7 +232,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onZoom from MapListener */
|
/* Overrides onZoom from MapListener */
|
||||||
override fun onZoom(event: ZoomEvent?): Boolean {
|
override fun onZoom(event: ZoomEvent?): Boolean {
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
|
@ -274,7 +242,6 @@ data class TrackFragmentLayoutHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Overrides onScroll from MapListener */
|
/* Overrides onScroll from MapListener */
|
||||||
override fun onScroll(event: ScrollEvent?): Boolean {
|
override fun onScroll(event: ScrollEvent?): Boolean {
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:width="24dp">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.0"
|
||||||
android:fillColor="@color/default_red"
|
android:fillColor="@color/default_red"
|
||||||
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
|
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
|
||||||
<path
|
<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"/>
|
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
|
||||||
</vector>
|
</vector>
|
|
@ -3,10 +3,6 @@
|
||||||
android:viewportHeight="96.0"
|
android:viewportHeight="96.0"
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
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
|
<path
|
||||||
android:fillColor="@color/default_blue"
|
android:fillColor="@color/default_blue"
|
||||||
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
|
android:pathData="M48,48m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
|
||||||
|
|
|
@ -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>
|
|
|
@ -4,7 +4,7 @@
|
||||||
android:viewportWidth="96.0"
|
android:viewportWidth="96.0"
|
||||||
android:width="24dp">
|
android:width="24dp">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="0.33"
|
android:fillAlpha="0.0"
|
||||||
android:fillColor="@color/default_red"
|
android:fillColor="@color/default_red"
|
||||||
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
|
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
|
||||||
<path
|
<path
|
||||||
|
|
Loading…
Reference in New Issue