Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Link elements in Waypoints #2740

Merged
merged 2 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/androidTest/java/de/blau/android/gpx/GpxTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import android.location.Location;
import android.location.LocationManager;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
Expand Down Expand Up @@ -343,6 +344,55 @@ public void importWayPoints() {
fail(ex.getMessage());
}
}

/**
* Import a track file with waypoints with links
*/
// @SdkSuppress(minSdkVersion = 26)
@Test
public void importWayPointsWithLinks() {
try {
File zippedGpxFile = JavaResources.copyFileFromResources(ApplicationProvider.getApplicationContext(), "2011-06-08_13-21-55 OT.zip", null, "/");
assertTrue(FileUtil.unpackZip(FileUtil.getPublicDirectory(FileUtil.getPublicDirectory(), "/").getAbsolutePath() + "/", zippedGpxFile.getName()));
assertTrue(TestUtils.clickResource(device, true, device.getCurrentPackageName() + ":id/layers", true));
assertTrue(TestUtils.clickButton(device, device.getCurrentPackageName() + ":id/add", true));
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.layer_add_gpx), true, false));
TestUtils.selectFile(device, main, "2011-06-08_13-21-55 OT", "2011-06-08_13-21-55.gpx", true);
TestUtils.textGone(device, "Imported", 10000);
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.okay), true, false));
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.Done), true, false));
MapViewLayer foundLayer = null;
for (MapViewLayer layer : main.getMap().getLayers()) {
if (layer instanceof de.blau.android.layer.gpx.MapOverlay && "2011-06-08_13-21-55.gpx".equals(layer.getName())) {
foundLayer = layer;
break;
}
}
assertNotNull(foundLayer);

Track track = ((de.blau.android.layer.gpx.MapOverlay)foundLayer).getTrack();
assertEquals(3, track.getWayPoints().size());
WayPoint wp = track.getWayPoints().get(0);

Map map = main.getMap();
ViewBox viewBox = map.getViewBox();
App.getLogic().setZoom(map, 19);
viewBox.moveTo(map, wp.getLon(), wp.getLat()); // NOSONAR
map.invalidate();

TestUtils.unlock(device);

TestUtils.clickAtCoordinates(device, map, wp.getLon(), wp.getLat(), true);
assertTrue(TestUtils.findText(device, false, "2011-06-08_13-22-47.3gpp", 1000, true));
assertTrue(TestUtils.clickText(device, false, "2011-06-08_13-22-47.3gpp", true, false));
// unblear what an assertion should look like here
TestUtils.sleep(10000);
device.pressBack();
} catch (Exception ex) {
fail(ex.getMessage());
}
}


/**
* Replay a track and pretend the output are network generated locations
Expand Down
31 changes: 28 additions & 3 deletions src/main/java/de/blau/android/dialogs/TableLayoutUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,23 @@ public static TableRow createRow(@NonNull Context context, int cell1, @Nullable
@SuppressLint("NewApi")
@NonNull
public static TableRow createRow(@NonNull Context context, int cell1, @Nullable CharSequence cell2, boolean isUrl, @NonNull TableLayout.LayoutParams tp) {
return createRow(context, context.getString(cell1), cell2, isUrl, null, tp);
}

/**
* Get a new TableRow with the provided contents - two columns
*
* @param context an Android Context
* @param cell1 a string for the first cell
* @param cell2 text for the second cell
* @param isUrl if true don't allow C&P on the values so that they can be clicked on
* @param tp LayoutParams for the row
* @return a TableRow
*/
@SuppressLint("NewApi")
@NonNull
public static TableRow createRow(@NonNull Context context, @NonNull CharSequence cell1, @Nullable CharSequence cell2, boolean isUrl,
@Nullable View.OnClickListener listener, @NonNull TableLayout.LayoutParams tp) {
TableRow tr = new TableRow(context);
TextView cell = new TextView(context);
cell.setMinEms(FIRST_CELL_WIDTH);
Expand All @@ -343,13 +360,20 @@ public static TableRow createRow(@NonNull Context context, int cell1, @Nullable
cell.setTypeface(null, Typeface.BOLD);
}
cell.setEllipsize(TruncateAt.MARQUEE);
cell.setTextIsSelectable(true);
tr.addView(cell);
TableRow.LayoutParams trp = new TableRow.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (cell2 == null) {
trp.span = 2;
}
addCell(context, cell2, isUrl, tr, trp);

if (listener != null) {
SpannableString span = new SpannableString(cell2);
ThemeUtils.setSpanColor(context, span, android.R.attr.textColorLink, R.color.ccc_blue);
addCell(context, span, isUrl, tr, trp).setOnClickListener(listener);
tr.setOnClickListener(listener);
} else {
addCell(context, cell2, isUrl, tr, trp);
}
tr.setLayoutParams(tp);
return tr;
}
Expand All @@ -365,7 +389,8 @@ public static TableRow createRow(@NonNull Context context, int cell1, @Nullable
* @return the TextView added
*/
@NonNull
private static TextView addCell(@NonNull Context context, @Nullable CharSequence cellText, boolean isUrl, TableRow tr, @Nullable TableRow.LayoutParams tp) {
private static TextView addCell(@NonNull Context context, @Nullable CharSequence cellText, boolean isUrl, @NonNull TableRow tr,
@Nullable TableRow.LayoutParams tp) {
TextView cell = new TextView(context);
if (cellText != null) {
cell.setText(cellText);
Expand Down
132 changes: 101 additions & 31 deletions src/main/java/de/blau/android/dialogs/ViewWayPoint.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package de.blau.android.dialogs;

import static de.blau.android.contract.Constants.LOG_TAG_LEN;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
Expand All @@ -33,6 +41,7 @@
import de.blau.android.presets.PresetElement;
import de.blau.android.presets.PresetElementPath;
import de.blau.android.presets.PresetGroup;
import de.blau.android.util.ContentResolverUtil;
import de.blau.android.util.DateFormatter;
import de.blau.android.util.ImmersiveDialogFragment;
import de.blau.android.util.ScreenMessage;
Expand All @@ -47,25 +56,29 @@
*/
public class ViewWayPoint extends ImmersiveDialogFragment {

private static final String WAYPOINT = "waypoint";

private static final String DEBUG_TAG = ViewWayPoint.class.getSimpleName().substring(0, Math.min(23, ViewWayPoint.class.getSimpleName().length()));
private static final int TAG_LEN = Math.min(LOG_TAG_LEN, ViewWayPoint.class.getSimpleName().length());
private static final String DEBUG_TAG = ViewWayPoint.class.getSimpleName().substring(0, TAG_LEN);

private static final String TAG = "fragment_view_waypoint";

private static final String URI_KEY = "uri";
private static final String WAYPOINT_KEY = "waypoint";

private WayPoint wp = null;
private String uriString;

/**
* Show dialog for a WayPoint
*
* @param activity the calling activity
* @param uriString String version of uri of the enclosing file
* @param wp the WayPoint
*/
public static void showDialog(FragmentActivity activity, WayPoint wp) {
public static void showDialog(@NonNull FragmentActivity activity, @NonNull String uriString, @NonNull WayPoint wp) {
dismissDialog(activity);
try {
FragmentManager fm = activity.getSupportFragmentManager();
ViewWayPoint elementInfoFragment = newInstance(wp);
ViewWayPoint elementInfoFragment = newInstance(uriString, wp);
elementInfoFragment.show(fm, TAG);
} catch (IllegalStateException isex) {
Log.e(DEBUG_TAG, "showDialog", isex);
Expand All @@ -77,21 +90,24 @@ public static void showDialog(FragmentActivity activity, WayPoint wp) {
*
* @param activity the calling activity
*/
private static void dismissDialog(FragmentActivity activity) {
private static void dismissDialog(@NonNull FragmentActivity activity) {
de.blau.android.dialogs.Util.dismissDialog(activity, TAG);
}

/**
* Create a new instance of this dialog
*
* @param uriString String version of uri of the enclosing file
* @param wp the WayPoint
*
* @return the FragmentDialog instance
*/
private static ViewWayPoint newInstance(WayPoint wp) {
private static ViewWayPoint newInstance(@NonNull String uriString, @NonNull WayPoint wp) {
ViewWayPoint f = new ViewWayPoint();

Bundle args = new Bundle();
args.putSerializable(WAYPOINT, wp);
args.putString(URI_KEY, uriString);
args.putSerializable(WAYPOINT_KEY, wp);

f.setArguments(args);
f.setShowsDialog(true);
Expand All @@ -111,9 +127,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
if (savedInstanceState != null) {
Log.d(DEBUG_TAG, "restoring from saved state");
wp = de.blau.android.util.Util.getSerializeable(savedInstanceState, WAYPOINT, WayPoint.class);
uriString = savedInstanceState.getString(URI_KEY);
wp = de.blau.android.util.Util.getSerializeable(savedInstanceState, WAYPOINT_KEY, WayPoint.class);
} else {
wp = de.blau.android.util.Util.getSerializeable(getArguments(), WAYPOINT, WayPoint.class);
uriString = getArguments().getString(URI_KEY);
wp = de.blau.android.util.Util.getSerializeable(getArguments(), WAYPOINT_KEY, WayPoint.class);
}

FragmentActivity activity = getActivity();
Expand All @@ -123,35 +141,50 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(activity);

ScrollView sv = (ScrollView) inflater.inflate(R.layout.element_info_view, null, false);
if (wp == null) {
Log.e(DEBUG_TAG, "Null WayPoint");
return builder.create();
}
TableLayout tl = (TableLayout) sv.findViewById(R.id.element_info_vertical_layout);

TableLayout.LayoutParams tp = new TableLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tp.setMargins(10, 2, 10, 2);

if (wp != null) {
tl.setColumnShrinkable(1, true);
if (wp.getName() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.name, wp.getName(), tp));
}
if (wp.getDescription() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.description, wp.getDescription(), tp));
}
if (wp.getType() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.type, wp.getType(), tp));
}
long timestamp = wp.getTime();
if (timestamp > 0) {
tl.addView(
TableLayoutUtils.createRow(activity, R.string.created, DateFormatter.getUtcFormat(OsmParser.TIMESTAMP_FORMAT).format(timestamp), tp));
}
tl.setColumnShrinkable(1, true);
if (wp.getName() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.name, wp.getName(), tp));
}
if (wp.getDescription() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.description, wp.getDescription(), tp));
}
if (wp.getType() != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.type, wp.getType(), tp));
}
long timestamp = wp.getTime();
if (timestamp > 0) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.created, DateFormatter.getUtcFormat(OsmParser.TIMESTAMP_FORMAT).format(timestamp), tp));
}

tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lon_label, String.format(Locale.US, "%.7f", wp.getLongitude()) + "°", tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lat_label, String.format(Locale.US, "%.7f", wp.getLatitude()) + "°", tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lon_label, String.format(Locale.US, "%.7f", wp.getLongitude()) + "°", tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lat_label, String.format(Locale.US, "%.7f", wp.getLatitude()) + "°", tp));

if (wp.hasAltitude()) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.altitude, String.format(Locale.US, "%.0f", wp.getAltitude()) + "m", tp));
if (wp.hasAltitude()) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.altitude, String.format(Locale.US, "%.0f", wp.getAltitude()) + "m", tp));
}
Uri gpxUri = Uri.parse(uriString);
List<WayPoint.Link> links = wp.getLinks();
if (de.blau.android.util.Util.notEmpty(links)) {
for (WayPoint.Link link : links) {
final String description = link.getDescription();
TableRow row = TableLayoutUtils.createRow(activity, getString(R.string.waypoint_link),
de.blau.android.util.Util
.notEmpty(description) ? description : link.getUrl(), false,
(View v) -> playLinkUri(activity, gpxUri, link), tp);
tl.addView(row);
row.requestFocus();
}
}

builder.setView(sv);
builder.setTitle(R.string.waypoint_title);
builder.setPositiveButton(R.string.create_osm_object, (dialog, which) -> createObjectFromWayPoint(wp, false));
Expand All @@ -161,6 +194,42 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
return builder.create();
}

/**
* Attempt to play/view whatever is linked to in the Link
*
* This uses a hack to find the content Uri for the file which is dubious
*
* @param context an Android Context
* @param gpxUri the URI for the GPX file
* @param link the Link Element
*/
private void playLinkUri(@NonNull Context context, @NonNull Uri gpxUri, @NonNull WayPoint.Link link) {
Uri uri = Uri.parse(link.getUrl());
if (uri.getScheme() != null) {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(uri));
return;
}
Uri actualUri = Uri.parse(ContentResolverUtil.getPath(context, gpxUri));
Uri.Builder uriBuilder = new Uri.Builder();
List<String> pathSegments = actualUri.getPathSegments();
for (String segment : pathSegments.subList(0, pathSegments.size() - 1)) {
uriBuilder.appendPath(segment);
}
for (String segment : uri.getPathSegments()) {
uriBuilder.appendPath(segment);
}
uri = uriBuilder.build();
// the following is a hack suggested in
// https://stackoverflow.com/questions/7305504/convert-file-uri-to-content-uri
MediaScannerConnection.scanFile(getContext(), new String[] { uri.getPath() }, null, (String s, Uri scanUri) -> {
if (scanUri == null) {
ScreenMessage.barError(getActivity(), getString(R.string.toast_file_not_found, s));
return;
}
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(scanUri));
});
}

/**
* Create a Node from information in and the position of a way point
*
Expand Down Expand Up @@ -199,6 +268,7 @@ private void createObjectFromWayPoint(final WayPoint wp, final boolean useSearch
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(WAYPOINT, wp);
outState.putString(URI_KEY, uriString);
outState.putSerializable(WAYPOINT_KEY, wp);
}
}
Loading
Loading