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