Compare commits

..

19 Commits

Author SHA1 Message Date
Dennis Guse 306522863a Release v3.5.0 2020-03-21 23:18:57 +01:00
Dennis Guse e54d85e04d ChartFragment: unregister from preference changes on pause. 2020-03-21 20:54:49 +01:00
Dennis Guse cb30ad27c3 Removed that tracks store count of the locations. 2020-03-20 19:44:31 +01:00
Dennis Guse 8685c9cc88 Merge pull request #162 from rgmf/issue#89
Share pictures from marker.
2020-03-18 22:08:42 +01:00
Román a6f03d0f23 Share pictures from marker.
Fixes #89.
2020-03-18 19:33:57 +01:00
Dennis Guse e3c812b155 TrackDeleteActivity now has a transparant background. 2020-03-18 07:43:21 +01:00
Dennis Guse f149de9ce3 Merge pull request #167 from rgmf/issue#163
Add android:key in PreferenceCategory with expandable.
2020-03-17 21:54:08 +01:00
Dennis Guse 3447dd2569 Merge pull request #165 from OpenTracksApp/dbCleanup
Database cleanup and optimization
2020-03-17 21:44:28 +01:00
Dennis Guse b2b8c7fd54 Database upgrade (incl. tests): removed unused columns and added indices.
Fixes #108.
2020-03-17 21:39:10 +01:00
Román 486bcd0aec Add android:key in PreferenceCategory with expandable.
Fixes #163.
2020-03-17 10:57:04 +01:00
Dennis Guse c8a33201f5 Moved annoucement text generation into util class. 2020-03-16 21:43:23 +01:00
Dennis Guse 4a37c2cc7e Cleanup. 2020-03-16 21:43:23 +01:00
Dennis Guse d23db5bafd Charts use preferred units and rate.
Fixes #150.
2020-03-16 21:43:23 +01:00
Dennis Guse ac528f9a10 Use trackname for sharing files.
Fixes #130.
2020-03-16 21:43:23 +01:00
Dennis Guse 256eadd49d Added some french translation provided by @edern-appere.
Fixes #147.
2020-03-16 21:43:23 +01:00
Dennis Guse 64e58934a5 Settings: fix label for elevation (removed "Grade" as it is not shown anymore). 2020-03-16 21:43:23 +01:00
Dennis Guse 92dfadfdba Remove elevation min/max from UI as GPS-derived data often lead to invalid data (especially initial fix phase).
Fixes #148.
2020-03-16 21:43:23 +01:00
Román cbce70143b All marker management is now in MarkerEditActivity (including take photo and added gallery image to the marker).
Issue 122.
2020-03-11 19:59:15 +01:00
Román f4b9e3159e New screenshots for F-Droid/PlayStore. Fixes #132. 2020-03-11 19:40:57 +01:00
105 changed files with 921 additions and 633 deletions
-5
View File
@@ -45,19 +45,16 @@ captures/
# IntelliJ IDEA
*.iml
*.iws
/out/
# User-specific configurations
.idea/.name
.idea/assetWizardSettings.xml
.idea/caches/
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/dataSources.ids
.idea/datasources.xml
.idea/dictionaries
.idea/dynamic.xml
.idea/encodings.xml
.idea/jsLibraryMappings.xml
@@ -67,10 +64,8 @@ captures/
.idea/scopes/scope_settings.xml
.idea/shelf/
.idea/sqlDataSources.xml
.idea/tasks.xml
.idea/vcs.xml
.idea/uiDesigner.xml
.idea/workspace.xml
# Keystore files
*.jks
@@ -0,0 +1,14 @@
v3.5.0: OpenTracks
Changes:
- Marker: select pictures from gallery
- Marker: share picture
- Use track name as filename when sharing
- Removed elevation min/max (was imprecise)
Bugfix:
- Charts use preferred units and rate
Developer:
- Database: remove unused columns
- Database: add indices
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

@@ -19,7 +19,6 @@ public class TestDataUtil {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
for (int i = 0; i < numPoints; i++) {
track.addLocation(createLocation(i));
}
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.content.provider;
import android.content.ContentUris;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
@@ -38,30 +37,15 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
*/
public class CustomContentProviderTest {
private static final String DATABASE_NAME = "test.db";
private SQLiteDatabase db;
private CustomContentProvider customContentProvider;
private Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase();
customContentProvider = new CustomContentProvider() {
};
}
/**
* Tests {@link CustomSQLiteOpenHelper#onCreate(SQLiteDatabase)}.
*/
@Test
public void testDatabaseHelper_OnCreate() {
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME));
}
/**
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
*/
@@ -84,70 +68,4 @@ public class CustomContentProviderTest {
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
}
/**
* Creates a table, containing one column.
*
* @param table the table name
*/
private void createTable(String table) {
db.execSQL("CREATE TABLE " + table + " (test INTEGER)");
}
/**
* Drops a table in database.
*
* @param table the table name
*/
private void dropTable(String table) {
db.execSQL("Drop TABLE " + table);
}
/**
* Returns true if the table exists.
*
* @param table the table name
*/
private boolean hasTable(String table) {
try {
db.rawQuery("select count(*) from " + table, null);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Returns true if the column in the table exists.
*
* @param table the table name
* @param column the column name
*/
private boolean hasColumn(String table, String column) {
try {
db.execSQL("SElECT count(*) from " + table + " order by " + column);
} catch (Exception e) {
if (e.getMessage().contains("no such column")) {
return false;
}
}
return true;
}
/**
* Sets up upgrade.
*
* @param oldVersion thd old database version
*/
private void setupUpgrade(int oldVersion) {
dropTable(TracksColumns.TABLE_NAME);
dropTable(TrackPointsColumns.TABLE_NAME);
dropTable(WaypointsColumns.TABLE_NAME);
createTable(TracksColumns.TABLE_NAME);
createTable(TrackPointsColumns.TABLE_NAME);
createTable(WaypointsColumns.TABLE_NAME);
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
databaseHelper.onUpgrade(db, oldVersion, CustomSQLiteOpenHelper.DATABASE_VERSION);
}
}
@@ -135,7 +135,7 @@ public class CustomContentProviderUtilsTest {
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, TrackPointFactory trackPointFactory) {
long lastPointId = initializeTrack(trackId, numPoints);
((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, trackPointFactory)) {
while (it.hasNext()) {
@@ -155,7 +155,6 @@ public class CustomContentProviderUtilsTest {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
@@ -183,7 +182,6 @@ public class CustomContentProviderUtilsTest {
}
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
Assert.assertEquals(numPoints, track.getNumberOfPoints());
Assert.assertEquals(numPoints, counter);
return lastPointId;
@@ -616,8 +614,8 @@ public class CustomContentProviderUtilsTest {
when(cursorMock.getBlob(index++)).thenReturn(sensor);
Location location = contentProviderUtils.createTrackPoint(cursorMock);
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
Assert.assertEquals(time, location.getTime(), 0.01);
Assert.assertEquals(speed, location.getSpeed(), 0.01);
}
@@ -661,7 +659,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getLocations().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
@@ -680,7 +678,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getLocations().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
@@ -699,7 +697,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getLocations().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
@@ -726,7 +724,7 @@ public class CustomContentProviderUtilsTest {
Track track = TestDataUtil.getTrack(trackId, 10);
contentProviderUtils.insertTrack(track);
long[] trackpointIds = new long[track.getNumberOfPoints()];
long[] trackpointIds = new long[track.getLocations().size()];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
}
@@ -0,0 +1,179 @@
package de.dennisguse.opentracks.content.provider;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Map;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
@RunWith(AndroidJUnit4.class)
public class CustomSQLiteOpenHelperTest {
private static final String TRACKS_CREATE_TABLE_V23 = "CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)";
private static final String TRACKPOINTS_CREATE_TABLE_V23 = "CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT)";
private static final String WAYPOINTS_CREATE_TABLE_V23 = "CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)";
private static final String DATABASE_NAME = "test.db";
private Context context = ApplicationProvider.getApplicationContext();
/**
* Get the SQL create statements for all tables (ordered by name).
*
* @return Map(TableName, SQL)
*/
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
HashMap<String, String> tableSQL = new HashMap<>();
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
if (cursor != null) {
while (cursor.moveToNext()) {
tableSQL.put(cursor.getString(0), cursor.getString(1));
}
}
}
return tableSQL;
}
/**
* Get the SQL create statements for all indices (ordered by name).
*
* @return Map(tableName, SQL)
*/
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
HashMap<String, String> indexSQL = new HashMap<>();
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
indexSQL.put(cursor.getString(0), cursor.getString(1));
}
}
}
return indexSQL;
}
/**
* Returns true if a SQL create statement is in the schema.
*
* @param sqlCreate the table name
*/
private static boolean hasSqlCreate(SQLiteDatabase db, String sqlCreate) {
try (Cursor cursor = db.rawQuery("SELECT SQL FROM sqlite_master", null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
String sql = cursor.getString(0);
if (sqlCreate.equals(sql)) {
return true;
}
}
}
}
return false;
}
@Before
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
}
@Test
public void onCreate() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
}
}
@Test
public void onUpgrade_FromVersion23() {
// Manually create database schema with version 23 (base version)
{
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}.getWritableDatabase();
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
dbBase.close();
}
// Open database with SQL upgrade
Map<String, String> tableByUpgrade;
Map<String, String> indicesByUpgrade;
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tableByUpgrade = getTableSQL(dbUpgraded);
indicesByUpgrade = getIndexSQL(dbUpgraded);
}
// Open database via
context.deleteDatabase(DATABASE_NAME);
Map<String, String> tablesByCreate;
Map<String, String> indicesByCreate;
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
tablesByCreate = getTableSQL(dbCreated);
indicesByCreate = getIndexSQL(dbCreated);
}
// then - verify table structure
Assert.assertEquals(3, tableByUpgrade.size());
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size());
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
// then - verify custom indices
Assert.assertEquals(2, indicesByCreate.size());
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
}
@Test
public void onDowngrade_ToVersion23() {
// Create most recent database schema
new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase().close();
// Downgrade schema to version 23 (base version)
Map<String, String> tablesByDowngrade;
Map<String, String> indicesByDowngrade;
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
tablesByDowngrade = getTableSQL(db);
indicesByDowngrade = getIndexSQL(db);
}
// then - verify table structure
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
// then - verify custom indices
Assert.assertEquals(0, indicesByDowngrade.size());
}
}
@@ -0,0 +1,28 @@
package de.dennisguse.opentracks.content.provider;
import android.net.Uri;
import android.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
public class ShareContentProviderTest {
@Test
public void testCreateandParseURI() {
long[] trackIds = {1, 3, 5};
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
}
@Test
public void testCreateURIescapeFilename() {
long[] trackIds = {1};
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
}
}
@@ -0,0 +1,34 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.stats.TripStatistics;
@RunWith(AndroidJUnit4.class)
public class AnnouncementUtilsTest {
private Context context = ApplicationProvider.getApplicationContext();
@Test
public void getAnnouncement() {
TripStatistics stats = new TripStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
// then
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
}
}
+5 -3
View File
@@ -16,8 +16,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.dennisguse.opentracks"
android:installLocation="auto"
android:versionCode="3287"
android:versionName="v3.4.0"> <!-- For FDroid. -->
android:versionCode="3305"
android:versionName="v3.5.0"> <!-- For FDroid. -->
<!-- Permission to initialize services -->
<application
android:name=".Startup"
@@ -31,7 +31,9 @@ limitations under the License.
<!-- Activities -->
<activity android:name=".AboutActivity" />
<activity android:name=".TrackDeleteActivity" />
<activity
android:name=".TrackDeleteActivity"
android:theme="@style/ThemeCustomTransparent" />
<activity
android:name=".HelpActivity"
@@ -16,18 +16,38 @@
package de.dennisguse.opentracks;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* An activity to add/edit a marker.
@@ -39,16 +59,27 @@ public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final int CAMERA_REQUEST_CODE = 5;
private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId;
private long markerId;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Waypoint waypoint;
private MenuItem insertPhotoMenuItem;
private MenuItem insertGalleryImgMenuItem;
private Uri photoUri;
private boolean hasCamera;
// UI elements
private EditText waypointName;
private AutoCompleteTextView waypointMarkerType;
private EditText waypointDescription;
private ImageView waypointPhoto;
private ImageView waypointDeletePhotoBtn;
private Button done;
@Override
@@ -59,12 +90,27 @@ public class MarkerEditActivity extends AbstractActivity {
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
// Setup UI elements
waypointName = findViewById(R.id.marker_edit_waypoint_name);
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
waypointMarkerType.setAdapter(adapter);
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (waypoint != null && waypoint.hasPhoto())
waypoint.setPhotoUrl(null);
waypointPhoto.setImageBitmap(null);
photoUri = null;
hideAndShowOptions();
}
});
Button cancel = findViewById(R.id.marker_edit_cancel);
cancel.setOnClickListener(new OnClickListener() {
@@ -94,6 +140,64 @@ public class MarkerEditActivity extends AbstractActivity {
return R.layout.marker_edit;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_edit, menu);
insertPhotoMenuItem = menu.findItem(R.id.marker_edit_insert_photo);
insertPhotoMenuItem.setVisible(hasCamera);
insertGalleryImgMenuItem = menu.findItem(R.id.marker_edit_insert_gallery_img);
hideAndShowOptions();
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.marker_edit_insert_photo:
createWaypointWithPicture();
return true;
case R.id.marker_edit_insert_gallery_img:
createWaypointWithGalleryImage();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
setWaypointImageView(photoUri);
}
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
Uri srcUri = data.getData();
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
FileDescriptor srcFd = parcelFd.getFileDescriptor();
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
FileUtils.copy(srcFd, dstFile);
photoUri = FileUtils.getUriForFile(this, dstFile);
setWaypointImageView(photoUri);
} catch(Exception e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
return;
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Updates the UI based on the marker id.
*/
@@ -133,7 +237,62 @@ public class MarkerEditActivity extends AbstractActivity {
waypointName.setText(waypoint.getName());
waypointMarkerType.setText(waypoint.getCategory());
waypointDescription.setText(waypoint.getDescription());
if (waypoint.hasPhoto()) {
photoUri = waypoint.getPhotoURI();
setWaypointImageView(photoUri);
}
}
hideAndShowOptions();
}
/**
* Checks and hide/shows all buttons/options about marker photo options.
*
* If a photo is set then one's options are shown, otherwise another ones are shown.
*/
private void hideAndShowOptions() {
boolean isPhotoSet = (waypoint != null && waypoint.hasPhoto()) || photoUri != null;
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
insertPhotoMenuItem.setVisible(isPhotoSet ? false : true);
insertGalleryImgMenuItem.setVisible(isPhotoSet ? false : true);
}
waypointDeletePhotoBtn.setVisibility(isPhotoSet ? View.VISIBLE : View.GONE);
}
/**
* Returns the trackId. If trackId == -1 then get it from waypoint object.
*/
private long getTrackId() {
return trackId == -1 ? waypoint.getTrackId() : trackId;
}
/**
* Sets the ImageView waypointPhoto with the uri photo.
*
* @param uri the uri photo.
*/
private void setWaypointImageView(Uri uri) {
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
FileDescriptor fd = pfd.getFileDescriptor();
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
waypointPhoto.setImageBitmap(bitmap);
hideAndShowOptions();
} catch(IOException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
}
}
private void createWaypointWithPicture() {
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
photoUri = intentAndPhotoUri.second;
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
}
private void createWaypointWithGalleryImage() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
}
/**
@@ -144,7 +303,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointName.getText().toString(),
waypointMarkerType.getText().toString(),
waypointDescription.getText().toString(),
null);
photoUri != null ? photoUri.toString() : null);
}
/**
@@ -154,6 +313,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypoint.setName(waypointName.getText().toString());
waypoint.setCategory(waypointMarkerType.getText().toString());
waypoint.setDescription(waypointDescription.getText().toString());
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
new ContentProviderUtils(this).updateWaypoint(waypoint);
}
@@ -61,7 +61,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils;
@@ -179,7 +179,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.STARTTIME + " IS NULL", null, null);
null, null, null);
}
}
@@ -16,25 +16,18 @@
package de.dennisguse.opentracks;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
@@ -70,16 +63,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private static final String PHOTO_URI_KEY = "photo_uri_key";
private static final String HAS_PHOTO_KEY = "has_photo_key";
private static final int CAMERA_REQUEST_CODE = 5;
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
// The following are set in onCreate
private boolean hasCamera;
private Uri photoUri;
private boolean hasPhoto;
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -102,10 +87,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
@Override
public void run() {
trackController.update(isRecording(), recordingTrackPaused);
if (hasPhoto && photoUri != null) {
hasPhoto = false;
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, null, null, null, photoUri.toString());
}
}
});
}
@@ -148,7 +129,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
};
private MenuItem insertMarkerMenuItem;
private MenuItem insertPhotoMenuItem;
private MenuItem markerListMenuItem;
private MenuItem shareMenuItem;
@@ -182,10 +162,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
super.onCreate(savedInstanceState);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
contentProviderUtils = new ContentProviderUtils(this);
handleIntent(getIntent());
@@ -307,23 +283,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
if (photoUri != null) {
outState.putParcelable(PHOTO_URI_KEY, photoUri);
}
outState.putBoolean(HAS_PHOTO_KEY, hasPhoto);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
return;
}
hasPhoto = resultCode == RESULT_OK;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
@@ -343,8 +302,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
getMenuInflater().inflate(R.menu.track_detail, menu);
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
shareMenuItem = menu.findItem(R.id.track_detail_share);
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
@@ -372,9 +329,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.track_detail_insert_photo:
createWaypointWithPicture();
return true;
case R.id.track_detail_menu_show_on_map:
IntentUtils.showTrackOnMap(this, new long[]{trackId});
return true;
@@ -400,20 +354,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
} else {
createWaypointWithPicture();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
return trackRecordingServiceConnection;
@@ -471,7 +411,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
*/
private void updateMenuItems(boolean isPaused) {
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
shareMenuItem.setVisible(!isRecording());
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
String title;
@@ -484,12 +423,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
setTitle(title);
}
private void createWaypointWithPicture() {
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, trackId);
photoUri = intentAndPhotoUri.second;
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
}
public void chooseActivityType(String category) {
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
}
@@ -295,6 +295,10 @@ public class ChartView extends View {
series[index].setEnabled(enabled);
}
public boolean getMetricUnits() {
return metricUnits;
}
/**
* Sets metric units.
*
@@ -304,6 +308,10 @@ public class ChartView extends View {
metricUnits = value;
}
public boolean getReportSpeed() {
return reportSpeed;
}
/**
* Sets report speed.
*
@@ -486,7 +494,6 @@ public class ChartView extends View {
@Override
protected void onDraw(Canvas canvas) {
synchronized (chartData) {
canvas.save();
canvas.drawColor(Color.WHITE);
@@ -38,10 +38,6 @@ public class Track {
private String description = "";
private String category = "";
/*
* The number of location points (present even if the points themselves are not loaded)
*/
private int numberOfPoints = 0;
private String icon = "";
private TripStatistics tripStatistics = new TripStatistics();
@@ -84,14 +80,6 @@ public class Track {
this.category = category;
}
public int getNumberOfPoints() {
return numberOfPoints;
}
public void setNumberOfPoints(int numberOfPoints) {
this.numberOfPoints = numberOfPoints;
}
public String getIcon() {
return icon;
}
@@ -64,5 +64,7 @@ public interface TrackPointsColumns extends BaseColumns {
+ BEARING + " FLOAT, "
+ SENSOR_HEARTRATE + " FLOAT, "
+ SENSOR_CADENCE + " FLOAT, "
+ SENSOR_POWER + " FLOAT);";
+ SENSOR_POWER + " FLOAT)";
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
}
@@ -38,34 +38,20 @@ public interface TracksColumns extends BaseColumns {
String NAME = "name"; // track name
String DESCRIPTION = "description"; // track description
String CATEGORY = "category"; // track activity type
@Deprecated
String STARTID = "startid"; // first track point id // TODO UNUSED
@Deprecated
String STOPID = "stopid"; // last track point id // TODO UNUSED
String STARTTIME = "starttime"; // track start time
String STOPTIME = "stoptime"; // track stop time
String NUMPOINTS = "numpoints"; // number of track points
@Deprecated
String NUMPOINTS = "numpoints"; // number of track points //TODO UNUSED
String TOTALDISTANCE = "totaldistance"; // total distance
String TOTALTIME = "totaltime"; // total time
String MOVINGTIME = "movingtime"; // moving time
@Deprecated
String MINLAT = "minlat"; // minimum latitude // TODO UNUSED
@Deprecated
String MAXLAT = "maxlat"; // maximum latitude // TODO UNUSED
@Deprecated
String MINLON = "minlon"; // minimum longitude //TODO UNUSED
@Deprecated
String MAXLON = "maxlon"; // maximum longitude //TODO UNUSED
String AVGSPEED = "avgspeed"; // average speed
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
String MAXSPEED = "maxspeed"; // maximum speed
String MINELEVATION = "minelevation"; // minimum elevation
String MAXELEVATION = "maxelevation"; // maximum elevation
String ELEVATIONGAIN = "elevationgain"; // elevation gain
String MINGRADE = "mingrade"; // minimum grade
String MAXGRADE = "maxgrade"; // maximum grade
String ICON = "icon"; // track activity type icon
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table
@@ -73,25 +59,17 @@ public interface TracksColumns extends BaseColumns {
+ NAME + " TEXT, " // name
+ DESCRIPTION + " TEXT, " // description
+ CATEGORY + " TEXT, " // category
+ STARTID + " INTEGER, " // start id
+ STOPID + " INTEGER, " // stop id
+ STARTTIME + " INTEGER, " // start time
+ STOPTIME + " INTEGER, " // stop time
+ NUMPOINTS + " INTEGER, " // num points
+ TOTALDISTANCE + " FLOAT, " // total distance
+ TOTALTIME + " INTEGER, " // total time
+ MOVINGTIME + " INTEGER, " // moving time
+ MINLAT + " INTEGER, " // min latitude
+ MAXLAT + " INTEGER, " // max latitude
+ MINLON + " INTEGER, " // min longitude
+ MAXLON + " INTEGER, " // max longitude
+ AVGSPEED + " FLOAT, " // average speed
+ AVGMOVINGSPEED + " FLOAT, " // average moving speed
+ MAXSPEED + " FLOAT, " // max speed
+ MINELEVATION + " FLOAT, " // min elevation
+ MAXELEVATION + " FLOAT, " // max elevation
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
+ MINGRADE + " FLOAT, " // min grade
+ MAXGRADE + " FLOAT, " // max grade
+ ICON + " TEXT);"; // icon
+ ICON + " TEXT)"; // icon
}
@@ -40,49 +40,17 @@ public interface WaypointsColumns extends BaseColumns {
String CATEGORY = "category"; // waypoint category
String ICON = "icon"; // waypoint icon
String TRACKID = "trackid"; // track id
@Deprecated
String TYPE = "type"; // type // TODO UNUSED
String LENGTH = "length"; // length of the track (without smoothing)
String DURATION = "duration"; // total duration of the track (not from last waypoint)
String STARTTIME = "starttime"; // start time of the trip statistics
@Deprecated
String STARTID = "startid"; // start track point id // TODO UNUSED
@Deprecated
String STOPID = "stopid"; // stop track point id // TODO UNUSED
String DURATION = "duration"; // total duration of the track from the beginning until now
String LONGITUDE = "longitude"; // longitude
String LATITUDE = "latitude"; // latitude
String TIME = "time"; // time
String ALTITUDE = "elevation"; // altitude
String ACCURACY = "accuracy"; // accuracy
String SPEED = "speed"; // speed
String BEARING = "bearing"; // bearing
@Deprecated
String TOTALDISTANCE = "totaldistance"; // total distance
@Deprecated
String TOTALTIME = "totaltime"; // total time
@Deprecated
String MOVINGTIME = "movingtime"; // moving time
@Deprecated
String AVGSPEED = "avgspeed"; // average speed
@Deprecated
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed // TODO UNUSED
@Deprecated
String MAXSPEED = "maxspeed"; // max speed // TODO UNUSED
@Deprecated
String MINELEVATION = "minelevation"; // min elevation // TODO UNUSED
@Deprecated
String MAXELEVATION = "maxelevation"; // max elevation // TODO UNUSED
@Deprecated
String ELEVATIONGAIN = "elevationgain"; // elevation gain // TODO UNUSED
@Deprecated
String MINGRADE = "mingrade"; // min grade // TODO UNUSED
@Deprecated
String MAXGRADE = "maxgrade"; // max grade // TODO UNUSED
String PHOTOURL = "photoUrl"; // url for the photo
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
@@ -92,30 +60,16 @@ public interface WaypointsColumns extends BaseColumns {
+ CATEGORY + " TEXT, "
+ ICON + " TEXT, "
+ TRACKID + " INTEGER, "
+ TYPE + " INTEGER, "
+ LENGTH + " FLOAT, "
+ DURATION + " INTEGER, "
+ STARTTIME + " INTEGER, "
+ STARTID + " INTEGER, "
+ STOPID + " INTEGER, "
+ LONGITUDE + " INTEGER, "
+ LATITUDE + " INTEGER, "
+ TIME + " INTEGER, "
+ ALTITUDE + " FLOAT, "
+ ACCURACY + " FLOAT, "
+ SPEED + " FLOAT, "
+ BEARING + " FLOAT, "
+ TOTALDISTANCE + " FLOAT, "
+ TOTALTIME + " INTEGER, "
+ MOVINGTIME + " INTEGER, "
+ AVGSPEED + " FLOAT, "
+ AVGMOVINGSPEED + " FLOAT, "
+ MAXSPEED + " FLOAT, "
+ MINELEVATION + " FLOAT, "
+ MAXELEVATION + " FLOAT, "
+ ELEVATIONGAIN + " FLOAT, "
+ MINGRADE + " FLOAT, "
+ MAXGRADE + " FLOAT, "
+ PHOTOURL + " TEXT"
+ ");";
+ ")";
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
}
@@ -111,7 +111,6 @@ public class ContentProviderUtils {
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
int numPointsIndex = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
@@ -119,8 +118,6 @@ public class ContentProviderUtils {
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
int minGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE);
int maxGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE);
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
Track track = new Track();
@@ -143,9 +140,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(stopTimeIndex)) {
tripStatistics.setStopTime(cursor.getLong(stopTimeIndex));
}
if (!cursor.isNull(numPointsIndex)) {
track.setNumberOfPoints(cursor.getInt(numPointsIndex));
}
if (!cursor.isNull(totalDistanceIndex)) {
tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
}
@@ -167,12 +161,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(elevationGainIndex)) {
tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
}
if (!cursor.isNull(minGradeIndex)) {
tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex));
}
if (!cursor.isNull(maxGradeIndex)) {
tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex));
}
if (!cursor.isNull(iconIndex)) {
track.setIcon(cursor.getString(iconIndex));
}
@@ -207,7 +195,6 @@ public class ContentProviderUtils {
/**
* Deletes track points and waypoints of a track.
* Assumes {@link TracksColumns#NUMPOINTS} will be updated by the caller.
*
* @param trackId the track id
*/
@@ -332,7 +319,6 @@ public class ContentProviderUtils {
values.put(TracksColumns.CATEGORY, track.getCategory());
values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime());
values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime());
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime());
values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime());
@@ -342,8 +328,6 @@ public class ContentProviderUtils {
values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation());
values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation());
values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
values.put(TracksColumns.MINGRADE, tripStatistics.getMinGrade());
values.put(TracksColumns.MAXGRADE, tripStatistics.getMaxGrade());
values.put(TracksColumns.ICON, track.getIcon());
return values;
@@ -369,7 +353,6 @@ public class ContentProviderUtils {
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
@@ -414,9 +397,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(accuracyIndex)) {
location.setAccuracy(cursor.getFloat(accuracyIndex));
}
if (!cursor.isNull(speedIndex)) {
location.setSpeed(cursor.getFloat(speedIndex));
}
if (!cursor.isNull(bearingIndex)) {
location.setBearing(cursor.getFloat(bearingIndex));
}
@@ -613,9 +593,6 @@ public class ContentProviderUtils {
if (location.hasAccuracy()) {
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
}
if (location.hasSpeed()) {
values.put(WaypointsColumns.SPEED, location.getSpeed());
}
if (location.hasBearing()) {
values.put(WaypointsColumns.BEARING, location.getBearing());
}
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.content.provider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -17,11 +18,13 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
@VisibleForTesting
static final int DATABASE_VERSION = 23;
static final int DATABASE_VERSION = 25;
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
@VisibleForTesting
static final String DATABASE_NAME = "database.db";
public CustomSQLiteOpenHelper(Context context) {
this(context, DATABASE_NAME);
}
@@ -31,14 +34,112 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
super(context, databaseName, null, DATABASE_VERSION);
}
@VisibleForTesting
public CustomSQLiteOpenHelper(Context context, String databaseName, int databaseVersion) {
super(context, databaseName, null, databaseVersion);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(TrackPointsColumns.CREATE_TABLE);
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
db.execSQL(TracksColumns.CREATE_TABLE);
db.execSQL(WaypointsColumns.CREATE_TABLE);
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
switch (toVersion) {
case 24:
upgradeFrom23to24(db);
break;
case 25:
upgradeFrom24to25(db);
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
}
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
switch (toVersion) {
case 23:
downgradeFrom24to23(db);
break;
case 24:
downgradeFrom25to24(db);
break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
}
}
/**
* Upgrade from database version 23 (waypoints, tracks): remove unused columns.
* SQLite3 does not support drop columns; therefore new tables are created and data is copied.
*/
private void upgradeFrom23to24(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT)");
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, icon FROM tracks_old");
db.execSQL("DROP TABLE tracks_old");
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, length FLOAT, duration INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT)");
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, length, duration, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM waypoints_old");
db.execSQL("DROP TABLE waypoints_old");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom24to23(SQLiteDatabase db) {
//Not needed as the delete columns did not contain any data
db.beginTransaction();
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, 0, 0, icon FROM tracks_old");
db.execSQL("DROP TABLE tracks_old");
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)");
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, 0, length, duration, 0, 0, 0, longitude, latitude, time, elevation, accuracy, 0, bearing, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, photoUrl FROM waypoints_old");
db.execSQL("DROP TABLE waypoints_old");
db.setTransactionSuccessful();
db.endTransaction();
}
private void upgradeFrom24to25(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
db.execSQL("CREATE INDEX waypoints_trackid_index ON waypoints(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom25to24(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("DROP INDEX trackpoints_trackid_index");
db.execSQL("DROP INDEX waypoints_trackid_index");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -19,6 +19,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Track;
@@ -58,30 +59,30 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
private static final String TRACKID_DELIMITER = "_";
static {
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*", URI_GPX);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*/*", URI_GPX);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*", URI_KML_ONLY);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*", URI_KML_WITH_TRACKDETAIL);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*", URI_KMZ_ONLY_TRACK);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*/*", URI_KML_ONLY);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*/*", URI_KMZ_ONLY_TRACK);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
}
public static Pair<Uri, String> createURI(long[] trackIds, @NonNull TrackFileFormat trackFileFormat) {
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.length == 0) {
throw new UnsupportedOperationException();
}
StringBuilder builder = new StringBuilder();
StringBuilder trackIdBuilder = new StringBuilder();
for (long trackId : trackIds) {
builder.append(trackId).append(TRACKID_DELIMITER);
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER);
}
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
String mime = getTypeMime(uri);
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
@@ -89,18 +90,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
return new Pair<>(uri, mime);
}
private static long[] parseURI(Uri uri) {
String lastPathSegment = uri.getLastPathSegment();
if (lastPathSegment == null) {
protected static long[] parseURI(Uri uri) {
List<String> uriPaths = uri.getPathSegments();
if (uriPaths == null || uriPaths.size() < 2) {
Log.d(TAG, "URI does not contain any trackIds.");
return new long[]{};
}
String fileExtension = "." + getTrackFileFormat(uri).getExtension();
String[] lastPathSegmentSplit = lastPathSegment.replace(fileExtension, "").split(TRACKID_DELIMITER);
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
long[] trackIds = new long[lastPathSegmentSplit.length];
long[] trackIds = new long[uriTrackIds.length];
for (int i = 0; i < trackIds.length; i++) {
trackIds[i] = Long.valueOf(lastPathSegmentSplit[i]);
trackIds[i] = Long.valueOf(uriTrackIds[i]);
}
return trackIds;
}
@@ -108,4 +108,13 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
public void remove() {
throw new UnsupportedOperationException();
}
public int getCount() {
if (cursor == null) {
return -1;
}
return cursor.getCount();
}
}
@@ -76,8 +76,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private TripStatisticsUpdater tripStatisticsUpdater;
private long startTime;
private boolean metricUnits = true;
private boolean reportSpeed = true;
private int recordingDistanceInterval;
// Modes of operation
@@ -90,13 +88,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (!isResumed()) {
return;
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
boolean metric = PreferencesUtils.isMetricUnits(getContext());
if (metricUnits != metric) {
metricUnits = metric;
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
if (metricUnits != chartView.getMetricUnits()) {
chartView.setMetricUnits(metricUnits);
runOnUiThread(new Runnable() {
@Override
@@ -109,9 +103,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
boolean speed = PreferencesUtils.isReportSpeed(getContext());
if (reportSpeed != speed) {
reportSpeed = speed;
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
if (reportSpeed != chartView.getReportSpeed()) {
chartView.setReportSpeed(reportSpeed);
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
@@ -152,7 +145,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, true);
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
@@ -178,6 +171,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
super.onResume();
resumeTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
checkChartSettings();
getActivity().runOnUiThread(updateChart);
@@ -187,7 +181,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onPause() {
super.onPause();
pauseTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
}
@Override
@@ -277,10 +271,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private void checkChartSettings() {
boolean needUpdate = false;
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartView.getReportSpeed())) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
if (setSeriesEnabled(ChartView.PACE_SERIES, !chartView.getReportSpeed())) {
needUpdate = true;
}
@@ -381,7 +375,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
if (!chartView.getMetricUnits()) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
@@ -390,12 +384,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
elevation = tripStatisticsUpdater.getSmoothedElevation();
if (!metricUnits) {
if (!chartView.getMetricUnits()) {
elevation *= UnitConversions.M_TO_FT;
}
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
if (!chartView.getMetricUnits()) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
@@ -441,12 +435,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@VisibleForTesting
void setMetricUnits(boolean value) {
metricUnits = value;
chartView.setMetricUnits(value);
}
@VisibleForTesting
void setReportSpeed(boolean value) {
reportSpeed = value;
chartView.setReportSpeed(value);
}
@VisibleForTesting
@@ -60,6 +60,8 @@ public class MarkerDetailFragment extends Fragment {
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
private MenuItem shareMarkerImageMenuItem;
private ContentProviderUtils contentProviderUtils;
private Handler handler;
private ImageView photoView;
@@ -145,6 +147,7 @@ public class MarkerDetailFragment extends Fragment {
// Need to update the waypoint in case returning after an edit
updateWaypoint(true);
updateUi();
updateMenuItems();
}
@Override
@@ -170,8 +173,14 @@ public class MarkerDetailFragment extends Fragment {
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
shareMarkerImageMenuItem = menu.findItem(R.id.marker_detail_share);
updateWaypoint(false);
updateMenuItems();
}
private void updateMenuItems() {
if (shareMarkerImageMenuItem != null)
shareMarkerImageMenuItem.setVisible(waypoint.hasPhoto());
}
@Override
@@ -188,6 +197,13 @@ public class MarkerDetailFragment extends Fragment {
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_share:
if (waypoint.hasPhoto()) {
intent = IntentUtils.newShareImageIntent(getContext(), waypoint.getPhotoURI());
intent = Intent.createChooser(intent, null);
startActivity(intent);
}
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
return true;
@@ -139,15 +139,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private TextView speedMovingUnit;
private View gradeElevationSeparator;
private View gradeElevationContainer;
private TextView elevationMinValue;
private TextView elevationMinUnit;
private TextView elevationMaxValue;
private TextView elevationMaxUnit;
private View speedContainer;
private TextView speedLabel;
private TextView speedValue;
private TextView speedUnit;
private View elevationContainer;
private TextView elevationValue;
private TextView elevationUnit;
private View coordinateSeparator;
@@ -188,18 +183,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
speedContainer = view.findViewById(R.id.stats_speed);
speedLabel = view.findViewById(R.id.stats_speed_label);
speedValue = view.findViewById(R.id.stats_speed_value);
speedUnit = view.findViewById(R.id.stats_speed_unit);
elevationContainer = view.findViewById(R.id.stats_elevation);
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
@@ -302,18 +290,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
gradeElevationSeparator = null;
gradeElevationContainer = null;
elevationMinValue = null;
elevationMinUnit = null;
elevationMaxValue = null;
elevationMaxUnit = null;
speedContainer = null;
speedLabel = null;
speedValue = null;
speedUnit = null;
elevationContainer = null;
elevationValue = null;
elevationUnit = null;
@@ -542,31 +523,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
speedMovingUnit.setText(parts.second);
}
// Set elevation
// Make elevation visible?
{
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
if (showElevation) {
{
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMinValue.setText(parts.first);
elevationMinUnit.setText(parts.second);
}
{
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
elevationMaxValue.setText(parts.first);
elevationMaxUnit.setText(parts.second);
}
}
}
}
@@ -598,7 +558,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
// Set elevation
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
@@ -161,7 +161,7 @@ public class FileTrackExporter implements TrackExporter {
// Write the current location
trackWriter.writeLocation(location);
if (trackExporterListener != null) {
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
trackExporterListener.onProgressUpdate(locationNumber, locationIterator.getCount());
}
} else {
if (wroteSegment) {
@@ -262,7 +262,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
}
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
contentProviderUtils.updateTrack(trackData.track);
}
@@ -439,7 +439,7 @@ public class TrackRecordingService extends Service {
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
// Update the recording track time
updateRecordingTrack(track, false);
updateRecordingTrack(track);
}
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTripStatistics().getStartTime());
@@ -650,7 +650,7 @@ public class TrackRecordingService extends Service {
try {
contentProviderUtils.insertTrackPoint(location, track.getId());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
updateRecordingTrack(track, LocationUtils.isValidLocation(location));
updateRecordingTrack(track);
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -663,16 +663,10 @@ public class TrackRecordingService extends Service {
/**
* Updates the recording track time.
* Increase the number of points if it is a new and valid track point.
*
* @param track the track
* @param increaseNumberOfPoints true to increase the number of points
* @param track the track
*/
private void updateRecordingTrack(Track track, boolean increaseNumberOfPoints) {
if (increaseNumberOfPoints) {
track.setNumberOfPoints(track.getNumberOfPoints() + 1);
}
private void updateRecordingTrack(Track track) {
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
contentProviderUtils.updateTrack(track);
@@ -25,12 +25,9 @@ import android.util.Log;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.util.AnnouncementUtils;
/**
* This class will periodically announce the user's trip statistics.
@@ -160,7 +157,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
Log.i(TAG, "Speech is not allowed at this time.");
return;
}
speakAnnouncement(getAnnouncement(tripStatistics));
String announcement = AnnouncementUtils.getAnnouncement(context, tripStatistics);
speakAnnouncement(announcement);
}
@Override
@@ -169,8 +167,6 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
tts.shutdown();
tts = null;
}
}
private void onTtsReady() {
@@ -196,81 +192,4 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
}
private String getAnnouncement(TripStatistics tripStatistics) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
if (distance == 0) {
return context.getString(R.string.voice_total_distance_zero);
}
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI;
}
String rate;
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
} else {
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(time));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
return context.getString(R.string.voice_template, totalDistance,
getAnnounceTime(tripStatistics.getMovingTime()), rate);
}
private String getAnnounceTime(long time) {
int[] parts = StringUtils.getTimeParts(time);
String seconds = context.getResources()
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
String minutes = context.getResources()
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
String hours = context.getResources()
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
StringBuilder sb = new StringBuilder();
if (parts[2] != 0) {
sb.append(hours);
sb.append(" ");
}
sb.append(minutes);
sb.append(" ");
sb.append(seconds);
return sb.toString();
}
/**
* Gets the plural count to be used by getQuantityString.
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
* <p>
* As a temporary workaround, we convert a double quantity to an integer quantity.
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
* Otherwise, we cast the double quantity to an integer quantity.
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
*
* @param d the double value
*/
private int getQuantityCount(double d) {
if (d == 0) {
return 0;
} else if (d == 1) {
return 1;
} else if (d == 2) {
return 2;
} else {
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
int count = (int) d;
return count < 3 ? 3 : count;
}
}
}
@@ -29,6 +29,7 @@ public class TripStatistics {
// The min and max elevation (meters) seen on this trip.
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
// The min and max grade seen on this trip.
@Deprecated //Remove grade min/max completely
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
// The trip start time. This is the system time, might not match the GPs time.
@@ -0,0 +1,88 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TripStatistics;
public class AnnouncementUtils {
private AnnouncementUtils() {}
public static String getAnnouncement(Context context, TripStatistics tripStatistics) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
if (distance == 0) {
return context.getString(R.string.voice_total_distance_zero);
}
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI;
}
String rate;
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
} else {
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(context, time));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, tripStatistics.getMovingTime()), rate);
}
public static String getAnnounceTime(Context context, long time) {
int[] parts = StringUtils.getTimeParts(time);
String seconds = context.getResources()
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
String minutes = context.getResources()
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
String hours = context.getResources()
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
StringBuilder sb = new StringBuilder();
if (parts[2] != 0) {
sb.append(hours);
sb.append(" ");
}
sb.append(minutes);
sb.append(" ");
sb.append(seconds);
return sb.toString();
}
/**
* Gets the plural count to be used by getQuantityString.
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
* <p>
* As a temporary workaround, we convert a double quantity to an integer quantity.
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
* Otherwise, we cast the double quantity to an integer quantity.
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
*
* @param d the double value
*/
public static int getQuantityCount(double d) {
if (d == 0) {
return 0;
} else if (d == 1) {
return 1;
} else if (d == 2) {
return 2;
} else {
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
int count = (int) d;
return count < 3 ? 3 : count;
}
}
}
@@ -16,11 +16,21 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import androidx.core.content.FileProvider;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import de.dennisguse.opentracks.BuildConfig;
@@ -31,6 +41,8 @@ import de.dennisguse.opentracks.BuildConfig;
*/
public class FileUtils {
private static final String TAG = FileUtils.class.getSimpleName();
/**
* Used to transfer picture from the camera.
*/
@@ -38,6 +50,8 @@ public class FileUtils {
public static final String EXPORT_DIR = "OpenTracks";
private static final String JPEG_EXTENSION = "jpeg";
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
@@ -196,4 +210,46 @@ public class FileUtils {
return name;
}
/**
* Returns the image's absolute path from a track identified by trackId.
*
* @param context the context.
* @param trackId the track id.
*/
public static String getImageUrl(Context context, long trackId) {
File dir = FileUtils.getPhotoDir(context, trackId);
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION));
return file.getAbsolutePath();
}
/**
* Copy a File (src) to a File (dst).
*
* @param src source file.
* @param dst destination file.
* @throws IOException
*/
public static void copy(FileDescriptor src, File dst) throws IOException {
try (FileChannel in = new FileInputStream(src).getChannel();
FileChannel out = new FileOutputStream(dst).getChannel()) {
in.transferTo(0, in.size(), out);
} catch (Exception e) {
// post to log
Log.e(TAG, e.getMessage());
}
}
/**
* Returns a Uri for the file.
*
* @param context the context.
* @param file the file.
*/
public static Uri getUriForFile(Context context, File file) {
return FileProvider.getUriForFile(context, FileUtils.FILEPROVIDER, file);
}
}
@@ -75,9 +75,12 @@ public class IntentUtils {
if (trackIds.length == 0) {
throw new RuntimeException("Need to share at least one track.");
}
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
String trackDescription = "";
if (trackIds.length == 1) {
Track track = new ContentProviderUtils(context).getTrack(trackIds[0]);
Track track = contentProviderUtils.getTrack(trackIds[0]);
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
}
@@ -86,7 +89,12 @@ public class IntentUtils {
ArrayList<Uri> uris = new ArrayList<>();
for (long trackId : trackIds) {
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
}
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
uris.add(uriAndMime.first);
mime = uriAndMime.second;
}
@@ -99,6 +107,30 @@ public class IntentUtils {
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription));
}
/**
* Creates an intent to share a waypoint image with an app.
*
* @param context the context.
* @param uri uri with the image to share.
*/
public static Intent newShareImageIntent(Context context, Uri uri) {
String mime = context.getContentResolver().getType(uri);
/*
* Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo.
* The photos should be content:/// not file:/// because getType(uri) always returns null for file:///
* In .setType, to avoid side effects because the #166 bug described above it checks if mime is null.
* If it is then it hardcode "images/*".
*/
return new Intent(Intent.ACTION_SEND)
.setType(mime != null ? mime : "image/*")
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject))
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
}
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
}
@@ -128,7 +160,6 @@ public class IntentUtils {
}
/**
* Send intent to show tracks on a map (needs an another app).
*
@@ -147,7 +178,7 @@ public class IntentUtils {
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", TrackFileFormat.KMZ_WITH_TRACKDETAIL);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@android:color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,4V1h2v3h3v2H5v3H3V6H0V4H3zM6,10V7h3V4h7l1.83,2H21c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2V10H6zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5s-5,2.24 -5,5S10.24,19 13,19zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2s-1.43,-3.2 -3.2,-3.2S9.8,12.23 9.8,14z"/>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@android:color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"/>
</vector>
@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>
@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M15,1L9,1v2h6L15,1zM11,14h2L13,8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>
@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
</vector>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/text_gradient_center"/>
</shape>
+33
View File
@@ -67,6 +67,39 @@ limitations under the License.
android:imeOptions="actionDone"
android:importantForAutofill="no" />
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:id="@+id/marker_edit_waypoint_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:scaleType="fitStart"
android:adjustViewBounds="true"
android:contentDescription="@string/image_photo_marker" />
<ImageView
android:id="@+id/marker_edit_waypoint_photo_delete"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/shape_circle_darkgray"
android:padding="4dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_margin="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_delete_forever_24dp" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
+11 -90
View File
@@ -209,98 +209,19 @@ limitations under the License.
android:layout_gravity="center">
<!-- Current elevation -->
<TableRow
android:id="@+id/stats_elevation"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_current_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_elevation_current_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_elevation_current_unit"
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
<!-- Minimal elevation -->
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_min_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown" />
<TextView
android:id="@+id/stats_elevation_min_unit"
style="@style/StatsUnit" />
</LinearLayout>
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
style="@style/StatsSmallValue"
android:visibility="invisible" />
<TextView
style="@style/StatsSmallLabel"
android:layout_gravity="bottom"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/stats_min" />
</LinearLayout>
</TableRow>
<!-- Maximal elevation -->
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
android:id="@+id/stats_elevation_max_value"
style="@style/StatsSmallValue" />
<TextView
android:id="@+id/stats_elevation_max_unit"
style="@style/StatsUnit" />
</LinearLayout>
<LinearLayout
style="@style/StatsSmallValueContainer"
android:layout_gravity="start|bottom">
<TextView
style="@style/StatsSmallValue"
android:visibility="invisible" />
<TextView
style="@style/StatsSmallLabel"
android:layout_gravity="bottom"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/stats_max" />
</LinearLayout>
</TableRow>
<TextView
android:id="@+id/stats_elevation_current_unit"
style="@style/StatsUnit" />
</LinearLayout>
</TableLayout>
</LinearLayout>
</LinearLayout>
+5
View File
@@ -20,6 +20,11 @@ limitations under the License.
android:icon="@drawable/ic_map_24dp"
android:title="@string/menu_show_on_map"
app:showAsAction="ifRoom" />
<item
android:id="@+id/marker_detail_share"
android:icon="@drawable/ic_share_24dp"
android:title="@string/menu_share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/marker_detail_edit"
android:icon="@drawable/ic_edit_24dp"
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/marker_edit_insert_photo"
android:icon="@drawable/ic_add_a_photo_24dp"
android:orderInCategory="2"
app:showAsAction="ifRoom"
android:title="@string/menu_insert_photo" />
<item
android:id="@+id/marker_edit_insert_gallery_img"
android:icon="@drawable/ic_add_photo_alternate_24dp"
android:orderInCategory="2"
app:showAsAction="ifRoom"
android:title="@string/menu_insert_gallery_img" />
</menu>
-6
View File
@@ -21,12 +21,6 @@ limitations under the License.
android:orderInCategory="1"
app:showAsAction="ifRoom"
android:title="@string/menu_insert_marker" />
<item
android:id="@+id/track_detail_insert_photo"
android:icon="@drawable/ic_camera_24dp"
android:orderInCategory="2"
app:showAsAction="ifRoom"
android:title="@string/menu_insert_photo" />
<item
android:id="@+id/track_detail_menu_show_on_map"
android:icon="@drawable/ic_map_24dp"
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">مستشعر بلوتوث</string>
<string name="settings_sensor_connected">تم التوصيل بـ %1$s</string>
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
<string name="settings_stats_grade_elevation">الدرجة/الارتفاع</string>
<string name="settings_stats_rate_title">المعدل المفضل</string>
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
<string name="settings_sensor_connected">Connected to %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_grade_elevation">Grade/elevation</string>
<string name="settings_stats_rate_title">Preferred rate</string>
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
+4 -1
View File
@@ -192,6 +192,7 @@
<string name="menu_import_all">Importar todo</string>
<string name="menu_insert_marker">Insertar marcador</string>
<string name="menu_insert_photo">Insertar foto</string>
<string name="menu_insert_gallery_img">Insertar imagen de galería</string>
<string name="menu_map">Mapa</string>
<string name="menu_markers">Marcadores</string>
<string name="menu_play">Reproducir</string>
@@ -262,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
<string name="settings_sensor_connected">Conectado a %1$s</string>
<string name="settings_stats_coordinate">Latitud/longitud</string>
<string name="settings_stats_grade_elevation">Pendiente/elevación</string>
<string name="settings_stats_rate_title">Tasa preferida</string>
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
<string name="settings_stats_units_metric">Métricas (km, m)</string>
@@ -446,4 +446,7 @@
<string name="waypoint_type_waterfall">cascada</string>
<string name="waypoint_type_water_fountain">fuente de agua</string>
<string name="waypoint_type_waypoint">punto de referencia</string>
<!-- Share image -->
<string name="share_image_subject">Me gustaría compartir una imagen contigo</string>
<string name="share_image_body">Creo que esta imagen podría interesarte.</string>
</resources>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth сензор</string>
<string name="settings_sensor_connected">Свързан с %1$s</string>
<string name="settings_stats_coordinate">Ширина и дължина</string>
<string name="settings_stats_grade_elevation">Наклон/кота</string>
<string name="settings_stats_rate_title">Предпочитана мярка</string>
<string name="settings_stats_units_imperial">Имперски (мил, фт)</string>
<string name="settings_stats_units_metric">Метрични (км, м)</string>
+3 -1
View File
@@ -265,7 +265,6 @@ es vegi el cel. Torneu-ho a provar més tard.</string>
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
<string name="settings_sensor_connected">Connectat a %1$s</string>
<string name="settings_stats_coordinate">Latitud/Longitud:</string>
<string name="settings_stats_grade_elevation">Desnivell/elevació</string>
<string name="settings_stats_rate_title">Mesura preferida</string>
<string name="settings_stats_units_imperial">Sistema anglosaxó (milles, peus)</string>
<string name="settings_stats_units_metric">Sistema mètric (km, m)</string>
@@ -449,4 +448,7 @@ es vegi el cel. Torneu-ho a provar més tard.</string>
<string name="waypoint_type_waterfall">salt d\'aigua</string>
<string name="waypoint_type_water_fountain">font d\'aigua</string>
<string name="waypoint_type_waypoint">punt de ruta</string>
<!-- Share image -->
<string name="share_image_subject">Vull compartir una imatge amb tu</string>
<string name="share_image_body">Crec que aquesta imatge et podria interessar.</string>
</resources>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
<string name="settings_sensor_connected">Připojeno k senzoru %1$s</string>
<string name="settings_stats_coordinate">Zeměpisná šířka/délka</string>
<string name="settings_stats_grade_elevation">Sklon/Převýšení</string>
<string name="settings_stats_rate_title">Preferovaná rychlost</string>
<string name="settings_stats_units_imperial">Imperiální jednotky (míle, stopy)</string>
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Tilsluttet %1$s</string>
<string name="settings_stats_coordinate">Breddegrad/længdegrad</string>
<string name="settings_stats_grade_elevation">Hældning/stigning</string>
<string name="settings_stats_rate_title">Foretrukkenfartenhed</string>
<string name="settings_stats_units_imperial">Britisk (mil, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-Sensor</string>
<string name="settings_sensor_connected">Verbunden mit %1$s</string>
<string name="settings_stats_coordinate">Breiten-/Längengrad</string>
<string name="settings_stats_grade_elevation">Gefälle/Höhe</string>
<string name="settings_stats_rate_title">Bevorzugtes Maß</string>
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Αισθητήρας Bluetooth</string>
<string name="settings_sensor_connected">Έγινε σύνδεση με %1$s</string>
<string name="settings_stats_coordinate">Πλάτος/Μήκος</string>
<string name="settings_stats_grade_elevation">Κλίση/Ανύψωση</string>
<string name="settings_stats_rate_title">Προτιμώμενος ρυθμός</string>
<string name="settings_stats_units_imperial">Αγγλοσαξονικό (μίλια, πόδια)</string>
<string name="settings_stats_units_metric">Μετρικό (χλμ, μέτρα)</string>
+5 -1
View File
@@ -169,6 +169,7 @@
<string name="marker_add_error">No se puede insertar el marcador. No se recibe señal de GPS. Inténtalo de nuevo.</string>
<string name="marker_add_success">Se ha insertado un marcador</string>
<string name="marker_add_canceled">Cancelado, no se ha añadido ningún marcador</string>
<string name="marker_add_photo_canceled">Cancelado, no se ha añadido la foto al marcador</string>
<string name="marker_delete_all_confirm_message">Todos los marcadores de esta ruta se eliminarán del dispositivo de forma permanente</string>
<string name="marker_delete_one_confirm_message">El marcador se eliminará del dispositivo de forma permanente</string>
<string name="marker_delete_one_confirm_title">¿Eliminar marcador?</string>
@@ -195,6 +196,7 @@
<string name="menu_import_all">Importar todo</string>
<string name="menu_insert_marker">Insertar marcador</string>
<string name="menu_insert_photo">Insertar foto</string>
<string name="menu_insert_gallery_img">Insertar imagen de galería</string>
<string name="menu_map">Mapa</string>
<string name="menu_markers">Marcadores</string>
<string name="menu_play">Reproducir</string>
@@ -265,7 +267,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connected">Conectado a %1$s</string>
<string name="settings_stats_coordinate">Latitud/Longitud</string>
<string name="settings_stats_grade_elevation">Pendiente/Elevación</string>
<string name="settings_stats_rate_title">Ritmo preferido</string>
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
<string name="settings_stats_units_metric">Métricas (km, m)</string>
@@ -449,4 +450,7 @@
<string name="waypoint_type_waterfall">cascada</string>
<string name="waypoint_type_water_fountain">fuente de agua</string>
<string name="waypoint_type_waypoint">Punto de ruta</string>
<!-- Share image -->
<string name="share_image_subject">Me gustaría compartir una imagen contigo</string>
<string name="share_image_body">Creo que esta imagen te podría interesar.</string>
</resources>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetoothi andur</string>
<string name="settings_sensor_connected">Ühendatud anduriga %1$s</string>
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
<string name="settings_stats_grade_elevation">Kalle/tõus</string>
<string name="settings_stats_rate_title">Eelistatud määr</string>
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth sentsorea</string>
<string name="settings_sensor_connected">Honekin konektatuta: %1$s</string>
<string name="settings_stats_coordinate">Latitudea/Longitudea</string>
<string name="settings_stats_grade_elevation">Inklinazioa/Altitudea</string>
<string name="settings_stats_rate_title">Erritmo hobetsia</string>
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
<string name="settings_stats_units_metric">Metrikoa (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">حسگر بلوتوث</string>
<string name="settings_sensor_connected">به %1$s متصل شد</string>
<string name="settings_stats_coordinate">عرض جغرافیایی/ طول جغرافیایی</string>
<string name="settings_stats_grade_elevation">درجه / ارتفاع</string>
<string name="settings_stats_rate_title">اولویت سنجه سرعت</string>
<string name="settings_stats_units_imperial">انگلیسی (مایل، فوت)</string>
<string name="settings_stats_units_metric">متریک (کیلومتر، متر)</string>
-1
View File
@@ -265,7 +265,6 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
<string name="settings_sensor_bluetooth_sensor">Bluetooth-anturi</string>
<string name="settings_sensor_connected">Yhdistetty: %1$s</string>
<string name="settings_stats_coordinate">Leveys-/pituusaste</string>
<string name="settings_stats_grade_elevation">Korkeustaso/korkeus</string>
<string name="settings_stats_rate_title">Haluttu nopeus</string>
<string name="settings_stats_units_imperial">Brittiläinen järj.
(maili, jalka)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
<string name="settings_sensor_connected">Connecté à %1$s</string>
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
-1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
<string name="settings_sensor_connected">Connecté à %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
+7 -1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">Capteur Bluetooth</string>
<string name="settings_sensor_connected">Connecté à %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_grade_elevation">Inclinaison/Altitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
@@ -448,4 +447,11 @@
<string name="waypoint_type_waterfall">cascade</string>
<string name="waypoint_type_water_fountain">fontaine</string>
<string name="waypoint_type_waypoint">point de cheminement</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Afficher les statistiques sur l\'écran de verrouillage</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Pendant l\'enregistrement, afficher les statistiques sans déverrouiller l\'appareil.</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Garder l\'écran allumé</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Pendant l\'enregistrement, garder l\'écran allumé.</string>
<string name="about_preference_title">À propos d\'OpenTracks</string>
<string name="export_with_photos">avec les photos</string>
<string name="export_without_photos">sans les photos</string>
</resources>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
<string name="settings_sensor_connected">Conectado a %1$s</string>
<string name="settings_stats_coordinate">Latitude/lonxitude</string>
<string name="settings_stats_grade_elevation">Pendente/elevación</string>
<string name="settings_stats_rate_title">Indicador preferido</string>
<string name="settings_stats_units_imperial">Imperiais (millas, pés)</string>
<string name="settings_stats_units_metric">Métricas (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth सेंसर</string>
<string name="settings_sensor_connected">%1$s से जुड़ा है</string>
<string name="settings_stats_coordinate">अक्षांश/देशांतर</string>
<string name="settings_stats_grade_elevation">ढलान/ऊंचाई</string>
<string name="settings_stats_rate_title">पसंदीदा दर</string>
<string name="settings_stats_units_imperial">इंपीरियल (मील, फुट)</string>
<string name="settings_stats_units_metric">मेट्रिक (किमी, मी)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetootha</string>
<string name="settings_sensor_connected">Povezan sa senzorom %1$s</string>
<string name="settings_stats_coordinate">Geografska širina/duljina</string>
<string name="settings_stats_grade_elevation">Nagib/elevacija</string>
<string name="settings_stats_rate_title">Preferirana stopa</string>
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
<string name="settings_stats_units_metric">Metričke (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth érzékelő</string>
<string name="settings_sensor_connected">Kapcsolódva ehhez: %1$s</string>
<string name="settings_stats_coordinate">Szélesség/hosszúság</string>
<string name="settings_stats_grade_elevation">Lejtés/magasság</string>
<string name="settings_stats_rate_title">Mérték választása</string>
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
-1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connected">Tersambung ke %1$s</string>
<string name="settings_stats_coordinate">Lintang/Bujur</string>
<string name="settings_stats_grade_elevation">Kemiringan/Tinggi</string>
<string name="settings_stats_rate_title">Laju yang dipilih</string>
<string name="settings_stats_units_imperial">Imperial (mil, kaki)</string>
<string name="settings_stats_units_metric">Metrik (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensore bluetooth</string>
<string name="settings_sensor_connected">Connesso a %1$s</string>
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
<string name="settings_stats_grade_elevation">Pendenza/altezza</string>
<string name="settings_stats_rate_title">Velocità preferita</string>
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
<string name="settings_stats_units_metric">Metrico (km, m)</string>
-1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">חיישן Bluetooth</string>
<string name="settings_sensor_connected">מחובר ל-%1$s</string>
<string name="settings_stats_coordinate">קו רוחב/קו אורך</string>
<string name="settings_stats_grade_elevation">שיפוע/גובה</string>
<string name="settings_stats_rate_title">קצב מועדף</string>
<string name="settings_stats_units_imperial">אימפריאלי (מייל, רגל)</string>
<string name="settings_stats_units_metric">מטרי (ק\"מ, מ\')</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetoothのセンサー</string>
<string name="settings_sensor_connected">接続先: %1$s</string>
<string name="settings_stats_coordinate">緯度/経度</string>
<string name="settings_stats_grade_elevation">傾斜/標高</string>
<string name="settings_stats_rate_title">計測方法</string>
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
<string name="settings_stats_units_metric">メートル法(km、m)</string>
-1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">블루투스 센서</string>
<string name="settings_sensor_connected">%1$s에 연결됨</string>
<string name="settings_stats_coordinate">위도/경도</string>
<string name="settings_stats_grade_elevation">등급/고도</string>
<string name="settings_stats_rate_title">선호하는 속도</string>
<string name="settings_stats_units_imperial">야드파운드법(마일, 피트)</string>
<string name="settings_stats_units_metric">미터법(km,m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">„Bluetooth“ jutiklis</string>
<string name="settings_sensor_connected">Prijungta prie %1$s</string>
<string name="settings_stats_coordinate">Platuma ir ilguma</string>
<string name="settings_stats_grade_elevation">Laipsnis / Aukštis</string>
<string name="settings_stats_rate_title">Pageidaujamas greitis</string>
<string name="settings_stats_units_imperial">Imperinės (myl., pėdos)</string>
<string name="settings_stats_units_metric">Metrinės (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensors</string>
<string name="settings_sensor_connected">Savienots ar %1$s</string>
<string name="settings_stats_coordinate">Platums/garums</string>
<string name="settings_stats_grade_elevation">Slīpums/pacēlums</string>
<string name="settings_stats_rate_title">Vēlamais rādītājs</string>
<string name="settings_stats_units_imperial">Standarta (jūdzes, pēdas)</string>
<string name="settings_stats_units_metric">Metriskās (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
<string name="settings_sensor_connected">Conectat la %1$s</string>
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
<string name="settings_stats_grade_elevation">Înclinare/Elevație</string>
<string name="settings_stats_rate_title">Rata de preferat</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connected">Disambungkan ke %1$s</string>
<string name="settings_stats_coordinate">Latitud/Longitud</string>
<string name="settings_stats_grade_elevation">Cerun/Ketinggian</string>
<string name="settings_stats_rate_title">Kadar pilihan</string>
<string name="settings_stats_units_imperial">Imperial (bt, kaki)</string>
<string name="settings_stats_units_metric">Metrik (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Koblet til %1$s</string>
<string name="settings_stats_coordinate">Bredde- og lengdegrad</string>
<string name="settings_stats_grade_elevation">Stigning/høyde</string>
<string name="settings_stats_rate_title">Foretrukket måling</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Verbonden met %1$s</string>
<string name="settings_stats_coordinate">Breedte-/lengtegraad</string>
<string name="settings_stats_grade_elevation">Helling/hoogte</string>
<string name="settings_stats_rate_title">Voorkeursverhouding</string>
<string name="settings_stats_units_imperial">Engels (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Koblet til %1$s</string>
<string name="settings_stats_coordinate">Bredde- og lengdegrad</string>
<string name="settings_stats_grade_elevation">Stigning/høyde</string>
<string name="settings_stats_rate_title">Foretrukket måling</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Czujnik Bluetooth</string>
<string name="settings_sensor_connected">Połączono z: %1$s</string>
<string name="settings_stats_coordinate">Szerokość/długość geo.</string>
<string name="settings_stats_grade_elevation">Nachylenie/wzniesienie</string>
<string name="settings_stats_rate_title">Preferowana wartość</string>
<string name="settings_stats_units_imperial">Imperialne (mile, stopy)</string>
<string name="settings_stats_units_metric">Metryczne (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connected">Conexão com %1$s</string>
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_grade_elevation">Inclinação/Elevação</string>
<string name="settings_stats_rate_title">Taxa preferencial</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor de Bluetooth</string>
<string name="settings_sensor_connected">Ligado a %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_grade_elevation">Grau/Elevação</string>
<string name="settings_stats_rate_title">Avaliação preferida</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrico (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
<string name="settings_sensor_connected">Conexão com %1$s</string>
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_grade_elevation">Inclinação/Elevação</string>
<string name="settings_stats_rate_title">Taxa preferencial</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
<string name="settings_sensor_connected">Conectat la %1$s</string>
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
<string name="settings_stats_grade_elevation">Înclinare/Elevație</string>
<string name="settings_stats_rate_title">Rata de preferat</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Датчик Bluetooth</string>
<string name="settings_sensor_connected">Подключен датчик \"%1$s\"</string>
<string name="settings_stats_coordinate">Широта/долгота</string>
<string name="settings_stats_grade_elevation">Наклон/высота</string>
<string name="settings_stats_rate_title">Предпочтение</string>
<string name="settings_stats_units_imperial">Британская (мили, футы)</string>
<string name="settings_stats_units_metric">Метрическая (км, м)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
<string name="settings_sensor_connected">Pripojené k %1$s</string>
<string name="settings_stats_coordinate">Zemepis. šírka/dĺžka</string>
<string name="settings_stats_grade_elevation">Stúpanie / výška</string>
<string name="settings_stats_rate_title">Preferovaná hodnota</string>
<string name="settings_stats_units_imperial">Imperiálne jednotky (mi, ft)</string>
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
-1
View File
@@ -264,7 +264,6 @@
<string name="settings_sensor_bluetooth_sensor">Senzor Bluetooth</string>
<string name="settings_sensor_connected">Povezava vzpostavljena z: %1$s</string>
<string name="settings_stats_coordinate">Zemlj. širina/dolžina</string>
<string name="settings_stats_grade_elevation">Naklon/nadmorska višina</string>
<string name="settings_stats_rate_title">Pred. enot. za hitr.</string>
<string name="settings_stats_units_imperial">Imperialni sistem (mi, ft)</string>
<string name="settings_stats_units_metric">Metrični sistem (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth сензор</string>
<string name="settings_sensor_connected">Повезано са сензором %1$s</string>
<string name="settings_stats_coordinate">Географска шир./дуж.</string>
<string name="settings_stats_grade_elevation">Нагиб/надморска висина</string>
<string name="settings_stats_rate_title">Жељени темпо</string>
<string name="settings_stats_units_imperial">Империјалне (ми, ст)</string>
<string name="settings_stats_units_metric">Метричке (км, м)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth-sensor</string>
<string name="settings_sensor_connected">Ansluten till %1$s</string>
<string name="settings_stats_coordinate">Latitud/longitud</string>
<string name="settings_stats_grade_elevation">Lutning/höjd</string>
<string name="settings_stats_rate_title">Föredragen mätning</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">เซ็นเซอร์บลูทูธ</string>
<string name="settings_sensor_connected">เชื่อมต่อกับ %1$s</string>
<string name="settings_stats_coordinate">ละติจูด/ลองจิจูด</string>
<string name="settings_stats_grade_elevation">ระดับความเอียง/ระดับความสูง</string>
<string name="settings_stats_rate_title">อัตราที่ต้องการ</string>
<string name="settings_stats_units_imperial">หน่วยอังกฤษ (ไมล์, ฟุต)</string>
<string name="settings_stats_units_metric">หน่วยเมตริก (กม., เมตร)</string>
-1
View File
@@ -262,7 +262,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensor</string>
<string name="settings_sensor_connected">Konektado sa %1$s</string>
<string name="settings_stats_coordinate">Latitude/Longtitude</string>
<string name="settings_stats_grade_elevation">Grade/Elevation</string>
<string name="settings_stats_rate_title">Gustong rate</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Bluetooth sensörü</string>
<string name="settings_sensor_connected">Şuna bağlandı: %1$s</string>
<string name="settings_stats_coordinate">Enlem/Boylam</string>
<string name="settings_stats_grade_elevation">Eğim/Yükseklik</string>
<string name="settings_stats_rate_title">Tercih edilen oran</string>
<string name="settings_stats_units_imperial">İngiliz (mil, fit)</string>
<string name="settings_stats_units_metric">Metrik (km, m)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Датчик Bluetooth</string>
<string name="settings_sensor_connected">Підключено до %1$s</string>
<string name="settings_stats_coordinate">Широта/довгота</string>
<string name="settings_stats_grade_elevation">Нахил і висота</string>
<string name="settings_stats_rate_title">Система вимірювання</string>
<string name="settings_stats_units_imperial">Британська (милі, фути)</string>
<string name="settings_stats_units_metric">Метрична (км, м)</string>
-1
View File
@@ -263,7 +263,6 @@
<string name="settings_sensor_bluetooth_sensor">Cảm biến bluetooth</string>
<string name="settings_sensor_connected">Kết nối tới %1$s</string>
<string name="settings_stats_coordinate">Vĩ độ/Kinh độ</string>
<string name="settings_stats_grade_elevation">Độ dốc/Độ cao</string>
<string name="settings_stats_rate_title">Tốc độ ưa thích</string>
<string name="settings_stats_units_imperial">Hệ đo lường Anh (mi, ft)</string>
<string name="settings_stats_units_metric">Hệ mét (km, m)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">藍牙感應器</string>
<string name="settings_sensor_connected">連接到 %1$s</string>
<string name="settings_stats_coordinate">緯度/經度</string>
<string name="settings_stats_grade_elevation">坡度/海拔高度</string>
<string name="settings_stats_rate_title">偏好速率</string>
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">藍牙感應器</string>
<string name="settings_sensor_connected">連接到 %1$s</string>
<string name="settings_stats_coordinate">緯度/經度</string>
<string name="settings_stats_grade_elevation">坡度/海拔高度</string>
<string name="settings_stats_rate_title">偏好速率</string>
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
-1
View File
@@ -265,7 +265,6 @@
<string name="settings_sensor_bluetooth_sensor">蓝牙传感器</string>
<string name="settings_sensor_connected">已连接至 %1$s</string>
<string name="settings_stats_coordinate">纬度/经度</string>
<string name="settings_stats_grade_elevation">坡度/海拔</string>
<string name="settings_stats_rate_title">首选速率</string>
<string name="settings_stats_units_imperial">英制(英里、英尺)</string>
<string name="settings_stats_units_metric">公制(公里、米)</string>

Some files were not shown because too many files have changed in this diff Show More