Marker is now a record.

This commit is contained in:
Dennis Guse
2025-11-29 16:44:33 +01:00
committed by Dennis Guse
parent 9c271972d2
commit 5b4b54694c
22 changed files with 515 additions and 402 deletions
@@ -50,7 +50,6 @@ import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
@@ -319,7 +318,7 @@ public class ContentProviderUtils {
int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ACCURACY);
int accuracyIndex = cursor.getColumnIndexOrThrow(MarkerColumns.HORIZONTAL_ACCURACY);
int bearingIndex = cursor.getColumnIndexOrThrow(MarkerColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL);
@@ -353,37 +352,27 @@ public class ContentProviderUtils {
bearing,
null);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
Marker marker = new Marker(trackId, position);
if (!cursor.isNull(idIndex)) {
marker.setId(new Marker.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(nameIndex)) {
marker.setName(cursor.getString(nameIndex));
}
if (!cursor.isNull(descriptionIndex)) {
marker.setDescription(cursor.getString(descriptionIndex));
}
if (!cursor.isNull(categoryIndex)) {
marker.setCategory(cursor.getString(categoryIndex));
}
if (!cursor.isNull(iconIndex)) {
marker.setIcon(cursor.getString(iconIndex));
}
Uri photoUrl = null;
if (!cursor.isNull(photoUrlIndex)) {
String photoUrl = cursor.getString(photoUrlIndex);
if (photoUrl.isEmpty()) {
if (cursor.getString(photoUrlIndex).isEmpty()) {
// Before v4.18.0: a marker without a picture as URL ""
// TODO Data should be migrated.
marker.setPhotoUrl(null);
photoUrl = null;
} else {
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
photoUrl = Uri.parse(cursor.getString(photoUrlIndex));
}
}
return marker;
return new Marker(
new Marker.Id(cursor.getLong(idIndex)),
new Track.Id(cursor.getLong(trackIdIndex)),
!cursor.isNull(nameIndex) ? cursor.getString(nameIndex) : "",
!cursor.isNull(descriptionIndex) ? cursor.getString(descriptionIndex) : "",
!cursor.isNull(categoryIndex) ? cursor.getString(categoryIndex) : "",
!cursor.isNull(iconIndex) ? cursor.getString(iconIndex) : "",
position,
photoUrl
);
}
public void deleteMarker(Context context, Marker.Id markerId) {
@@ -445,24 +434,26 @@ public class ContentProviderUtils {
* @return true if successful.
*/
public boolean updateMarker(Context context, Marker updateMarker) {
Marker savedMarker = getMarker(updateMarker.getId());
if (!updateMarker.hasPhoto()) {
Marker savedMarker = getMarker(updateMarker.id());
if (updateMarker.photoUrl() == null) {
deleteMarkerPhoto(context, savedMarker);
}
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.getId().id())});
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker, updateMarker.trackId()), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.id().id())});
return rows == 1;
}
public Marker.Id insertMarker(@NonNull Marker marker) {
marker.setId(null);
Uri uri = contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker));
assert marker.id() == null;
assert marker.trackId() != null;
Uri uri = contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker, marker.trackId()));
return new Marker.Id(ContentUris.parseId(uri));
}
private void deleteMarkerPhoto(Context context, Marker marker) {
if (marker != null && marker.hasPhoto()) {
Uri uri = marker.getPhotoUrl();
File file = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), uri);
if (marker != null && marker.photoUrl() != null) {
Uri uri = marker.photoUrl();
File file = MarkerUtils.buildInternalPhotoFile(context, marker.trackId(), uri);
if (file.exists()) {
File parent = file.getParentFile();
file.delete();
@@ -473,33 +464,33 @@ public class ContentProviderUtils {
}
}
ContentValues createContentValues(@NonNull Marker marker) {
ContentValues createContentValues(@NonNull Marker marker, @NonNull Track.Id trackId) {
ContentValues values = new ContentValues();
if (marker.getId() != null) {
values.put(MarkerColumns._ID, marker.getId().id());
if (marker.id() != null) {
values.put(MarkerColumns._ID, marker.id().id());
}
values.put(MarkerColumns.NAME, marker.getName());
values.put(MarkerColumns.DESCRIPTION, marker.getDescription());
values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon());
values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
values.put(MarkerColumns.LONGITUDE, (int) (marker.getPosition().longitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.getPosition().latitude() * 1E6));
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
if (marker.hasAltitude()) {
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
values.put(MarkerColumns.TRACKID, trackId.id());
values.put(MarkerColumns.NAME, marker.name());
values.put(MarkerColumns.DESCRIPTION, marker.description());
values.put(MarkerColumns.CATEGORY, marker.category());
values.put(MarkerColumns.ICON, marker.icon());
values.put(MarkerColumns.LONGITUDE, (int) (marker.position().longitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.position().latitude() * 1E6));
values.put(MarkerColumns.TIME, marker.position().time().toEpochMilli());
if (marker.position().hasAltitude()) {
values.put(MarkerColumns.ALTITUDE, marker.position().altitude().toM());
}
if (marker.hasAccuracy()) {
values.put(MarkerColumns.ACCURACY, marker.getAccuracy().toM());
if (marker.position().hasHorizontalAccuracy()) {
values.put(MarkerColumns.HORIZONTAL_ACCURACY, marker.position().horizontalAccuracy().toM());
}
if (marker.hasBearing()) {
values.put(MarkerColumns.BEARING, marker.getBearing());
if (marker.position().hasBearing()) {
values.put(MarkerColumns.BEARING, marker.position().bearing());
}
if (marker.hasPhoto()) {
values.put(MarkerColumns.PHOTOURL, marker.getPhotoUrl().toString());
}
values.put(MarkerColumns.PHOTOURL, marker.photoUrl() != null ? marker.photoUrl().toString() : null);
return values;
}
@@ -603,7 +594,7 @@ public class ContentProviderUtils {
public int bulkInsertMarkers(List<Marker> markers, Track.Id trackId) {
ContentValues[] values = new ContentValues[markers.size()];
for (int i = 0; i < markers.size(); i++) {
values[i] = createContentValues(markers.get(i));
values[i] = createContentValues(markers.get(i), trackId);
}
return contentResolver.bulkInsert(MarkerColumns.CONTENT_URI, values);
}
@@ -1,19 +1,3 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.data.models;
import android.net.Uri;
@@ -23,151 +7,32 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.Instant;
import java.util.Objects;
/**
* NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
* * NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*/
//TODO All data should be final; no default values.
public final class Marker {
public record Marker(
private Id id;
private String name = "";
private String description = "";
private String category = "";
private String icon = "";
private Track.Id trackId;
@NonNull
Id id,
//Some data might not be used.
private final Position position;
@NonNull
Track.Id trackId,
private Uri photoUrl = null;
@NonNull
String name,
@NonNull
String description,
@NonNull
String category,
@NonNull
String icon,
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.trackId = trackId;
if (!trackPoint.hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location.");
this.position = trackPoint.getPosition();
}
public Marker(@Nullable Track.Id trackId, @NonNull Position position) {
this.trackId = trackId;
Objects.requireNonNull(position);
this.position = position;
}
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String category, String icon, Uri photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.photoUrl = photoUrl;
}
/**
* May be null if the Marker was not loaded from the database.
*/
@Nullable
public Id getId() {
return id;
}
public void setId(Id id) {
this.id = id;
}
public Instant getTime() {
return position.time();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Track.Id getTrackId() {
return trackId;
}
@Deprecated
public void setTrackId(@NonNull Track.Id trackId) {
this.trackId = trackId;
}
public Position getPosition() {
return position;
}
public boolean hasAccuracy() {
return position.hasHorizontalAccuracy();
}
public Distance getAccuracy() {
return position.horizontalAccuracy();
}
public boolean hasAltitude() {
return position.hasAltitude();
}
public Altitude getAltitude() {
return position.altitude();
}
public boolean hasBearing() {
return position.hasBearing();
}
public Float getBearing() {
return position.bearing();
}
public Uri getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(Uri photoUrl) {
this.photoUrl = photoUrl;
}
public boolean hasPhoto() {
return photoUrl != null;
}
//Some data might not be used.
Position position,
@Nullable
Uri photoUrl
) {
public record Id(long id) implements Parcelable {
@NonNull
@@ -0,0 +1,148 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.data.models;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.time.Instant;
import java.util.Objects;
/**
* NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
//TODO All data should be final; no default values.
public final class MarkerBuilder {
private Marker.Id id;
private String name;
private String description;
private String category;
private String icon;
private final Track.Id trackId;
//Some data might not be used.
private final Position position;
private Uri photoUrl = null;
@VisibleForTesting
public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.id = null;
this.trackId = trackId;
this.name = "";
this.description = "";
this.category = "";
this.icon = "";
this.photoUrl = null;
if (!trackPoint.hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location.");
this.position = trackPoint.getPosition();
}
public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String category, String icon, Uri photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.photoUrl = photoUrl;
}
public MarkerBuilder(Marker marker) {
this.id = marker.id();
this.trackId = marker.trackId();
this.name = marker.name();
this.description = marker.description();
this.category = marker.category();
this.icon = marker.icon();
this.position = marker.position();
this.photoUrl = marker.photoUrl();
}
/**
* May be null if the Marker was not loaded from the database.
*/
@Nullable
public Marker.Id getId() {
return id;
}
//TODO Remove
@VisibleForTesting
@Deprecated
public void setId(Marker.Id id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setCategory(String category) {
this.category = category;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Track.Id getTrackId() {
return trackId;
}
public Uri getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(Uri photoUrl) {
this.photoUrl = photoUrl;
}
public boolean hasPhoto() {
return photoUrl != null;
}
public Marker getMarker() {
return new Marker(
id,
trackId,
name,
description,
category,
icon,
position,
photoUrl
);
}
}
@@ -39,13 +39,14 @@ public interface MarkerColumns extends BaseColumns {
String NAME = "name"; // marker name
String DESCRIPTION = "description"; // marker description
String CATEGORY = "category"; // marker category
@Deprecated //TODO Remove: it is always: position
String ICON = "icon"; // marker icon
String TRACKID = "trackid"; // track id
String LONGITUDE = "longitude"; // longitude
String LATITUDE = "latitude"; // latitude
String TIME = "time"; // time
String ALTITUDE = "elevation"; // altitude //TODO RENAME column
String ACCURACY = "accuracy"; // accuracy
String HORIZONTAL_ACCURACY = "accuracy"; // accuracy
String BEARING = "bearing"; // bearing
String PHOTOURL = "photoUrl"; // url for the photo
@@ -61,7 +62,7 @@ public interface MarkerColumns extends BaseColumns {
+ LATITUDE + " INTEGER, "
+ TIME + " INTEGER, "
+ ALTITUDE + " FLOAT, "
+ ACCURACY + " FLOAT, "
+ HORIZONTAL_ACCURACY + " FLOAT, "
+ BEARING + " FLOAT, "
+ PHOTOURL + " TEXT, "
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
@@ -250,14 +250,14 @@ public class GPXTrackExporter implements TrackExporter {
}
private void writeMarker(ZoneOffset zoneOffset, Marker marker) {
printWriter.println("<wpt " + formatLocation(marker.getPosition()) + ">");
if (marker.hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
printWriter.println("<wpt " + formatLocation(marker.position()) + ">");
if (marker.position().hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.position().altitude().toM()) + "</ele>");
}
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime(), zoneOffset) + "</time>");
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(marker.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(marker.getCategory()) + "</type>"); //TODO This is localized; may be better to export in English only. See #1608
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.position().time(), zoneOffset) + "</time>");
printWriter.println("<name>" + StringUtils.formatCData(marker.name()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(marker.description()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(marker.category()) + "</type>"); //TODO This is localized; may be better to export in English only. See #1608
printWriter.println("</wpt>");
}
@@ -266,11 +266,11 @@ public class KMLTrackExporter implements TrackExporter {
}
private void writeMarker(Marker marker, ZoneOffset zoneOffset) {
boolean existsPhoto = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoUrl()) != null;
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
boolean existsPhoto = MarkerUtils.buildInternalPhotoFile(context, marker.trackId(), marker.photoUrl()) != null;
if (marker.photoUrl() != null && exportPhotos && existsPhoto) {
writePhotoOverlay(marker, zoneOffset);
} else {
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getPosition(), marker.getTime(), zoneOffset);
writePlacemark(marker.name(), marker.category(), marker.description(), marker.position(), marker.position().time(), zoneOffset);
}
}
@@ -428,17 +428,17 @@ public class KMLTrackExporter implements TrackExporter {
private void writePhotoOverlay(Marker marker, ZoneOffset zoneOffset) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(marker.getDescription()) + "</description>");
printWriter.println("<name>" + StringUtils.formatCData(marker.name()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(marker.description()) + "</description>");
printWriter.print("<Camera>");
printWriter.print("<longitude>" + marker.getPosition().longitude() + "</longitude>");
printWriter.print("<latitude>" + marker.getPosition().latitude() + "</latitude>");
printWriter.print("<longitude>" + marker.position().longitude() + "</longitude>");
printWriter.print("<latitude>" + marker.position().latitude() + "</latitude>");
printWriter.print("<altitude>20</altitude>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getTime()) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.position().time()) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
writeTypeLocalized(marker.getCategory());
writeTypeLocalized(marker.category());
if (exportPhotos) {
printWriter.println("<Icon><href>" + KMZTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
@@ -452,7 +452,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.print("<topFov>45</topFov>");
printWriter.println("</ViewVolume>");
printWriter.println("<Point>");
printWriter.println("<coordinates>" + getCoordinates(marker.getPosition(), ",") + "</coordinates>");
printWriter.println("<coordinates>" + getCoordinates(marker.position(), ",") + "</coordinates>");
printWriter.println("</Point>");
printWriter.println("</PhotoOverlay>");
}
@@ -95,8 +95,8 @@ public class KMZTrackExporter implements TrackExporter {
if (Thread.interrupted()) throw new InterruptedException();
Marker marker = markerIterator.next();
if (marker.hasPhoto()) {
Uri uriPhoto = marker.getPhotoUrl();
if (marker.photoUrl() != null) {
Uri uriPhoto = marker.photoUrl();
boolean existsPhoto = MarkerUtils.buildInternalPhotoFile(context, track.id(), uriPhoto) != null;
if (existsPhoto) {
addImage(context, zipOutputStream, uriPhoto, marker);
@@ -135,8 +135,8 @@ public class KMZTrackExporter implements TrackExporter {
* Builds and returns the path for the image that will be saved inside KMZ_IMAGES_DIR for the marker.
*/
public static String buildKmzImageFilePath(Marker marker) {
String ext = FileUtils.getExtension(marker.getPhotoUrl());
String ext = FileUtils.getExtension(marker.photoUrl());
ext = ext == null ? "" : "." + ext;
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(marker.getId().id() + ext);
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(marker.id().id() + ext);
}
}
@@ -34,6 +34,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
@@ -429,22 +430,16 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getPosition());
return;
}
Marker marker = new Marker(null, trackPoint);
if (name != null) {
marker.setName(name);
}
if (description != null) {
marker.setDescription(description);
}
if (markerType != null) {
marker.setCategory(markerType);
}
if (photoUrl != null) {
marker.setPhotoUrl(photoUrl);
}
markers.add(marker);
markers.add(new Marker(
null,
null,
name != null ? name : "",
description != null ? description : "",
markerType != null ? markerType : "",
null,
trackPoint.getPosition(),
photoUrl
));
}
private String createErrorMessage(String message) {
@@ -35,6 +35,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
@@ -263,12 +264,16 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
}
position = position.with(whenList.get(0));
Marker marker = new Marker(null, new TrackPoint(TrackPoint.Type.TRACKPOINT, position)); //TODO Creating marker without need
marker.setName(name != null ? name : "");
marker.setDescription(description != null ? description : "");
marker.setCategory(activityTypeLocalized != null ? activityTypeLocalized : "");
marker.setPhotoUrl(photoUrl);
markers.add(marker);
markers.add(new Marker(
null,
null,
name != null ? name : "",
description != null ? description : "",
activityTypeLocalized != null ? activityTypeLocalized : "",
null,
position,
photoUrl
));
name = null;
description = null;
@@ -36,6 +36,7 @@ import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.util.AutoCloseableIterator;
import de.dennisguse.opentracks.util.FileUtils;
@@ -191,8 +192,8 @@ public class KMZTrackImporter {
List<String> photosName = new ArrayList<>();
try (AutoCloseableIterator<Marker> markerIterator = new ContentProviderUtils(context).getMarkerIterator(trackId)) {
markerIterator.forEachRemaining(marker -> {
if (marker.hasPhoto()) {
String photoUrl = Uri.decode(marker.getPhotoUrl().toString()); //TODO Why Uri.decode()?
if (marker.photoUrl() != null) {
String photoUrl = Uri.decode(marker.photoUrl().toString()); //TODO Why Uri.decode()?
photosName.add(photoUrl.substring(photoUrl.lastIndexOf(File.separatorChar) + 1));
}
});
@@ -23,6 +23,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder;
@@ -58,7 +59,7 @@ public class TrackImporter {
// Current track
private TrackData trackData;
private final List<TrackPoint> trackPoints = new LinkedList<>();
private final List<Marker> markers = new LinkedList<>();
private final List<MarkerBuilder> markers = new LinkedList<>();
public TrackImporter(Context context, ContentProviderUtils contentProviderUtils, Distance maxRecordingDistance, boolean preventReimport) {
this.context = context;
@@ -86,7 +87,7 @@ public class TrackImporter {
}
void addMarkers(List<Marker> markers) {
this.markers.addAll(markers);
this.markers.addAll(markers.stream().map(MarkerBuilder::new).toList());
}
void setTrackData(Context context, String name, String uuid, String description, String activityTypeLocalized, String activityTypeId, @Nullable ZoneOffset zoneOffset) {
@@ -165,10 +166,8 @@ public class TrackImporter {
// Store Markers
updateMarkers(trackId);
for (Marker marker : markers)
marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers
contentProviderUtils.bulkInsertMarkers(markers, trackId);
contentProviderUtils.bulkInsertMarkers(markers.stream().map(it -> it.getMarker()).toList(), trackId);
//Clear up.
trackPoints.clear();
@@ -37,6 +37,7 @@ import java.time.Duration;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.GpsStatusValue;
@@ -249,7 +250,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (trackPoint == null) {
return null;
}
Marker marker = new Marker(recordingStatus.trackId(), trackPoint);
Marker marker = new Marker(
null,
recordingStatus.trackId(),
"",
"",
"",
"",
trackPoint.getPosition(),
null
);
return new ContentProviderUtils(this).insertMarker(marker);
}
@@ -94,14 +94,14 @@ public class ShareUtils {
Log.e(TAG, "MarkerId " + markerId.id() + " could not be resolved.");
continue;
}
if (marker.getPhotoUrl() == null) {
if (marker.photoUrl() == null) {
Log.e(TAG, "MarkerId " + markerId.id() + " has no picture.");
continue;
}
mime = context.getContentResolver().getType(marker.getPhotoUrl());
mime = context.getContentResolver().getType(marker.photoUrl());
uris.add(marker.getPhotoUrl());
uris.add(marker.photoUrl());
}
if (uris.isEmpty()) {
@@ -67,14 +67,14 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Marker marker = contentProviderUtils.getMarker(markerId);
markerAutoCloseableIterator = contentProviderUtils.getMarkerIterator(marker.getTrackId());
markerAutoCloseableIterator = contentProviderUtils.getMarkerIterator(marker.trackId());
if (markerAutoCloseableIterator == null) {
finish();
}
int markerIndex = -1;
while (markerAutoCloseableIterator.hasNext()) {
if (markerId.equals(markerAutoCloseableIterator.next().getId())) {
if (markerId.equals(markerAutoCloseableIterator.next().id())) {
markerIndex = markerAutoCloseableIterator.getPosition();
break;
}
@@ -124,7 +124,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@NonNull
public Fragment createFragment(int position) {
markerAutoCloseableIterator.moveToPosition(position);
return MarkerDetailFragment.newInstance(markerAutoCloseableIterator.next().getId());
return MarkerDetailFragment.newInstance(markerAutoCloseableIterator.next().id());
}
@Override
@@ -135,7 +135,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Nullable
public CharSequence getPageTitle(int position) {
markerAutoCloseableIterator.moveToPosition(position);
return getString(R.string.marker_detail_title, position + 1, getItemCount(), markerAutoCloseableIterator.next().getName());
return getString(R.string.marker_detail_title, position + 1, getItemCount(), markerAutoCloseableIterator.next().name());
}
}
}
@@ -111,8 +111,8 @@ public class MarkerDetailFragment extends Fragment {
}
if (item.getItemId() == R.id.marker_detail_share) {
if (marker.hasPhoto()) {
Intent intent = ShareUtils.newShareFileIntent(getContext(), marker.getId());
if (marker.photoUrl() != null) {
Intent intent = ShareUtils.newShareFileIntent(getContext(), marker.id());
intent = Intent.createChooser(intent, null);
startActivity(intent);
}
@@ -180,15 +180,15 @@ public class MarkerDetailFragment extends Fragment {
}
private void updateUi() {
if (marker.hasPhoto()) {
viewBinding.markerDetailMarkerPhoto.setImageURI(marker.getPhotoUrl());
if (marker.photoUrl() != null) {
viewBinding.markerDetailMarkerPhoto.setImageURI(marker.photoUrl());
} else {
viewBinding.markerDetailMarkerPhoto.setImageDrawable(MarkerUtils.getDefaultPhoto(getContext()));
}
viewBinding.markerDetailMarkerCategory.setText(StringUtils.getCategory(marker.getCategory()));
viewBinding.markerDetailMarkerDescription.setText(marker.getDescription());
viewBinding.markerDetailMarkerCategory.setText(StringUtils.getCategory(marker.category()));
viewBinding.markerDetailMarkerDescription.setText(marker.description());
viewBinding.markerDetailMarkerLocation.setText(StringUtils.formatCoordinate(getContext(), marker.getPosition()));
viewBinding.markerDetailMarkerLocation.setText(StringUtils.formatCoordinate(getContext(), marker.position()));
}
}
@@ -47,6 +47,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -181,11 +182,11 @@ public class MarkerEditActivity extends AbstractActivity {
private void loadMarkerData(Marker data) {
marker = data;
viewBinding.markerEditName.setText(marker.getName());
viewBinding.markerEditMarkerType.setText(marker.getCategory());
viewBinding.markerEditDescription.setText(marker.getDescription());
if (marker.hasPhoto()) {
setMarkerImageView(marker.getPhotoUrl());
viewBinding.markerEditName.setText(marker.name());
viewBinding.markerEditMarkerType.setText(marker.category());
viewBinding.markerEditDescription.setText(marker.description());
if (marker.photoUrl() != null) {
setMarkerImageView(marker.photoUrl());
} else {
viewBinding.markerEditPhoto.setImageDrawable(null);
}
@@ -247,7 +248,7 @@ public class MarkerEditActivity extends AbstractActivity {
* If a photo is set then one's options are shown, otherwise another ones are shown.
*/
private void hideAndShowOptions() {
boolean isPhotoSet = (marker != null && marker.hasPhoto());
boolean isPhotoSet = (marker != null && marker.photoUrl() != null);
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
insertPhotoMenuItem.setVisible(!isPhotoSet);
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
@@ -259,7 +260,7 @@ public class MarkerEditActivity extends AbstractActivity {
* Returns the trackId; either from track or marker.
*/
private Track.Id getTrackId() {
return trackId == null ? marker.getTrackId() : trackId;
return trackId == null ? marker.trackId() : trackId;
}
private void setMarkerImageView(@NonNull Uri uri) {
@@ -20,6 +20,7 @@ import java.util.NoSuchElementException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.util.FileUtils;
@@ -40,8 +41,8 @@ public class MarkerEditViewModel extends AndroidViewModel {
markerData = new MutableLiveData<>();
Marker marker = new ContentProviderUtils(getApplication()).getMarker(markerId);
if (marker.hasPhoto()) {
photoOriginalUri = marker.getPhotoUrl();
if (marker.photoUrl() != null) {
photoOriginalUri = marker.photoUrl();
}
markerData.postValue(marker);
@@ -63,20 +64,20 @@ public class MarkerEditViewModel extends AndroidViewModel {
return;
}
File photoFile = MarkerUtils.getPhotoFileIfExists(getApplication(), markerData.getValue().getTrackId(), photoUri);
File photoFile = MarkerUtils.getPhotoFileIfExists(getApplication(), markerData.getValue().trackId(), photoUri);
if (photoFile != null) {
FileUtils.deleteDirectoryRecurse(photoFile);
}
}
private void deletePhoto(Marker marker) {
if (marker.hasPhoto()) {
deletePhoto(marker.getPhotoUrl());
if (marker.photoUrl() != null) {
deletePhoto(marker.photoUrl());
}
}
public void onPhotoDelete(String name, String category, String description) {
Marker marker = getMarker();
MarkerBuilder marker = new MarkerBuilder(getMarker());
if (marker.hasPhoto()) {
if (!marker.getPhotoUrl().equals(photoOriginalUri)) {
deletePhoto(marker.getPhotoUrl());
@@ -85,21 +86,21 @@ public class MarkerEditViewModel extends AndroidViewModel {
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
markerData.postValue(marker.getMarker());
}
}
public void onNewCameraPhoto(@NonNull Uri photoUri, String name, String category, String description) {
Marker marker = getMarker();
MarkerBuilder marker = new MarkerBuilder(getMarker());
marker.setPhotoUrl(photoUri);
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
markerData.postValue(marker.getMarker());
}
public void onNewGalleryPhoto(@NonNull Uri srcUri, String name, String category, String description) {
Marker marker = getMarker();
MarkerBuilder marker = new MarkerBuilder(getMarker());
try (ParcelFileDescriptor parcelFd = getApplication().getContentResolver().openFileDescriptor(srcUri, "r")) {
FileDescriptor srcFd = parcelFd.getFileDescriptor();
@@ -111,7 +112,8 @@ public class MarkerEditViewModel extends AndroidViewModel {
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
markerData.postValue(marker.getMarker());
} catch (IOException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(getApplication(), R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
@@ -119,15 +121,15 @@ public class MarkerEditViewModel extends AndroidViewModel {
}
public void onDone(String name, String category, String description) {
Marker marker = getMarker();
MarkerBuilder marker = new MarkerBuilder(getMarker());
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
if (marker.getId() == null) {
new ContentProviderUtils(getApplication()).insertMarker(marker);
new ContentProviderUtils(getApplication()).insertMarker(marker.getMarker());
} else {
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker.getMarker());
}
if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoUrl()))) {
@@ -143,7 +145,15 @@ public class MarkerEditViewModel extends AndroidViewModel {
String name = getApplication().getString(R.string.marker_name_format, nextMarkerNumber + 1);
String icon = getApplication().getString(R.string.marker_icon_url);
Marker marker = new Marker(trackId, trackPoint, name, "", "", icon, null);
Marker marker = new Marker(
null,
trackId,
name,
"",
icon,
"",
trackPoint.getPosition(),
null);
if (markerData == null) {
markerData = new MutableLiveData<>();
@@ -158,7 +168,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
// it's new marker -> clean all photos.
deletePhoto(marker);
deletePhoto(photoOriginalUri);
} else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoUrl().equals(photoOriginalUri))) {
} else if (photoOriginalUri == null || (marker.photoUrl() != null && !marker.photoUrl().equals(photoOriginalUri))) {
// it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case).
deletePhoto(marker);
}
@@ -137,7 +137,7 @@ public class MarkerListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
public void setAllSelected(boolean isSelected) {
if (isSelected) {
for (Marker marker : markers) {
selection.put((int) marker.getId().id(), true);
selection.put((int) marker.id().id(), true);
}
} else {
selection.clear();
@@ -179,24 +179,24 @@ public class MarkerListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
public void bind(Marker marker) {
this.marker = marker;
viewBinding.markerListItemPhoto.setVisibility(marker.hasPhoto() ? View.VISIBLE : View.GONE);
if (marker.hasPhoto()) {
viewBinding.markerListItemPhoto.setVisibility(marker.photoUrl() != null ? View.VISIBLE : View.GONE);
if (marker.photoUrl() != null) {
int height = ThemeUtils.getPhotoHeight(context);
ViewGroup.LayoutParams params = viewBinding.markerListItemPhoto.getLayoutParams();
params.height = height;
viewBinding.markerListItemPhoto.setLayoutParams(params);
asyncLoadPhoto(viewBinding.markerListItemPhoto, marker.getPhotoUrl(), marker.getId());
asyncLoadPhoto(viewBinding.markerListItemPhoto, marker.photoUrl(), marker.id());
}
viewBinding.markerListItemName.setText(marker.getName());
viewBinding.markerListItemName.setText(marker.name());
{
ZoneOffset timeZone = new ContentProviderUtils(context).getTrack(marker.getTrackId()).zoneOffset();
ListItemUtils.setDateAndTime(context, viewBinding.markerListItemDate, viewBinding.markerListItemTime, marker.getTime(), timeZone);
ZoneOffset timeZone = new ContentProviderUtils(context).getTrack(marker.trackId()).zoneOffset();
ListItemUtils.setDateAndTime(context, viewBinding.markerListItemDate, viewBinding.markerListItemTime, marker.position().time(), timeZone);
}
String categoryDescription = StringUtils.getCategoryDescription(marker.getCategory(), marker.getDescription());
String categoryDescription = StringUtils.getCategoryDescription(marker.category(), marker.description());
viewBinding.markerListItemTimeDistance.setText(categoryDescription);
setSelected(selection.get((int) getId()));
@@ -208,7 +208,7 @@ public class MarkerListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
public long getId() {
return marker.getId().id();
return marker.id().id();
}
@Override
@@ -218,7 +218,7 @@ public class MarkerListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
actionMode.invalidate();
} else {
Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, marker.getId());
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, marker.id());
context.startActivity(intent);
}
}
@@ -20,11 +20,13 @@ import android.content.Context;
import android.content.Intent;
import android.content.UriPermission;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder;
/**
* Utilities for creating intents.
@@ -55,9 +57,9 @@ public class IntentUtils {
*/
public static void showCoordinateOnMap(Context context, Marker marker) {
//SEE https://developer.android.com/guide/components/intents-common.html#Maps
String uri = "geo:0,0?q=" + marker.getPosition().latitude() + "," + marker.getPosition().longitude();
if (marker.getName() != null && !marker.getName().isEmpty()) {
uri += "(" + marker.getName() + ")";
String uri = "geo:0,0?q=" + marker.position().latitude() + "," + marker.position().longitude();
if (!TextUtils.isEmpty(marker.name())) {
uri += "(" + marker.name() + ")";
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(uri));