package net.osmand.plus.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.os.Vibrator;
import android.support.annotation.DimenRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import net.osmand.data.LatLon;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.OsmAndLocationProvider;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.quickaction.QuickAction;
import net.osmand.plus.quickaction.QuickActionFactory;
import net.osmand.plus.quickaction.QuickActionRegistry;
import net.osmand.plus.quickaction.QuickActionsWidget;
import static net.osmand.plus.views.ContextMenuLayer.VIBRATE_SHORT;
/**
* Created by okorsun on 23.12.16.
*/
public class MapQuickActionLayer extends OsmandMapLayer implements QuickActionRegistry.QuickActionUpdatesListener, QuickAction.QuickActionSelectionListener {
private final ContextMenuLayer contextMenuLayer;
private ImageView contextMarker;
private final MapActivity mapActivity;
private final OsmandApplication app;
private final OsmandSettings settings;
private final QuickActionRegistry quickActionRegistry;
private ImageButton quickActionButton;
private QuickActionsWidget quickActionsWidget;
private OsmandMapTileView view;
private boolean wasCollapseButtonVisible;
private int previousMapPosition;
private boolean inChangeMarkerPositionMode;
private boolean isLayerOn;
public MapQuickActionLayer(MapActivity activity, ContextMenuLayer contextMenuLayer) {
this.mapActivity = activity;
this.contextMenuLayer = contextMenuLayer;
app = activity.getMyApplication();
settings = activity.getMyApplication().getSettings();
quickActionRegistry = activity.getMapLayers().getQuickActionRegistry();
}
@Override
public void initLayer(OsmandMapTileView view) {
this.view = view;
quickActionsWidget = (QuickActionsWidget) mapActivity.findViewById(R.id.quick_action_widget);
quickActionButton = (ImageButton) mapActivity.findViewById(R.id.map_quick_actions_button);
setQuickActionButtonMargin();
isLayerOn = quickActionRegistry.isQuickActionOn();
quickActionButton.setImageResource(R.drawable.map_quick_action);
quickActionButton.setContentDescription(mapActivity.getString(R.string.configure_screen_quick_action));
quickActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!showTutorialIfNeeded())
setLayerState(quickActionsWidget.getVisibility() == View.VISIBLE);
}
});
Context context = view.getContext();
contextMarker = new ImageView(context);
contextMarker.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
contextMarker.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.map_pin_context_menu));
contextMarker.setClickable(true);
int minw = contextMarker.getDrawable().getMinimumWidth();
int minh = contextMarker.getDrawable().getMinimumHeight();
contextMarker.layout(0, 0, minw, minh);
quickActionButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Vibrator vibrator = (Vibrator) mapActivity.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_SHORT);
quickActionButton.setScaleX(1.5f);
quickActionButton.setScaleY(1.5f);
quickActionButton.setAlpha(0.95f);
quickActionButton.setOnTouchListener(onQuickActionTouchListener);
return true;
}
});
}
public void refreshLayer() {
setLayerState(true);
isLayerOn = quickActionRegistry.isQuickActionOn();
setUpQuickActionBtnVisibility();
}
private boolean showTutorialIfNeeded() {
if (isLayerOn && !app.accessibilityEnabled() && !settings.IS_QUICK_ACTION_TUTORIAL_SHOWN.get() && android.os.Build.VERSION.SDK_INT >= 14) {
TapTargetView.showFor(mapActivity, // `this` is an Activity
TapTarget.forView(quickActionButton, mapActivity.getString(R.string.quick_action_btn_tutorial_title), mapActivity.getString(R.string.quick_action_btn_tutorial_descr))
// All options below are optional
.outerCircleColor(R.color.osmand_orange) // Specify a color for the outer circle
.targetCircleColor(R.color.color_white) // Specify a color for the target circle
.titleTextSize(20) // Specify the size (in sp) of the title text
.descriptionTextSize(16) // Specify the size (in sp) of the description text
.descriptionTextColor(R.color.color_white) // Specify a color for both the title and description text
.titleTextColor(R.color.color_white) // Specify a color for both the title and description text
.drawShadow(true) // Whether to draw a drop shadow or not
.cancelable(false) // Whether tapping outside the outer circle dismisses the view
.tintTarget(false) // Whether to tint the target view's color
.transparentTarget(false) // Specify whether the target is transparent (displays the content underneath)
.targetRadius(50), // Specify the target radius (in dp)
new TapTargetView.Listener() { // The listener can listen for regular clicks, long clicks or cancels
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view); // This call is optional
settings.IS_QUICK_ACTION_TUTORIAL_SHOWN.set(true);
}
});
return true;
} else
return false;
}
private void setQuickActionButtonMargin() {
FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) quickActionButton.getLayoutParams();
if (AndroidUiHelper.isOrientationPortrait(mapActivity)) {
Pair<Integer, Integer> fabMargin = settings.getPortraitFabMargin();
if (fabMargin != null) {
param.rightMargin = fabMargin.first;
param.bottomMargin = fabMargin.second;
} else {
param.bottomMargin = calculateTotalSizePx(R.dimen.map_button_size, R.dimen.map_button_spacing) * 2;
}
} else {
Pair<Integer, Integer> fabMargin = settings.getLandscapeFabMargin();
if (fabMargin != null) {
param.rightMargin = fabMargin.first;
param.bottomMargin = fabMargin.second;
} else {
param.rightMargin = calculateTotalSizePx(R.dimen.map_button_size, R.dimen.map_button_spacing_land) * 2;
}
}
quickActionButton.setLayoutParams(param);
}
private int calculateTotalSizePx(@DimenRes int... dimensId) {
int result = 0;
for (int id : dimensId) {
result += mapActivity.getResources().getDimensionPixelSize(id);
}
return result;
}
/**
* @param isClosed
* @return true, if state was changed
*/
public boolean setLayerState(boolean isClosed) {
if ((quickActionsWidget.getVisibility() == View.VISIBLE) != isClosed) // check if state change is needed
return false;
quickActionButton.setImageResource(isClosed ? R.drawable.map_quick_action : R.drawable.map_action_cancel);
quickActionButton.setContentDescription(mapActivity.getString(isClosed ? R.string.configure_screen_quick_action : R.string.shared_string_cancel));
quickActionsWidget.setVisibility(isClosed ? View.GONE : View.VISIBLE);
if (isClosed) {
quitMovingMarker();
quickActionRegistry.setUpdatesListener(null);
quickActionsWidget.setSelectionListener(null);
} else {
enterMovingMode(mapActivity.getMapView().getCurrentRotatedTileBox());
quickActionsWidget.setActions(quickActionRegistry.getFilteredQuickActions());
quickActionRegistry.setUpdatesListener(MapQuickActionLayer.this);
quickActionsWidget.setSelectionListener(MapQuickActionLayer.this);
}
return true;
}
private void enterMovingMode(RotatedTileBox tileBox) {
previousMapPosition = view.getMapPosition();
view.setMapPosition(OsmandSettings.MIDDLE_CONSTANT);
MapContextMenu menu = mapActivity.getContextMenu();
LatLon ll = menu.isActive() && tileBox.containsLatLon(menu.getLatLon()) ? menu.getLatLon() : tileBox.getCenterLatLon();
boolean isFollowPoint = isFolowPoint(tileBox, menu);
menu.updateMapCenter(null);
menu.close();
RotatedTileBox rb = new RotatedTileBox(tileBox);
if (!isFollowPoint && previousMapPosition != OsmandSettings.BOTTOM_CONSTANT)
rb.setCenterLocation(0.5f, 0.3f);
rb.setLatLonCenter(ll.getLatitude(), ll.getLongitude());
double lat = rb.getLatFromPixel(tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
double lon = rb.getLonFromPixel(tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
view.setLatLon(lat, lon);
inChangeMarkerPositionMode = true;
mark(View.INVISIBLE, R.id.map_ruler_layout,
R.id.map_left_widgets_panel, R.id.map_right_widgets_panel, R.id.map_center_info);
View collapseButton = mapActivity.findViewById(R.id.map_collapse_button);
if (collapseButton != null && collapseButton.getVisibility() == View.VISIBLE) {
wasCollapseButtonVisible = true;
collapseButton.setVisibility(View.INVISIBLE);
} else {
wasCollapseButtonVisible = false;
}
view.refreshMap();
}
private boolean isFolowPoint(RotatedTileBox tileBox, MapContextMenu menu) {
return OsmAndLocationProvider.isLocationPermissionAvailable(mapActivity) &&
mapActivity.getMapViewTrackingUtilities().isMapLinkedToLocation() ||
menu.isActive() && tileBox.containsLatLon(menu.getLatLon()); // remove if not to folow if there is selected point on map
}
private void quitMovingMarker() {
RotatedTileBox tileBox = mapActivity.getMapView().getCurrentRotatedTileBox();
if (!isFolowPoint(tileBox, mapActivity.getContextMenu()) && previousMapPosition != OsmandSettings.BOTTOM_CONSTANT){
RotatedTileBox rb = tileBox.copy();
rb.setCenterLocation(0.5f, 0.5f);
LatLon ll = tileBox.getCenterLatLon();
rb.setLatLonCenter(ll.getLatitude(), ll.getLongitude());
double lat = tileBox.getLatFromPixel(rb.getCenterPixelX(), rb.getCenterPixelY());
double lon = tileBox.getLonFromPixel(rb.getCenterPixelX(), rb.getCenterPixelY());
view.setLatLon(lat, lon);
}
view.setMapPosition(previousMapPosition);
inChangeMarkerPositionMode = false;
mark(View.VISIBLE, R.id.map_ruler_layout,
R.id.map_left_widgets_panel, R.id.map_right_widgets_panel, R.id.map_center_info);
View collapseButton = mapActivity.findViewById(R.id.map_collapse_button);
if (collapseButton != null && wasCollapseButtonVisible) {
collapseButton.setVisibility(View.VISIBLE);
}
view.refreshMap();
}
private void mark(int status, int... widgets) {
for (int widget : widgets) {
View v = mapActivity.findViewById(widget);
if (v != null) {
v.setVisibility(status);
}
}
}
@Override
public boolean onSingleTap(PointF point, RotatedTileBox tileBox) {
if (isInChangeMarkerPositionMode() && !pressedQuickActionWidget(point.x, point.y)) {
setLayerState(true);
return true;
} else
return false;
}
private boolean pressedQuickActionWidget(float px, float py) {
return py <= quickActionsWidget.getHeight();
}
@Override
public void onDraw(Canvas canvas, RotatedTileBox box, DrawSettings settings) {
if (isInChangeMarkerPositionMode()) {
canvas.translate(box.getCenterPixelX() - contextMarker.getWidth() / 2, box.getCenterPixelY() - contextMarker.getHeight());
contextMarker.draw(canvas);
}
setUpQuickActionBtnVisibility();
}
private void setUpQuickActionBtnVisibility() {
boolean hideQuickButton = !isLayerOn ||
contextMenuLayer.isInChangeMarkerPositionMode() ||
contextMenuLayer.isInGpxDetailsMode() ||
mapActivity.getContextMenu().isVisible() && !mapActivity.getContextMenu().findMenuFragment().get().isRemoving() ||
mapActivity.getContextMenu().isVisible() && mapActivity.getContextMenu().findMenuFragment().get().isAdded() ||
mapActivity.getContextMenu().getMultiSelectionMenu().isVisible() && mapActivity.getContextMenu().getMultiSelectionMenu().getFragmentByTag().isAdded() ||
mapActivity.getContextMenu().getMultiSelectionMenu().isVisible() && !mapActivity.getContextMenu().getMultiSelectionMenu().getFragmentByTag().isRemoving();
quickActionButton.setVisibility(hideQuickButton ? View.GONE : View.VISIBLE);
}
@Override
public void destroyLayer() {
}
@Override
public boolean drawInScreenPixels() {
return true;
}
@Override
public void onActionsUpdated() {
quickActionsWidget.setActions(quickActionRegistry.getFilteredQuickActions());
}
@Override
public void onActionSelected(QuickAction action) {
QuickActionFactory.produceAction(action).execute(mapActivity);
setLayerState(true);
}
public PointF getMovableCenterPoint(RotatedTileBox tb) {
return new PointF(tb.getPixWidth() / 2, tb.getPixHeight() / 2);
}
public boolean isInChangeMarkerPositionMode() {
return isLayerOn && inChangeMarkerPositionMode;
}
public boolean isLayerOn() {
return isLayerOn;
}
public boolean onBackPressed() {
return setLayerState(true);
}
View.OnTouchListener onQuickActionTouchListener = new View.OnTouchListener() {
private int initialMarginX;
private int initialMarginY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setUpInitialValues(v, event);
return true;
case MotionEvent.ACTION_UP:
quickActionButton.setOnTouchListener(null);
quickActionButton.setPressed(false);
quickActionButton.setScaleX(1);
quickActionButton.setScaleY(1);
quickActionButton.setAlpha(1f);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
if (AndroidUiHelper.isOrientationPortrait(mapActivity))
settings.setPortraitFabMargin(params.rightMargin, params.bottomMargin);
else
settings.setLandscapeFabMargin(params.rightMargin, params.bottomMargin);
return true;
case MotionEvent.ACTION_MOVE:
if (initialMarginX == 0 && initialMarginY == 0 && initialTouchX == 0 && initialTouchY == 0)
setUpInitialValues(v, event);
int padding = calculateTotalSizePx(R.dimen.map_button_margin);
FrameLayout parent = (FrameLayout) v.getParent();
FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) v.getLayoutParams();
int deltaX = (int) (initialTouchX - event.getRawX());
int deltaY = (int) (initialTouchY - event.getRawY());
int newMarginX = interpolate(initialMarginX + deltaX, v.getWidth(), parent.getWidth() - padding * 2);
int newMarginY = interpolate(initialMarginY + deltaY, v.getHeight(), parent.getHeight() - padding * 2);
if (v.getHeight() + newMarginY <= parent.getHeight() - padding * 2 && newMarginY > 0)
param.bottomMargin = newMarginY;
if (v.getWidth() + newMarginX <= parent.getWidth() - padding * 2 && newMarginX > 0) {
param.rightMargin = newMarginX;
}
v.setLayoutParams(param);
return true;
}
return false;
}
private int interpolate(int value, int divider, int boundsSize) {
int viewSize = divider;
if (value <= divider && value > 0)
return value * value / divider;
else {
int leftMargin = boundsSize - value - viewSize;
if (leftMargin <= divider && value < boundsSize - viewSize)
return leftMargin - (leftMargin * leftMargin / divider) + value;
else
return value;
}
}
private void setUpInitialValues(View v, MotionEvent event) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
initialMarginX = params.rightMargin;
initialMarginY = params.bottomMargin;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
}
};
}