package net.osmand.plus.parkingpoint;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.support.v7.app.AlertDialog;
import android.text.format.DateFormat;
import android.text.format.Time;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.TimePicker;
import net.osmand.data.LatLon;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.OsmandSettings.CommonPreference;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.dashboard.tools.DashFragmentData;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.views.AnimateDraggingMapThread;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.TextInfoWidget;
import java.util.Calendar;
/**
*
* The plugin facilitates a storage of the location of a parked car.
*
* @author Alena Fedasenka
*/
public class ParkingPositionPlugin extends OsmandPlugin {
public static final String ID = "osmand.parking.position";
public static final String PARKING_PLUGIN_COMPONENT = "net.osmand.parkingPlugin"; //$NON-NLS-1$
public final static String PARKING_POINT_LAT = "parking_point_lat"; //$NON-NLS-1$
public final static String PARKING_POINT_LON = "parking_point_lon"; //$NON-NLS-1$
public final static String PARKING_TYPE = "parking_type"; //$NON-NLS-1$
public final static String PARKING_TIME = "parking_limit_time"; //$//$NON-NLS-1$
public final static String PARKING_START_TIME = "parking_time"; //$//$NON-NLS-1$
public final static String PARKING_EVENT_ADDED = "parking_event_added"; //$//$NON-NLS-1$
private LatLon parkingPosition;
private OsmandApplication app;
private ParkingPositionLayer parkingLayer;
private TextInfoWidget parkingPlaceControl;
private final CommonPreference<Float> parkingLat;
private final CommonPreference<Float> parkingLon;
private CommonPreference<Boolean> parkingType;
private CommonPreference<Boolean> parkingEvent;
private CommonPreference<Long> parkingTime;
private CommonPreference<Long> parkingStartTime;
public ParkingPositionPlugin(OsmandApplication app) {
this.app = app;
OsmandSettings set = app.getSettings();
ApplicationMode.regWidgetVisibility("parking", (ApplicationMode[]) null);
parkingLat = set.registerFloatPreference(PARKING_POINT_LAT, 0f).makeGlobal();
parkingLon = set.registerFloatPreference(PARKING_POINT_LON, 0f).makeGlobal();
parkingType = set.registerBooleanPreference(PARKING_TYPE, false).makeGlobal();
parkingEvent = set.registerBooleanPreference(PARKING_EVENT_ADDED, false).makeGlobal();
parkingTime = set.registerLongPreference(PARKING_TIME, -1).makeGlobal();
parkingStartTime = set.registerLongPreference(PARKING_START_TIME, -1).makeGlobal();
parkingPosition = constructParkingPosition();
}
public LatLon getParkingPosition() {
return parkingPosition;
}
public LatLon constructParkingPosition() {
float lat = parkingLat.get();
float lon = parkingLon.get();
if (lat == 0 && lon == 0) {
return null;
}
return new LatLon(lat, lon);
}
public boolean getParkingType() {
return parkingType.get();
}
public boolean isParkingEventAdded() {
return parkingEvent.get();
}
public boolean addOrRemoveParkingEvent(boolean added) {
return parkingEvent.set(added);
}
public long getParkingTime() {
return parkingTime.get();
}
public long getStartParkingTime() {
return parkingStartTime.get();
}
public boolean clearParkingPosition() {
parkingLat.resetToDefault();
parkingLon.resetToDefault();
parkingType.resetToDefault();
parkingTime.resetToDefault();
parkingEvent.resetToDefault();
parkingStartTime.resetToDefault();
parkingPosition = null;
return true;
}
public boolean setParkingPosition(double latitude, double longitude) {
parkingLat.set((float) latitude);
parkingLon.set((float) longitude);
parkingPosition = constructParkingPosition();
return true;
}
public boolean setParkingType(boolean limited) {
if (!limited)
parkingTime.set(-1L);
parkingType.set(limited);
return true;
}
public boolean setParkingTime(long timeInMillis) {
parkingTime.set(timeInMillis);
return true;
}
public boolean setParkingStartTime(long timeInMillis) {
parkingStartTime.set(timeInMillis);
return true;
}
@Override
public String getId() {
return ID;
}
@Override
public String getDescription() {
return app.getString(R.string.osmand_parking_plugin_description);
}
@Override
public String getName() {
return app.getString(R.string.osmand_parking_plugin_name);
}
@Override
public String getHelpFileName() {
return "feature_articles/parking-plugin.html";
}
@Override
public void registerLayers(MapActivity activity) {
// remove old if existing after turn
if(parkingLayer != null) {
activity.getMapView().removeLayer(parkingLayer);
}
parkingLayer = new ParkingPositionLayer(activity, this);
activity.getMapView().addLayer(parkingLayer, 5.5f);
registerWidget(activity);
}
@Override
public void updateLayers(OsmandMapTileView mapView, MapActivity activity) {
if (isActive()) {
if (parkingLayer == null) {
registerLayers(activity);
}
if (parkingPlaceControl == null) {
registerWidget(activity);
}
} else {
if (parkingLayer != null) {
activity.getMapView().removeLayer(parkingLayer);
parkingLayer = null;
}
MapInfoLayer mapInfoLayer = activity.getMapLayers().getMapInfoLayer();
if (mapInfoLayer != null && parkingPlaceControl != null) {
mapInfoLayer.removeSideWidget(parkingPlaceControl);
mapInfoLayer.recreateControls();
parkingPlaceControl = null;
}
}
}
private void registerWidget(MapActivity activity) {
MapInfoLayer mapInfoLayer = activity.getMapLayers().getMapInfoLayer();
if (mapInfoLayer != null) {
parkingPlaceControl = createParkingPlaceInfoControl(activity);
mapInfoLayer.registerSideWidget(parkingPlaceControl,
R.drawable.ic_action_parking_dark, R.string.map_widget_parking, "parking", false, 10);
mapInfoLayer.recreateControls();
}
}
@Override
public void registerMapContextMenuActions(final MapActivity mapActivity,
final double latitude, final double longitude,
ContextMenuAdapter adapter, Object selectedObj) {
ItemClickListener addListener = new ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int resId,
int pos, boolean isChecked) {
if (resId == R.string.context_menu_item_add_parking_point) {
showAddParkingDialog(mapActivity, latitude, longitude);
}
return true;
}
};
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.context_menu_item_add_parking_point, mapActivity)
.setIcon(R.drawable.ic_action_parking_dark)
.setListener(addListener)
.createItem());
}
/**
* Method dialog for adding of a parking location.
* It allows user to choose a type of parking (time-limited or time-unlimited).
*/
public void showAddParkingDialog(final MapActivity mapActivity, final double latitude, final double longitude) {
final boolean wasEventPreviouslyAdded = isParkingEventAdded();
final ContextMenuAdapter menuAdapter = new ContextMenuAdapter();
ContextMenuItem.ItemBuilder itemBuilder = new ContextMenuItem.ItemBuilder();
menuAdapter.addItem(itemBuilder.setTitleId(R.string.osmand_parking_no_lim_text, app)
.setIcon(R.drawable.ic_action_time_start).setTag(1).createItem());
menuAdapter.addItem(itemBuilder.setTitleId(R.string.osmand_parking_time_limit, app)
.setIcon(R.drawable.ic_action_time_span).setTag(2).createItem());
final AlertDialog.Builder builder = new AlertDialog.Builder(mapActivity);
boolean light = app.getSettings().isLightContent() && !app.getDaynightHelper().isNightMode();
final ArrayAdapter<ContextMenuItem> listAdapter = menuAdapter.createListAdapter(mapActivity, light);
builder.setTitle(R.string.parking_options);
builder.setAdapter(listAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int which) {
ContextMenuItem item = menuAdapter.getItem(which);
int index = item.getTag();
if (index == 1) {
dialog.dismiss();
if (wasEventPreviouslyAdded) {
showDeleteEventWarning(mapActivity);
}
addOrRemoveParkingEvent(false);
setParkingPosition(mapActivity, latitude, longitude, false);
showContextMenuIfNeeded(mapActivity);
mapActivity.getMapView().refreshMap();
} else if (index == 2) {
if (wasEventPreviouslyAdded) {
showDeleteEventWarning(mapActivity);
}
setParkingPosition(mapActivity, latitude, longitude, true);
showSetTimeLimitDialog(mapActivity, dialog);
mapActivity.getMapView().refreshMap();
}
}
});
builder.setNegativeButton(R.string.shared_string_cancel, null);
builder.create().show();
}
private void showContextMenuIfNeeded(final MapActivity mapActivity) {
if (parkingLayer != null) {
MapContextMenu menu = mapActivity.getContextMenu();
if (menu.isVisible()) {
menu.show(new LatLon(parkingPosition.getLatitude(), parkingPosition.getLongitude()),
parkingLayer.getObjectName(parkingPosition), parkingPosition);
}
}
}
/**
* Method creates confirmation dialog for deletion of a parking location.
*/
public AlertDialog showDeleteDialog(final Activity activity) {
AlertDialog.Builder confirm = new AlertDialog.Builder(activity);
confirm.setTitle(activity.getString(R.string.osmand_parking_delete));
confirm.setMessage(activity.getString(R.string.osmand_parking_delete_confirm));
confirm.setCancelable(true);
confirm.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showDeleteEventWarning(activity);
cancelParking();
if (activity instanceof MapActivity) {
((MapActivity) activity).getContextMenu().close();
}
}
});
confirm.setNegativeButton(R.string.shared_string_cancel, null);
return confirm.show();
}
/**
* Opens the dialog to set a time limit for time-limited parking.
* The dialog has option to add a notification to Calendar app.
* Anyway the time-limit can be seen from parking point description.
* @param mapActivity
* @param choose
*/
private void showSetTimeLimitDialog(final MapActivity mapActivity, final DialogInterface choose) {
final View setTimeParking = mapActivity.getLayoutInflater().inflate(R.layout.parking_set_time_limit, null);
AlertDialog.Builder setTime = new AlertDialog.Builder(mapActivity);
setTime.setView(setTimeParking);
setTime.setTitle(mapActivity.getString(R.string.osmand_parking_time_limit_title));
setTime.setNegativeButton(R.string.shared_string_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancelParking();
}
});
final TextView textView = (TextView) setTimeParking.findViewById(R.id.parkTime);
final TimePicker timePicker = (TimePicker) setTimeParking.findViewById(R.id.parking_time_picker);
timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
private static final int TIME_PICKER_INTERVAL = 5;
private boolean mIgnoreEvent = false;
private Calendar cal = Calendar.getInstance();
@Override
public void onTimeChanged(TimePicker timePicker, int hourOfDay, int minute) {
if (mIgnoreEvent) {
return;
}
if (minute % TIME_PICKER_INTERVAL != 0) {
int minuteFloor = minute - (minute % TIME_PICKER_INTERVAL);
minute = minuteFloor + (minute == minuteFloor + 1 ? TIME_PICKER_INTERVAL : 0);
if (minute == 60) {
minute = 0;
}
mIgnoreEvent = true;
timePicker.setCurrentMinute(minute);
mIgnoreEvent = false;
}
long timeInMillis = cal.getTimeInMillis() + hourOfDay * 60 * 60 * 1000 + minute * 60 * 1000;
textView.setText(mapActivity.getString(R.string.osmand_parking_position_description_add)
+ " " + parkingLayer.getFormattedTime(timeInMillis));
}
});
//to set the same 24-hour or 12-hour mode as it is set in the device
timePicker.setIs24HourView(true);
timePicker.setCurrentHour(0);
timePicker.setCurrentMinute(0);
setTime.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
choose.dismiss();
Calendar cal = Calendar.getInstance();
//int hour = cal.get(Calendar.HOUR_OF_DAY );
//int minute = cal.get(Calendar.MINUTE);
cal.add(Calendar.HOUR_OF_DAY, timePicker.getCurrentHour());
cal.add(Calendar.MINUTE, timePicker.getCurrentMinute());
setParkingTime(cal.getTimeInMillis());
CheckBox addCalendarEvent = (CheckBox) setTimeParking.findViewById(R.id.check_event_in_calendar);
if (addCalendarEvent.isChecked()) {
addCalendarEvent(setTimeParking);
addOrRemoveParkingEvent(true);
} else {
addOrRemoveParkingEvent(false);
}
showContextMenuIfNeeded(mapActivity);
}
});
setTime.create();
setTime.show();
}
/**
* Opens a Calendar app with added notification to pick up the car from time-limited parking.
* @param view
*/
private void addCalendarEvent(View view) {
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event"); //$NON-NLS-1$
intent.putExtra("calendar_id", 1); //$NON-NLS-1$
intent.putExtra("title", view.getContext().getString(R.string.osmand_parking_event)); //$NON-NLS-1$
intent.putExtra("beginTime", getParkingTime()); //$NON-NLS-1$
intent.putExtra("endTime", getParkingTime() + 60 * 60 * 1000); //$NON-NLS-1$
view.getContext().startActivity(intent);
}
/**
* Method shows warning, if previously the event for time-limited parking was added to Calendar app.
* @param activity
*/
private void showDeleteEventWarning(final Activity activity) {
if (isParkingEventAdded()) {
AlertDialog.Builder deleteEventWarning = new AlertDialog.Builder(activity);
deleteEventWarning.setTitle(activity.getString(R.string.osmand_parking_warning));
deleteEventWarning.setMessage(activity.getString(R.string.osmand_parking_warning_text));
deleteEventWarning.setNeutralButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
deleteEventWarning.create();
deleteEventWarning.show();
}
}
/**
* Method sets a parking point on a ParkingLayer.
* @param mapActivity
* @param latitude
* @param longitude
* @param isLimited
*/
private void setParkingPosition(final MapActivity mapActivity, final double latitude, final double longitude, boolean isLimited) {
setParkingPosition(latitude, longitude);
setParkingType(isLimited);
setParkingStartTime(Calendar.getInstance().getTimeInMillis());
if (parkingLayer != null) {
parkingLayer.refresh();
}
}
private void cancelParking() {
if (parkingLayer != null) {
parkingLayer.refresh();
}
clearParkingPosition();
}
@Override
public void registerOptionsMenuItems(final MapActivity mapActivity, ContextMenuAdapter helper) {
}
/**
* @return the control to be added on a MapInfoLayer
* that shows a distance between
* the current position on the map
* and the location of the parked car
*/
private TextInfoWidget createParkingPlaceInfoControl(final MapActivity map) {
TextInfoWidget parkingPlaceControl = new TextInfoWidget(map) {
private float[] calculations = new float[1];
private int cachedMeters = 0;
@Override
public boolean updateInfo(DrawSettings drawSettings) {
LatLon parkingPoint = parkingLayer.getParkingPoint();
if( parkingPoint != null && !map.getRoutingHelper().isFollowingMode()) {
OsmandMapTileView view = map.getMapView();
int d = 0;
if (d == 0) {
net.osmand.Location.distanceBetween(view.getLatitude(), view.getLongitude(), parkingPoint.getLatitude(), parkingPoint.getLongitude(), calculations);
d = (int) calculations[0];
}
if (distChanged(cachedMeters, d)) {
cachedMeters = d;
if (cachedMeters <= 20) {
cachedMeters = 0;
setText(null, null);
} else {
String ds = OsmAndFormatter.getFormattedDistance(cachedMeters, map.getMyApplication());
int ls = ds.lastIndexOf(' ');
if (ls == -1) {
setText(ds, null);
} else {
setText(ds.substring(0, ls), ds.substring(ls + 1));
}
}
return true;
}
} else if (cachedMeters != 0) {
cachedMeters = 0;
setText(null, null);
return true;
}
return false;
}
/**
* Utility method.
* @param oldDist
* @param dist
* @return
*/
private boolean distChanged(int oldDist, int dist){
if(oldDist != 0 && Math.abs(oldDist - dist) < 30){
return false;
}
return true;
}
};
parkingPlaceControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
OsmandMapTileView view = map.getMapView();
AnimateDraggingMapThread thread = view.getAnimatedDraggingThread();
LatLon parkingPoint = parkingPosition;
if (parkingPoint != null) {
int fZoom = view.getZoom() < 15 ? 15 : view.getZoom();
thread.startMoving(parkingPoint.getLatitude(), parkingPoint.getLongitude(), fZoom, true);
}
}
});
parkingPlaceControl.setText(null, null);
parkingPlaceControl.setIcons(R.drawable.widget_parking_day, R.drawable.widget_parking_night);
return parkingPlaceControl;
}
@Override
public Class<? extends Activity> getSettingsActivity() {
return null;
}
@Override
public int getAssetResourceName() {
return R.drawable.parking_position;
}
@Override
public int getLogoResourceId() {
return R.drawable.ic_action_parking_dark;
}
String getFormattedTime(long timeInMillis, Activity ctx) {
StringBuilder timeStringBuilder = new StringBuilder();
Time time = new Time();
time.set(timeInMillis);
timeStringBuilder.append(time.hour);
timeStringBuilder.append(":");
int minute = time.minute;
timeStringBuilder.append(minute < 10 ? "0" + minute : minute);
if (!DateFormat.is24HourFormat(ctx)) {
timeStringBuilder.append(time.hour >= 12 ? ctx.getString(R.string.osmand_parking_pm) : ctx
.getString(R.string.osmand_parking_am));
}
return timeStringBuilder.toString();
}
String getFormattedTimeInterval(long timeInMillis, Activity ctx) {
if (timeInMillis < 0) {
timeInMillis *= -1;
}
StringBuilder timeStringBuilder = new StringBuilder();
int hours = (int) timeInMillis / (1000 * 60 * 60);
int minMills = (int) timeInMillis % (1000 * 60 * 60);
int minutes = minMills / (1000 * 60);
if (hours > 0) {
timeStringBuilder.append(hours);
timeStringBuilder.append(" ");
timeStringBuilder.append(ctx.getString(R.string.osmand_parking_hour));
}
if (timeStringBuilder.length() > 0) {
timeStringBuilder.append(" ");
}
timeStringBuilder.append(minutes);
timeStringBuilder.append(" ");
timeStringBuilder.append(ctx.getString(R.string.osmand_parking_minute));
return timeStringBuilder.toString();
}
public String getParkingStartDesc(Activity ctx) {
return ctx.getString(R.string.osmand_parking_position_description_add_time)
+ " " + getFormattedTime(getStartParkingTime(), ctx);
}
public String getParkingLeftDesc(Activity ctx) {
StringBuilder descr = new StringBuilder();
if (getParkingType()) {
long endtime = getParkingTime();
long currTime = Calendar.getInstance().getTimeInMillis();
long timeDiff = endtime - currTime;
descr.append(getFormattedTimeInterval(timeDiff, ctx)).append(" ");
if (timeDiff < 0) {
descr.append(ctx.getString(R.string.osmand_parking_overdue));
} else {
descr.append(ctx.getString(R.string.osmand_parking_time_left));
}
}
return descr.toString();
}
public String getParkingDescription(Activity ctx) {
StringBuilder timeLimitDesc = new StringBuilder();
timeLimitDesc.append(ctx.getString(R.string.osmand_parking_position_description_add_time) + " ");
timeLimitDesc.append(getFormattedTime(getStartParkingTime(), ctx) + ".");
if (getParkingType()) {
// long parkingTime = settings.getParkingTime();
// long parkingStartTime = settings.getStartParkingTime();
// Time time = new Time();
// time.set(parkingTime);
// timeLimitDesc.append(map.getString(R.string.osmand_parking_position_description_add) + " ");
// timeLimitDesc.append(time.hour);
// timeLimitDesc.append(":");
// int minute = time.minute;
// timeLimitDesc.append(minute<10 ? "0" + minute : minute);
// if (!DateFormat.is24HourFormat(map.getApplicationContext())) {
// timeLimitDesc.append(time.hour >= 12 ? map.getString(R.string.osmand_parking_pm) :
// map.getString(R.string.osmand_parking_am));
// }
timeLimitDesc.append(ctx.getString(R.string.osmand_parking_position_description_add) + " ");
timeLimitDesc.append(getFormattedTime(getParkingTime(),ctx));
}
return ctx.getString(R.string.osmand_parking_position_description, timeLimitDesc.toString());
}
@Override
public DashFragmentData getCardFragment() {
return DashParkingFragment.FRAGMENT_DATA;
}
}