checkpoint

master
voussoir 2023-03-11 12:30:24 -08:00
parent ffd5fb6af3
commit 172ca703a9
14 changed files with 325 additions and 13 deletions

View File

@ -58,6 +58,7 @@ object Keys {
const val PREF_OMIT_RESTS: String = "prefOmitRests" const val PREF_OMIT_RESTS: String = "prefOmitRests"
const val PREF_COMMIT_INTERVAL: String = "prefCommitInterval" const val PREF_COMMIT_INTERVAL: String = "prefCommitInterval"
const val PREF_DEVICE_ID: String = "prefDeviceID" const val PREF_DEVICE_ID: String = "prefDeviceID"
const val PREF_DATABASE_DIRECTORY: String = "prefDatabaseDirectory"
// states // states
const val STATE_TRACKING_STOPPED: Int = 0 const val STATE_TRACKING_STOPPED: Int = 0
@ -117,6 +118,7 @@ object Keys {
const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 5_000_000_000L // 5s in nanoseconds const val DEFAULT_THRESHOLD_LOCATION_AGE: Long = 5_000_000_000L // 5s in nanoseconds
const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters const val DEFAULT_THRESHOLD_DISTANCE: Float = 15f // 15 meters
const val DEFAULT_ZOOM_LEVEL: Double = 16.0 const val DEFAULT_ZOOM_LEVEL: Double = 16.0
const val DEFAULT_OMIT_RESTS: Boolean = true
const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD = 10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded const val ALTITUDE_MEASUREMENT_ERROR_THRESHOLD = 10 // altitude changes of 10 meter or more (per 15 seconds) are being discarded
// notification // notification

View File

@ -19,6 +19,7 @@ package org.y20k.trackbook
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
@ -145,6 +146,11 @@ class MainActivity: AppCompatActivity()
Log.i("VOUSSOIR", "MainActivity: device_id has changed.") Log.i("VOUSSOIR", "MainActivity: device_id has changed.")
trackbook.load_database() trackbook.load_database()
} }
Keys.PREF_DATABASE_DIRECTORY ->
{
trackbook.load_database()
}
} }
} }

View File

@ -72,6 +72,8 @@ class MapFragment : Fragment()
lateinit var rootView: View lateinit var rootView: View
var userInteraction: Boolean = false var userInteraction: Boolean = false
lateinit var currentLocationButton: FloatingActionButton lateinit var currentLocationButton: FloatingActionButton
lateinit var zoom_in_button: FloatingActionButton
lateinit var zoom_out_button: FloatingActionButton
lateinit var mainButton: ExtendedFloatingActionButton lateinit var mainButton: ExtendedFloatingActionButton
private lateinit var mapView: MapView private lateinit var mapView: MapView
private var current_position_overlays = ArrayList<Overlay>() private var current_position_overlays = ArrayList<Overlay>()
@ -117,6 +119,8 @@ class MapFragment : Fragment()
rootView = inflater.inflate(R.layout.fragment_map, container, false) rootView = inflater.inflate(R.layout.fragment_map, container, false)
mapView = rootView.findViewById(R.id.map) mapView = rootView.findViewById(R.id.map)
currentLocationButton = rootView.findViewById(R.id.location_button) currentLocationButton = rootView.findViewById(R.id.location_button)
zoom_in_button = rootView.findViewById(R.id.zoom_in_button)
zoom_out_button = rootView.findViewById(R.id.zoom_out_button)
mainButton = rootView.findViewById(R.id.main_button) mainButton = rootView.findViewById(R.id.main_button)
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE) locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
@ -167,11 +171,17 @@ class MapFragment : Fragment()
addInteractionListener() addInteractionListener()
// set up buttons // set up buttons
mainButton.setOnClickListener {
handleTrackingManagementMenu()
}
currentLocationButton.setOnClickListener { currentLocationButton.setOnClickListener {
centerMap(currentBestLocation, animated = true) centerMap(currentBestLocation, animated = true)
} }
mainButton.setOnClickListener { zoom_in_button.setOnClickListener {
handleTrackingManagementMenu() controller.zoomTo(mapView.zoomLevelDouble + 0.5, 250)
}
zoom_out_button.setOnClickListener {
controller.zoomTo(mapView.zoomLevelDouble - 0.5, 250)
} }
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@ -481,7 +491,7 @@ class MapFragment : Fragment()
currentLocationButton.isVisible = true currentLocationButton.isVisible = true
if (! trackbook.database.ready) if (! trackbook.database.ready)
{ {
mainButton.text = "Database not ready" mainButton.text = requireContext().getString(R.string.button_not_ready)
mainButton.icon = null mainButton.icon = null
} }
else if (trackingState == Keys.STATE_TRACKING_STOPPED) else if (trackingState == Keys.STATE_TRACKING_STOPPED)

View File

@ -17,21 +17,35 @@
package org.y20k.trackbook package org.y20k.trackbook
import YesNoDialog import YesNoDialog
import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.preference.* import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.contains
import get_path_from_uri
import org.y20k.trackbook.helpers.AppThemeHelper import org.y20k.trackbook.helpers.AppThemeHelper
import org.y20k.trackbook.helpers.LengthUnitHelper import org.y20k.trackbook.helpers.LengthUnitHelper
import org.y20k.trackbook.helpers.LogHelper import org.y20k.trackbook.helpers.LogHelper
import org.y20k.trackbook.helpers.PreferencesHelper import org.y20k.trackbook.helpers.PreferencesHelper
import org.y20k.trackbook.helpers.random_device_id import org.y20k.trackbook.helpers.random_device_id
const val INTENT_DATABASE_DIRECTORY_PICKER = 12121
/* /*
* SettingsFragment class * SettingsFragment class
*/ */
@ -101,7 +115,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
screen.addPreference(preferenceThemeSelection) screen.addPreference(preferenceThemeSelection)
// set up "Recording Accuracy" preference // set up "Recording Accuracy" preference
val DEFAULT_OMIT_RESTS = true
val preferenceOmitRests: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context) val preferenceOmitRests: SwitchPreferenceCompat = SwitchPreferenceCompat(activity as Context)
preferenceOmitRests.isSingleLineTitle = false preferenceOmitRests.isSingleLineTitle = false
preferenceOmitRests.title = getString(R.string.pref_omit_rests_title) preferenceOmitRests.title = getString(R.string.pref_omit_rests_title)
@ -109,7 +122,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceOmitRests.key = Keys.PREF_OMIT_RESTS preferenceOmitRests.key = Keys.PREF_OMIT_RESTS
preferenceOmitRests.summaryOn = getString(R.string.pref_omit_rests_on) preferenceOmitRests.summaryOn = getString(R.string.pref_omit_rests_on)
preferenceOmitRests.summaryOff = getString(R.string.pref_omit_rests_off) preferenceOmitRests.summaryOff = getString(R.string.pref_omit_rests_off)
preferenceOmitRests.setDefaultValue(DEFAULT_OMIT_RESTS) preferenceOmitRests.setDefaultValue(Keys.DEFAULT_OMIT_RESTS)
preferenceCategoryGeneral.contains(preferenceOmitRests) preferenceCategoryGeneral.contains(preferenceOmitRests)
screen.addPreference(preferenceOmitRests) screen.addPreference(preferenceOmitRests)
@ -119,13 +132,56 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceDeviceID.key = Keys.PREF_DEVICE_ID preferenceDeviceID.key = Keys.PREF_DEVICE_ID
preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + PreferencesHelper.load_device_id() preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + PreferencesHelper.load_device_id()
preferenceDeviceID.setDefaultValue(random_device_id()) preferenceDeviceID.setDefaultValue(random_device_id())
preferenceCategoryGeneral.contains(preferenceDeviceID)
preferenceDeviceID.setOnPreferenceChangeListener { preference, newValue -> preferenceDeviceID.setOnPreferenceChangeListener { preference, newValue ->
preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + newValue preferenceDeviceID.summary = getString(R.string.pref_device_id_summary) + "\n" + newValue
return@setOnPreferenceChangeListener true return@setOnPreferenceChangeListener true
} }
preferenceCategoryGeneral.contains(preferenceDeviceID)
screen.addPreference(preferenceDeviceID) screen.addPreference(preferenceDeviceID)
val preferenceDatabaseFolder: Preference = Preference(context)
var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Log.i("VOUSSOIR", "I'm not dead yet.")
if (result.resultCode != Activity.RESULT_OK)
{
return@registerForActivityResult
}
if (result.data == null)
{
return@registerForActivityResult
}
if (result.data!!.data == null)
{
return@registerForActivityResult
}
val uri: Uri = result.data!!.data!!
val docUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri))
val path: String = get_path_from_uri(context, docUri) ?: ""
Log.i("VOUSSOIR", "We got " + path)
PreferencesHelper.save_database_folder(path)
preferenceDatabaseFolder.summary = (getString(R.string.pref_database_folder_summary) + "\n" + path).trim()
}
preferenceDatabaseFolder.title = "Database Directory"
preferenceDatabaseFolder.setIcon(R.drawable.ic_save_to_storage_24dp)
preferenceDatabaseFolder.key = Keys.PREF_DATABASE_DIRECTORY
preferenceDatabaseFolder.summary = (getString(R.string.pref_database_folder_summary) + "\n" + PreferencesHelper.load_database_folder()).trim()
preferenceDatabaseFolder.setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
resultLauncher.launch(intent)
}
return@setOnPreferenceClickListener true
}
preferenceDatabaseFolder.setOnPreferenceChangeListener { preference, newValue ->
preferenceDatabaseFolder.summary = "Directory to contain your database file." + "\n" + newValue
return@setOnPreferenceChangeListener true
}
preferenceCategoryGeneral.contains(preferenceDatabaseFolder)
screen.addPreference(preferenceDatabaseFolder)
val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context) val preferenceCategoryAbout: PreferenceCategory = PreferenceCategory(context)
preferenceCategoryAbout.title = getString(R.string.pref_about_title) preferenceCategoryAbout.title = getString(R.string.pref_about_title)
screen.addPreference(preferenceCategoryAbout) screen.addPreference(preferenceCategoryAbout)

View File

@ -66,9 +66,20 @@ class Trackbook(): Application() {
fun load_database() fun load_database()
{ {
Log.i("VOUSSOIR", "Trackbook.load_database") Log.i("VOUSSOIR", "Trackbook.load_database")
val folder = PreferencesHelper.load_database_folder()
this.database.commit()
if (this.database.ready)
{
this.database.close()
}
if (folder == "")
{
this.database.ready = false
return
}
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) 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.database.connect(File(folder + "/trkpt_${PreferencesHelper.load_device_id()}.db"))
this.load_homepoints() this.load_homepoints()
} }
else else
@ -77,6 +88,7 @@ class Trackbook(): Application() {
} }
this.call_database_changed_listeners() this.call_database_changed_listeners()
} }
fun load_homepoints() fun load_homepoints()
{ {
Log.i("VOUSSOIR", "Trackbook.load_homepoints") Log.i("VOUSSOIR", "Trackbook.load_homepoints")

View File

@ -59,6 +59,7 @@ class TrackerService: Service(), SensorEventListener
var commitInterval: Int = Keys.COMMIT_INTERVAL var commitInterval: Int = Keys.COMMIT_INTERVAL
var currentBestLocation: Location = getDefaultLocation() var currentBestLocation: Location = getDefaultLocation()
var lastCommit: Date = Keys.DEFAULT_DATE var lastCommit: Date = Keys.DEFAULT_DATE
var location_min_time_ms: Long = 0
var stepCountOffset: Float = 0f var stepCountOffset: Float = 0f
lateinit var track: Track lateinit var track: Track
var gpsLocationListenerRegistered: Boolean = false var gpsLocationListenerRegistered: Boolean = false
@ -98,7 +99,7 @@ class TrackerService: Service(), SensorEventListener
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
0, location_min_time_ms,
0f, 0f,
gpsLocationListener, gpsLocationListener,
) )

View File

@ -1,9 +1,18 @@
package org.y20k.trackbook.helpers package org.y20k.trackbook.helpers
import android.annotation.TargetApi
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import java.lang.Math.abs import java.lang.Math.abs
import java.security.SecureRandom import java.security.SecureRandom
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.random.Random
val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US) val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
private val RNG = SecureRandom() private val RNG = SecureRandom()

View File

@ -0,0 +1,144 @@
// Thank you @asifmujteba!
// https://gist.github.com/asifmujteba/d89ba9074bc941de1eaa
import android.annotation.TargetApi
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
@TargetApi(Build.VERSION_CODES.KITKAT)
fun get_path_from_uri(context: Context, uri: Uri): String?
{
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri))
{
// ExternalStorageProvider
if (isExternalStorageDocument(uri))
{
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true))
{
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
// TODO handle non-primary volumes
}
else if (isDownloadsDocument(uri))
{
val id = DocumentsContract.getDocumentId(uri)
val contentUri: Uri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
}
else if (isMediaDocument(uri))
{
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type)
{
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
else if ("video" == type)
{
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
else if ("audio" == type)
{
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(
split[1]
)
return getDataColumn(context, contentUri, selection, selectionArgs)
}
}
else if ("content".equals(uri.getScheme(), ignoreCase = true))
{
// Return the remote address
return if (isGooglePhotosUri(uri)) uri.getLastPathSegment()
else getDataColumn(context,
uri,
null,
null)
}
else if ("file".equals(uri.getScheme(), ignoreCase = true))
{
return uri.getPath()
}
return null
}
fun getDataColumn(
context: Context, uri: Uri?, selection: String?,
selectionArgs: Array<String>?,
): String?
{
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(
column
)
try
{
cursor = context.getContentResolver().query(uri!!, projection, selection, selectionArgs,
null)
if (cursor != null && cursor.moveToFirst())
{
val index: Int = cursor.getColumnIndexOrThrow(column)
return cursor.getString(index)
}
} finally
{
if (cursor != null) cursor.close()
}
return null
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean
{
return "com.android.externalstorage.documents" == uri.getAuthority()
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean
{
return "com.android.providers.downloads.documents" == uri.getAuthority()
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean
{
return "com.android.providers.media.documents" == uri.getAuthority()
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean
{
return "com.google.android.apps.photos.content" == uri.getAuthority()
}

View File

@ -71,7 +71,7 @@ fun createTrackOverlay(context: Context, map_view: MapView, track: Track, tracki
.setPointStyle(style) .setPointStyle(style)
.setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp) .setRadius(6F * scalingFactor) // radius is set in px - scaling factor makes that display density independent (= dp)
.setIsClickable(true) .setIsClickable(true)
.setCellSize(15) // Sets the grid cell size used for indexing, in pixels. Larger cells result in faster rendering speed, but worse fidelity. Default is 10 pixels, for large datasets (>10k points), use 15. .setCellSize(12) // Sets the grid cell size used for indexing, in pixels. Larger cells result in faster rendering speed, but worse fidelity. Default is 10 pixels, for large datasets (>10k points), use 15.
val overlay = SimpleFastPointOverlay(pointTheme, overlayOptions) val overlay = SimpleFastPointOverlay(pointTheme, overlayOptions)
map_view.overlays.add(overlay) map_view.overlays.add(overlay)
} }

View File

@ -56,6 +56,16 @@ object PreferencesHelper {
return v return v
} }
fun load_database_folder(): String
{
return sharedPreferences.getString(Keys.PREF_DATABASE_DIRECTORY, "") ?: ""
}
fun save_database_folder(path: String)
{
sharedPreferences.edit { putString(Keys.PREF_DATABASE_DIRECTORY, path) }
}
fun loadZoomLevel(): Double fun loadZoomLevel(): Double
{ {
return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL) return sharedPreferences.getDouble(Keys.PREF_MAP_ZOOM_LEVEL, Keys.DEFAULT_ZOOM_LEVEL)

View File

@ -0,0 +1,16 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="@color/location_button_background"
android:pathData="M 1.98 11 L 22.02 11 L 22.02 13 L 1.98 13 Z"/>
<path
android:name="path_1"
android:fillColor="@color/location_button_background"
android:pathData="M 11 22.02 L 11 1.98 L 13 1.98 L 13 22.02 Z"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="@color/location_button_background"
android:pathData="M 1.98 11 L 22.02 11 L 22.02 13 L 1.98 13 Z"/>
</vector>

View File

@ -58,6 +58,36 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/location_button_icon" /> app:tint="@color/location_button_icon" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/zoom_out_button"
style="@style/Widget.MaterialComponents.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/descr_button_zoom_out"
android:src="@drawable/ic_zoom_out_24dp"
app:backgroundTint="@color/location_button_background"
app:fabSize="mini"
app:layout_constraintBottom_toTopOf="@+id/location_button"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/location_button_icon" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/zoom_in_button"
style="@style/Widget.MaterialComponents.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/descr_button_zoom_in"
android:src="@drawable/ic_zoom_in_24dp"
app:backgroundTint="@color/location_button_background"
app:fabSize="mini"
app:layout_constraintBottom_toTopOf="@+id/zoom_out_button"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/location_button_icon" />
<!-- GROUPS --> <!-- GROUPS -->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -23,6 +23,7 @@
<string name="button_pause">Stop</string> <string name="button_pause">Stop</string>
<string name="button_save">Save</string> <string name="button_save">Save</string>
<string name="button_start">Record</string> <string name="button_start">Record</string>
<string name="button_not_ready">Database not set up.</string>
<!-- Dialogs --> <!-- Dialogs -->
<string name="dialog_delete_current_recording_message">Discard current recording?</string> <string name="dialog_delete_current_recording_message">Discard current recording?</string>
<string name="dialog_delete_current_recording_button_discard">Discard</string> <string name="dialog_delete_current_recording_button_discard">Discard</string>
@ -84,7 +85,8 @@
<string name="pref_altitude_smoothing_value_summary" translatable="false">Number of waypoints used to smooth the elevation curve.</string> <string name="pref_altitude_smoothing_value_summary" translatable="false">Number of waypoints used to smooth the elevation curve.</string>
<string name="pref_altitude_smoothing_value_title" translatable="false">Altitude Smoothing</string> <string name="pref_altitude_smoothing_value_title" translatable="false">Altitude Smoothing</string>
<string name="pref_auto_export_interval_summary">Automatically export GPX file after this many hours.</string> <string name="pref_auto_export_interval_summary">Automatically export GPX file after this many hours.</string>
<string name="pref_device_id_summary">A unique ID to distinguish tracks recorded across multiple devices.</string> <string name="pref_device_id_summary">A unique ID to distinguish tracks recorded across multiple devices:</string>
<string name="pref_database_folder_summary">Directory to contain your database file:</string>
<string name="pref_auto_export_interval_title">Auto Export Interval</string> <string name="pref_auto_export_interval_title">Auto Export Interval</string>
<string name="pref_device_id">Device ID</string> <string name="pref_device_id">Device ID</string>
<string name="pref_advanced_title">Advanced</string> <string name="pref_advanced_title">Advanced</string>
@ -117,6 +119,8 @@
<!-- Descriptions --> <!-- Descriptions -->
<string name="descr_button_delete">Discard recording</string> <string name="descr_button_delete">Discard recording</string>
<string name="descr_button_location">Center on current location</string> <string name="descr_button_location">Center on current location</string>
<string name="descr_button_zoom_out">Zoom out</string>
<string name="descr_button_zoom_in">Zoom in</string>
<string name="descr_button_pause">Stop recording</string> <string name="descr_button_pause">Stop recording</string>
<string name="descr_button_resume">Resume recording</string> <string name="descr_button_resume">Resume recording</string>
<string name="descr_button_save">Save recording</string> <string name="descr_button_save">Save recording</string>