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);