/**
* Copyright (C) 2016 eBusiness Information
*
* This file is part of OSM Contributor.
*
* OSM Contributor 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.
*
* OSM Contributor 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 OSM Contributor. If not, see <http://www.gnu.org/licenses/>.
*/
package io.jawg.osmcontributor.ui.presenters;
import android.graphics.Bitmap;
import com.mapbox.mapboxsdk.annotations.IconFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import io.jawg.osmcontributor.BuildConfig;
import io.jawg.osmcontributor.OsmTemplateApplication;
import io.jawg.osmcontributor.model.entities.Note;
import io.jawg.osmcontributor.model.entities.Poi;
import io.jawg.osmcontributor.model.entities.PoiNodeRef;
import io.jawg.osmcontributor.model.entities.PoiType;
import io.jawg.osmcontributor.model.events.NotesLoadedEvent;
import io.jawg.osmcontributor.model.events.PleaseLoadNotesEvent;
import io.jawg.osmcontributor.model.events.PleaseLoadPoiTypes;
import io.jawg.osmcontributor.model.events.PleaseLoadPoisEvent;
import io.jawg.osmcontributor.model.events.PoiTypesLoaded;
import io.jawg.osmcontributor.model.events.PoisAndNotesDownloadedEvent;
import io.jawg.osmcontributor.model.events.PoisLoadedEvent;
import io.jawg.osmcontributor.model.events.RevertFinishedEvent;
import io.jawg.osmcontributor.rest.events.SyncDownloadPoisAndNotesEvent;
import io.jawg.osmcontributor.ui.events.map.PleaseChangeValuesDetailNoteFragmentEvent;
import io.jawg.osmcontributor.ui.events.map.PleaseChangeValuesDetailPoiFragmentEvent;
import io.jawg.osmcontributor.ui.events.map.PleaseInitializeDrawer;
import io.jawg.osmcontributor.ui.fragments.MapFragment;
import io.jawg.osmcontributor.ui.utils.MapMode;
import io.jawg.osmcontributor.ui.utils.views.map.marker.LocationMarkerView;
import io.jawg.osmcontributor.ui.utils.views.map.marker.LocationMarkerViewOptions;
import io.jawg.osmcontributor.utils.Box;
import io.jawg.osmcontributor.utils.ConfigManager;
import io.jawg.osmcontributor.utils.OsmAnswers;
import io.jawg.osmcontributor.utils.core.MapElement;
import timber.log.Timber;
public class MapFragmentPresenter {
/*=========================================*/
/*------------ATTRIBUTES-------------------*/
/*=========================================*/
private boolean forceRefreshPoi = false;
private boolean forceRefreshNotes = false;
private List<PoiType> poiTypes = null;
private Float previousZoom;
private LatLngBounds triggerReloadPoiLatLngBounds;
private MapFragment mapFragment;
@Inject
EventBus eventBus;
@Inject
ConfigManager configManager;
/*=========================================*/
/*------------CONSTRUCTORS-----------------*/
/*=========================================*/
public MapFragmentPresenter(MapFragment mapFragment) {
this.mapFragment = mapFragment;
((OsmTemplateApplication) mapFragment.getActivity().getApplication()).getOsmTemplateComponent().inject(this);
}
/*=========================================*/
/*------------GETTER/SETTER----------------*/
/*=========================================*/
public int getNumberOfPoiTypes() {
return poiTypes != null ? poiTypes.size() : 0;
}
public List<PoiType> getPoiTypes() {
return poiTypes == null ? new ArrayList<PoiType>() : poiTypes;
}
public PoiType getPoiType(int id) {
return poiTypes.get(id);
}
public void setForceRefreshPoi() {
this.forceRefreshPoi = true;
}
public void setForceRefreshNotes() {
this.forceRefreshNotes = true;
}
/*=========================================*/
/*----------------EVENTS-------------------*/
/*=========================================*/
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onPoiTypesLoaded(PoiTypesLoaded event) {
Timber.d("Received event PoiTypesLoaded");
poiTypes = event.getPoiTypes();
setForceRefreshPoi();
setForceRefreshNotes();
eventBus.post(new PleaseInitializeDrawer(poiTypes, mapFragment.getPoiTypeHidden()));
mapFragment.loadPoiTypeSpinner();
mapFragment.loadPoiTypeFloatingBtn();
eventBus.removeStickyEvent(event);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onRevertFinishedEvent(RevertFinishedEvent event) {
Timber.d("Received event RevertFinishedEvent");
Object object = event.getRelatedObject();
if (object == null) {
mapFragment.removeAllPoiMarkers();
setForceRefreshPoi();
loadPoisIfNeeded();
} else if (object instanceof Poi) {
Poi poi = (Poi) object;
LocationMarkerViewOptions markerOptions = mapFragment.getMarkerOptions(event.getMarkerType(), poi.getId());
if (markerOptions == null) {
markerOptions = new LocationMarkerViewOptions<Poi>().position(poi.getPosition()).relatedObject(poi);
setIcon(markerOptions, poi, false);
mapFragment.addPoi(markerOptions);
} else {
markerOptions.position(poi.getPosition()).relatedObject(poi);
}
setIcon(markerOptions, poi, false);
OsmAnswers.localPoiAction(poi.getType().getTechnicalName(), "cancel");
} else if (object instanceof PoiNodeRef) {
mapFragment.switchMode(MapMode.WAY_EDITION);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPoisAndNotesDownloadedEvent(PoisAndNotesDownloadedEvent event) {
mapFragment.showProgressBar(false);
forceRefreshPoi = true;
loadPoisIfNeeded();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPoisLoadedEvent(PoisLoadedEvent event) {
List<Poi> pois = event.getPois();
forceRefreshPoi = false;
List<MapElement> mapElements = new ArrayList<>(pois.size());
for (Poi poi : pois) {
mapElements.add(poi);
}
onLoaded(mapElements, LocationMarkerView.MarkerType.POI);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onNotesLoadedEvent(NotesLoadedEvent event) {
mapFragment.removeAllNotes();
List<Note> notes = event.getNotes();
List<MapElement> mapElements = new ArrayList<>(notes.size());
for (Note note : notes) {
mapElements.add(note);
}
forceRefreshNotes = false;
onLoaded(mapElements, LocationMarkerView.MarkerType.NOTE);
}
public void register() {
eventBus.register(this);
}
public void unregister() {
eventBus.unregister(this);
}
/*=========================================*/
/*------------CODE-------------------------*/
/*=========================================*/
public void loadPoisIfNeeded() {
if (poiTypes == null) {
Timber.v("PleaseLoadPoiTypes");
eventBus.post(new PleaseLoadPoiTypes());
}
LatLngBounds viewLatLngBounds = mapFragment.getViewLatLngBounds();
if (viewLatLngBounds != null) {
if (mapFragment.getZoomLevel() > BuildConfig.ZOOM_MARKER_MIN) {
if (shouldReload(viewLatLngBounds)) {
Timber.d("Reloading pois");
previousZoom = mapFragment.getZoomLevel();
triggerReloadPoiLatLngBounds = enlarge(viewLatLngBounds, 1.5);
eventBus.post(new PleaseLoadPoisEvent(enlarge(viewLatLngBounds, 1.75)));
eventBus.post(new PleaseLoadNotesEvent(enlarge(viewLatLngBounds, 1.75)));
}
}
}
}
public void downloadAreaPoisAndNotes() {
mapFragment.showProgressBar(true);
eventBus.post(new SyncDownloadPoisAndNotesEvent(Box.convertFromLatLngBounds(enlarge(mapFragment.getViewLatLngBounds(), 1.75))));
}
/*=========================================*/
/*------------PRIVATE CODE-----------------*/
/*=========================================*/
private boolean shouldReload(LatLngBounds viewLatLngBounds) {
if (forceRefreshPoi || forceRefreshNotes) {
forceRefreshPoi = false;
forceRefreshNotes = false;
return true;
}
if (previousZoom != null && previousZoom < BuildConfig.ZOOM_MARKER_MIN) {
return true;
}
return triggerReloadPoiLatLngBounds == null
|| !triggerReloadPoiLatLngBounds.union(viewLatLngBounds).equals(triggerReloadPoiLatLngBounds);
}
private LatLngBounds enlarge(LatLngBounds viewLatLngBounds, double factor) {
double n = viewLatLngBounds.getLatNorth();
double e = viewLatLngBounds.getLonEast();
double s = viewLatLngBounds.getLatSouth();
double w = viewLatLngBounds.getLonWest();
double f = (factor - 1) / 2;
return new LatLngBounds.Builder()
.include(new LatLng(n + f * (n - s), e + f * (e - w)))
.include(new LatLng(s - f * (n - s), w - f * (e - w)))
.build();
}
private void setIcon(LocationMarkerViewOptions markerOptions, Object relatedObject, boolean selected) {
Bitmap bitmap;
if (relatedObject instanceof Poi) {
Poi poi = (Poi) relatedObject;
bitmap = mapFragment.getBitmapHandler().getMarkerBitmap(poi.getType(), Poi.computeState(selected, false, poi.getUpdated()));
} else {
Note note = (Note) relatedObject;
bitmap = mapFragment.getBitmapHandler().getNoteBitmap(Note.computeState(note, selected, false));
}
if (bitmap != null) {
markerOptions.icon(IconFactory.getInstance(mapFragment.getActivity()).fromBitmap(bitmap));
}
}
private void onLoaded(List<MapElement> mapElements, LocationMarkerView.MarkerType markerType) {
LocationMarkerView markerSelected = mapFragment.getMarkerSelected();
List<Long> ids = new ArrayList<>(mapElements.size());
for (MapElement mapElement : mapElements) {
ids.add(mapElement.getId());
LocationMarkerViewOptions markerOptions = mapFragment.getMarkerOptions(markerType, mapElement.getId());
boolean selected = false;
if (markerOptions == null) {
markerOptions = new LocationMarkerViewOptions<>().relatedObject(mapElement).position(mapElement.getPosition());
if (mapFragment.getSelectedMarkerType().equals(markerType) && mapElement.getId().equals(mapFragment.getMarkerSelectedId())) {
selected = true;
mapFragment.setMarkerSelected(markerOptions.getMarker());
} else if (mapFragment.getSelectedMarkerType().equals(LocationMarkerView.MarkerType.POI) && markerSelected != null && mapElement.getId().equals(((Poi) markerSelected.getRelatedObject()).getId())) {
selected = true;
}
//the poi in edition should be hidden
if (!(markerSelected != null && mapFragment.getMapMode() == MapMode.POI_POSITION_EDITION && markerSelected.equals(markerOptions.getMarker())) && (mapElement instanceof Poi && !((Poi) mapElement).getToDelete())) {
setIcon(markerOptions, mapElement, selected);
mapFragment.addPoi(markerOptions);
}
if (markerType == LocationMarkerView.MarkerType.NOTE) {
if (mapFragment.getSelectedMarkerType().equals(LocationMarkerView.MarkerType.NOTE) && mapElement.getId().equals(mapFragment.getMarkerSelectedId())) {
mapFragment.setMarkerSelected(markerOptions.getMarker());
}
setIcon(markerOptions, mapElement, false);
mapFragment.addNote(markerOptions);
}
} else {
if (markerType == LocationMarkerView.MarkerType.POI) {
Poi poi = (Poi) mapElement;
Poi oldPoi = (Poi) markerOptions.getMarker().getRelatedObject();
oldPoi.setName(poi.getName());
oldPoi.setUpdated(poi.getUpdated());
selected = false;
if (mapFragment.getSelectedMarkerType().equals(LocationMarkerView.MarkerType.POI)
&& (mapElement.getId().equals(mapFragment.getMarkerSelectedId())
|| markerSelected != null
&& mapElement.getId().equals(((Poi) markerSelected.getRelatedObject()).getId()))) {
selected = true;
}
setIcon(markerOptions, oldPoi, selected);
} else {
if (mapFragment.getSelectedMarkerType().equals(LocationMarkerView.MarkerType.NOTE)
&& markerSelected != null
&& mapElement.getId().equals(((Note) markerSelected.getRelatedObject()).getId())) {
selected = true;
}
setIcon(markerOptions, mapElement, selected);
}
//update the detail banner data
if (selected) {
if (mapFragment.getMapMode() == MapMode.DETAIL_NOTE) {
eventBus.post(new PleaseChangeValuesDetailNoteFragmentEvent((Note) mapElement));
} else {
Poi poi = (Poi) mapElement;
eventBus.post(new PleaseChangeValuesDetailPoiFragmentEvent(poi));
}
}
}
}
if (markerType == LocationMarkerView.MarkerType.NOTE) {
mapFragment.removeNoteMarkersNotIn(ids);
} else {
mapFragment.removePoiMarkersNotIn(ids);
}
if ((mapFragment.getMapMode() == MapMode.DEFAULT || mapFragment.getMapMode() == MapMode.POI_CREATION)) {
mapFragment.reselectMarker();
}
if (mapFragment.getSelectedMarkerType().equals(markerType) && markerSelected == null) {
mapFragment.setMarkerSelectedId(-1L);
}
}
}