Compare commits

...

22 Commits

Author SHA1 Message Date
Dennis Guse 793928a5a6 Release: v3.14.2 2021-01-24 21:27:25 +01:00
Dennis Guse 50bb0b14b4 Segments: database upgrade/downgrade functionality for version 30; version 29 is still used.
This will store segment data in TrackPoint.type.
This commit will enable downgrading to previous version.

#316.
2021-01-24 21:14:50 +01:00
Román Martínez 8a08e7b2b5 Creates thumbnail from marker's photos for marker's list.
These thumbnails are created and loaded async using cache from free memory available.
Fixes #545.
2021-01-24 17:43:18 +01:00
FTno e9e9fdcc3f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 83c471ccb6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 17479c438b Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.6% (514 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 943c80f9ec Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.6% (514 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
FTno 23751306a4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.4% (513 of 516 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-01-20 07:52:54 +01:00
dependabot[bot] 78b712a291 Bump gradle from 4.1.1 to 4.1.2
Bumps gradle from 4.1.1 to 4.1.2.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-20 07:52:48 +01:00
dependabot[bot] f8432adaa0 Bump mockito-android from 3.7.0 to 3.7.7
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.7.0 to 3.7.7.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.7.0...v3.7.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 17:27:56 +01:00
Dennis Guse 80b26f2a04 MarkerDetail accepts Marker.Id also as long.
Fixes #567.
2021-01-17 17:54:07 +01:00
Dennis Guse 41e6e80cfc Statistics: fix position of moving time. 2021-01-16 23:09:15 +01:00
dependabot[bot] 7e72b29c3f Bump espresso-core from 3.4.0-alpha02 to 3.4.0-alpha03
Bumps espresso-core from 3.4.0-alpha02 to 3.4.0-alpha03.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-14 12:28:38 +01:00
Román Martínez 860df0197e ImportActivity offers to open track option (when only one track is imported). Fixes #558. 2021-01-10 20:40:14 +01:00
Román Martínez f1d791a62c Import/Export summary messages improved (only show useful data). 2021-01-10 20:40:14 +01:00
Dennis Guse e9551bea36 Introduced TrackPoint.Id. 2021-01-10 20:25:59 +01:00
Dennis Guse 577b978a5c Cleanup. 2021-01-10 20:25:59 +01:00
Dennis Guse a689b6eb2c Cleanup: removed descending iterator for TrackPoints. 2021-01-10 20:25:59 +01:00
Dennis Guse ccd93dd17d Track.Id must now always be valid (if present). 2021-01-10 20:25:59 +01:00
Dennis Guse f4fe1d8d97 Marker.Id must now always be valid (if present). 2021-01-10 20:25:59 +01:00
Dennis Guse d1290c8f22 Prevent re-import should be translatable. 2021-01-09 12:51:12 +01:00
dependabot[bot] a340cb57de Bump mockito-android from 3.6.28 to 3.7.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.6.28 to 3.7.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.6.28...v3.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 18:56:18 +01:00
47 changed files with 1175 additions and 533 deletions
+5 -5
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:4.1.2'
}
}
@@ -51,8 +51,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 3799
versionName "v3.14.1"
versionCode 3821
versionName "v3.14.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -86,7 +86,7 @@ android {
}
dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha02'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha03'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
@@ -99,5 +99,5 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'org.mockito:mockito-android:3.6.28'
androidTestImplementation 'org.mockito:mockito-android:3.7.7'
}
@@ -0,0 +1,10 @@
v3.14.2: OpenTracks
Changes:
- Downgrade database from upcoming release v3.15.0
- Import/Export UI improved
- Import offers to show imported track
- Marker list performance improved
Developer:
- Track.Id/Marker.Id: use null
@@ -7,6 +7,8 @@ import android.util.Pair;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.FileUtils;
@@ -36,19 +38,19 @@ public class TestDataUtil {
* @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track
*/
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
public static Pair<Track, List<TrackPoint>> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
for (int i = 0; i < numPoints; i++) {
trackPoints[i] = createTrackPoint(i);
trackPoints.add(createTrackPoint(i));
}
return new Pair<>(track, trackPoints);
}
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
Pair<Track, List<TrackPoint>> pair = createTrack(trackId, numPoints);
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
@@ -83,7 +85,7 @@ public class TestDataUtil {
* @param track track to be inserted
* @param trackPoints trackPoints to be inserted
*/
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, List<TrackPoint> trackPoints) {
contentProviderUtils.insertTrack(track);
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
}
@@ -34,9 +34,9 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
@@ -64,6 +64,7 @@ import static org.mockito.Mockito.when;
*/
@RunWith(MockitoJUnitRunner.class)
public class CustomContentProviderUtilsTest {
private static final String NAME_PREFIX = "test name";
private static final String MOCK_DESC = "Mock Next Marker Desc!";
private static final String TEST_DESC = "Test Desc!";
@@ -87,50 +88,37 @@ public class CustomContentProviderUtilsTest {
@Test
public void testLocationIterator_noPoints() {
testIterator(new Track.Id(1), 0, 1, false);
testIterator(new Track.Id(1), 0, 1);
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(new Track.Id(1), 50, 100, false);
testIterator(new Track.Id(2), 50, 50, false);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(new Track.Id(1), 50, 100, true);
testIterator(new Track.Id(2), 50, 50, true);
testIterator(new Track.Id(1), 50, 100);
testIterator(new Track.Id(2), 50, 50);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(new Track.Id(1), 50, 11, false);
testIterator(new Track.Id(2), 50, 25, false);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(new Track.Id(1), 50, 11, true);
testIterator(new Track.Id(2), 50, 25, true);
testIterator(new Track.Id(1), 50, 11);
testIterator(new Track.Id(2), 50, 25);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(new Track.Id(1), 20000, 2000, false);
testIterator(new Track.Id(1), 20000, 2000);
}
private void testIterator(Track.Id trackId, int numPoints, int batchSize, boolean descending) {
private void testIterator(Track.Id trackId, int numPoints, int batchSize) {
long lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
while (it.hasNext()) {
TrackPoint loc = it.next();
assertNotNull(loc);
locations.add(loc);
TrackPoint trackPoint = it.next();
assertNotNull(trackPoint);
locations.add(trackPoint);
// Make sure the IDs are returned in the right order.
assertEquals(descending ? lastPointId - locations.size() + 1
: lastPointId - numPoints + locations.size(), it.getTrackPointId());
assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId());
}
assertEquals(numPoints, locations.size());
}
@@ -144,24 +132,24 @@ public class CustomContentProviderUtilsTest {
track = contentProviderUtils.getTrack(id);
assertNotNull(track);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
for (int i = 0; i < numPoints; ++i) {
Location loc = new Location("test");
loc.setLatitude(37.0 + (double) i / 10000.0);
loc.setLongitude(57.0 - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * 2.5);
trackPoints[i] = new TrackPoint(loc);
trackPoints.add(new TrackPoint(loc));
}
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
// Load all inserted trackPoints.
long lastPointId = -1;
int counter = 0;
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false)) {
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
while (it.hasNext()) {
it.next();
lastPointId = it.getTrackPointId();
TrackPoint trackPoint = it.next();
lastPointId = trackPoint.getId().getId();
counter++;
}
}
@@ -433,7 +421,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testCreateContentValues_marker() {
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
// Bottom
long startTime = 1000L;
@@ -452,7 +440,7 @@ public class CustomContentProviderUtilsTest {
track.first.setTrackStatistics(statistics);
contentProviderUtils.insertTrack(track.first);
Marker marker = new Marker(trackId, track.second[0]);
Marker marker = new Marker(trackId, track.second.get(0));
marker.setDescription(TEST_DESC);
contentProviderUtils.insertMarker(marker);
@@ -767,20 +755,20 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], Track.Id)}.
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(List, Track.Id)}.
*/
@Test
public void testBulkInsertTrackPoint() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
// when / then
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(track.second, 0, 8), trackId);
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
}
/**
@@ -875,104 +863,45 @@ public class CustomContentProviderUtilsTest {
checkLocation(9, lastTrackPoint.getLocation());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in descending.
*/
@Test
public void testGetTrackPointCursor_desc() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
long[] trackpointIds = new long[track.second.length];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
}
// when
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
// then
assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in ascending.
*/
@Test
public void testGetTrackPointCursor_asc() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
long[] trackpointIds = new long[track.second.length];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
}
List<TrackPoint.Id> trackpointIds = track.second.stream()
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
.map(TrackPoint.Id::new).collect(Collectors.toList());
// when
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5);
// then
assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
long[] trackpointIds = new long[track.second.length];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
}
long startTrackPointId = trackpointIds[9];
// when
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
// then
for (int i = 0; i < trackpointIds.length; i++) {
assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next();
assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
}
assertFalse(trackPointIterator.hasNext());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
long[] trackpointIds = new long[track.second.length];
for (int i = 0; i < trackpointIds.length; i++) {
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
}
List<TrackPoint.Id> trackpointIds = track.second.stream()
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
.map(TrackPoint.Id::new).collect(Collectors.toList());
long startTrackPointId = trackpointIds[0];
TrackPoint.Id startTrackPointId = trackpointIds.get(0);
// when
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, trackpointIds.get(0));
// then
for (int i = 0; i < trackpointIds.length; i++) {
for (int i = 0; i < trackpointIds.size(); i++) {
assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next();
assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
assertEquals(startTrackPointId.getId() + i, trackPoint.getId().getId());
checkLocation(i, trackPoint.getLocation());
}
@@ -5,6 +5,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -14,7 +15,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.dennisguse.opentracks.content.data.MarkerColumns;
@@ -165,6 +168,101 @@ public class CustomSQLiteOpenHelperTest {
}
}
@Test
public void upgrade_data_to_30() {
// given: a track in version 29
createVersion23();
try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) {
db29.beginTransaction();
db29.execSQL("INSERT INTO tracks (_id) VALUES (1)");
// Record -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.1, 2.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.2, 2.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.3, 2.3)");
// Record -> pause -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (3)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.1, 3.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.2, 3.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 0.0, 100)"); //manual PAUSE
// Record -> segment marker (distance to previous) -> pause -> resume -> stop
db29.execSQL("INSERT INTO tracks (_id) VALUES (4)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.1, 4.1)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.2, 4.2)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //SegmentEndMarker; will be deleted
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.3, 4.3)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.4, 4.4)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //manual PAUSE
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 200 * 1E6)"); //manual RESUME
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.5, 4.5)");
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.6, 4.6)");
db29.setTransactionSuccessful();
db29.endTransaction();
}
// when / then
try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) {
{
// Track 1
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 1");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(0, cursor.getCount());
}
}
{
// Track 2
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 2");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(3, cursor.getCount());
}
}
{
// Track 3
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 3");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(3, cursor.getCount());
}
}
{
// Track 4
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("trackpoints");
queryBuilder.appendWhere("trackid = 4");
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
assertEquals(8, cursor.getCount());
List<Integer> types = new ArrayList<>();
List<Double> latitude = new ArrayList<>();
cursor.moveToFirst();
do {
types.add(cursor.getInt(cursor.getColumnIndexOrThrow("type")));
if (!cursor.isNull(cursor.getColumnIndexOrThrow("latitude"))) {
latitude.add(cursor.getDouble(cursor.getColumnIndexOrThrow("latitude")));
} else {
latitude.add(-1.0);
}
} while (cursor.moveToNext());
assertEquals(List.of(0, 0, -1, 0, 1, -2, 0, 0), types);
assertEquals(List.of(4.1, 4.2, 4.3, 4.4, -1.0, -1.0, 4.5, 4.6), latitude);
}
}
}
}
private void createVersion23() {
// Manually create database schema with version 23 (base version)
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
@@ -18,7 +18,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
@@ -62,7 +61,7 @@ public class ExportImportTest {
@Before
public void setUp() {
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
track.first.setIcon(TRACK_ICON);
track.first.setCategory(TRACK_CATEGORY);
track.first.setDescription(TRACK_DESCRIPTION);
@@ -70,10 +69,10 @@ public class ExportImportTest {
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
trackPoints.clear();
trackPoints.addAll(Arrays.asList(track.second));
trackPoints.addAll(track.second);
for (int i = 0; i < 3; i++) {
Marker marker = new Marker(trackId, track.second[i].getLocation());
Marker marker = new Marker(trackId, track.second.get(i).getLocation());
marker.setName("the marker " + i);
marker.setDescription("the marker description " + i);
marker.setCategory("the marker category" + i);
@@ -328,7 +328,7 @@ public class TrackRecordingServiceTest {
}
private void addTrack(Track track, boolean isRecording) {
assertTrue(track.getId().isValid());
assertNotNull(track.getId());
contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
@@ -203,7 +203,7 @@ public class TrackRecordingServiceTestLooper {
// Start a track.
Track.Id trackId = service.startNewTrack();
assertTrue(trackId.isValid());
assertNotNull(trackId);
assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
@@ -8,7 +8,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
@@ -50,7 +49,7 @@ public class AnnouncementUtilsTest {
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
List<TrackPoint> trackPoints = Arrays.asList(TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
@@ -1,12 +1,9 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
@@ -24,8 +21,7 @@ public class IntervalStatisticsTest {
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
Pair<Track, TrackPoint[]> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints);
return Arrays.asList(pair.second);
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
}
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
@@ -58,7 +58,15 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
Marker.Id markerId = null;
if (bundleMarkerId instanceof Marker.Id) {
markerId = (Marker.Id) bundleMarkerId;
}
if (bundleMarkerId instanceof Long) {
//Incoming Intent via Dashboard API.
markerId = new Marker.Id((Long) bundleMarkerId);
}
if (markerId == null) {
Log.d(TAG, "invalid marker id");
finish();
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -30,11 +29,12 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.adapters.MarkerResourceCursorAdapter;
import de.dennisguse.opentracks.adapters.ScrollVisibleViews;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
@@ -44,8 +44,6 @@ import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
@@ -81,7 +79,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
}
};
private Track track;
private ResourceCursorAdapter resourceCursorAdapter;
private MarkerResourceCursorAdapter resourceCursorAdapter;
private MarkerListBinding viewBinding;
@@ -123,33 +121,17 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
viewBinding.markerList.setEmptyView(viewBinding.markerListEmpty);
viewBinding.markerList.setOnItemClickListener((parent, view, position, id) -> {
resourceCursorAdapter.markerInvalid(id);
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Marker.Id(id));
startActivity(intent);
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
int iconId = MarkerUtils.ICON_ID;
String name = cursor.getString(nameIndex);
long time = cursor.getLong(timeIndex);
String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex);
ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, photoUrl);
}
};
resourceCursorAdapter = new MarkerResourceCursorAdapter(this, R.layout.list_item);
ScrollVisibleViews scrollVisibleViews = new ScrollVisibleViews(resourceCursorAdapter);
viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
viewBinding.markerList.setAdapter(resourceCursorAdapter);
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
loadData(getIntent());
}
@Override
@@ -163,6 +145,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onResume() {
super.onResume();
this.invalidateOptionsMenu();
loadData(getIntent());
}
@Override
@@ -171,6 +154,16 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
resourceCursorAdapter.clear();
viewBinding = null;
resourceCursorAdapter = null;
contentProviderUtils = null;
sharedPreferences = null;
}
@Override
protected View getRootView() {
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
@@ -237,6 +230,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
if (itemId == R.id.list_context_menu_edit) {
if (markerIds.length == 1) {
resourceCursorAdapter.markerInvalid(markerIds[0].getId());
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
startActivity(intent);
@@ -58,7 +58,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (!trackId.isValid()) {
if (trackId == null) {
Log.e(TAG, "invalid trackId");
finish();
return;
@@ -233,7 +233,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
startTime, true, category, description, null);
startTime, true, category, description, false);
}
};
viewBinding.trackList.setAdapter(resourceCursorAdapter);
@@ -0,0 +1,79 @@
package de.dennisguse.opentracks.adapters;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* ExecutorService wrap that avoid the execution of the same object in a ListView.
*
* @param <T> the type of the object that will be used to identified the Runnable.
*/
class ExecutorListViewService<T> {
private List<T> enqueueObjects = new ArrayList<>();
private ExecutorService executorService;
public ExecutorListViewService(int numThreads) {
executorService = Executors.newFixedThreadPool(numThreads);
}
public void shutdown() {
enqueueObjects.clear();
enqueueObjects = null;
executorService.shutdown();
}
/**
* Execute the runnable for the object.
*
* @param object the Object.
* @param runnable the Runnable.
*/
public void execute(T object, Runnable runnable) {
if (!preExecute(object)) {
return;
}
new Thread(() -> {
Future future = executorService.submit(runnable);
try {
future.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
postExecute(object);
}).start();
}
/**
* Before execution it must checks if the object is already enqueued.
*
* @param object the object to check.
* @return true if it can be executed or false otherwise.
*/
private boolean preExecute(T object) {
synchronized (enqueueObjects) {
if (!enqueueObjects.contains(object)) {
enqueueObjects.add(object);
return true;
} else {
return false;
}
}
}
/**
* After execution remove the object from the queue.
*
* @param object the object to be removed from the queue.
*/
private void postExecute(T object) {
synchronized (enqueueObjects) {
enqueueObjects.remove(object);
}
}
}
@@ -0,0 +1,184 @@
package de.dennisguse.opentracks.adapters;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.collection.LruCache;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import java.io.IOException;
import java.io.InputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implements ScrollVisibleViews.VisibleViewsListener {
private static final String TAG = MarkerResourceCursorAdapter.class.getSimpleName();
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
private final Activity activity;
private ExecutorListViewService executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
private boolean scroll = false;
// Cache size is in bytes.
private final LruCache<String, Bitmap> memoryCache;
public MarkerResourceCursorAdapter(Activity activity, int layout) {
super(activity, layout, null, 0);
this.activity = activity;
// Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
final long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() - usedMemory) / 8;
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
int idIndex = cursor.getColumnIndex(MarkerColumns._ID);
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
long id = cursor.getLong(idIndex);
int iconId = MarkerUtils.ICON_ID;
String name = cursor.getString(nameIndex);
long time = cursor.getLong(timeIndex);
String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex);
view.setTag(String.valueOf(id));
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
ImageView imageView = view.findViewById(R.id.list_item_photo);
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
imageView.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
imageView.setImageBitmap(null);
if (hasPhoto) {
int height = getPhotoHeight(activity);
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = height;
imageView.setLayoutParams(params);
if (getBitmapFromMemCache(String.valueOf(id)) != null || !scroll) {
asyncLoadPhoto(view, imageView, photoUrl, id);
}
}
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, hasPhoto);
}
public void clear() {
executorService.shutdown();
}
@Override
public void onViewVisible(View view, int position) {
scroll = true;
Cursor cursor = getCursor();
if (!cursor.moveToPosition(position)) {
return;
}
long id = cursor.getLong(cursor.getColumnIndex(MarkerColumns._ID));
String photoUrl = cursor.getString(cursor.getColumnIndex(MarkerColumns.PHOTOURL));
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
if (hasPhoto) {
ImageView imageView = view.findViewById(R.id.list_item_photo);
asyncLoadPhoto(view, imageView, photoUrl, id);
}
}
public void markerInvalid(long id) {
memoryCache.remove(String.valueOf(id));
scroll = false;
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
synchronized (memoryCache) {
if (getBitmapFromMemCache(key) == null) {
memoryCache.put(key, bitmap);
}
}
}
private Bitmap getBitmapFromMemCache(String key) {
return memoryCache.get(key);
}
/**
* It loads the photoUrl in the imageView from view.
* It takes the photo from cache or from storage if isn't in the cache.
*
* @param view item's view.
* @param imageView view object where photo will be loaded.
* @param photoUrl photo's url.
* @param id marker's id where photo belong.
*/
private void asyncLoadPhoto(View view, ImageView imageView, String photoUrl, long id) {
Bitmap photo = getBitmapFromMemCache(String.valueOf(id));
imageView.setImageBitmap(photo);
if (photo == null) {
executorService.execute(id, () -> {
try (InputStream inputStream = activity.getContentResolver().openInputStream(Uri.parse(photoUrl))) {
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
int height = getPhotoHeight(activity);
Log.d(TAG, "Width : " + (bitmap.getWidth() / (bitmap.getHeight() / height)) + " | Height: " + height);
final Bitmap finalPhoto = ThumbnailUtils.extractThumbnail(bitmap, bitmap.getWidth() / (bitmap.getHeight() / height), height);
addBitmapToMemoryCache(String.valueOf(id), finalPhoto);
if (view.getTag().equals(String.valueOf(id))) {
activity.runOnUiThread(() -> imageView.setImageBitmap(finalPhoto));
}
} catch (IOException e) {
Log.e(TAG, "Failed to image " + e);
}
});
}
}
/**
* Gets the photo height.
*
* @param context the context
*/
private static int getPhotoHeight(Context context) {
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
TypedArray typeArray = context.obtainStyledAttributes(attrs);
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
typeArray.recycle();
return 2 * height;
}
}
@@ -0,0 +1,42 @@
package de.dennisguse.opentracks.adapters;
import android.view.View;
import android.widget.AbsListView;
import androidx.annotation.NonNull;
/**
* AbsListView.OnScrollListener class that can be used to know what views in a ListView are currently visible while scrolling.
*/
public class ScrollVisibleViews implements AbsListView.OnScrollListener {
private int from = -1;
private int to = -1;
private VisibleViewsListener visibleViewsListener;
public ScrollVisibleViews(@NonNull VisibleViewsListener visibleViewsListener) {
this.visibleViewsListener = visibleViewsListener;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
if (from >= 0 && to >= 0) {
for (int i = from; i < to; i++) {
View viewChild = view.getChildAt(i - from);
visibleViewsListener.onViewVisible(viewChild, i);
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
from = firstVisibleItem;
to = firstVisibleItem + visibleItemCount;
}
interface VisibleViewsListener {
void onViewVisible(View view, int position);
}
}
@@ -76,10 +76,10 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
private class ViewHolder extends RecyclerView.ViewHolder {
TextView label;
TextView sensorValue;
TextView value;
TextView unit;
final TextView label;
final TextView sensorValue;
final TextView value;
final TextView unit;
public ViewHolder(@NonNull View itemView) {
super(itemView);
@@ -83,8 +83,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
// Track points sampling state
private int numLoadedPoints;
private long firstSeenTrackPointId;
private long lastSeenTrackPointId;
private TrackPoint.Id firstSeenTrackPointId;
private TrackPoint.Id lastSeenTrackPointId;
// Registered listeners
private ContentObserver tracksTableObserver;
@@ -351,36 +351,42 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
}
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L;
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
TrackPoint.Id localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : null;
TrackPoint.Id localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : null;
TrackPoint.Id maxPointId = updateSamplingState ? null : lastSeenTrackPointId;
if (selectedTrackId == null) {
Log.w(TAG, "This should not happen, but it does"); //TODO
return;
}
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1, false)) {
TrackPoint.Id next;
if (localLastSeenTrackPointIdId != null) {
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
}
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, null)) {
while (trackPointIterator.hasNext()) {
TrackPoint trackPoint = trackPointIterator.next();
long trackPointId = trackPointIterator.getTrackPointId();
TrackPoint.Id trackPointId = trackPoint.getId();
// Stop if past the last wanted point
if (maxPointId != -1L && trackPointId > maxPointId) {
if (maxPointId != null && trackPointId.getId() > maxPointId.getId()) {
break;
}
if (localFirstSeenTrackPointId == -1) {
if (localFirstSeenTrackPointId == null) {
localFirstSeenTrackPointId = trackPointId;
}
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId);
long numTotalPoints = Math.max(0L, lastTrackPointId.getId() - localFirstSeenTrackPointId.getId()); //TODO That is an assumption; should be derived from the DB.
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
@@ -421,8 +427,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
*/
private void resetSamplingState() {
numLoadedPoints = 0;
firstSeenTrackPointId = -1L;
lastSeenTrackPointId = -1L;
firstSeenTrackPointId = null;
lastSeenTrackPointId = null;
}
/**
@@ -27,8 +27,6 @@ import androidx.annotation.VisibleForTesting;
import java.util.Objects;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
@@ -84,8 +82,8 @@ public final class Marker {
/**
* May be null if the it was not loaded from the database.
*/
public @Nullable
Id getId() {
@Nullable
public Id getId() {
return id;
}
@@ -125,8 +123,8 @@ public final class Marker {
this.icon = icon;
}
public @NonNull
Track.Id getTrackId() {
@NonNull
public Track.Id getTrackId() {
return trackId;
}
@@ -146,8 +144,8 @@ public final class Marker {
this.duration = duration;
}
public @NonNull
Location getLocation() {
@NonNull
public Location getLocation() {
return location;
}
@@ -180,11 +178,6 @@ public final class Marker {
return id;
}
@Deprecated //TODO Use a Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -26,7 +26,6 @@ import java.util.Objects;
import java.util.UUID;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* A track.
@@ -53,8 +52,8 @@ public class Track {
/**
* May be null if the track was not loaded from the database.
*/
public @Nullable
Id getId() {
@Nullable
public Id getId() {
return id;
}
@@ -127,11 +126,6 @@ public class Track {
return id;
}
@Deprecated //TODO Use a Track.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -17,16 +17,23 @@ package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.location.LocationManager;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
/**
* This class extends the standard Android location with extra information.
*
* @author Sandor Dornbush
*/
//TODO Merge constructors by use case; we have too many.
public class TrackPoint {
private TrackPoint.Id id;
private final Location location;
private Float heartRate_bpm = null;
@@ -92,8 +99,20 @@ public class TrackPoint {
return new TrackPoint(resume);
}
public @NonNull
Location getLocation() {
/**
* May be null if the track was not loaded from the database.
*/
@Nullable
public TrackPoint.Id getId() {
return id;
}
public void setId(TrackPoint.Id id) {
this.id = id;
}
@Nullable
public Location getLocation() {
return location;
}
@@ -251,4 +270,41 @@ public class TrackPoint {
public String toString() {
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
}
public static class Id {
private final long id;
public Id(long id) {
this.id = id;
}
protected Id(Parcel in) {
id = in.readLong();
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TrackPoint.Id id1 = (TrackPoint.Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@NonNull
@Override
public String toString() {
return String.valueOf(id);
}
}
}
@@ -23,7 +23,6 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -224,7 +223,7 @@ public class ContentProviderUtils {
* @param trackId the track id.
*/
public Track getTrack(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
if (trackId == null) {
return null;
}
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
@@ -286,7 +285,7 @@ public class ContentProviderUtils {
TrackStatistics trackStatistics = track.getTrackStatistics();
// Value < 0 indicates no id is available
if (track.getId() != null && track.getId().isValid()) {
if (track.getId() != null) {
values.put(TracksColumns._ID, track.getId().getId());
}
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
@@ -385,10 +384,7 @@ public class ContentProviderUtils {
/**
* @return -1 if not able to get the next marker number.
*/
public int getNextMarkerNumber(Track.Id trackId) {
if (!trackId.isValid()) {
return -1;
}
public int getNextMarkerNumber(@NonNull Track.Id trackId) {
String[] projection = {MarkerColumns._ID};
String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
@@ -400,10 +396,7 @@ public class ContentProviderUtils {
return -1;
}
public Marker getMarker(Marker.Id markerId) {
if (!markerId.isValid()) {
return null;
}
public Marker getMarker(@NonNull Marker.Id markerId) {
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())}, MarkerColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return createMarker(cursor);
@@ -419,14 +412,10 @@ public class ContentProviderUtils {
* @param minMarkerId the minimum marker id. null to ignore
* @param maxCount the maximum number of markers to return. -1 for no limit
*/
public Cursor getMarkerCursor(Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
if (!trackId.isValid()) {
return null;
}
public Cursor getMarkerCursor(@NonNull Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
String selection;
String[] selectionArgs;
if (minMarkerId != null && minMarkerId.isValid()) {
if (minMarkerId != null) {
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.getId())};
} else {
@@ -452,10 +441,6 @@ public class ContentProviderUtils {
@Deprecated //TODO TracksColumns.MARKER_COUNT while querying for tracks
public int getMarkerCount(Track.Id trackId) {
if (!trackId.isValid()) {
return 0;
}
String[] projection = new String[]{"count(*) AS count"};
String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
@@ -506,7 +491,7 @@ public class ContentProviderUtils {
ContentValues createContentValues(@NonNull Marker marker) {
ContentValues values = new ContentValues();
if (marker.getId() != null && marker.getId().isValid()) {
if (marker.getId() != null) {
values.put(MarkerColumns._ID, marker.getId().getId());
}
values.put(MarkerColumns.NAME, marker.getName());
@@ -560,6 +545,9 @@ public class ContentProviderUtils {
*/
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
TrackPoint trackPoint = new TrackPoint();
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
}
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
@@ -603,22 +591,6 @@ public class ContentProviderUtils {
return trackPoint;
}
/**
* Inserts multiple trackPoints.
*
* @param trackPoints an array of trackPoints
* @param trackId the trackPoints id
* @return the number of trackPoints inserted
*/
//TODO Only used for testing; might be better to replace it.
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.length];
for (int i = 0; i < values.length; i++) {
values[i] = createContentValues(trackPoints[i], trackId);
}
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
}
//TODO Only used for file import; might be better to replace it.
public int bulkInsertTrackPoint(List<TrackPoint> trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.size()];
@@ -636,9 +608,6 @@ public class ContentProviderUtils {
*/
@Deprecated
public Track.Id getFirstTrackPointId(Track.Id trackId) {
if (!trackId.isValid()) {
return null;
}
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
@@ -656,19 +625,15 @@ public class ContentProviderUtils {
* @param trackId the track id
*/
@Deprecated
public long getLastTrackPointId(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
Log.w(TAG, "Fix callers who do this.");
return -1L;
}
public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return -1L;
return null;
}
/**
@@ -678,18 +643,15 @@ public class ContentProviderUtils {
* @param location the location
* @return trackPoint id if the location is in the track. -1L otherwise.
*/
public long getTrackPointId(Track.Id trackId, Location location) {
if (!trackId.isValid()) {
return -1L;
}
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return -1L;
return null;
}
/**
@@ -705,31 +667,22 @@ public class ContentProviderUtils {
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param startTrackPointId the starting trackPoint id. -1L to ignore
* @param maxLocations maximum number of locations to return. -1 for no limit
* @param descending true to sort the result in descending order (latest location first)
* @param startTrackPointId the starting trackPoint id. `null` to ignore
* @param maxLocations maximum number of locations to return. `null` for no limit
*/
public Cursor getTrackPointCursor(Track.Id trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (!trackId.isValid()) {
return null;
}
public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) {
String selection;
String[] selectionArgs;
if (startTrackPointId >= 0) {
String comparison = descending ? "<=" : ">=";
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)};
if (startTrackPointId != null) {
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId.getId())};
} else {
selection = TrackPointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
String sortOrder = TrackPointsColumns._ID;
if (descending) {
sortOrder += " DESC";
}
if (maxLocations >= 0) {
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
if (maxLocations != null) {
sortOrder += " LIMIT " + maxLocations;
}
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
@@ -743,9 +696,6 @@ public class ContentProviderUtils {
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
if (!trackId.isValid()) {
return null;
}
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
@@ -816,11 +766,10 @@ public class ContentProviderUtils {
* When done with iteration, {@link TrackPointIterator#close()} must be called.
*
* @param trackId the track id
* @param startTrackPointId the starting trackPoint id. -1L to ignore
* @param descending true to sort the result in descending order (latest location first)
* @param startTrackPointId the starting trackPoint id. `null` to ignore
*/
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final long startTrackPointId, final boolean descending) {
return new TrackPointIterator(this, trackId, startTrackPointId, descending);
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) {
return new TrackPointIterator(this, trackId, startTrackPointId);
}
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
@@ -848,7 +797,7 @@ public class ContentProviderUtils {
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
List<TrackPoint> trackPoints = null;
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) {
if (trackPointCursor != null) {
trackPointCursor.moveToFirst();
trackPoints = new ArrayList<>(trackPointCursor.getCount());
@@ -79,6 +79,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 29:
upgradeFrom28to29(db);
break;
case 30:
upgradeFrom29to30(db);
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
@@ -109,6 +112,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 28:
downgradeFrom29to28(db);
break;
case 29:
downgradeFrom30to29(db);
break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
@@ -330,4 +336,62 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* Move TrackPoint type (segment start vs. segment end) into separate column.
*/
private void upgradeFrom29to30(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints
db.execSQL("ALTER TABLE trackpoints ADD COLUMN type TEXT CHECK(type IN (-2, -1, 0, 1))");
db.execSQL("UPDATE trackpoints SET type = -2, latitude = NULL, longitude = NULL WHERE latitude = 200 * 1E6");
db.execSQL("UPDATE trackpoints SET type = 1, latitude = NULL, longitude = NULL WHERE latitude = 100 * 1E6");
db.execSQL("UPDATE trackpoints SET type = 0 WHERE type IS NULL");
// PAUSE markers without RESUME were inserted automatically as segment markers if the distance between subsequent trackpoints was too great.
// Only stored data is time (local device time); not meaningful as trackpoints were stored with GPS time.
// 1. Mark there successors as SEGMENT_START_AUTOMATIC
db.execSQL(
"UPDATE trackpoints " +
"SET type = -1 " +
"WHERE type = 0 AND 1 = " +
"(SELECT type FROM trackpoints AS T1 " +
"WHERE trackpoints._id > t1._id " +
"AND trackpoints.trackid = t1.trackid " +
"ORDER BY t1._id DESC " +
"LIMIT 1)");
// 2. Delete old PAUSE trackpoint
db.execSQL(
"DELETE FROM trackpoints" +
" WHERE type = 1 AND -1 = " +
"(SELECT type FROM trackpoints AS T1 " +
"WHERE trackpoints._id < t1._id " +
"AND trackpoints.trackid = t1.trackid " +
"ORDER BY t1._id ASC " +
"LIMIT 1)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom30to29(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints
//TODO That does not restore deleted trackpoints
db.execSQL("UPDATE trackpoints SET latitude = 200 * 1E6, longitude = NULL WHERE type = -2");
db.execSQL("UPDATE trackpoints SET latitude = 100 * 1E6, longitude = NULL WHERE type = 1");
// TrackPoints; identical to upgradeFrom27to28()
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain FROM trackpoints_old");
db.execSQL("DROP TABLE trackpoints_old");
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -18,16 +18,14 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private final ContentProviderUtils contentProviderUtils;
private final Track.Id trackId;
private final boolean descending;
private final CachedTrackPointsIndexes indexes;
private long lastTrackPointId = -1L;
private TrackPoint.Id lastTrackPointId = null;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, long startTrackPointId, boolean descending) {
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
this.descending = descending;
cursor = getCursor(startTrackPointId);
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
@@ -39,25 +37,21 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
*
* @param trackPointId the starting track point id
*/
private Cursor getCursor(long trackPointId) {
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize(), descending);
private Cursor getCursor(TrackPoint.Id trackPointId) {
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize());
}
/**
* Advances the cursor to the next batch. Returns true if successful.
*/
private boolean advanceCursorToNextBatch() {
long trackPointId = lastTrackPointId == -1L ? -1L : lastTrackPointId + (descending ? -1 : 1);
TrackPoint.Id trackPointId = lastTrackPointId == null ? null : new TrackPoint.Id(lastTrackPointId.getId() + 1);
Log.d(TAG, "Advancing track point id: " + trackPointId);
cursor.close();
cursor = getCursor(trackPointId);
return cursor != null;
}
public long getTrackPointId() {
return lastTrackPointId;
}
@Override
public boolean hasNext() {
if (cursor == null) {
@@ -85,7 +79,7 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
throw new NoSuchElementException();
}
}
lastTrackPointId = cursor.getLong(indexes.idIndex);
lastTrackPointId = new TrackPoint.Id(cursor.getLong(indexes.idIndex));
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
}
@@ -268,7 +268,14 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
viewBinding.exportProgressBar.setProgress((int) ((float) done / (float) trackExportTotalCount * 100f));
viewBinding.exportProgressSummary.setText(getString(R.string.export_progress_review, getTotalDone(), trackExportSuccessCount, trackExportOverwrittenCount, trackExportSkippedCount, trackExportErrorCount));
viewBinding.exportProgressSummaryNew.setText(String.valueOf(trackExportSuccessCount));
viewBinding.exportProgressSummaryOverwrite.setText(String.valueOf(trackExportOverwrittenCount));
viewBinding.exportProgressSummarySkip.setText(String.valueOf(trackExportSkippedCount));
viewBinding.exportProgressSummaryErrors.setText(String.valueOf(trackExportErrorCount));
viewBinding.exportProgressSummaryNewGroup.setVisibility(trackExportSuccessCount > 0 ? View.VISIBLE : View.GONE);
viewBinding.exportProgressSummaryOverwriteGroup.setVisibility(trackExportOverwrittenCount > 0 ? View.VISIBLE : View.GONE);
viewBinding.exportProgressSummarySkipGroup.setVisibility(trackExportSkippedCount > 0 ? View.VISIBLE : View.GONE);
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
}
private void onExportCompleted(Track.Id trackId) {
@@ -126,7 +126,7 @@ public class FileTrackExporter implements TrackExporter {
boolean isLastLocationValid = false;
TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) {
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import java.io.OutputStream;
@@ -399,19 +398,16 @@ public class KmlTrackWriter implements TrackWriter {
* @param location the location
*/
private float getHeading(Track.Id trackId, Location location) {
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
if (trackPointId == -1L) {
TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
if (trackPointId == null) {
return location.getBearing();
}
TrackPoint viewLocation;
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) {
if (cursor == null || cursor.getCount() == 0) {
return location.getBearing();
}
cursor.moveToPosition(cursor.getCount() - 1);
viewLocation = contentProviderUtils.createTrackPoint(cursor);
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
if (viewLocation != null) {
return viewLocation.bearingTo(location);
}
return viewLocation.bearingTo(location);
return location.getBearing();
}
private String getCoordinates(Location location, String separator) {
@@ -160,7 +160,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// TODO Should not be necessary anymore?
TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (true) {
if (marker == null) {
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -30,8 +31,10 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
import de.dennisguse.opentracks.io.file.ErrorListDialog;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* An activity to import files from the external storage.
@@ -127,7 +130,9 @@ public class ImportActivity extends FragmentActivity {
private void initViews() {
viewBinding.importProgressDone.setText("0");
viewBinding.importProgressTotal.setText("0");
viewBinding.importProgressSummary.setText(getString(R.string.import_progress_review, 0, 0, 0, 0));
viewBinding.importProgressSummaryOk.setText("0");
viewBinding.importProgressSummaryExists.setText("0");
viewBinding.importProgressSummaryErrors.setText("0");
}
private int getTotalDone() {
@@ -141,7 +146,12 @@ public class ImportActivity extends FragmentActivity {
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
viewBinding.importProgressBar.setProgress((int) ((float) done / (float) summary.getTotalCount() * 100f));
viewBinding.importProgressSummary.setText(getString(R.string.import_progress_review, getTotalDone(), summary.getSuccessCount(), summary.getExistsCount(), summary.getErrorCount()));
viewBinding.importProgressSummaryOk.setText(String.valueOf(summary.getSuccessCount()));
viewBinding.importProgressSummaryExists.setText(String.valueOf(summary.getExistsCount()));
viewBinding.importProgressSummaryErrors.setText(String.valueOf(summary.getErrorCount()));
viewBinding.importProgressSummaryOkGroup.setVisibility(summary.getSuccessCount() > 0 ? View.VISIBLE : View.GONE);
viewBinding.importProgressSummaryExistsGroup.setVisibility(summary.getExistsCount() > 0 ? View.VISIBLE : View.GONE);
viewBinding.importProgressSummaryErrorsGroup.setVisibility(summary.getErrorCount() > 0 ? View.VISIBLE : View.GONE);
if (done == summary.getTotalCount()) {
onImportEnded();
@@ -149,6 +159,9 @@ public class ImportActivity extends FragmentActivity {
}
private void onImportEnded() {
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
viewBinding.importProgressRightButton.setVisibility(View.VISIBLE);
viewBinding.importProgressRightButton.setText(getString(R.string.generic_ok));
viewBinding.importProgressRightButton.setOnClickListener((view) -> {
@@ -156,19 +169,37 @@ public class ImportActivity extends FragmentActivity {
finish();
});
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
if (summary.getErrorCount() > 0) {
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_report_problem_24));
String msg = getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount());
viewBinding.importProgressAlertMsg.setText(msg);
toggleUIEndWithErrors();
} else {
viewBinding.importProgressLeftButton.setVisibility(View.GONE);
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
toggleUIEndOk();
}
}
}
private void toggleUIEndWithErrors() {
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_report_problem_24));
viewBinding.importProgressAlertMsg.setText(getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()));
}
private void toggleUIEndOk() {
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
if (summary.getTotalCount() == 1 && summary.getImportedTrackIds().size() == 1) {
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_open_track));
viewBinding.importProgressLeftButton.setOnClickListener((view) -> {
Intent newIntent = IntentUtils.newIntent(ImportActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, summary.getImportedTrackIds().get(0));
startActivity(newIntent);
getViewModelStore().clear();
finish();
});
} else {
viewBinding.importProgressLeftButton.setVisibility(View.GONE);
}
}
}
@@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.FileUtils;
public class ImportViewModel extends AndroidViewModel implements ImportServiceResultReceiver.Receiver {
@@ -65,6 +66,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
}
Track.Id trackId = resultData.getParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
@@ -74,6 +76,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
break;
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
summary.importedTrackIds.add(trackId);
summary.successCount++;
break;
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
@@ -92,6 +95,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
private int successCount;
private int existsCount;
private int errorCount;
private ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
private final ArrayList<String> fileErrors = new ArrayList<>();
public int getTotalCount() {
@@ -110,6 +114,10 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
return errorCount;
}
public ArrayList<Track.Id> getImportedTrackIds() {
return importedTrackIds;
}
public ArrayList<String> getFileErrors() {
return fileErrors;
}
@@ -153,7 +153,6 @@ public class KmzTrackImporter implements TrackImporter {
* TODO: May load multiple tracks, but only returns the last Track.Id.
*
* @param inputStream kmz input stream.
* @return null if error or the id of the track otherwise.
*/
private Track.Id findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
@@ -198,29 +197,27 @@ public class KmzTrackImporter implements TrackImporter {
* @param trackId the id of the Track.
*/
private void deleteOrphanImages(Context context, Track.Id trackId) {
if (!trackId.isValid()) {
// 1.- Gets all photo names in the markers of the track identified by id.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Marker> markers = contentProviderUtils.getMarkers(trackId);
List<String> photosName = new ArrayList<>();
for (Marker marker : markers) {
if (marker.hasPhoto()) {
String photoUrl = Uri.decode(marker.getPhotoUrl());
photosName.add(photoUrl.substring(photoUrl.lastIndexOf(File.separatorChar) + 1));
// 1.- Gets all photo names in the markers of the track identified by id.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Marker> markers = contentProviderUtils.getMarkers(trackId);
List<String> photosName = new ArrayList<>();
for (Marker marker : markers) {
if (marker.hasPhoto()) {
String photoUrl = Uri.decode(marker.getPhotoUrl());
photosName.add(photoUrl.substring(photoUrl.lastIndexOf(File.separatorChar) + 1));
}
}
// 2.- Deletes all orphan photos from external storage.
File dir = FileUtils.getPhotoDir(context, trackId);
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (!photosName.contains(file.getName())) {
file.delete();
}
}
// 2.- Deletes all orphan photos from external storage.
File dir = FileUtils.getPhotoDir(context, trackId);
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (!photosName.contains(file.getName())) {
file.delete();
}
}
if (dir.listFiles().length == 0) {
dir.delete();
}
if (dir.listFiles().length == 0) {
dir.delete();
}
}
}
@@ -352,7 +352,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval);
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
@@ -16,10 +16,9 @@
package de.dennisguse.opentracks.util;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.net.Uri;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.format.DateUtils;
import android.view.Gravity;
import android.view.View;
@@ -37,15 +36,13 @@ import de.dennisguse.opentracks.R;
*/
public class ListItemUtils {
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
private ListItemUtils() {
}
/**
* Sets a list item.
*
* @param activity the activity
* @param context the context
* @param view the list item view
* @param isRecording true if recording
* @param isPaused true if paused
@@ -59,23 +56,9 @@ public class ListItemUtils {
* @param useRelativeTime true to display relative time if appropriate
* @param category the category value
* @param description the description value
* @param photoUrl the photo url
* @param hasPhoto true if this list item has photo
*/
public static void setListItem(Activity activity, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, String photoUrl) {
// Set photo
ImageView photo = view.findViewById(R.id.list_item_photo);
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
if (hasPhoto) {
photo.getLayoutParams().height = getPhotoHeight(activity);
photo.setImageResource(android.R.color.transparent);
photo.setImageURI(Uri.parse(photoUrl));
}
public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, boolean hasPhoto) {
// Set icon
if (isRecording) {
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
@@ -84,27 +67,27 @@ public class ListItemUtils {
ImageView iconImageView = view.findViewById(R.id.list_item_icon);
iconImageView.setImageResource(iconId);
iconImageView.setContentDescription(activity.getString(iconContentDescriptionId));
iconImageView.setContentDescription(context.getString(iconContentDescriptionId));
// Set name
TextView nameTextView = view.findViewById(R.id.list_item_name);
setTextView(activity, nameTextView, name, hasPhoto);
setTextView(context, nameTextView, name, hasPhoto);
// Set totalTime/totalDistance
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
String timeDistanceText;
if (isRecording) {
timeDistanceTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
timeDistanceText = activity.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
timeDistanceTextView.setTextColor(context.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
timeDistanceText = context.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
} else {
// Match list_item_time_distance in list_item.xml
timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall);
timeDistanceTextView.setTextAppearance(context, R.style.TextSmall);
timeDistanceText = getTimeDistance(totalTime, totalDistance);
if (markerCount > 0) {
timeDistanceText += " \u2027";
}
}
setTextView(activity, timeDistanceTextView, timeDistanceText, hasPhoto);
setTextView(context, timeDistanceTextView, timeDistanceText, hasPhoto);
// Set markerCount
ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon);
@@ -119,15 +102,15 @@ public class ListItemUtils {
layoutParams.width = lineHeight;
layoutParams.height = lineHeight;
}
setTextView(activity, markerCountTextView, markerCountValue, hasPhoto);
setTextView(context, markerCountTextView, markerCountValue, hasPhoto);
// Set date/time
String[] dateTime = getDateTime(isRecording, activity, startTime, useRelativeTime);
String[] dateTime = getDateTime(isRecording, context, startTime, useRelativeTime);
TextView dateTextView = view.findViewById(R.id.list_item_date);
setTextView(activity, dateTextView, dateTime[0], hasPhoto);
setTextView(context, dateTextView, dateTime[0], hasPhoto);
TextView timeTextView = view.findViewById(R.id.list_item_time);
setTextView(activity, timeTextView, dateTime[1], hasPhoto);
setTextView(context, timeTextView, dateTime[1], hasPhoto);
// Set category and description
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
@@ -135,15 +118,15 @@ public class ListItemUtils {
// Place categoryDescription in either ownerTimeDistanceTextView or categoryDescriptionTextView
if (timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE) {
setTextView(activity, categoryDescriptionTextView, null, hasPhoto);
setTextView(context, categoryDescriptionTextView, null, hasPhoto);
// Match list_item_category_description in list_item.xml
timeDistanceTextView.setSingleLine(false);
timeDistanceTextView.setMaxLines(2);
setTextView(activity, timeDistanceTextView, categoryDescription, hasPhoto);
setTextView(context, timeDistanceTextView, categoryDescription, hasPhoto);
} else {
// Match list_item_time_distance in list_item.xml
timeDistanceTextView.setSingleLine(true);
setTextView(activity, categoryDescriptionTextView, categoryDescription, hasPhoto);
setTextView(context, categoryDescriptionTextView, categoryDescription, hasPhoto);
}
// Adjust iconImageView layout gravity
@@ -151,6 +134,41 @@ public class ListItemUtils {
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
}
private static Bitmap decodeSampledBitmapFromResource(String photoUrl, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(photoUrl, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(photoUrl, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* Gets a string for share owner, total time, and total distance.
*
@@ -220,17 +238,4 @@ public class ListItemUtils {
textView.setShadowLayer(0, 0, 0, 0);
}
}
/**
* Gets the photo height.
*
* @param context the context
*/
private static int getPhotoHeight(Context context) {
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
TypedArray typeArray = context.obtainStyledAttributes(attrs);
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
typeArray.recycle();
return 2 * height;
}
}
+143 -13
View File
@@ -21,9 +21,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_vertical_half"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.7" />
<TextView
android:id="@+id/export_progress_done"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
@@ -36,7 +43,7 @@
<TextView
android:id="@+id/export_progress_slash"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/generic_separator_done_total"
@@ -47,7 +54,7 @@
<TextView
android:id="@+id/export_progress_total"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
@@ -70,17 +77,140 @@
app:layout_constraintTop_toBottomOf="@id/export_progress_done"
tools:progress="25" />
<!-- Start Summary -->
<!-- Tracks exported (new tracks) -->
<TextView
android:id="@+id/export_progress_summary"
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:text="@string/export_progress_review"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/export_progress_summary_new_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/export_progress_summary_new_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/export_progress_bar" />
<TextView
android:id="@+id/export_progress_summary_new"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_new_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/export_progress_summary_new_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/export_progress_summary_new_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="export_progress_summary_new_msg,export_progress_summary_new" />
<androidx.constraintlayout.widget.Group
android:id="@+id/export_progress_summary_new_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="export_progress_summary_new_msg,export_progress_summary_new" />
<!-- Tracks overwritten -->
<TextView
android:id="@+id/export_progress_summary_overwrite_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/export_progress_summary_overwrite_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/export_progress_summary_new_barrier" />
<TextView
android:id="@+id/export_progress_summary_overwrite"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_overwrite_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/export_progress_summary_overwrite_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/export_progress_summary_overwrite_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="export_progress_summary_overwrite_msg,export_progress_summary_overwrite" />
<androidx.constraintlayout.widget.Group
android:id="@+id/export_progress_summary_overwrite_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="export_progress_summary_overwrite_msg,export_progress_summary_overwrite" />
<!-- Tracks skipped -->
<TextView
android:id="@+id/export_progress_summary_skip_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/export_progress_summary_skip_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/export_progress_summary_overwrite_barrier" />
<TextView
android:id="@+id/export_progress_summary_skip"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_skip_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/export_progress_summary_skip_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/export_progress_summary_skip_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="export_progress_summary_skip_msg,export_progress_summary_skip" />
<androidx.constraintlayout.widget.Group
android:id="@+id/export_progress_summary_skip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="export_progress_summary_skip_msg,export_progress_summary_skip" />
<!-- Tracks errors -->
<TextView
android:id="@+id/export_progress_summary_errors_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/export_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/export_progress_summary_skip_barrier" />
<TextView
android:id="@+id/export_progress_summary_errors"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/export_progress_summary_errors_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/export_progress_summary_errors_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="export_progress_summary_errors_msg,export_progress_summary_errors" />
<androidx.constraintlayout.widget.Group
android:id="@+id/export_progress_summary_errors_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="export_progress_summary_errors_msg,export_progress_summary_errors" />
<!-- End Summary -->
<ImageView
android:id="@+id/export_progress_alert_icon"
android:layout_width="wrap_content"
@@ -96,16 +226,16 @@
<TextView
android:id="@+id/export_progress_alert_msg"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toTopOf="@id/export_progress_apply_to_all"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/export_progress_alert_icon"
app:layout_constraintTop_toBottomOf="@id/export_progress_summary"
app:layout_constraintTop_toBottomOf="@id/export_progress_summary_errors_barrier"
app:layout_constraintVertical_chainStyle="packed"
tools:text="The track already exists" />
+111 -14
View File
@@ -19,9 +19,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_vertical_half"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.7" />
<TextView
android:id="@+id/import_progress_done"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
@@ -34,7 +41,7 @@
<TextView
android:id="@+id/import_progress_slash"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/generic_separator_done_total"
@@ -45,7 +52,7 @@
<TextView
android:id="@+id/import_progress_total"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
@@ -68,17 +75,107 @@
app:layout_constraintTop_toBottomOf="@id/import_progress_done"
tools:progress="25" />
<!-- Start Summary -->
<!-- Files imported correctly -->
<TextView
android:id="@+id/import_progress_summary"
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:text="@string/import_progress_review"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/import_progress_summary_ok_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/import_progress_summary_ok_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/import_progress_bar" />
<TextView
android:id="@+id/import_progress_summary_ok"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_ok_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/import_progress_summary_ok_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/import_progress_summary_ok_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="import_progress_summary_ok_msg,import_progress_summary_ok" />
<androidx.constraintlayout.widget.Group
android:id="@+id/import_progress_summary_ok_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="import_progress_summary_ok_msg,import_progress_summary_ok" />
<!-- Already exists files -->
<TextView
android:id="@+id/import_progress_summary_exists_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/import_progress_summary_exists_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/import_progress_summary_ok_barrier" />
<TextView
android:id="@+id/import_progress_summary_exists"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_exists_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/import_progress_summary_exists_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/import_progress_summary_exists_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="import_progress_summary_exists_msg,import_progress_summary_exists" />
<androidx.constraintlayout.widget.Group
android:id="@+id/import_progress_summary_exists_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="import_progress_summary_exists_msg,import_progress_summary_exists" />
<!-- Importing error files -->
<TextView
android:id="@+id/import_progress_summary_errors_msg"
style="@style/TextImportExportSummaryStart"
android:text="@string/import_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/import_progress_summary_exists_barrier" />
<TextView
android:id="@+id/import_progress_summary_errors"
style="@style/TextImportExportSummaryEnd"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
app:layout_constraintTop_toTopOf="@id/import_progress_summary_errors_msg"
tools:text="10" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/import_progress_summary_errors_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="import_progress_summary_errors_msg,import_progress_summary_errors" />
<androidx.constraintlayout.widget.Group
android:id="@+id/import_progress_summary_errors_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="import_progress_summary_errors_msg,import_progress_summary_errors" />
<!-- End Summary -->
<ImageView
android:id="@+id/import_progress_alert_icon"
android:layout_width="wrap_content"
@@ -94,18 +191,18 @@
<TextView
android:id="@+id/import_progress_alert_msg"
style="@style/TextMedium"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/import_progress_alert_icon"
app:layout_constraintTop_toBottomOf="@id/import_progress_summary"
app:layout_constraintTop_toBottomOf="@id/import_progress_summary_errors_barrier"
app:layout_constraintVertical_chainStyle="packed"
tools:text="The file already exists" />
tools:text="Import progress alert message" />
</androidx.constraintlayout.widget.ConstraintLayout>
+2
View File
@@ -36,6 +36,8 @@ limitations under the License.
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha=".6"
android:visibility="gone"
tools:visibility="visible"
android:contentDescription="@string/image_photo_marker"
android:src="@drawable/text_gradient" />
<!-- Table 1 -->
+3 -2
View File
@@ -102,10 +102,11 @@
style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintStart_toEndOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/stats_total_time_label"
app:layout_constraintBottom_toBottomOf="@+id/stats_total_time_label" />
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/stats_moving_time_value"
+42 -41
View File
@@ -36,17 +36,17 @@
style="@style/StatsLargeLabel"
android:text="@string/stats_distance"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/stats_distance_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
app:layout_constraintTop_toBottomOf="@id/stats_distance_label"
tools:text="100" />
@@ -54,8 +54,8 @@
android:id="@+id/stats_distance_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
tools:text="km" />
<!-- Activity Type -->
@@ -64,8 +64,8 @@
style="@style/StatsLargeLabel"
android:text="@string/track_edit_activity_type_hint"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
@@ -73,9 +73,9 @@
style="@style/StatsLargeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="@id/stats_distance_value"
app:tint="?android:textColorPrimary" />
@@ -85,15 +85,15 @@
style="@style/StatsLargeLabel"
android:text="@string/stats_total_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_distance_value" />
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsLargeValue"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_total_time_label"
tools:text="00:00:00" />
@@ -103,17 +103,18 @@
style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintBottom_toBottomOf="@id/stats_total_time_label"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="@id/stats_total_time_label"
app:layout_constraintBottom_toBottomOf="@id/stats_total_time_label" />
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_moving_time_label"
tools:text="00:00:00" />
@@ -130,8 +131,8 @@
android:id="@+id/stats_speed_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_time_barrier" />
<!-- Speed/pace -->
@@ -139,8 +140,8 @@
android:id="@+id/stats_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Speed" />
@@ -148,9 +149,9 @@
android:id="@+id/stats_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_speed_label"
tools:text="30.00" />
@@ -158,8 +159,8 @@
android:id="@+id/stats_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_speed_value"
tools:text="km/h" />
<!-- Average speed/pace -->
@@ -167,8 +168,8 @@
android:id="@+id/stats_average_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Avg. Speed" />
@@ -176,9 +177,9 @@
android:id="@+id/stats_average_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_label"
tools:text="30.00" />
@@ -186,8 +187,8 @@
android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
tools:text="km/h" />
<!-- Speed max -->
@@ -195,8 +196,8 @@
android:id="@+id/stats_max_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Speed" />
@@ -204,9 +205,9 @@
android:id="@+id/stats_max_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_max_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_max_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_max_speed_label"
tools:text="30.00" />
@@ -214,8 +215,8 @@
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_max_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
tools:text="km/h" />
<!-- Moving speed max -->
@@ -223,8 +224,8 @@
android:id="@+id/stats_moving_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Moving Speed" />
@@ -232,9 +233,9 @@
android:id="@+id/stats_moving_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_moving_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_moving_speed_unit"
app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_label"
tools:text="30.00" />
@@ -242,8 +243,8 @@
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_moving_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
tools:text="km/h" />
<!-- Barrier -->
@@ -259,8 +260,8 @@
android:id="@+id/stats_elevation_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
<!-- Elevation -->
@@ -276,17 +277,17 @@
style="@style/StatsSmallLabel"
android:text="@string/stats_elevation"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView
android:id="@+id/stats_elevation_current_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_current_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_current_unit"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_current_label"
tools:text="100" />
@@ -294,8 +295,8 @@
android:id="@+id/stats_elevation_current_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_elevation_current_value"
app:layout_constraintStart_toEndOf="@id/stats_elevation_current_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_elevation_current_value"
tools:text="m" />
<!-- Elevation gain -->
@@ -304,17 +305,17 @@
style="@style/StatsSmallLabel"
android:text="@string/stats_gain"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
<TextView
android:id="@+id/stats_elevation_gain_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_gain_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_gain_unit"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_gain_label"
tools:text="100" />
@@ -322,8 +323,8 @@
android:id="@+id/stats_elevation_gain_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_elevation_gain_value"
app:layout_constraintStart_toEndOf="@id/stats_elevation_gain_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_elevation_gain_value"
tools:text="m" />
<!-- Elevation loss -->
@@ -332,17 +333,17 @@
style="@style/StatsSmallLabel"
android:text="@string/stats_loss"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_gain_value" />
<TextView
android:id="@+id/stats_elevation_loss_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_loss_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/stats_elevation_loss_unit"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_loss_label"
tools:text="100" />
@@ -350,8 +351,8 @@
android:id="@+id/stats_elevation_loss_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_elevation_loss_value"
app:layout_constraintStart_toEndOf="@id/stats_elevation_loss_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_elevation_loss_value"
tools:text="m" />
<!-- Coordinate group -->
@@ -374,8 +375,8 @@
android:id="@+id/stats_coordinate_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_elevation_barrier" />
<!-- Latitude -->
@@ -384,15 +385,15 @@
style="@style/StatsSmallLabel"
android:text="@string/stats_latitude"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
<TextView
android:id="@+id/stats_latitude_value"
style="@style/StatsSmallValue"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_latitude_label"
tools:text="0.834937848" />
@@ -402,16 +403,16 @@
style="@style/StatsSmallLabel"
android:text="@string/stats_longitude"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
<TextView
android:id="@+id/stats_longitude_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_longitude_label"
tools:text="0.834937848" />
@@ -428,8 +429,8 @@
android:id="@+id/stats_sensor_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_location_barrier" />
<!-- Sensors data -->
@@ -437,8 +438,8 @@
android:id="@+id/stats_sensors_recycler_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_horizontal_line" />
</androidx.constraintlayout.widget.ConstraintLayout>
-9
View File
@@ -547,10 +547,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="gps_disabled_msg">GPS vypnuto</string>
<string name="gps_fixed_and_ready">GPS zaměřeno a připraveno</string>
<string name="import_no_kml_file_found">Soubor doc.kml nenalezen v KMZ</string>
<string name="import_progress_review">Celkem importovaných souborů: %1$d
\n - Importovaných souborů: %2$d
\n - Již existuje: %3$d
\n - Chyb: %4$d</string>
<string name="settings_default_trackfileformat">Formát souboru pro export/sdílení</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Na celou obrazovku</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Při nahrávání využívat celou obrazovku.</string>
@@ -583,11 +579,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="export_error_post_workout">Export po ukončení aktivity selhal, zkontrolujte prosím adresář pro export.</string>
<string name="stats_loss">Ztráta</string>
<string name="export_track_already_exists_msg">Trasa %1$s již existuje v cílovém adresáři.</string>
<string name="export_progress_review">Celkem exportovaných tras: %1$d
\n - Nových exportovaných tras: %2$d
\n - Přepsaných tras: %3$d
\n - Přeskočených tras: %4$d
\n - Chyb: %5$d</string>
<string name="generic_overwrite">Přepsat</string>
<string name="generic_show_errors">Zobrazit chyby</string>
<string name="generic_click_twice_cancel">Opakovaně kliknout zpět pro ukončení</string>
-9
View File
@@ -586,11 +586,6 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="settings_default_export_uri_title">Carpeta de pistas exportadas</string>
<string name="open_track_using_dashboard">Abrir con OpenTracks-Dashboard</string>
<string name="export_track_already_exists_msg">La pista %1$s ya existe en la carpeta de destino.</string>
<string name="export_progress_review">Total de pistas exportadas: %1$d
\n - Nuevas pistas exportadas: %2$d
\n - Pistas sobreescritas: %3$d
\n - Pistas ignoradas: %4$d
\n - Errores: %5$d</string>
<string name="generic_overwrite">Sobreescribe</string>
<string name="generic_click_twice_cancel">Clic otra vez para cancelar</string>
<string name="gps_disabled_msg">GPS desactivado</string>
@@ -598,10 +593,6 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="import_unsupported_format">Formato no soportado</string>
<string name="import_parser_error">Error al analizar: %1$s</string>
<string name="import_unable_to_import_file">No se ha podido importar el fichero: %1$s</string>
<string name="import_progress_review">Total de ficheros importados: %1$d
\n - Ficheros importados: %2$d
\n - Ya existen: %3$d
\n - Errores: %4$d</string>
<string name="settings_default_trackfileformat">Formato para exportar/compartir</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Pantalla completa</string>
<string name="interval_list_empty_message">No hay intervalos todavía</string>
-9
View File
@@ -580,20 +580,11 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="instant_export_enabled_title">Exportación instantánea tras o adestramento</string>
<string name="settings_default_export_uri_title">Directorio onde exportar as rutas</string>
<string name="export_track_already_exists_msg">A ruta %1$s xa existe no directorio de destino.</string>
<string name="export_progress_review">Total de rutas exportadas: %1$d
\n- Novas pistas: %2$d
\n- Pistas sobrescritas: %3$d
\n- Pistas omitidas: %4$d
\n- Erros: %5$d</string>
<string name="generic_overwrite">Sobrescribir</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Completado cun erro</item>
<item quantity="other">Completado con %1$d erros</item>
</plurals>
<string name="import_progress_review">Total de ficheiros importados: %1$d
\n- Ficheiros importados: %2$d
\n- Xa existentes: %3$d
\n- Erros: %4$d</string>
<string name="settings_night_mode_title">Decorado IU</string>
<string name="settings_night_mode_option_system">Sistema</string>
<string name="stats_intervals">Intervalos</string>
-9
View File
@@ -548,10 +548,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
</plurals>
<string name="generic_click_twice_cancel">Premi il tasto indietro di nuovo per annullare</string>
<string name="gps_fixed_and_ready">GPS pronto e corretto</string>
<string name="import_progress_review">File importati: %1$d, di cui
\n - %2$d nuovi
\n - %3$d già esistenti
\n - %4$d errori</string>
<string name="menu_insert_gallery_img">Inserisci un immagine dalla galleria</string>
<string name="permission_gps_failed">OpenTracks richiede il permesso di utilizzare il GPS.</string>
<string name="settings_default_trackfileformat">Formato file nell\' esportazione/condivisione</string>
@@ -559,11 +555,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="description_default_speed_or_pace">Per tipo di attività</string>
<string name="export_all_do_it_for_all">Esegui ciò per tutti i conflitti</string>
<string name="export_track_errors">Percorsi non esportati</string>
<string name="export_progress_review">Totale percorsi esportati: %1$d
\n - Nuovi percorsi esportati: %2$d
\n - Percorsi sovrascritti: %3$d
\n - Percorsi saltati: %4$d
\n - Errori avvenuti: %5$d</string>
<string name="generic_overwrite">Sovrascrivere</string>
<string name="generic_skip">Saltare</string>
<string name="generic_separator_done_total">/</string>
+18 -13
View File
@@ -124,7 +124,7 @@ limitations under the License.
<string name="export_option">som %1$s til %2$s</string>
<string name="export_progress_message">Eksporterer til %1$s…</string>
<string name="export_success">%1$s ble eksportert til %2$s</string>
<string name="export_title">Eksporter til …</string>
<string name="export_title">Eksporter til…</string>
<!-- External Storage -->
<!-- Files -->
<string name="files_few">%1$d filer</string>
@@ -288,8 +288,8 @@ limitations under the License.
<!-- Stats -->
<string name="stats_average_moving_pace">Gjennomsnittlig forflytningstempo</string>
<string name="stats_average_moving_speed">Gjennomsnittlig forflytningshastighet</string>
<string name="stats_average_pace">Gjennomsnittstempo</string>
<string name="stats_average_speed">Gjennomsnittshastighet</string>
<string name="stats_average_pace">Gjennomsnittlig tempo</string>
<string name="stats_average_speed">Gjennomsnittlig hastighet</string>
<string name="stats_elevation">Høyde</string>
<string name="stats_fastest_pace">Raskeste tempo</string>
<string name="stats_gain">Total stigning</string>
@@ -511,7 +511,7 @@ limitations under the License.
<string name="hold_to_stop">Hold nede for å stoppe</string>
<string name="toast_message_instant_track_was_exported">Siste spor eksportert</string>
<string name="instant_export_enabled_summary">Eksporter spor etter at opptak er ferdig</string>
<string name="instant_export_enabled_title">Automatisk eksport etter opptak</string>
<string name="instant_export_enabled_title">Auto-eksport etter opptak</string>
<string name="settings_default_export_uri_title">Mappe for sporeksport</string>
<string name="share_image_body">Jeg tror du kan være interessert i dette bildet.</string>
<string name="about_preference_title">Om OpenTracks</string>
@@ -521,16 +521,7 @@ limitations under the License.
<string name="app_not_installed_show_on_map">Installer en app som støtter presentasjon av kartdata.</string>
<string name="export_with_photos">med bilder</string>
<string name="export_track_already_exists_msg">Sporet %1$s finnes allerede i målmappen.</string>
<string name="export_progress_review">Totalt eksporterte spor: %1$d
\n - Nye spor eksportert: %2$d
\n - Spor overskrevet: %3$d
\n - Spor hoppet over: %4$d
\n - Feil: %5$d</string>
<string name="import_unsupported_format">Filformatet støttes ikke</string>
<string name="import_progress_review">Totalt importerte filer: %1$d
\n - Filer importert: %2$d
\n - Finnes allerede: %3$d
\n - Feil: %4$d</string>
<string name="menu_insert_gallery_img">Sett inn galleribilde</string>
<string name="permission_gps_failed">OpenTracks krever tillatelse til å bruke GPS.</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Vis statistikk under opptak uten å låse opp enheten.</string>
@@ -594,4 +585,18 @@ limitations under the License.
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
<string name="value_float_mile_hour">%1$.2f mph</string>
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
<string name="description_elevation_loss">Total nedstigning: %1$d m (%2$d ft)</string>
<string name="stats_loss">Total nedstigning</string>
<string name="import_prevent_reimport">Du har stilt inn å forhindre at spor importeres om igjen</string>
<string name="settings_prevent_reimport_tracks_title">Forhindre reimport av spor</string>
<string name="export_error_post_workout">Eksport etter opptak feilet, sjekk eksportmappen.</string>
<string name="export_progress_summary_new_msg">Nye spor eksportert</string>
<string name="import_progress_summary_ok_msg">Filer importert</string>
<string name="import_no_kml_file_found">Ingen doc.kml-fil funnet i KMZ</string>
<string name="export_progress_summary_overwrite_msg">Spor overskrevet</string>
<string name="export_progress_summary_skip_msg">Spor hoppet over</string>
<string name="export_progress_summary_errors_msg">Feil</string>
<string name="generic_open_track">Åpne spor</string>
<string name="import_progress_summary_exists_msg">Finnes allerede</string>
<string name="import_progress_summary_errors_msg">Feil</string>
</resources>
-9
View File
@@ -469,21 +469,12 @@
\n g) faça tudo o que você acha que ajuda a OpenTracks. </string>
<string name="activity_type_escooter">escooter</string>
<string name="app_not_installed_show_on_map">Instale um aplicativo que suporte a apresentação de dados de mapas.</string>
<string name="export_progress_review">Total de trilhas exportadas: %1$d
\n - Novas trilhas exportadas: %2$d
\n - Trilhas substituídas: %3$d
\n - Trilhas ignoradas: %4$d
\n - Erros: %5$d</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Concluído com %1$d erro</item>
<item quantity="other">Concluído com %1$d erros</item>
</plurals>
<string name="import_unsupported_format">Formato de arquivo não suportado</string>
<string name="import_unable_to_import_file">Não foi possível importar o arquivo: %1$s</string>
<string name="import_progress_review">Total de arquivos importados: %1$d
\n - Arquivos importados: %2$d
\n - Já existe: %3$d
\n - Erros: %4$d</string>
<string name="marker_add_photo_canceled">Cancelado, nenhum marcador de foto adicionado</string>
<string name="menu_insert_gallery_img">Inserir imagem da galeria</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostrar estatísticas na tela de bloqueio</string>
+9 -14
View File
@@ -221,13 +221,10 @@ limitations under the License.
<string name="export_track_already_exists_msg">The track %1$s already exists at the destination directory.</string>
<string name="export_all_do_it_for_all">do it for all conflicts</string>
<string name="export_track_errors">Tracks not exported</string>
<string name="export_progress_review">
Total exported tracks: %1$d\n
- New tracks exported: %2$d\n
- Tracks overwritten: %3$d\n
- Tracks skipped: %4$d\n
- Errors: %5$d
</string>
<string name="export_progress_summary_new_msg">New tracks exported</string>
<string name="export_progress_summary_overwrite_msg">Tracks overwritten</string>
<string name="export_progress_summary_skip_msg">Tracks skipped</string>
<string name="export_progress_summary_errors_msg">Errors</string>
<!-- External Storage -->
<!-- Files -->
<string name="files_few">%1$d files</string>
@@ -248,6 +245,7 @@ limitations under the License.
<string name="generic_name_line">Name: %1$s</string>
<string name="generic_no">No</string>
<string name="generic_ok">OK</string>
<string name="generic_open_track">Open Track</string>
<string name="generic_paused">Paused</string>
<string name="generic_progress_title">Please wait&#8230;</string>
<string name="generic_recording">Recording&#8230;</string>
@@ -304,12 +302,9 @@ limitations under the License.
<string name="import_selection_option">All %1$s</string>
<string name="import_selection_title">Import from external storage</string>
<string name="import_success">Imported %1$s from %2$s</string>
<string name="import_progress_review">
Total imported files: %1$d\n
- Files imported: %2$d\n
- Already exists: %3$d\n
- Errors: %4$d
</string>
<string name="import_progress_summary_ok_msg">Files imported</string>
<string name="import_progress_summary_exists_msg">Already exists</string>
<string name="import_progress_summary_errors_msg">Errors</string>
<!-- Marker -->
<string name="marker_add_error">Unable to insert a marker. No GPS signal. Try again.</string>
<string name="marker_add_success">A marker was inserted</string>
@@ -435,7 +430,7 @@ limitations under the License.
<string name="settings_stats_units_metric">Metric (km, m)</string>
<string name="settings_stats_units_title">Preferred units</string>
<string name="settings_prevent_reimport_tracks_title" translatable="false">Prevent re-import of tracks</string>
<string name="settings_prevent_reimport_tracks_title">Prevent re-import of tracks</string>
<!-- Share Track -->
<string name="share_track_share_file">Share track file</string>
<string name="share_track_share_file_body">I think you might be interested in this track.\n\n%1$s</string>
+16
View File
@@ -241,5 +241,21 @@ limitations under the License.
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextImportExportSummaryStart" parent="TextMediumPrimary">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">12dp</item>
<item name="android:layout_marginEnd">8dp</item>
<item name="android:layout_marginStart">24dp</item>
<item name="android:gravity">end</item>
</style>
<style name="TextImportExportSummaryEnd" parent="TextMediumPrimary">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginEnd">24dp</item>
</style>
<style name="ProgressBar" parent="@style/Widget.AppCompat.ProgressBar.Horizontal" />
</resources>