diff --git a/oeffi/build.gradle b/oeffi/build.gradle
index a7f25d7..0c62bf5 100644
--- a/oeffi/build.gradle
+++ b/oeffi/build.gradle
@@ -16,6 +16,7 @@ dependencies {
compile 'com.squareup.okhttp3:okhttp:3.11.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.11.0'
compile 'com.google.guava:guava:26.0-android'
+ compile 'org.osmdroid:osmdroid-android:6.0.2'
compile 'org.slf4j:slf4j-api:1.7.25'
compile 'com.github.tony19:logback-android:1.3.0-2'
compile 'com.google.code.findbugs:jsr305:3.0.0'
diff --git a/oeffi/res/layout/directions_content.xml b/oeffi/res/layout/directions_content.xml
index 137b81c..33a3ccd 100644
--- a/oeffi/res/layout/directions_content.xml
+++ b/oeffi/res/layout/directions_content.xml
@@ -88,4 +88,35 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/oeffi/res/layout/directions_trip_details_content.xml b/oeffi/res/layout/directions_trip_details_content.xml
index 82b066a..0bead36 100644
--- a/oeffi/res/layout/directions_trip_details_content.xml
+++ b/oeffi/res/layout/directions_trip_details_content.xml
@@ -134,4 +134,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/oeffi/res/layout/network_picker_content.xml b/oeffi/res/layout/network_picker_content.xml
index adf5ed9..3ed4040 100644
--- a/oeffi/res/layout/network_picker_content.xml
+++ b/oeffi/res/layout/network_picker_content.xml
@@ -65,4 +65,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/oeffi/res/layout/stations_content.xml b/oeffi/res/layout/stations_content.xml
index 7892cd0..3cabe7f 100644
--- a/oeffi/res/layout/stations_content.xml
+++ b/oeffi/res/layout/stations_content.xml
@@ -398,4 +398,35 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/oeffi/res/layout/stations_station_details_content.xml b/oeffi/res/layout/stations_station_details_content.xml
index 2b68ab3..8bdd011 100644
--- a/oeffi/res/layout/stations_station_details_content.xml
+++ b/oeffi/res/layout/stations_station_details_content.xml
@@ -74,4 +74,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/oeffi/res/values-large/dimens.xml b/oeffi/res/values-large/dimens.xml
index cbc827a..0d9b52e 100644
--- a/oeffi/res/values-large/dimens.xml
+++ b/oeffi/res/values-large/dimens.xml
@@ -25,6 +25,8 @@
- 75%
+ 390dp
+
6
\ No newline at end of file
diff --git a/oeffi/res/values-w420dp/dimens.xml b/oeffi/res/values-w420dp/dimens.xml
new file mode 100644
index 0000000..c7b5152
--- /dev/null
+++ b/oeffi/res/values-w420dp/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ @dimen/layout_list_width
+
+ true
+
+
\ No newline at end of file
diff --git a/oeffi/res/values-xlarge/dimens.xml b/oeffi/res/values-xlarge/dimens.xml
index a72a1f5..e95eba0 100644
--- a/oeffi/res/values-xlarge/dimens.xml
+++ b/oeffi/res/values-xlarge/dimens.xml
@@ -27,4 +27,6 @@
- 70%
8sp
+ 420dp
+
\ No newline at end of file
diff --git a/oeffi/res/values/dimens.xml b/oeffi/res/values/dimens.xml
index 5f32abb..75f2621 100644
--- a/oeffi/res/values/dimens.xml
+++ b/oeffi/res/values/dimens.xml
@@ -42,7 +42,10 @@
12dp
24dp
300dp
+ 360dp
+ true
+ false
false
5
diff --git a/oeffi/src/de/schildbach/oeffi/Application.java b/oeffi/src/de/schildbach/oeffi/Application.java
index 2c3468e..4f4d0db 100644
--- a/oeffi/src/de/schildbach/oeffi/Application.java
+++ b/oeffi/src/de/schildbach/oeffi/Application.java
@@ -21,6 +21,8 @@ import java.io.File;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import org.osmdroid.config.Configuration;
+import org.osmdroid.config.IConfigurationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,6 +86,8 @@ public class Application extends android.app.Application {
throw new RuntimeException(x);
}
+ initMaps();
+
log.info("=== Starting app version {} ({})", packageInfo.versionName, packageInfo.versionCode);
final Stopwatch watch = Stopwatch.createStarted();
@@ -185,6 +189,12 @@ public class Application extends android.app.Application {
log.setLevel(Level.DEBUG);
}
+ private void initMaps() {
+ final IConfigurationProvider config = Configuration.getInstance();
+ config.setOsmdroidBasePath(new File(getCacheDir(), "org.osmdroid"));
+ config.setUserAgentValue(getPackageName());
+ }
+
private void migrateSelectedNetwork(final String fromName, final NetworkId to) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
diff --git a/oeffi/src/de/schildbach/oeffi/Constants.java b/oeffi/src/de/schildbach/oeffi/Constants.java
index 4ee1e4b..a912705 100644
--- a/oeffi/src/de/schildbach/oeffi/Constants.java
+++ b/oeffi/src/de/schildbach/oeffi/Constants.java
@@ -47,6 +47,8 @@ public class Constants {
public static final int MAX_NUMBER_OF_STOPS = 150;
public static final int MAX_HISTORY_ENTRIES = 50;
public static final float BEARING_ACCURACY_THRESHOLD = 0.5f;
+ public static final double INITIAL_MAP_ZOOM_LEVEL_NETWORK = 12.0;
+ public static final double INITIAL_MAP_ZOOM_LEVEL = 17.0;
public static final int MAX_TRIES_ON_IO_PROBLEM = 2;
public static final Locale DEFAULT_LOCALE = Locale.GERMAN;
diff --git a/oeffi/src/de/schildbach/oeffi/OeffiActivity.java b/oeffi/src/de/schildbach/oeffi/OeffiActivity.java
index 95988e3..416b43c 100644
--- a/oeffi/src/de/schildbach/oeffi/OeffiActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/OeffiActivity.java
@@ -29,11 +29,15 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
import android.widget.TextView;
public abstract class OeffiActivity extends Activity {
@@ -50,6 +54,27 @@ public abstract class OeffiActivity extends Activity {
prefs = PreferenceManager.getDefaultSharedPreferences(this);
}
+ protected void updateFragments(final int listFrameResId, final int mapFrameResId) {
+ final Resources res = getResources();
+
+ final View listFrame = findViewById(listFrameResId);
+ final boolean listShow = res.getBoolean(R.bool.layout_list_show);
+ listFrame.setVisibility(isInMultiWindowMode() || listShow ? View.VISIBLE : View.GONE);
+
+ final View mapFrame = findViewById(mapFrameResId);
+ final boolean mapShow = res.getBoolean(R.bool.layout_map_show);
+ mapFrame.setVisibility(!isInMultiWindowMode() && mapShow ? View.VISIBLE : View.GONE);
+
+ listFrame.getLayoutParams().width = listShow && mapShow ? res.getDimensionPixelSize(R.dimen.layout_list_width)
+ : LinearLayout.LayoutParams.MATCH_PARENT;
+
+ final ViewGroup navigationDrawer = (ViewGroup) findViewById(R.id.navigation_drawer_layout);
+ if (navigationDrawer != null) {
+ final View child = navigationDrawer.getChildAt(1);
+ child.getLayoutParams().width = res.getDimensionPixelSize(R.dimen.layout_navigation_drawer_width);
+ }
+ }
+
protected String prefsGetNetwork() {
return prefs.getString(Constants.PREFS_KEY_NETWORK_PROVIDER, null);
}
diff --git a/oeffi/src/de/schildbach/oeffi/OeffiMapView.java b/oeffi/src/de/schildbach/oeffi/OeffiMapView.java
new file mode 100644
index 0000000..81e95e0
--- /dev/null
+++ b/oeffi/src/de/schildbach/oeffi/OeffiMapView.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.schildbach.oeffi;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.osmdroid.api.IGeoPoint;
+import org.osmdroid.util.BoundingBox;
+import org.osmdroid.util.GeoPoint;
+import org.osmdroid.views.MapView;
+import org.osmdroid.views.Projection;
+import org.osmdroid.views.overlay.Overlay;
+
+import de.schildbach.oeffi.stations.LineView;
+import de.schildbach.oeffi.stations.Station;
+import de.schildbach.oeffi.util.ZoomControls;
+import de.schildbach.pte.dto.Location;
+import de.schildbach.pte.dto.Point;
+import de.schildbach.pte.dto.Product;
+import de.schildbach.pte.dto.Trip;
+import de.schildbach.pte.dto.Trip.Leg;
+import de.schildbach.pte.dto.Trip.Public;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.TextView;
+
+public class OeffiMapView extends MapView {
+ private ZoomControls zoomControls = null;
+ private FromViaToAware fromViaToAware = null;
+ private TripAware tripAware = null;
+ private StationsAware stationsAware = null;
+ private LocationAware locationAware = null;
+ private AreaAware areaAware = null;
+ private boolean firstLocation = true;
+ private boolean zoomLocked = true;
+
+ private final int AREA_FILL_COLOR = Color.parseColor("#22000000");
+ private final Animation zoomControlsAnimation;
+
+ public OeffiMapView(final Context context) {
+ this(context, null);
+ }
+
+ public OeffiMapView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ final Resources res = context.getResources();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+
+ final float stationFontSize = res.getDimension(R.dimen.font_size_normal);
+ final float tripStrokeWidth = res.getDimension(R.dimen.map_trip_stroke_width);
+ final float tripStrokeWidthSelected = res.getDimension(R.dimen.map_trip_stroke_width_selected);
+ final float tripStrokeWidthSelectedGlow = res.getDimension(R.dimen.map_trip_stroke_width_selected_glow);
+
+ final Drawable startIcon = drawablePointer(R.drawable.ic_maps_indicator_startpoint_list, 2);
+ final Drawable pointIcon = drawableCenter(R.drawable.ic_maps_product_default, 2);
+ final Drawable endIcon = drawablePointer(R.drawable.ic_maps_indicator_endpoint_list, 2);
+
+ final Drawable deviceLocationIcon = drawableCenter(R.drawable.location_on, 2);
+ final Drawable referenceLocationIcon = drawablePointer(R.drawable.da_marker_red, 2);
+
+ final Drawable glowIcon = drawableCenter(R.drawable.station_glow, 2);
+ final Drawable stationDefaultIcon = drawableCenter(R.drawable.ic_maps_product_default, 2);
+ final Drawable stationHighspeedIcon = drawableCenter(R.drawable.product_highspeed_color_22dp, 2);
+ final Drawable stationTrainIcon = drawableCenter(R.drawable.product_train_color_22dp, 2);
+ final Drawable stationSuburbanIcon = drawableCenter(R.drawable.product_suburban_color_22dp, 2);
+ final Drawable stationSubwayIcon = drawableCenter(R.drawable.product_subway_color_22dp, 2);
+ final Drawable stationTramIcon = drawableCenter(R.drawable.product_tram_color_22dp, 2);
+ final Drawable stationBusIcon = drawableCenter(R.drawable.product_bus_color_22dp, 2);
+ final Drawable stationFerryIcon = drawableCenter(R.drawable.product_ferry_color_22dp, 2);
+ final Drawable stationCablecarIcon = drawableCenter(R.drawable.product_cablecar_color_22dp, 2);
+ final Drawable stationCallIcon = drawableCenter(R.drawable.product_call_color_22dp, 2);
+
+ zoomControlsAnimation = AnimationUtils.loadAnimation(context, R.anim.zoom_controls);
+ zoomControlsAnimation.setFillAfter(true); // workaround: set through code because XML does not work
+
+ setBuiltInZoomControls(false);
+ setMultiTouchControls(true);
+ setTilesScaledToDpi(true);
+ getController().setZoom(Constants.INITIAL_MAP_ZOOM_LEVEL);
+
+ getOverlays().add(new Overlay() {
+ @Override
+ public void draw(final Canvas canvas, final MapView mapView, final boolean shadow) {
+ if (!shadow) {
+ final Projection projection = mapView.getProjection();
+ final android.graphics.Point point = new android.graphics.Point();
+
+ if (areaAware != null) {
+ final Point[] area = areaAware.getArea();
+ if (area != null) {
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(AREA_FILL_COLOR);
+
+ final Path path = pointsToPath(projection, area);
+ path.close();
+
+ path.setFillType(FillType.INVERSE_WINDING);
+
+ canvas.drawPath(path, paint);
+ }
+ }
+
+ if (fromViaToAware != null) {
+ final List path = new ArrayList<>(3);
+ final Point from = fromViaToAware.getFrom();
+ if (from != null)
+ path.add(from);
+ final Point via = fromViaToAware.getVia();
+ if (via != null)
+ path.add(via);
+ final Point to = fromViaToAware.getTo();
+ if (to != null)
+ path.add(to);
+
+ if (path.size() >= 2) {
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+
+ paint.setColor(Color.DKGRAY);
+ paint.setAlpha(92);
+ paint.setStrokeWidth(tripStrokeWidth);
+ canvas.drawPath(pointsToPath(projection, path), paint);
+ }
+
+ if (from != null) {
+ projection.toPixels(new GeoPoint(from.getLatAsDouble(), from.getLonAsDouble()), point);
+ drawAt(canvas, startIcon, point.x, point.y, false, 0);
+ }
+
+ if (to != null) {
+ projection.toPixels(new GeoPoint(to.getLatAsDouble(), to.getLonAsDouble()), point);
+ drawAt(canvas, endIcon, point.x, point.y, false, 0);
+ }
+ }
+
+ if (tripAware != null) {
+ final Trip trip = tripAware.getTrip();
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+
+ // first paint all unselected legs
+ for (final Trip.Leg leg : trip.legs) {
+ if (!tripAware.isSelectedLeg(leg)) {
+ final Path path = pointsToPath(projection, leg.path);
+
+ paint.setColor(leg instanceof Public ? Color.RED : Color.DKGRAY);
+ paint.setAlpha(92);
+ paint.setStrokeWidth(tripStrokeWidth);
+ canvas.drawPath(path, paint);
+ }
+ }
+
+ // then paint selected legs
+ for (final Trip.Leg leg : trip.legs) {
+ if (tripAware.isSelectedLeg(leg)) {
+ final List points = leg.path;
+ final Path path = pointsToPath(projection, points);
+
+ paint.setColor(Color.GREEN);
+ paint.setAlpha(92);
+ paint.setStrokeWidth(tripStrokeWidthSelectedGlow);
+ canvas.drawPath(path, paint);
+
+ paint.setColor(leg instanceof Public ? Color.RED : Color.DKGRAY);
+ paint.setAlpha(128);
+ paint.setStrokeWidth(tripStrokeWidthSelected);
+ canvas.drawPath(path, paint);
+
+ if (leg instanceof Public && !points.isEmpty()) {
+ final Public publicLeg = (Public) leg;
+
+ final double lat;
+ final double lon;
+
+ final int size = points.size();
+ if (size >= 2) {
+ final int pivot = size / 2;
+ final Point p1 = points.get(pivot - 1);
+ final Point p2 = points.get(pivot);
+ lat = (p1.getLatAsDouble() + p2.getLatAsDouble()) / 2.0;
+ lon = (p1.getLonAsDouble() + p2.getLonAsDouble()) / 2.0;
+ } else if (size == 1) {
+ final Point p = points.get(0);
+ lat = p.getLatAsDouble();
+ lon = p.getLonAsDouble();
+ } else {
+ lat = 0;
+ lon = 0;
+ }
+ projection.toPixels(new GeoPoint(lat, lon), point);
+
+ final LineView lineView = (LineView) inflater.inflate(R.layout.map_trip_line, null);
+ lineView.setLine(publicLeg.line);
+ lineView.measure(MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0),
+ MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0));
+ final int width = lineView.getMeasuredWidth();
+ final int height = lineView.getMeasuredHeight();
+ lineView.layout(point.x - width / 2, point.y - height / 2, point.x + width / 2,
+ point.y + height / 2);
+ // since point.x is ignored in layout (why?), we need to translate canvas
+ // ourselves
+ canvas.save();
+ canvas.translate(point.x - width / 2, point.y - height / 2);
+ lineView.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+
+ // then paint decorators
+ final Leg firstLeg = trip.legs.get(0);
+ final Leg lastLeg = trip.legs.get(trip.legs.size() - 1);
+
+ for (final Trip.Leg leg : trip.legs) {
+ if (!leg.path.isEmpty()) {
+ final Point firstPoint = leg.path.get(0);
+ final Point lastPoint = leg.path.get(leg.path.size() - 1);
+
+ if (firstPoint == lastPoint) {
+ projection.toPixels(
+ new GeoPoint(firstPoint.getLatAsDouble(), firstPoint.getLonAsDouble()),
+ point);
+ drawAt(canvas, startIcon, point.x, point.y, false, 0);
+ } else if (leg == firstLeg || leg == lastLeg) {
+ if (leg == firstLeg) {
+ projection.toPixels(
+ new GeoPoint(firstPoint.getLatAsDouble(), firstPoint.getLonAsDouble()),
+ point);
+ drawAt(canvas, startIcon, point.x, point.y, false, 0);
+ }
+
+ if (leg == lastLeg) {
+ projection.toPixels(
+ new GeoPoint(lastPoint.getLatAsDouble(), lastPoint.getLonAsDouble()),
+ point);
+ drawAt(canvas, endIcon, point.x, point.y, false, 0);
+ }
+ } else {
+ projection.toPixels(
+ new GeoPoint(firstPoint.getLatAsDouble(), firstPoint.getLonAsDouble()),
+ point);
+ drawAt(canvas, pointIcon, point.x, point.y, false, 0);
+ projection.toPixels(
+ new GeoPoint(lastPoint.getLatAsDouble(), lastPoint.getLonAsDouble()),
+ point);
+ drawAt(canvas, pointIcon, point.x, point.y, false, 0);
+ }
+ }
+ }
+ }
+
+ if (locationAware != null) {
+ final Point deviceLocation = locationAware.getDeviceLocation();
+ if (deviceLocation != null) {
+ projection.toPixels(
+ new GeoPoint(deviceLocation.getLatAsDouble(), deviceLocation.getLonAsDouble()),
+ point);
+ drawAt(canvas, deviceLocationIcon, point.x, point.y, false, 0);
+ }
+
+ final Location referenceLocation = locationAware.getReferenceLocation();
+ if (referenceLocation != null) {
+ projection.toPixels(new GeoPoint(referenceLocation.getLatAsDouble(),
+ referenceLocation.getLonAsDouble()), point);
+ drawAt(canvas, referenceLocationIcon, point.x, point.y, false, 0);
+ }
+ }
+
+ if (stationsAware != null) {
+ final List stations = stationsAware.getStations();
+ if (stations != null) {
+ Station selectedStation = null;
+
+ for (final Station station : stations) {
+ if (station.location.hasLocation()) {
+ projection.toPixels(new GeoPoint(station.location.getLatAsDouble(),
+ station.location.getLonAsDouble()), point);
+
+ if (stationsAware.isSelectedStation(station.location.id)) {
+ drawAt(canvas, glowIcon, point.x, point.y, false, 0);
+ selectedStation = station;
+ }
+
+ final Product product = station.getRelevantProduct();
+ if (product == null)
+ drawAt(canvas, stationDefaultIcon, point.x, point.y, false, 0);
+ else if (product == Product.HIGH_SPEED_TRAIN)
+ drawAt(canvas, stationHighspeedIcon, point.x, point.y, false, 0);
+ else if (product == Product.REGIONAL_TRAIN)
+ drawAt(canvas, stationTrainIcon, point.x, point.y, false, 0);
+ else if (product == Product.SUBURBAN_TRAIN)
+ drawAt(canvas, stationSuburbanIcon, point.x, point.y, false, 0);
+ else if (product == Product.SUBWAY)
+ drawAt(canvas, stationSubwayIcon, point.x, point.y, false, 0);
+ else if (product == Product.TRAM)
+ drawAt(canvas, stationTramIcon, point.x, point.y, false, 0);
+ else if (product == Product.BUS)
+ drawAt(canvas, stationBusIcon, point.x, point.y, false, 0);
+ else if (product == Product.FERRY)
+ drawAt(canvas, stationFerryIcon, point.x, point.y, false, 0);
+ else if (product == Product.CABLECAR)
+ drawAt(canvas, stationCablecarIcon, point.x, point.y, false, 0);
+ else if (product == Product.ON_DEMAND)
+ drawAt(canvas, stationCallIcon, point.x, point.y, false, 0);
+ else
+ drawAt(canvas, stationDefaultIcon, point.x, point.y, false, 0);
+ }
+ }
+
+ if (selectedStation != null) {
+ projection.toPixels(new GeoPoint(selectedStation.location.getLatAsDouble(),
+ selectedStation.location.getLonAsDouble()), point);
+ final TextView bubble = new TextView(getContext());
+ bubble.setBackgroundResource(R.drawable.popup_dir_pointer_button);
+ bubble.setText(selectedStation.location.name);
+ bubble.setTypeface(Typeface.DEFAULT_BOLD);
+ bubble.setTextSize(TypedValue.COMPLEX_UNIT_PX, stationFontSize);
+ bubble.setIncludeFontPadding(false);
+ bubble.measure(MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0),
+ MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0));
+ final int width = bubble.getMeasuredWidth();
+ final int height = bubble.getMeasuredHeight();
+ bubble.layout(point.x - width / 2, point.y - height / 2, point.x + width / 2,
+ point.y + height / 2);
+ // since point.x is ignored in layout (why?), we need to translate canvas
+ // ourselves
+ canvas.save();
+ canvas.translate(point.x - width / 2,
+ point.y - height - stationDefaultIcon.getIntrinsicHeight() / 2.5f);
+ bubble.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+ }
+ }
+
+ private Path pointsToPath(final Projection projection, final List points) {
+ final Path path = new Path();
+ path.incReserve(points.size());
+
+ final android.graphics.Point point = new android.graphics.Point();
+
+ for (final Point p : points) {
+ projection.toPixels(new GeoPoint(p.getLatAsDouble(), p.getLonAsDouble()), point);
+ if (path.isEmpty())
+ path.moveTo(point.x, point.y);
+ else
+ path.lineTo(point.x, point.y);
+ }
+
+ return path;
+ }
+
+ private Path pointsToPath(final Projection projection, final Point[] points) {
+ final Path path = new Path();
+ path.incReserve(points.length);
+
+ final android.graphics.Point point = new android.graphics.Point();
+
+ for (final Point p : points) {
+ projection.toPixels(new GeoPoint(p.getLatAsDouble(), p.getLonAsDouble()), point);
+ if (path.isEmpty())
+ path.moveTo(point.x, point.y);
+ else
+ path.lineTo(point.x, point.y);
+ }
+
+ return path;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(final MotionEvent e, final MapView mapView) {
+ final IGeoPoint p = mapView.getProjection().fromPixels((int) e.getX(), (int) e.getY());
+ final double tappedLat = p.getLatitude();
+ final double tappedLon = p.getLongitude();
+ boolean consumed = false;
+
+ final float[] distanceBetweenResults = new float[1];
+
+ if (tripAware != null) {
+ int tappedLegIndex = -1;
+ float tappedPointDistance = 0;
+
+ int iRoute = 0;
+ for (final Leg leg : tripAware.getTrip().legs) {
+ for (final Point point : leg.path) {
+ android.location.Location.distanceBetween(tappedLat, tappedLon, point.getLatAsDouble(),
+ point.getLonAsDouble(), distanceBetweenResults);
+ final float distance = distanceBetweenResults[0];
+ if (tappedLegIndex == -1 || distance < tappedPointDistance) {
+ tappedLegIndex = iRoute;
+ tappedPointDistance = distance;
+ }
+ }
+
+ iRoute++;
+ }
+
+ if (tappedLegIndex != -1) {
+ tripAware.selectLeg(tappedLegIndex);
+ consumed = true;
+ }
+ }
+
+ if (stationsAware != null) {
+ Station tappedStation = null;
+ float tappedStationDistance = 0;
+
+ for (final Station station : stationsAware.getStations()) {
+ android.location.Location.distanceBetween(tappedLat, tappedLon,
+ station.location.getLatAsDouble(), station.location.getLonAsDouble(),
+ distanceBetweenResults);
+ final float distance = distanceBetweenResults[0];
+ if (tappedStation == null || distance < tappedStationDistance) {
+ tappedStation = station;
+ tappedStationDistance = distance;
+ }
+ }
+
+ if (locationAware != null) {
+ if (tappedStation == null) {
+ stationsAware.selectStation(null);
+ consumed = true;
+ } else {
+ final Point deviceLocation = locationAware.getDeviceLocation();
+ if (deviceLocation != null) {
+ android.location.Location.distanceBetween(tappedLat, tappedLon,
+ deviceLocation.getLatAsDouble(), deviceLocation.getLonAsDouble(),
+ distanceBetweenResults);
+ final float distance = distanceBetweenResults[0];
+ if (distance < tappedStationDistance) {
+ stationsAware.selectStation(null);
+ consumed = true;
+ }
+ }
+ }
+ }
+
+ if (!consumed && tappedStation != null) {
+ stationsAware.selectStation(tappedStation);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+ }
+ });
+ }
+
+ public void setZoomControls(final ZoomControls zoomControls) {
+ this.zoomControls = zoomControls;
+ zoomControls.setOnZoomInClickListener(new OnClickListener() {
+ public void onClick(final View v) {
+ showZoomControls();
+ getController().zoomIn();
+ }
+ });
+ zoomControls.setOnZoomOutClickListener(new OnClickListener() {
+ public void onClick(final View v) {
+ showZoomControls();
+ getController().zoomOut();
+ }
+ });
+ }
+
+ public void setFromViaToAware(final FromViaToAware fromViaToAware) {
+ this.fromViaToAware = fromViaToAware;
+ invalidate();
+ }
+
+ public void setTripAware(final TripAware tripAware) {
+ this.tripAware = tripAware;
+ invalidate();
+ }
+
+ public void setStationsAware(final StationsAware stationsAware) {
+ this.stationsAware = stationsAware;
+ invalidate();
+ }
+
+ public void setLocationAware(final LocationAware locationAware) {
+ this.locationAware = locationAware;
+ invalidate();
+ }
+
+ public void setAreaAware(final AreaAware areaAware) {
+ this.areaAware = areaAware;
+ invalidate();
+ }
+
+ public void animateToLocation(final double lat, final double lon) {
+ if (lat == 0 && lon == 0)
+ return;
+
+ final GeoPoint point = new GeoPoint(lat, lon);
+
+ if (firstLocation)
+ getController().setCenter(point);
+ else
+ getController().animateTo(point);
+
+ firstLocation = false;
+ }
+
+ public void zoomToAll() {
+ zoomLocked = true;
+
+ final boolean hasLegSelection = tripAware != null && tripAware.hasSelection();
+ final List points = new LinkedList<>();
+
+ if (areaAware != null) {
+ final Point[] area = areaAware.getArea();
+ if (area != null) {
+ for (final Point p : area)
+ points.add(new GeoPoint(p.getLatAsDouble(), p.getLonAsDouble()));
+ }
+ }
+
+ if (fromViaToAware != null) {
+ final Point from = fromViaToAware.getFrom();
+ if (from != null)
+ points.add(new GeoPoint(from.getLatAsDouble(), from.getLonAsDouble()));
+ final Point via = fromViaToAware.getVia();
+ if (via != null)
+ points.add(new GeoPoint(via.getLatAsDouble(), via.getLonAsDouble()));
+ final Point to = fromViaToAware.getTo();
+ if (to != null)
+ points.add(new GeoPoint(to.getLatAsDouble(), to.getLonAsDouble()));
+ }
+
+ if (tripAware != null) {
+ for (final Leg leg : tripAware.getTrip().legs) {
+ if (!hasLegSelection || tripAware.isSelectedLeg(leg)) {
+ for (final Point p : leg.path)
+ points.add(new GeoPoint(p.getLatAsDouble(), p.getLonAsDouble()));
+ }
+ }
+ }
+
+ if (locationAware != null && !hasLegSelection) {
+ final Location referenceLocation = locationAware.getReferenceLocation();
+ if (referenceLocation != null) {
+ points.add(new GeoPoint(referenceLocation.getLatAsDouble(), referenceLocation.getLonAsDouble()));
+ } else {
+ final Point location = locationAware.getDeviceLocation();
+ if (location != null)
+ points.add(new GeoPoint(location.getLatAsDouble(), location.getLonAsDouble()));
+ }
+ }
+
+ if (!points.isEmpty()) {
+ final BoundingBox boundingBox = BoundingBox.fromGeoPoints(points);
+ zoomToBoundingBox(boundingBox.increaseByScale(1.3f), !firstLocation);
+ }
+
+ firstLocation = false;
+ }
+
+ private void showZoomControls() {
+ zoomControls.clearAnimation();
+ zoomControls.startAnimation(zoomControlsAnimation);
+ }
+
+ @Override
+ protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
+ if (zoomLocked)
+ zoomToAll();
+
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent ev) {
+ zoomLocked = false;
+ if (zoomControls != null)
+ showZoomControls();
+
+ return super.onTouchEvent(ev);
+ }
+
+ private Drawable drawablePointer(final int resId, final int sizeDivider) {
+ final Resources res = getResources();
+ final Drawable drawable = res.getDrawable(resId);
+ drawable.setBounds(-drawable.getIntrinsicWidth() / sizeDivider, -drawable.getIntrinsicHeight(),
+ drawable.getIntrinsicWidth() / sizeDivider, 0);
+ return drawable;
+ }
+
+ private Drawable drawableCenter(final int resId, final int sizeDivider) {
+ final Resources res = getResources();
+ final Drawable drawable = res.getDrawable(resId);
+ drawable.setBounds(-drawable.getIntrinsicWidth() / sizeDivider, -drawable.getIntrinsicHeight() / sizeDivider,
+ drawable.getIntrinsicWidth() / sizeDivider, drawable.getIntrinsicHeight() / sizeDivider);
+ return drawable;
+ }
+}
diff --git a/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java b/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java
index 447c9ed..758d5f3 100644
--- a/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java
@@ -33,12 +33,18 @@ import java.util.Set;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
+import org.osmdroid.api.IGeoPoint;
+import org.osmdroid.api.IMapController;
+import org.osmdroid.views.MapView;
+import org.osmdroid.views.overlay.Overlay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.schildbach.oeffi.Constants;
+import de.schildbach.oeffi.FromViaToAware;
import de.schildbach.oeffi.MyActionBar;
import de.schildbach.oeffi.OeffiMainActivity;
+import de.schildbach.oeffi.OeffiMapView;
import de.schildbach.oeffi.R;
import de.schildbach.oeffi.directions.TimeSpec.DepArr;
import de.schildbach.oeffi.directions.list.QueryHistoryAdapter;
@@ -55,10 +61,12 @@ import de.schildbach.oeffi.util.ConnectivityBroadcastReceiver;
import de.schildbach.oeffi.util.DialogBuilder;
import de.schildbach.oeffi.util.DividerItemDecoration;
import de.schildbach.oeffi.util.Formats;
+import de.schildbach.oeffi.util.GeocoderThread;
import de.schildbach.oeffi.util.LocationUriParser;
import de.schildbach.oeffi.util.Toast;
import de.schildbach.oeffi.util.ToggleImageButton;
import de.schildbach.oeffi.util.ToggleImageButton.OnCheckedChangeListener;
+import de.schildbach.oeffi.util.ZoomControls;
import de.schildbach.pte.NetworkId;
import de.schildbach.pte.NetworkProvider;
import de.schildbach.pte.NetworkProvider.Accessibility;
@@ -68,6 +76,7 @@ import de.schildbach.pte.NetworkProvider.TripFlag;
import de.schildbach.pte.NetworkProvider.WalkSpeed;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.LocationType;
+import de.schildbach.pte.dto.Point;
import de.schildbach.pte.dto.Product;
import de.schildbach.pte.dto.QueryTripsResult;
import de.schildbach.pte.dto.SuggestLocationsResult;
@@ -90,7 +99,11 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.database.Cursor;
+import android.graphics.Canvas;
+import android.location.Address;
+import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Bundle;
@@ -106,6 +119,7 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
@@ -126,6 +140,7 @@ import okhttp3.HttpUrl;
public class DirectionsActivity extends OeffiMainActivity implements ActivityCompat.OnRequestPermissionsResultCallback,
QueryHistoryClickListener, QueryHistoryContextMenuItemListener {
private ConnectivityManager connectivityManager;
+ private LocationManager locationManager;
private ToggleImageButton buttonExpand;
private LocationView viewFromLocation;
@@ -141,6 +156,7 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
private RecyclerView viewQueryHistoryList;
private QueryHistoryAdapter queryHistoryListAdapter;
private TextView connectivityWarningView;
+ private OeffiMapView mapView;
private TimeSpec time = null;
@@ -229,6 +245,7 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
super.onCreate(savedInstanceState);
connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
@@ -257,6 +274,8 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
expandForm();
else
collapseForm();
+
+ updateMap();
}
});
actionBar.addButton(R.drawable.ic_shuffle_white_24dp, R.string.directions_action_return_trip_title)
@@ -293,6 +312,7 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
final LocationView.Listener locationChangeListener = new LocationView.Listener() {
public void changed() {
+ updateMap();
queryHistoryListAdapter.clearSelectedEntry();
requestFocusFirst();
}
@@ -414,6 +434,78 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
queryHistoryListAdapter = new QueryHistoryAdapter(this, network, this, this);
viewQueryHistoryList.setAdapter(queryHistoryListAdapter);
+ mapView = (OeffiMapView) findViewById(R.id.directions_map);
+ if (ContextCompat.checkSelfPermission(DirectionsActivity.this,
+ Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ android.location.Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
+ mapView.animateToLocation(location.getLatitude(), location.getLongitude());
+ }
+ mapView.getOverlays().add(new Overlay() {
+ private Location pinLocation;
+ private View pinView;
+
+ @Override
+ public void draw(final Canvas canvas, final MapView mapView, final boolean shadow) {
+ if (pinView != null)
+ pinView.requestLayout();
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(final MotionEvent e, final MapView mapView) {
+ final IGeoPoint p = mapView.getProjection().fromPixels((int) e.getX(), (int) e.getY());
+ pinLocation = Location.coord(Point.fromDouble(p.getLatitude(), p.getLongitude()));
+
+ final View view = getLayoutInflater().inflate(R.layout.directions_map_pin, null);
+ final LocationTextView locationView = (LocationTextView) view
+ .findViewById(R.id.directions_map_pin_location);
+ final View buttonGroup = view.findViewById(R.id.directions_map_pin_buttons);
+ buttonGroup.findViewById(R.id.directions_map_pin_button_from).setOnClickListener(new OnClickListener() {
+ public void onClick(final View v) {
+ viewFromLocation.setLocation(pinLocation);
+ mapView.removeAllViews();
+ }
+ });
+ buttonGroup.findViewById(R.id.directions_map_pin_button_to).setOnClickListener(new OnClickListener() {
+ public void onClick(final View v) {
+ viewToLocation.setLocation(pinLocation);
+ mapView.removeAllViews();
+ }
+ });
+ locationView.setLocation(pinLocation);
+ locationView.setShowLocationType(false);
+
+ // exchange view for the pin
+ if (pinView != null)
+ mapView.removeView(pinView);
+ pinView = view;
+ mapView.addView(pinView, new MapView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, p, MapView.LayoutParams.BOTTOM_CENTER, 0, 0));
+
+ new GeocoderThread(DirectionsActivity.this, p.getLatitude(), p.getLongitude(),
+ new GeocoderThread.Callback() {
+ public void onGeocoderResult(final Address address) {
+ pinLocation = LocationView.addressToLocation(address);
+ locationView.setLocation(pinLocation);
+ locationView.setShowLocationType(false);
+ }
+
+ public void onGeocoderFail(final Exception exception) {
+ log.info("Problem in geocoder: {}", exception.getMessage());
+ }
+ });
+
+ final IMapController controller = mapView.getController();
+ controller.animateTo(p);
+
+ return false;
+ }
+ });
+ ((TextView) findViewById(R.id.directions_map_disclaimer))
+ .setText(mapView.getTileProvider().getTileSource().getCopyrightNotice());
+
+ final ZoomControls zoom = (ZoomControls) findViewById(R.id.directions_map_zoom);
+ mapView.setZoomControls(zoom);
+
connectivityReceiver = new ConnectivityBroadcastReceiver(connectivityManager) {
@Override
protected void onConnected() {
@@ -464,6 +556,7 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
@Override
protected void onResume() {
super.onResume();
+ mapView.onResume();
// can do directions?
final NetworkProvider networkProvider = network != null ? NetworkProviderFactory.provider(network) : null;
@@ -491,6 +584,8 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
setActionBarSecondaryTitleFromNetwork();
updateGUI();
+ updateMap();
+ updateFragments();
}
@Override
@@ -532,6 +627,7 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
tickReceiver = null;
}
+ mapView.onPause();
super.onPause();
}
@@ -556,6 +652,13 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
super.onDestroy();
}
+ @Override
+ public void onConfigurationChanged(final Configuration config) {
+ super.onConfigurationChanged(config);
+
+ updateFragments();
+ }
+
@Override
public void onBackPressed() {
if (isNavigationOpen())
@@ -573,6 +676,10 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
viewGo.requestFocus();
}
+ private void updateFragments() {
+ updateFragments(R.id.navigation_drawer_layout, R.id.directions_map_fragment);
+ }
+
private void updateGUI() {
viewFromLocation.setHint(R.string.directions_from);
viewViaLocation.setHint(R.string.directions_via);
@@ -675,6 +782,33 @@ public class DirectionsActivity extends OeffiMainActivity implements ActivityCom
builder.show();
}
+ private void updateMap() {
+ mapView.removeAllViews();
+ mapView.setFromViaToAware(new FromViaToAware() {
+ public Point getFrom() {
+ final Location from = viewFromLocation.getLocation();
+ if (from == null || !from.hasLocation())
+ return null;
+ return new Point(from.lat, from.lon);
+ }
+
+ public Point getVia() {
+ final Location via = viewViaLocation.getLocation();
+ if (via == null || !via.hasLocation() || viewViaLocation.getVisibility() != View.VISIBLE)
+ return null;
+ return new Point(via.lat, via.lon);
+ }
+
+ public Point getTo() {
+ final Location to = viewToLocation.getLocation();
+ if (to == null || !to.hasLocation())
+ return null;
+ return new Point(to.lat, to.lon);
+ }
+ });
+ mapView.zoomToAll();
+ }
+
private void expandForm() {
initLayoutTransitions(true);
diff --git a/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java b/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java
index 5ad82cc..503b81b 100644
--- a/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java
@@ -197,7 +197,8 @@ public class DirectionsShortcutActivity extends OeffiActivity
}
private void query(final Point here) {
- final String hereName = String.format(Locale.ENGLISH, "%.6f, %.6f", here.getLatAsDouble(), here.getLonAsDouble());
+ final String hereName = String.format(Locale.ENGLISH, "%.6f, %.6f", here.getLatAsDouble(),
+ here.getLonAsDouble());
query(here, hereName);
}
diff --git a/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java b/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java
index dec34fe..f5d09e5 100644
--- a/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java
@@ -34,7 +34,9 @@ import de.schildbach.oeffi.Constants;
import de.schildbach.oeffi.LocationAware;
import de.schildbach.oeffi.MyActionBar;
import de.schildbach.oeffi.OeffiActivity;
+import de.schildbach.oeffi.OeffiMapView;
import de.schildbach.oeffi.R;
+import de.schildbach.oeffi.TripAware;
import de.schildbach.oeffi.directions.TimeSpec.DepArr;
import de.schildbach.oeffi.stations.LineView;
import de.schildbach.oeffi.stations.NetworkContentProvider;
@@ -65,6 +67,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
@@ -118,6 +121,7 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
private BroadcastReceiver tickReceiver;
private ViewGroup legsGroup;
+ private OeffiMapView mapView;
private ToggleImageButton trackButton;
private NetworkId network;
@@ -125,6 +129,7 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
private Date highlightedTime;
private Location highlightedLocation;
private Point location;
+ private int selectedLegIndex = -1;
private final Map legExpandStates = new HashMap<>();
private Intent scheduleTripIntent;
@@ -223,12 +228,16 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
location = LocationHelper.locationToPoint(lastKnownLocation);
else
location = null;
+ mapView.setLocationAware(TripDetailsActivity.this);
}
} else {
locationManager.removeUpdates(TripDetailsActivity.this);
location = null;
+
+ mapView.setLocationAware(null);
}
+ mapView.zoomToAll();
updateGUI();
}
});
@@ -281,6 +290,31 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
final TextView disclaimerSourceView = (TextView) findViewById(R.id.directions_trip_details_disclaimer_source);
updateDisclaimerSource(disclaimerSourceView, network.name(), null);
+
+ mapView = (OeffiMapView) findViewById(R.id.directions_trip_details_map);
+ mapView.setTripAware(new TripAware() {
+ public Trip getTrip() {
+ return trip;
+ }
+
+ public void selectLeg(final int partIndex) {
+ selectedLegIndex = partIndex;
+ mapView.zoomToAll();
+ }
+
+ public boolean hasSelection() {
+ return selectedLegIndex != -1;
+ }
+
+ public boolean isSelectedLeg(final Leg part) {
+ if (!hasSelection())
+ return false;
+
+ return trip.legs.get(selectedLegIndex).equals(part);
+ }
+ });
+ ((TextView) findViewById(R.id.directions_trip_details_map_disclaimer))
+ .setText(mapView.getTileProvider().getTileSource().getCopyrightNotice());
}
@Override
@@ -297,6 +331,19 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
registerReceiver(tickReceiver, new IntentFilter(Intent.ACTION_TIME_TICK));
updateGUI();
+ updateFragments();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mapView.onPause();
+ super.onPause();
}
@Override
@@ -321,6 +368,13 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
+ @Override
+ public void onConfigurationChanged(final Configuration config) {
+ super.onConfigurationChanged(config);
+
+ updateFragments();
+ }
+
private String requestLocationUpdates() {
final Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
@@ -347,6 +401,10 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
public void onProviderDisabled(final String provider) {
locationManager.removeUpdates(TripDetailsActivity.this);
+
+ final String newProvider = requestLocationUpdates();
+ if (newProvider == null)
+ mapView.setLocationAware(null);
}
public void onStatusChanged(final String provider, final int status, final Bundle extras) {
@@ -364,6 +422,10 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
return null;
}
+ private void updateFragments() {
+ updateFragments(R.id.directions_trip_details_list_frame, R.id.directions_trip_details_map_frame);
+ }
+
private void updateGUI() {
final Date now = new Date();
updateHighlightedTime(now);
@@ -377,7 +439,6 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen
updateIndividualLeg(legsGroup.getChildAt(i), (Individual) leg);
i++;
}
-
}
private void updateHighlightedTime(final Date now) {
diff --git a/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java b/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java
index 303bbba..6de7c60 100644
--- a/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java
@@ -31,8 +31,11 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import de.schildbach.oeffi.AreaAware;
import de.schildbach.oeffi.Constants;
+import de.schildbach.oeffi.LocationAware;
import de.schildbach.oeffi.MyActionBar;
+import de.schildbach.oeffi.OeffiMapView;
import de.schildbach.oeffi.R;
import de.schildbach.oeffi.network.list.NetworkClickListener;
import de.schildbach.oeffi.network.list.NetworkContextMenuItemListener;
@@ -46,6 +49,7 @@ import de.schildbach.oeffi.util.LocationHelper;
import de.schildbach.pte.AbstractNavitiaProvider;
import de.schildbach.pte.NetworkId;
import de.schildbach.pte.NetworkProvider;
+import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.Point;
import android.Manifest;
@@ -56,6 +60,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.location.Address;
import android.location.Criteria;
@@ -73,6 +78,8 @@ import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
public class NetworkPickerActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback,
LocationHelper.Callback, NetworkClickListener, NetworkContextMenuItemListener {
@@ -86,6 +93,7 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
private MyActionBar actionBar;
private RecyclerView listView;
private NetworksAdapter listAdapter;
+ private OeffiMapView mapView;
private final List lastNetworks = new LinkedList<>();
@@ -128,6 +136,10 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
listAdapter = new NetworksAdapter(this, network, this, this);
listView.setAdapter(listAdapter);
+ mapView = (OeffiMapView) findViewById(R.id.network_picker_map);
+ ((TextView) findViewById(R.id.network_picker_map_disclaimer))
+ .setText(mapView.getTileProvider().getTileSource().getCopyrightNotice());
+
if (network == null) {
((FrameLayout) findViewById(R.id.network_picker_firsttime_message_shadow)).setForeground(null);
} else {
@@ -137,6 +149,40 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
finish();
}
});
+ final NetworkId networkId = prefsGetNetworkId();
+ if (networkId != null) {
+ backgroundHandler.post(new GetAreaRunnable(NetworkProviderFactory.provider(networkId), handler) {
+
+ @Override
+ protected void onResult(final Point[] area) {
+ mapView.setAreaAware(new AreaAware() {
+ final Point[] myArea = area != null && area.length > 1 ? area : null;
+
+ public Point[] getArea() {
+ return myArea;
+ }
+ });
+ mapView.setLocationAware(new LocationAware() {
+ final Location referenceLocation = area != null && area.length == 1
+ ? Location.coord(area[0]) : null;
+
+ public Point getDeviceLocation() {
+ return deviceLocation;
+ }
+
+ public Location getReferenceLocation() {
+ return referenceLocation;
+ }
+
+ public Float getDeviceBearing() {
+ return null;
+ }
+ });
+
+ mapView.zoomToAll();
+ }
+ });
+ }
}
loadLastNetworks();
@@ -154,9 +200,22 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
protected void onStart() {
super.onStart();
+ updateFragments();
maybeStartLocation();
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mapView.onPause();
+ super.onPause();
+ }
+
@Override
protected void onStop() {
stopLocation();
@@ -210,6 +269,8 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
deviceLocation = here;
+ mapView.animateToLocation(here.getLatAsDouble(), here.getLonAsDouble());
+
parseIndex();
updateGUI();
@@ -232,6 +293,13 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
});
}
+ @Override
+ public void onConfigurationChanged(final Configuration config) {
+ super.onConfigurationChanged(config);
+
+ updateFragments();
+ }
+
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
// disable back key if no network is selected
@@ -267,8 +335,25 @@ public class NetworkPickerActivity extends Activity implements ActivityCompat.On
}
}
+ private void updateFragments() {
+ final Resources res = getResources();
+
+ final View listFrame = findViewById(R.id.network_picker_list_frame);
+ final boolean listShow = res.getBoolean(R.bool.layout_list_show);
+ listFrame.setVisibility(isInMultiWindowMode() || listShow ? View.VISIBLE : View.GONE);
+
+ final View mapFrame = findViewById(R.id.network_picker_map_frame);
+ final boolean mapShow = res.getBoolean(R.bool.layout_map_show);
+ mapFrame.setVisibility(!isInMultiWindowMode() && mapShow ? View.VISIBLE : View.GONE);
+
+ final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) listFrame.getLayoutParams();
+ layoutParams.width = listShow && mapShow ? res.getDimensionPixelSize(R.dimen.layout_list_width)
+ : LinearLayout.LayoutParams.MATCH_PARENT;
+ }
+
private void updateGUI() {
listAdapter.notifyDataSetChanged();
+ mapView.invalidate();
}
private String prefsGetNetwork() {
diff --git a/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java b/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java
index 4239aaa..c48740d 100644
--- a/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
+import org.osmdroid.util.GeoPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,6 +42,7 @@ import com.google.common.base.Strings;
import de.schildbach.oeffi.Constants;
import de.schildbach.oeffi.MyActionBar;
import de.schildbach.oeffi.OeffiActivity;
+import de.schildbach.oeffi.OeffiMapView;
import de.schildbach.oeffi.R;
import de.schildbach.oeffi.StationsAware;
import de.schildbach.oeffi.network.NetworkProviderFactory;
@@ -64,6 +66,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Typeface;
@@ -127,6 +130,7 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
private DeparturesAdapter listAdapter;
private TextView resultStatusView;
private TextView disclaimerSourceView;
+ private OeffiMapView mapView;
private BroadcastReceiver tickReceiver;
@@ -187,6 +191,11 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
listAdapter = new DeparturesAdapter(this);
listView.setAdapter(listAdapter);
+ mapView = (OeffiMapView) findViewById(R.id.stations_station_details_map);
+ mapView.setStationsAware(this);
+ ((TextView) findViewById(R.id.stations_station_details_map_disclaimer))
+ .setText(mapView.getTileProvider().getTileSource().getCopyrightNotice());
+
resultStatusView = (TextView) findViewById(R.id.stations_station_details_result_status);
final Intent intent = getIntent();
@@ -292,6 +301,20 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
registerReceiver(tickReceiver, new IntentFilter(Intent.ACTION_TIME_TICK));
load();
+
+ updateFragments();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mapView.onPause();
+ super.onPause();
}
@Override
@@ -316,6 +339,17 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
+ @Override
+ public void onConfigurationChanged(final Configuration config) {
+ super.onConfigurationChanged(config);
+
+ updateFragments();
+ }
+
+ private void updateFragments() {
+ updateFragments(R.id.stations_station_details_list_fragment, R.id.stations_station_details_map_fragment);
+ }
+
private void updateGUI() {
final List selectedDepartures = this.selectedDepartures;
if (selectedDepartures != null) {
@@ -517,6 +551,10 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
selectedFavState = FavoriteStationsProvider.favState(getContentResolver(), selectedNetwork, selectedStation);
+ if (selectedStation.hasLocation())
+ mapView.getController()
+ .animateTo(new GeoPoint(selectedStation.getLatAsDouble(), selectedStation.getLonAsDouble()));
+
updateGUI();
actionBar.setPrimaryTitle(selectedStation.name);
diff --git a/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java b/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java
index 09cdb5e..a7bfe8b 100644
--- a/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java
+++ b/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java
@@ -37,6 +37,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
+import org.osmdroid.util.GeoPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +50,7 @@ import de.schildbach.oeffi.Constants;
import de.schildbach.oeffi.LocationAware;
import de.schildbach.oeffi.MyActionBar;
import de.schildbach.oeffi.OeffiMainActivity;
+import de.schildbach.oeffi.OeffiMapView;
import de.schildbach.oeffi.R;
import de.schildbach.oeffi.StationsAware;
import de.schildbach.oeffi.directions.DirectionsActivity;
@@ -61,6 +63,7 @@ import de.schildbach.oeffi.util.DialogBuilder;
import de.schildbach.oeffi.util.DividerItemDecoration;
import de.schildbach.oeffi.util.LocationUriParser;
import de.schildbach.oeffi.util.Toast;
+import de.schildbach.oeffi.util.ZoomControls;
import de.schildbach.pte.NetworkId;
import de.schildbach.pte.NetworkProvider;
import de.schildbach.pte.NetworkProvider.Capability;
@@ -85,6 +88,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -148,6 +152,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
private String accurateLocationProvider, lowPowerLocationProvider;
private MyActionBar actionBar;
+ private OeffiMapView mapView;
private RecyclerView stationList;
private LinearLayoutManager stationListLayoutManager;
private StationsAdapter stationListAdapter;
@@ -237,6 +242,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
}
stationListAdapter.notifyDataSetChanged();
+ mapView.invalidate();
}
updateGUI();
@@ -286,6 +292,15 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
final Button missingCapabilityButton = (Button) findViewById(R.id.stations_network_missing_capability_button);
missingCapabilityButton.setOnClickListener(selectNetworkListener);
+ mapView = (OeffiMapView) findViewById(R.id.stations_map);
+ mapView.setStationsAware(this);
+ mapView.setLocationAware(this);
+ ((TextView) findViewById(R.id.stations_map_disclaimer))
+ .setText(mapView.getTileProvider().getTileSource().getCopyrightNotice());
+
+ final ZoomControls zoom = (ZoomControls) findViewById(R.id.stations_map_zoom);
+ mapView.setZoomControls(zoom);
+
connectivityWarningView = (TextView) findViewById(R.id.stations_connectivity_warning_box);
disclaimerSourceView = (TextView) findViewById(R.id.stations_disclaimer_source);
@@ -485,6 +500,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
@Override
protected void onResume() {
super.onResume();
+ mapView.onResume();
postLoadNextVisible(0);
}
@@ -497,6 +513,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
stationsMap.clear();
stationListAdapter.notifyDataSetChanged();
+ mapView.invalidate();
loading = true;
updateDisclaimerSource(disclaimerSourceView, network.name(), null);
@@ -507,10 +524,22 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
handler.post(initStationsRunnable);
}
+ @Override
+ public void onConfigurationChanged(final Configuration config) {
+ super.onConfigurationChanged(config);
+
+ updateFragments();
+ }
+
+ private void updateFragments() {
+ updateFragments(R.id.navigation_drawer_layout, R.id.stations_map_fragment);
+ }
+
@Override
protected void onPause() {
saveProductFilter();
+ mapView.onPause();
super.onPause();
}
@@ -563,11 +592,15 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
fixedLocation = locations != null && locations.length >= 1 ? locations[0] : null;
if (fixedLocation != null) {
+ mapView.animateToLocation(fixedLocation.getLatAsDouble(), fixedLocation.getLonAsDouble());
+
findViewById(R.id.stations_location_clear).setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
fixedLocation = null;
if (deviceLocation != null) {
+ mapView.animateToLocation(deviceLocation.getLatAsDouble(), deviceLocation.getLonAsDouble());
+
final float[] distanceBetweenResults = new float[2];
// remove non-favorites and re-calculate distances
@@ -645,6 +678,9 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
}
private void updateGUI() {
+ // fragments
+ updateFragments();
+
// filter indicator
final boolean isActive = products.size() < Product.values().length;
filterActionButton.setSelected(isActive);
@@ -986,6 +1022,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
if (added || changed) {
stationListAdapter.notifyDataSetChanged();
+ mapView.invalidate();
}
updateGUI();
@@ -1274,6 +1311,14 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
}
}
+ // scroll map
+ if (station != null && station.location.hasLocation())
+ mapView.getController()
+ .animateTo(new GeoPoint(station.location.getLatAsDouble(), station.location.getLonAsDouble()));
+ else if (station == null && deviceLocation != null)
+ mapView.getController()
+ .animateTo(new GeoPoint(deviceLocation.getLatAsDouble(), deviceLocation.getLonAsDouble()));
+
postLoadNextVisible(0);
}
@@ -1432,6 +1477,9 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
final double hereLat = here.getLatitude();
final double hereLon = here.getLongitude();
+ if (deviceLocation == null && fixedLocation == null)
+ mapView.animateToLocation(hereLat, hereLon);
+
deviceLocation = Point.fromDouble(hereLat, hereLon);
stationListAdapter.setDeviceLocation(here);