Skip to content

Commit

Permalink
Merge branch 'support_links_in_gpx_waypoints'
Browse files Browse the repository at this point in the history
  • Loading branch information
simonpoole committed Dec 6, 2024
2 parents d9816dc + e7f7a79 commit 2a62b46
Show file tree
Hide file tree
Showing 9 changed files with 414 additions and 69 deletions.
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

0 comments on commit 2a62b46

Please sign in to comment.