From cb5b12d5a831983e4647fe80b7c2f355b2430bdc Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 22 Mar 2019 17:26:56 +0100 Subject: [PATCH 1/9] initial implementation of a global map view --- res/menu/text_secure_normal.xml | 3 + res/values/strings.xml | 1 + .../securesms/ConversationActivity.java | 2 +- .../securesms/ConversationListActivity.java | 13 +- .../geolocation/DcLocationManager.java | 24 +++- .../securesms/map/MapActivity.java | 32 ++++- .../securesms/map/MapDataManager.java | 130 ++++++++++-------- .../securesms/map/model/FeatureTreeSet.java | 50 +++++++ .../securesms/map/{ => model}/MapSource.java | 2 +- .../map/model/TimeComparableFeature.java | 45 ++++++ 10 files changed, 237 insertions(+), 65 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java rename src/org/thoughtcrime/securesms/map/{ => model}/MapSource.java (98%) create mode 100644 src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java diff --git a/res/menu/text_secure_normal.xml b/res/menu/text_secure_normal.xml index 4f12ae3ed..f65d02225 100644 --- a/res/menu/text_secure_normal.xml +++ b/res/menu/text_secure_normal.xml @@ -19,4 +19,7 @@ + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 1981447ab..3c8dfe476 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -112,6 +112,7 @@ Toggle emoji keyboard Edit group Show map + Show all locations Archive chat Unarchive chat Add attachment diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 4ac442105..2af1c0247 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -63,7 +63,6 @@ import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEventCenter; import com.b44t.messenger.DcMsg; -import org.thoughtcrime.securesms.map.MapActivity; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.audio.AudioRecorder; @@ -83,6 +82,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.map.MapActivity; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AudioSlide; diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 407d6f617..f754a8f7d 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -27,18 +27,19 @@ import android.view.ViewGroup; import android.widget.ImageView; import com.b44t.messenger.DcChat; -import com.b44t.messenger.DcContext; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import org.thoughtcrime.securesms.components.SearchToolbar; -import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.map.MapActivity; import org.thoughtcrime.securesms.qr.QrScanHandler; import org.thoughtcrime.securesms.search.SearchFragment; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; +import java.util.ArrayList; + public class ConversationListActivity extends PassphraseRequiredActionBarActivity implements ConversationListFragment.ConversationSelectedListener { @@ -144,11 +145,19 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit case R.id.menu_qr_scan: handleQrScan(); return true; case R.id.menu_qr_show: handleQrShow(); return true; case R.id.menu_deaddrop: handleDeaddrop(); return true; + case R.id.menu_global_map: handleShowMap(); return true; } return false; } + private void handleShowMap() { + Intent intent = new Intent(this, MapActivity.class); + int[] chatIds = ApplicationContext.getInstance(this).dcLocationManager.getLocationStreamingChatIds(); + intent.putExtra(MapActivity.CHAT_IDS, chatIds); + startActivity(intent); + } + private void handleQrScan() { new IntentIntegrator(this).setCaptureActivity(QrScanActivity.class).initiateScan(); } diff --git a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index 30d4ec20d..5acd861a8 100644 --- a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -8,10 +8,17 @@ import android.location.Location; import android.os.IBinder; import android.util.Log; +import com.b44t.messenger.DcArray; +import com.b44t.messenger.DcChat; import com.b44t.messenger.DcChatlist; +import com.b44t.messenger.DcContext; import org.thoughtcrime.securesms.connect.DcHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.LinkedList; import java.util.Observable; import java.util.Observer; @@ -32,6 +39,7 @@ public class DcLocationManager implements Observer { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { + Log.d(TAG, "background service connected"); serviceBinder = (LocationBackgroundService.LocationBackgroundServiceBinder) service; while (pendingShareLastLocation.size() > 0) { shareLastLocation(pendingShareLastLocation.pop()); @@ -40,6 +48,7 @@ public class DcLocationManager implements Observer { @Override public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, "background service disconnected"); serviceBinder = null; } }; @@ -49,7 +58,7 @@ public class DcLocationManager implements Observer { DcLocation.getInstance().addObserver(this); DcChatlist chats = DcHelper.getContext(context).getChatlist(0, null, 0); for (int i = 0; i < chats.getCnt(); i++) { - if (DcHelper.getContext(context).isSendingLocationsToChat(chats.getChat(0).getId())) { + if (DcHelper.getContext(context).isSendingLocationsToChat(chats.getChat(i).getId())) { initializeLocationEngine(); return; } @@ -102,6 +111,19 @@ public class DcLocationManager implements Observer { DcHelper.getContext(context).deleteAllLocations(); } + + public int[] getLocationStreamingChatIds () { + DcContext dcContext = DcHelper.getContext(context); + DcChatlist chats = dcContext.getChatlist(0, null, 0); + int count = chats.getCnt(); + int[] chatIds = new int[count]; + for (int i = 0; i < count; i++) { + DcChat dcChat = chats.getChat(i); + chatIds[i] = dcChat.getId(); + } + return chatIds; + } + @Override public void update(Observable o, Object arg) { if (o instanceof DcLocation) { diff --git a/src/org/thoughtcrime/securesms/map/MapActivity.java b/src/org/thoughtcrime/securesms/map/MapActivity.java index 93d9a1cd7..b0fc1204b 100644 --- a/src/org/thoughtcrime/securesms/map/MapActivity.java +++ b/src/org/thoughtcrime/securesms/map/MapActivity.java @@ -7,9 +7,9 @@ import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; import android.util.Log; +import com.b44t.messenger.DcMsg; import com.mapbox.geojson.Feature; import com.mapbox.mapboxsdk.camera.CameraPosition; -import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.Style; @@ -27,6 +27,7 @@ import java.util.List; import java.util.Observable; import java.util.Observer; +import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_SELECTED; import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID; @@ -34,6 +35,7 @@ public class MapActivity extends BaseActivity implements Observer { public static final String TAG = MapActivity.class.getSimpleName(); public static final String CHAT_ID = "chat_id"; + public static final String CHAT_IDS = "chat_id"; public static final String MAP_TAG = "org.thoughtcrime.securesms.map"; private DcLocation dcLocation; @@ -44,8 +46,9 @@ public class MapActivity extends BaseActivity implements Observer { super.onCreate(savedInstanceState); setContentView(R.layout.activity_map); - int chatId = getIntent().getIntExtra(CHAT_ID, -1); - if (chatId == -1) { + final int[] chatIds = getChatIds(getIntent()); + + if (chatIds[0] == -1) { finish(); return; } @@ -76,7 +79,7 @@ public class MapActivity extends BaseActivity implements Observer { return; } - mapDataManager = new MapDataManager(this, mapBoxStyle, chatId, (latLngBounds) -> { + mapDataManager = new MapDataManager(this, mapBoxStyle, chatIds, (latLngBounds) -> { mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 50), 1000); }); @@ -99,13 +102,19 @@ public class MapActivity extends BaseActivity implements Observer { mapboxMap.addOnMapClickListener(point -> { final PointF pixel = mapboxMap.getProjection().toScreenLocation(point); - Log.d(TAG, "on info window clicked."); List features = mapboxMap.queryRenderedFeatures(pixel, mapDataManager.getInfoWindowLayers()); + Log.d(TAG, "on info window clicked." + features.size()); + for (Feature feature : features) { Log.d(TAG, "found feature: " + feature.toJson()); if (feature.hasProperty(MARKER_SELECTED) && feature.getBooleanProperty(MARKER_SELECTED)) { int messageId = feature.getNumberProperty(MESSAGE_ID).intValue(); + DcMsg dcMsg = ApplicationContext.getInstance(this).dcContext.getMsg(messageId); + int chatId = dcMsg.getChatId(); + if (chatId == DC_CHAT_NO_CHAT) { + continue; + } int msgs[] = DcHelper.getContext(MapActivity.this).getChatMsgs(chatId, 0, 0); int startingPosition = -1; @@ -171,4 +180,17 @@ public class MapActivity extends BaseActivity implements Observer { //TODO: consider implementing a button -> center map to current location } } + + private int[] getChatIds(Intent intent) { + if (intent == null) { + return new int[]{-1}; + } + + int[] chatIds = intent.getIntArrayExtra(CHAT_IDS); + if (chatIds == null || chatIds.length == 0) { + chatIds = new int[1]; + chatIds[0] = getIntent().getIntExtra(CHAT_ID, -1); + } + return chatIds; + } } diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index 75130c2c2..3857c36f3 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -10,11 +10,11 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.Log; import com.b44t.messenger.DcArray; +import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcEventCenter; import com.google.gson.JsonObject; @@ -35,6 +35,10 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.map.model.FeatureTreeSet; +import org.thoughtcrime.securesms.map.model.MapSource; +import org.thoughtcrime.securesms.map.model.TimeComparableFeature; import java.util.ArrayList; import java.util.HashMap; @@ -68,49 +72,54 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn private static final String TAG = MapDataManager.class.getSimpleName(); private Style mapboxStyle; private HashMap contactMapSources; - private HashMap> featureCollections; + private HashMap featureCollections; private Feature selectedFeature; - private int chatId; + private int[] chatIds = new int[1]; private Context context; + private boolean isInitial = true; public interface MapDataState { void onDataInitialized(LatLngBounds bounds); } - public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int chatId, MapDataState updateCallback) { + public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int[] chatIds, MapDataState updateCallback) { this.mapboxStyle = mapboxMapStyle; this.context = context; - this.chatId = chatId; + this.chatIds = chatIds; contactMapSources = new HashMap<>(); featureCollections = new HashMap<>(); LatLngBounds.Builder boundingBuilder = new LatLngBounds.Builder(); - int[] contactIds = ApplicationContext.getInstance(context).dcContext.getChatContacts(chatId); + for (int chatId : chatIds) { + int[] contactIds = ApplicationContext.getInstance(context).dcContext.getChatContacts(chatId); - for (int contactId : contactIds) { - if (contactId == 1) { - //skip self, it is explicitely added as 1:1 don't include self whereas groups and selftalk do - continue; + for (int contactId : contactIds) { + if (contactId == 1) { + //skip self, it is explicitely added as 1:1 don't include self whereas groups and selftalk do + continue; + } + addContactMapSource(contactId); + updateSource(chatId, contactId, boundingBuilder); + generateInfoWindows(contactId); } - addContactMapSource(contactId); - updateSource(contactId, boundingBuilder); - generateInfoWindows(contactId); - } - addContactMapSource(1); - updateSource(1, boundingBuilder); - generateMissingInfoWindows(1); + addContactMapSource(1); + updateSource(chatId, 1, boundingBuilder); + generateInfoWindows(1); - - try { - updateCallback.onDataInitialized(boundingBuilder.build()); - } catch (InvalidLatLngBoundsException e) { - e.printStackTrace(); + try { + updateCallback.onDataInitialized(boundingBuilder.build()); + } catch (InvalidLatLngBoundsException e) { + e.printStackTrace(); + } } } public void onResume() { ApplicationContext.getInstance(context).dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); - updateSources(); + if (!isInitial) { + updateSources(); + } + isInitial = false; } public void onPause() { @@ -118,6 +127,10 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn } public void addContactMapSource(int contactId) { + if (contactMapSources.get(contactId) != null) { + return; + } + DcContact contact = ApplicationContext.getInstance(context).dcContext.getContact(contactId); MapSource contactMapSource = new MapSource(contactId); contactMapSource.setColor(contact.getColor()); @@ -154,11 +167,16 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn return bitmap; } - private void updateSources() { - for (Integer contactId : contactMapSources.keySet()) { - updateSource(contactId); - generateMissingInfoWindows(contactId); + for (int chatId : chatIds) { + int[] contacts = DcHelper.getContext(context).getChatContacts(chatId); + for (int contactId : contacts) { + if (!contactMapSources.containsKey(contactId)) { + addContactMapSource(contactId); + } + updateSource(chatId, contactId); + generateMissingInfoWindows(contactId); + } } } @@ -181,71 +199,71 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn public void refreshSource(int contactId) { Log.d(TAG, "refreshSource start"); MapSource source = contactMapSources.get(contactId); - ArrayList collection = featureCollections.get(source.getMarkerFeatureCollection()); + FeatureTreeSet collection = featureCollections.get(source.getMarkerFeatureCollection()); GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(source.getMarkerSource()); - pointSource.setGeoJson(FeatureCollection.fromFeatures(collection)); + pointSource.setGeoJson(FeatureCollection.fromFeatures(collection.getFeatureList())); Log.d(TAG, "refreshSource finished"); } - private void updateSource(int contactId) { - updateSource(contactId, null); + + private void updateSource(int chatId, int contactId) { + updateSource(chatId, contactId, null); } - private void updateSource(int contactId, @Nullable LatLngBounds.Builder boundingBuilder) { + private void updateSource(int chatId, int contactId, LatLngBounds.Builder boundingBuilder) { DcArray locations = ApplicationContext.getInstance(context).dcContext.getLocations(chatId, contactId); - int count = locations.getCnt(); - ArrayList pointFeatureList = new ArrayList<>(); MapSource contactMapMetadata = contactMapSources.get(contactId); - if (count == 0) { - featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), pointFeatureList); - return; + FeatureTreeSet sortedPointFeatures = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + if (sortedPointFeatures == null) { + sortedPointFeatures = new FeatureTreeSet(); } - ArrayList coordinateList = new ArrayList<>(); + int count = locations.getCnt(); for (int i = 0; i < count; i++) { Point p = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i)); - coordinateList.add(p); - Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), contactId + "_" + i); + Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), chatId + "_" + contactId + "_" + i); pointFeature.addBooleanProperty(MARKER_SELECTED, false); pointFeature.addBooleanProperty(LAST_LOCATION, false); pointFeature.addNumberProperty(CONTACT_ID, contactId); - pointFeature.addStringProperty(INFO_WINDOW_ID, contactId + "_info_" + (count - 1 - i)); + pointFeature.addStringProperty(INFO_WINDOW_ID, chatId + "_" + contactId + "_info_" + (count - 1 - i)); pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i)); pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i)); pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i)); - pointFeatureList.add(pointFeature); + sortedPointFeatures.replace(new TimeComparableFeature(pointFeature)); + if (boundingBuilder != null) { boundingBuilder.include(new LatLng(locations.getLatitude(i), locations.getLongitude(i))); } } - if (pointFeatureList.size() > 0) { - pointFeatureList.get(0).addBooleanProperty(LAST_LOCATION, true); + if (sortedPointFeatures.size() > 0) { + sortedPointFeatures.first().getFeature().addBooleanProperty(LAST_LOCATION, true); } - FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(pointFeatureList); + FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(sortedPointFeatures.getFeatureList()); FeatureCollection lineFeatureCollection = FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry( - LineString.fromLngLats(coordinateList) + LineString.fromLngLats(sortedPointFeatures.getPointList()) )}); GeoJsonSource lineSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getLineSource()); lineSource.setGeoJson(lineFeatureCollection); GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getMarkerSource()); pointSource.setGeoJson(pointFeatureCollection); - featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), pointFeatureList); + featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), sortedPointFeatures); } private void generateMissingInfoWindows(int contactId) { MapSource contactMapMetadata = contactMapSources.get(contactId); - ArrayList featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + FeatureTreeSet featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + ArrayList missingWindows = new ArrayList<>(); - for (Feature f : featureList) { - String infoWindowId = f.getStringProperty(INFO_WINDOW_ID); + for (TimeComparableFeature tcf : featureList) { + String infoWindowId = tcf.getFeature().getStringProperty(INFO_WINDOW_ID); if (mapboxStyle.getImage(infoWindowId) == null) { Log.d(TAG, "create new infoWindow for " + infoWindowId); - missingWindows.add(f); + missingWindows.add(tcf.getFeature()); } else { // the list is ordered and thus, all older features should already have an info window break; @@ -260,8 +278,8 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn private void generateInfoWindows(int contactId) { MapSource contactMapMetadata = contactMapSources.get(contactId); - ArrayList featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); - new GenerateInfoWindowTask(this, contactId).execute(featureList); + FeatureTreeSet collection = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + new GenerateInfoWindowTask(this, contactId).execute(collection.getFeatureList()); } private void initGeoJsonSources(MapSource source) { @@ -313,12 +331,14 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn ).withFilter(filterInfoWindow)); } + //FIXME: consider to use data2 parameter to send chatID from core to Android @Override public void handleEvent(int eventId, Object data1, Object data2) { Log.d(TAG, "updateEvent in MapDataManager called. eventId: " + eventId); int contactId = (Integer) data1; if (contactMapSources.containsKey(contactId)) { - updateSource(contactId); + //FIXME: ---------v this is wrong, but there's no other opportunity for now + updateSource(chatIds[0], contactId); generateMissingInfoWindows(contactId); } } @@ -401,7 +421,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn private Feature getFeatureWithId(String id) { for (String key : featureCollections.keySet()) { - ArrayList featureCollection = featureCollections.get(key); + ArrayList featureCollection = featureCollections.get(key).getFeatureList(); for (Feature f : featureCollection) { if (f.id().equals(id)) { return f; diff --git a/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java b/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java new file mode 100644 index 000000000..edec0073b --- /dev/null +++ b/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms.map.model; + +import android.support.annotation.NonNull; + +import com.google.gson.JsonSyntaxException; +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.Point; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +/** + * Created by cyberta on 21.03.19. + */ + +public class FeatureTreeSet extends TreeSet { + + + public boolean replace(@NonNull TimeComparableFeature obj) { + boolean existed = remove(obj); + add(obj); + return existed; + } + + public ArrayList getFeatureList() { + ArrayList featureList = new ArrayList<>(); + Iterator iterator = this.iterator(); + while (iterator.hasNext()) { + TimeComparableFeature timeComparableFeature = iterator.next(); + featureList.add(timeComparableFeature.getFeature()); + } + return featureList; + } + + public List getPointList() { + ArrayList points = new ArrayList<>(); + Iterator iterator = this.iterator(); + while (iterator.hasNext()) { + TimeComparableFeature timeComparableFeature = iterator.next(); + try { + points.add(Point.fromJson(timeComparableFeature.getFeature().geometry().toJson())); + } catch (JsonSyntaxException | NullPointerException e) { + e.printStackTrace(); + } + } + return points; + } +} diff --git a/src/org/thoughtcrime/securesms/map/MapSource.java b/src/org/thoughtcrime/securesms/map/model/MapSource.java similarity index 98% rename from src/org/thoughtcrime/securesms/map/MapSource.java rename to src/org/thoughtcrime/securesms/map/model/MapSource.java index 6851429d0..60ecbcb16 100644 --- a/src/org/thoughtcrime/securesms/map/MapSource.java +++ b/src/org/thoughtcrime/securesms/map/model/MapSource.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.map; +package org.thoughtcrime.securesms.map.model; import android.graphics.Color; diff --git a/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java b/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java new file mode 100644 index 000000000..04829d002 --- /dev/null +++ b/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java @@ -0,0 +1,45 @@ +package org.thoughtcrime.securesms.map.model; + +import android.support.annotation.NonNull; + +import com.mapbox.geojson.Feature; + +import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP; + +/** + * Created by cyberta on 21.03.19. + */ + +public class TimeComparableFeature implements Comparable { + private Feature feature; + + public TimeComparableFeature(@NonNull Feature feature) { + this.feature = feature; + } + + public Feature getFeature() { + return feature; + } + + @Override + public int compareTo(@NonNull TimeComparableFeature o) { + if (this.feature.getNumberProperty(TIMESTAMP).longValue() == o.getFeature().getNumberProperty(TIMESTAMP).longValue()) { + return 0; + } + return this.feature.getNumberProperty(TIMESTAMP).longValue() < o.getFeature().getNumberProperty(TIMESTAMP).longValue() ? -1 : 1; + } + + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof TimeComparableFeature)) { + return false; + } + + TimeComparableFeature compare = (TimeComparableFeature) obj; + return this.feature.id() != null && + compare.feature.id() != null && + this.feature.id().equals(compare.feature.id()); + } + +} From 2ee94c9ba420957d9ac72269fd5423b0802f3b94 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 22 Mar 2019 22:21:30 +0100 Subject: [PATCH 2/9] cleanup and test FeatureTreeSet --- build.gradle | 1 + .../connect/ApplicationDcContext.java | 3 - .../geolocation/DcLocationManager.java | 5 - .../securesms/map/MapDataManager.java | 19 +-- .../securesms/map/model/FeatureTreeSet.java | 15 +- .../map/model/TimeComparableFeature.java | 18 ++- .../map/model/FeatureTreeSetTest.java | 131 ++++++++++++++++++ .../map/model/TimeComparableFeatureTest.java | 99 +++++++++++++ src/test/java/utils/TestUtils.java | 42 ++++++ 9 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 src/test/java/org/thoughtcrime/securesms/map/model/FeatureTreeSetTest.java create mode 100644 src/test/java/org/thoughtcrime/securesms/map/model/TimeComparableFeatureTest.java create mode 100644 src/test/java/utils/TestUtils.java diff --git a/build.gradle b/build.gradle index 3bfdefcac..61d10c7b5 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,7 @@ dependencies { implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' // glues the current time segment text in the gallery to the top. implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:7.2.0' + implementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12' testImplementation 'org.assertj:assertj-core:1.7.1' testImplementation 'org.mockito:mockito-core:1.9.5' diff --git a/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java b/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java index 6a6068042..1e223abe8 100644 --- a/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java +++ b/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java @@ -36,12 +36,9 @@ import org.thoughtcrime.securesms.util.Util; import java.io.BufferedInputStream; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; diff --git a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index 5acd861a8..eb30eb7da 100644 --- a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -8,17 +8,12 @@ import android.location.Location; import android.os.IBinder; import android.util.Log; -import com.b44t.messenger.DcArray; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcChatlist; import com.b44t.messenger.DcContext; import org.thoughtcrime.securesms.connect.DcHelper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.LinkedList; import java.util.Observable; import java.util.Observer; diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index 3857c36f3..7a563d68b 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -14,7 +14,6 @@ import android.support.v4.content.ContextCompat; import android.util.Log; import com.b44t.messenger.DcArray; -import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcEventCenter; import com.google.gson.JsonObject; @@ -33,8 +32,8 @@ import com.mapbox.mapboxsdk.style.layers.PropertyFactory; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.map.model.FeatureTreeSet; import org.thoughtcrime.securesms.map.model.MapSource; @@ -76,6 +75,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn private Feature selectedFeature; private int[] chatIds = new int[1]; private Context context; + private ApplicationDcContext dcContext; private boolean isInitial = true; public interface MapDataState { @@ -85,12 +85,13 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int[] chatIds, MapDataState updateCallback) { this.mapboxStyle = mapboxMapStyle; this.context = context; + this.dcContext = DcHelper.getContext(context); this.chatIds = chatIds; contactMapSources = new HashMap<>(); featureCollections = new HashMap<>(); LatLngBounds.Builder boundingBuilder = new LatLngBounds.Builder(); for (int chatId : chatIds) { - int[] contactIds = ApplicationContext.getInstance(context).dcContext.getChatContacts(chatId); + int[] contactIds = dcContext.getChatContacts(chatId); for (int contactId : contactIds) { if (contactId == 1) { @@ -115,7 +116,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn } public void onResume() { - ApplicationContext.getInstance(context).dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); + dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); if (!isInitial) { updateSources(); } @@ -123,7 +124,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn } public void onPause() { - ApplicationContext.getInstance(context).dcContext.eventCenter.removeObserver(DC_EVENT_LOCATION_CHANGED, this); + dcContext.eventCenter.removeObserver(DC_EVENT_LOCATION_CHANGED, this); } public void addContactMapSource(int contactId) { @@ -131,7 +132,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn return; } - DcContact contact = ApplicationContext.getInstance(context).dcContext.getContact(contactId); + DcContact contact = dcContext.getContact(contactId); MapSource contactMapSource = new MapSource(contactId); contactMapSource.setColor(contact.getColor()); contactMapSources.put(contactId, contactMapSource); @@ -169,7 +170,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn private void updateSources() { for (int chatId : chatIds) { - int[] contacts = DcHelper.getContext(context).getChatContacts(chatId); + int[] contacts = dcContext.getChatContacts(chatId); for (int contactId : contacts) { if (!contactMapSources.containsKey(contactId)) { addContactMapSource(contactId); @@ -211,7 +212,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn } private void updateSource(int chatId, int contactId, LatLngBounds.Builder boundingBuilder) { - DcArray locations = ApplicationContext.getInstance(context).dcContext.getLocations(chatId, contactId); + DcArray locations = dcContext.getLocations(chatId, contactId); MapSource contactMapMetadata = contactMapSources.get(contactId); FeatureTreeSet sortedPointFeatures = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); @@ -230,7 +231,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i)); pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i)); pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i)); - sortedPointFeatures.replace(new TimeComparableFeature(pointFeature)); + sortedPointFeatures.add(new TimeComparableFeature(pointFeature)); if (boundingBuilder != null) { boundingBuilder.include(new LatLng(locations.getLatitude(i), locations.getLongitude(i))); diff --git a/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java b/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java index edec0073b..0a5a80c00 100644 --- a/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java +++ b/src/org/thoughtcrime/securesms/map/model/FeatureTreeSet.java @@ -17,11 +17,16 @@ import java.util.TreeSet; public class FeatureTreeSet extends TreeSet { - - public boolean replace(@NonNull TimeComparableFeature obj) { + /** + * + * @param obj + * @return true if element was added newly, false if element was replaced + */ + @Override + public boolean add(@NonNull TimeComparableFeature obj) { boolean existed = remove(obj); - add(obj); - return existed; + super.add(obj); + return !existed; } public ArrayList getFeatureList() { @@ -34,7 +39,7 @@ public class FeatureTreeSet extends TreeSet { return featureList; } - public List getPointList() { + public ArrayList getPointList() { ArrayList points = new ArrayList<>(); Iterator iterator = this.iterator(); while (iterator.hasNext()) { diff --git a/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java b/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java index 04829d002..7b80185d1 100644 --- a/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java +++ b/src/org/thoughtcrime/securesms/map/model/TimeComparableFeature.java @@ -14,6 +14,10 @@ public class TimeComparableFeature implements Comparable private Feature feature; public TimeComparableFeature(@NonNull Feature feature) { + if (!feature.hasProperty(TIMESTAMP)) { + throw new IllegalArgumentException("Time comparable features need to have a TIMESTAMP property"); + } + this.feature = feature; } @@ -21,15 +25,25 @@ public class TimeComparableFeature implements Comparable return feature; } + /** + * + * @param o for same timestamps but different featureIds, this object is considered lower + * @return + */ @Override public int compareTo(@NonNull TimeComparableFeature o) { - if (this.feature.getNumberProperty(TIMESTAMP).longValue() == o.getFeature().getNumberProperty(TIMESTAMP).longValue()) { + if (this.equals(o) && this.feature.getNumberProperty(TIMESTAMP).longValue() == o.getFeature().getNumberProperty(TIMESTAMP).longValue()) { return 0; } - return this.feature.getNumberProperty(TIMESTAMP).longValue() < o.getFeature().getNumberProperty(TIMESTAMP).longValue() ? -1 : 1; + return this.feature.getNumberProperty(TIMESTAMP).longValue() > o.getFeature().getNumberProperty(TIMESTAMP).longValue() ? -1 : 1; } + @Override + public int hashCode() { + return this.feature.id().hashCode(); + } + @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof TimeComparableFeature)) { diff --git a/src/test/java/org/thoughtcrime/securesms/map/model/FeatureTreeSetTest.java b/src/test/java/org/thoughtcrime/securesms/map/model/FeatureTreeSetTest.java new file mode 100644 index 000000000..6b7a52b52 --- /dev/null +++ b/src/test/java/org/thoughtcrime/securesms/map/model/FeatureTreeSetTest.java @@ -0,0 +1,131 @@ +package org.thoughtcrime.securesms.map.model; + +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.Point; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Iterator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP; +import static utils.TestUtils.getPointFeature; + +/** + * Created by cyberta on 22.03.19. + */ +public class FeatureTreeSetTest { + + @Test + public void add_differentFeaturesIds_differentTimestamps_descendingOrder() { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + + Feature point2 = getPointFeature("3"); + point2.addNumberProperty(TIMESTAMP, 2); + featureTreeSet.add(new TimeComparableFeature(point2)); + + Feature point = getPointFeature("2"); + point.addNumberProperty(TIMESTAMP, 1); + featureTreeSet.add(new TimeComparableFeature(point)); + + featureTreeSet.add(new TimeComparableFeature(getPointFeature("1"))); + + Iterator iterator = featureTreeSet.iterator(); + assertEquals(123456789, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + assertEquals(2, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + assertEquals(1, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + + } + + + @Test + public void add_sameFeaturesIds_differentTimestamps_descendingOrder() { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + + Feature point2 = getPointFeature(); + point2.addNumberProperty(TIMESTAMP, 2); + featureTreeSet.add(new TimeComparableFeature(point2)); + + Feature point = getPointFeature(); + point.addNumberProperty(TIMESTAMP, 1); + featureTreeSet.add(new TimeComparableFeature(point)); + + featureTreeSet.add(new TimeComparableFeature(getPointFeature("1"))); + + + Iterator iterator = featureTreeSet.iterator(); + assertEquals(123456789, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + assertEquals(2, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + assertEquals(1, iterator.next().getFeature().getNumberProperty(TIMESTAMP).longValue()); + } + + @Test + public void add_sameFeaturesIds_sameTimestamps_noDuplicatedEntries() { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + featureTreeSet.add(new TimeComparableFeature(getPointFeature())); + featureTreeSet.add(new TimeComparableFeature(getPointFeature())); + assertEquals(1, featureTreeSet.size()); + } + + @Test + public void add_sameFeaturesIds_sameTimestamps_elementReplaced() { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + Feature feature = getPointFeature(); + feature.addStringProperty("Test", "element1"); + featureTreeSet.add(new TimeComparableFeature(feature)); + featureTreeSet.add(new TimeComparableFeature(getPointFeature())); + assertFalse(featureTreeSet.first().getFeature().hasProperty("Test")); + } + + + + @Test + public void getFeatureList_returnsOrderedList() throws Exception { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + + Feature point2 = getPointFeature(); + point2.addNumberProperty(TIMESTAMP, 2); + featureTreeSet.add(new TimeComparableFeature(point2)); + + Feature point = getPointFeature(); + point.addNumberProperty(TIMESTAMP, 1); + featureTreeSet.add(new TimeComparableFeature(point)); + + featureTreeSet.add(new TimeComparableFeature(getPointFeature("1"))); + + ArrayList features = featureTreeSet.getFeatureList(); + assertEquals(123456789, features.get(0).getNumberProperty(TIMESTAMP).longValue()); + assertEquals(2, features.get(1).getNumberProperty(TIMESTAMP).longValue()); + assertEquals(1, features.get(2).getNumberProperty(TIMESTAMP).longValue()); + + } + + @Test + public void getPointList_returnsOrderedPointList() throws Exception { + FeatureTreeSet featureTreeSet = new FeatureTreeSet(); + + Feature point = getPointFeature("id2", 1.00, 1.00); + point.addNumberProperty(TIMESTAMP, 2); + featureTreeSet.add(new TimeComparableFeature(point)); + + Feature point2 = getPointFeature("id1", 2.00, 2.00); + point2.addNumberProperty(TIMESTAMP, 1); + featureTreeSet.add(new TimeComparableFeature(point2)); + + + Feature point3 = getPointFeature("id3", 3.00, 3.00); + point3.addNumberProperty(TIMESTAMP, 3); + + featureTreeSet.add(new TimeComparableFeature(point3)); + + + ArrayList points = featureTreeSet.getPointList(); + assertEquals(new Double(3.00), points.get(0).coordinates().get(0)); + assertEquals(new Double(1.00), points.get(1).coordinates().get(0)); + assertEquals(new Double(2.00), points.get(2).coordinates().get(0)); + + } + +} \ No newline at end of file diff --git a/src/test/java/org/thoughtcrime/securesms/map/model/TimeComparableFeatureTest.java b/src/test/java/org/thoughtcrime/securesms/map/model/TimeComparableFeatureTest.java new file mode 100644 index 000000000..9dd4f48aa --- /dev/null +++ b/src/test/java/org/thoughtcrime/securesms/map/model/TimeComparableFeatureTest.java @@ -0,0 +1,99 @@ +package org.thoughtcrime.securesms.map.model; + +import com.mapbox.geojson.Feature; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP; +import static utils.TestUtils.getPointFeature; + +/** + * Created by cyberta on 22.03.19. + */ + +public class TimeComparableFeatureTest { + + @Test(expected = IllegalArgumentException.class) + public void init_noTimestamp_throwIllegalStateException() throws Exception { + Feature feature = getPointFeature(); + feature.removeProperty(TIMESTAMP); + new TimeComparableFeature(feature); + } + + @Test + public void compareTo_sameTimestamp_return0() throws Exception { + TimeComparableFeature tcf1 = new TimeComparableFeature(getPointFeature()); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(0, tcf1.compareTo(tcf2)); + assertEquals(0, tcf2.compareTo(tcf1)); + } + + @Test + public void compareTo_biggerTimestamp_return1() throws Exception { + Feature feature = getPointFeature(); + feature.addNumberProperty(TIMESTAMP, 234567890); + TimeComparableFeature tcf1 = new TimeComparableFeature(getPointFeature()); + TimeComparableFeature tcf2 = new TimeComparableFeature(feature); + + assertEquals(1, tcf1.compareTo(tcf2)); + } + + @Test + public void compareTo_biggerTimestamp_returnMinus1() throws Exception { + Feature feature = getPointFeature(); + feature.addNumberProperty(TIMESTAMP, 234567890); + TimeComparableFeature tcf1 = new TimeComparableFeature(feature); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(-1, tcf1.compareTo(tcf2)); + } + + @Test + public void compareTo_sameTimestamp_differntId_returnMinus1() throws Exception { + Feature feature = getPointFeature(); + feature.addNumberProperty(TIMESTAMP, 234567890); + TimeComparableFeature tcf1 = new TimeComparableFeature(feature); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(-1, tcf1.compareTo(tcf2)); + } + + @Test + public void equals_differentObjects_sameId_returnTrue() throws Exception { + TimeComparableFeature tcf1 = new TimeComparableFeature(getPointFeature()); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(true, tcf1.equals(tcf2)); + } + + @Test + public void equals_differentObjects_differentId_returnFalse() throws Exception { + Feature feature = getPointFeature("id2"); + TimeComparableFeature tcf1 = new TimeComparableFeature(feature); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(false, tcf1.equals(tcf2)); + } + + @Test + public void equals_differentObjects_differentId_sameTimeStamp_returnFalse() throws Exception { + Feature feature = getPointFeature("id2"); + TimeComparableFeature tcf1 = new TimeComparableFeature(feature); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(false, tcf1.equals(tcf2)); + } + + @Test + public void equals_differentObjects_sameId_differentTimeStamp_returnTrue() throws Exception { + Feature feature = getPointFeature(); + feature.addNumberProperty(TIMESTAMP, 234567890); + TimeComparableFeature tcf1 = new TimeComparableFeature(feature); + TimeComparableFeature tcf2 = new TimeComparableFeature(getPointFeature()); + + assertEquals(true, tcf1.equals(tcf2)); + } + +} \ No newline at end of file diff --git a/src/test/java/utils/TestUtils.java b/src/test/java/utils/TestUtils.java new file mode 100644 index 000000000..8a7b7ccfc --- /dev/null +++ b/src/test/java/utils/TestUtils.java @@ -0,0 +1,42 @@ +package utils; + +import com.google.gson.JsonObject; +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.Point; + +import static org.thoughtcrime.securesms.map.MapDataManager.ACCURACY; +import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID; +import static org.thoughtcrime.securesms.map.MapDataManager.INFO_WINDOW_ID; +import static org.thoughtcrime.securesms.map.MapDataManager.LAST_LOCATION; +import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_SELECTED; +import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID; +import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP; + +/** + * Created by cyberta on 22.03.19. + */ + +public class TestUtils { + + public static Feature getPointFeature(String id) { + return getPointFeature(id, 10.00, 52.00); + } + + public static Feature getPointFeature() { + return getPointFeature("id1"); + } + + public static Feature getPointFeature(String id, double latitude, double longitude) { + Point p = Point.fromLngLat(longitude, latitude); + Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), id); + pointFeature.addBooleanProperty(MARKER_SELECTED, false); + pointFeature.addBooleanProperty(LAST_LOCATION, false); + pointFeature.addNumberProperty(CONTACT_ID, 1); + pointFeature.addStringProperty(INFO_WINDOW_ID, "0_1_info_2"); + pointFeature.addNumberProperty(TIMESTAMP, 123456789); + pointFeature.addNumberProperty(MESSAGE_ID, 1); + pointFeature.addNumberProperty(ACCURACY, 12); + return Feature.fromJson(pointFeature.toJson()); + } + +} From 7897b392060b03de219a0319ad14b76a1e95aa8c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 22 Mar 2019 22:53:57 +0100 Subject: [PATCH 3/9] move getChatIds() to ApplicationDcContext --- .../securesms/ConversationListActivity.java | 5 ++--- .../connect/ApplicationDcContext.java | 18 ++++++++++++++++++ .../geolocation/DcLocationManager.java | 14 -------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index f754a8f7d..cc49c9507 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -31,6 +31,7 @@ import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import org.thoughtcrime.securesms.components.SearchToolbar; +import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.map.MapActivity; import org.thoughtcrime.securesms.qr.QrScanHandler; import org.thoughtcrime.securesms.search.SearchFragment; @@ -38,8 +39,6 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -import java.util.ArrayList; - public class ConversationListActivity extends PassphraseRequiredActionBarActivity implements ConversationListFragment.ConversationSelectedListener { @@ -153,7 +152,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private void handleShowMap() { Intent intent = new Intent(this, MapActivity.class); - int[] chatIds = ApplicationContext.getInstance(this).dcLocationManager.getLocationStreamingChatIds(); + int[] chatIds = DcHelper.getContext(this).getChatIds(); intent.putExtra(MapActivity.CHAT_IDS, chatIds); startActivity(intent); } diff --git a/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java b/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java index 1e223abe8..60b900d88 100644 --- a/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java +++ b/src/org/thoughtcrime/securesms/connect/ApplicationDcContext.java @@ -18,6 +18,7 @@ import android.webkit.MimeTypeMap; import android.widget.Toast; import com.b44t.messenger.DcChat; +import com.b44t.messenger.DcChatlist; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEventCenter; @@ -717,4 +718,21 @@ public class ApplicationDcContext extends DcContext { } return 0; } + + + /*********************************************************************************************** + * core related helper methods + **********************************************************************************************/ + + public int[] getChatIds() { + DcChatlist chats = getChatlist(0, null, 0); + int count = chats.getCnt(); + int[] chatIds = new int[count]; + for (int i = 0; i < count; i++) { + DcChat dcChat = chats.getChat(i); + chatIds[i] = dcChat.getId(); + } + return chatIds; + } + } diff --git a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index eb30eb7da..d3307ab45 100644 --- a/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -8,9 +8,7 @@ import android.location.Location; import android.os.IBinder; import android.util.Log; -import com.b44t.messenger.DcChat; import com.b44t.messenger.DcChatlist; -import com.b44t.messenger.DcContext; import org.thoughtcrime.securesms.connect.DcHelper; @@ -107,18 +105,6 @@ public class DcLocationManager implements Observer { } - public int[] getLocationStreamingChatIds () { - DcContext dcContext = DcHelper.getContext(context); - DcChatlist chats = dcContext.getChatlist(0, null, 0); - int count = chats.getCnt(); - int[] chatIds = new int[count]; - for (int i = 0; i < count; i++) { - DcChat dcChat = chats.getChat(i); - chatIds[i] = dcChat.getId(); - } - return chatIds; - } - @Override public void update(Observable o, Object arg) { if (o instanceof DcLocation) { From f596b3beb47f3e2f16e004afe8fa9688d8671e20 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 22 Mar 2019 23:51:23 +0100 Subject: [PATCH 4/9] show display name of sender in info window --- res/layout/map_bubble_layout.xml | 76 +++++++++---------- .../securesms/map/GenerateInfoWindowTask.java | 8 ++ 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/res/layout/map_bubble_layout.xml b/res/layout/map_bubble_layout.xml index 19e663e55..bd3c6fc4f 100644 --- a/res/layout/map_bubble_layout.xml +++ b/res/layout/map_bubble_layout.xml @@ -17,33 +17,6 @@ android:background="@color/white" tools:backgroundTint="@color/core_light_10"> - - - - - - - + android:orientation="horizontal"> + + + + + + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java b/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java index 42a6036e8..03fce0c58 100644 --- a/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java +++ b/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java @@ -14,6 +14,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; import com.mapbox.geojson.Feature; @@ -32,6 +33,7 @@ import java.util.HashMap; import java.util.Locale; import static android.view.View.GONE; +import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID; import static org.thoughtcrime.securesms.map.MapDataManager.INFO_WINDOW_ID; import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID; import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP; @@ -78,6 +80,12 @@ public class GenerateInfoWindowTask extends AsyncTask, HashMa TextView conversationItemBody = bubbleLayout.findViewById(R.id.conversation_item_body); Locale locale = DynamicLanguage.getSelectedLocale(callbackRef.get().getContext()); int messageId = (int) feature.getNumberProperty(MESSAGE_ID); + int contactId = (int) feature.getNumberProperty(CONTACT_ID); + + DcContact contact = DcHelper.getContext(callbackRef.get().getContext()).getContact(contactId); + TextView contactTextView = bubbleLayout.findViewById(R.id.message_sender); + contactTextView.setText(contact.getDisplayName()); + String msgText; if (messageId != 0) { DcContext dcContext = DcHelper.getContext(callbackRef.get().getContext()); From fc9971fa1519464eab1dfa4fa846d8ce02eb15a4 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 23 Mar 2019 00:42:23 +0100 Subject: [PATCH 5/9] reduce size of location markers that don't have a messageId --- src/org/thoughtcrime/securesms/map/MapDataManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index 7a563d68b..9b064c7e5 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -303,7 +303,10 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn mapboxStyle.addImage(source.getMarkerIcon(), generateColoredLocationIcon(source.getColorArgb())); - Expression markerSize = switchCase(toBool(get(MARKER_SELECTED)), literal(1.5f), literal(1.0f)); + Expression markerSize = + switchCase(toBool(get(MARKER_SELECTED)), literal(1.5f), + switchCase(toBool(get(LAST_LOCATION)), literal(1.0f), + switchCase(eq(get(MESSAGE_ID), literal(0)), literal(0.7f), literal(1.1f)))); Expression markerIcon = switchCase(toBool(get(LAST_LOCATION)), literal(source.getMarkerLastPositon()), literal(source.getMarkerIcon())); Expression allowOverlap = eq(get(LAST_LOCATION), literal(true)); mapboxStyle.addLayer(new SymbolLayer(source.getMarkerLayer(), source.getMarkerSource()) @@ -317,6 +320,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn .withProperties(PropertyFactory.lineCap(Property.LINE_CAP_ROUND), PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND), PropertyFactory.lineWidth(3f), + PropertyFactory.lineOpacity(0.5f), PropertyFactory.lineColor(source.getColorArgb()))); Expression filterInfoWindow = eq((get(MARKER_SELECTED)), literal(true)); From d88cae015d396f017c00cdf1fd1c575ec75df883 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 23 Mar 2019 11:16:40 +0100 Subject: [PATCH 6/9] fix live location updates for own locations --- src/org/thoughtcrime/securesms/map/MapActivity.java | 1 + src/org/thoughtcrime/securesms/map/MapDataManager.java | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/map/MapActivity.java b/src/org/thoughtcrime/securesms/map/MapActivity.java index b0fc1204b..f2ff98989 100644 --- a/src/org/thoughtcrime/securesms/map/MapActivity.java +++ b/src/org/thoughtcrime/securesms/map/MapActivity.java @@ -83,6 +83,7 @@ public class MapActivity extends BaseActivity implements Observer { mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 50), 1000); }); + mapboxMap.addOnMapClickListener(point -> { final PointF pixel = mapboxMap.getProjection().toScreenLocation(point); Log.d(TAG, "on item clicked."); diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index 648d8c5d4..920236bec 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -114,6 +114,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn e.printStackTrace(); } } + dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); } public void onResume() { @@ -340,17 +341,18 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn @Override public void handleEvent(int eventId, Object data1, Object data2) { Log.d(TAG, "updateEvent in MapDataManager called. eventId: " + eventId); - int contactId = (Integer) data1; + int contactId = ((Long) data1).intValue(); if (contactMapSources.containsKey(contactId)) { //FIXME: ---------v this is wrong, but there's no other opportunity for now updateSource(chatIds[0], contactId); generateMissingInfoWindows(contactId); + refreshSource(contactId); } } @Override public boolean runOnMain() { - return false; + return true; } From 1ea0ba0097ffd68961e43c0c2048b6f2bff6ab8b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 23 Mar 2019 11:36:42 +0100 Subject: [PATCH 7/9] change use core location ids for point feature --- src/org/thoughtcrime/securesms/map/MapDataManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index 920236bec..c984c1792 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -224,11 +224,11 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn int count = locations.getCnt(); for (int i = 0; i < count; i++) { Point p = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i)); - Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), chatId + "_" + contactId + "_" + i); + Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), String.valueOf(locations.getLocationId(i))); pointFeature.addBooleanProperty(MARKER_SELECTED, false); pointFeature.addBooleanProperty(LAST_LOCATION, false); pointFeature.addNumberProperty(CONTACT_ID, contactId); - pointFeature.addStringProperty(INFO_WINDOW_ID, chatId + "_" + contactId + "_info_" + (count - 1 - i)); + pointFeature.addStringProperty(INFO_WINDOW_ID, locations.getLocationId(i) + "_info_" + locations.getMsgId(i)); pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i)); pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i)); pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i)); From 7f702825f64161b2f455f4f752ef1ed80caf154d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 23 Mar 2019 12:22:26 +0100 Subject: [PATCH 8/9] use first name in info window as sender --- src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java b/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java index 03fce0c58..6f3699d9c 100644 --- a/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java +++ b/src/org/thoughtcrime/securesms/map/GenerateInfoWindowTask.java @@ -84,7 +84,7 @@ public class GenerateInfoWindowTask extends AsyncTask, HashMa DcContact contact = DcHelper.getContext(callbackRef.get().getContext()).getContact(contactId); TextView contactTextView = bubbleLayout.findViewById(R.id.message_sender); - contactTextView.setText(contact.getDisplayName()); + contactTextView.setText(contact.getFirstName()); String msgText; if (messageId != 0) { From 5f08c555c4725bac8c2969b3da82ccc55dbc3378 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 23 Mar 2019 18:49:38 +0100 Subject: [PATCH 9/9] use core ability to fetch all locations for a contact using chatId = 0 --- .../securesms/ConversationListActivity.java | 6 +- .../securesms/map/MapActivity.java | 12 +- .../securesms/map/MapDataManager.java | 435 +++++++++--------- 3 files changed, 231 insertions(+), 222 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index cc49c9507..cf43d9cca 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -31,7 +31,6 @@ import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import org.thoughtcrime.securesms.components.SearchToolbar; -import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.map.MapActivity; import org.thoughtcrime.securesms.qr.QrScanHandler; import org.thoughtcrime.securesms.search.SearchFragment; @@ -39,6 +38,8 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; +import static org.thoughtcrime.securesms.map.MapDataManager.ALL_CHATS_GLOBAL_MAP; + public class ConversationListActivity extends PassphraseRequiredActionBarActivity implements ConversationListFragment.ConversationSelectedListener { @@ -152,8 +153,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private void handleShowMap() { Intent intent = new Intent(this, MapActivity.class); - int[] chatIds = DcHelper.getContext(this).getChatIds(); - intent.putExtra(MapActivity.CHAT_IDS, chatIds); + intent.putExtra(MapActivity.CHAT_IDS, ALL_CHATS_GLOBAL_MAP); startActivity(intent); } diff --git a/src/org/thoughtcrime/securesms/map/MapActivity.java b/src/org/thoughtcrime/securesms/map/MapActivity.java index f2ff98989..a29cbe566 100644 --- a/src/org/thoughtcrime/securesms/map/MapActivity.java +++ b/src/org/thoughtcrime/securesms/map/MapActivity.java @@ -46,9 +46,9 @@ public class MapActivity extends BaseActivity implements Observer { super.onCreate(savedInstanceState); setContentView(R.layout.activity_map); - final int[] chatIds = getChatIds(getIntent()); + final int chatId = getIntent().getIntExtra(CHAT_ID, -1); - if (chatIds[0] == -1) { + if (chatId == -1) { finish(); return; } @@ -79,7 +79,7 @@ public class MapActivity extends BaseActivity implements Observer { return; } - mapDataManager = new MapDataManager(this, mapBoxStyle, chatIds, (latLngBounds) -> { + mapDataManager = new MapDataManager(this, mapBoxStyle, chatId, (latLngBounds) -> { mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 50), 1000); }); @@ -112,12 +112,12 @@ public class MapActivity extends BaseActivity implements Observer { if (feature.hasProperty(MARKER_SELECTED) && feature.getBooleanProperty(MARKER_SELECTED)) { int messageId = feature.getNumberProperty(MESSAGE_ID).intValue(); DcMsg dcMsg = ApplicationContext.getInstance(this).dcContext.getMsg(messageId); - int chatId = dcMsg.getChatId(); - if (chatId == DC_CHAT_NO_CHAT) { + int dcMsgChatId = dcMsg.getChatId(); + if (dcMsgChatId == DC_CHAT_NO_CHAT) { continue; } - int msgs[] = DcHelper.getContext(MapActivity.this).getChatMsgs(chatId, 0, 0); + int msgs[] = DcHelper.getContext(MapActivity.this).getChatMsgs(dcMsgChatId, 0, 0); int startingPosition = -1; for(int i=0; i< msgs.length; i++ ) { if(msgs[i] == messageId) { diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java index c984c1792..2cf377ea6 100644 --- a/src/org/thoughtcrime/securesms/map/MapDataManager.java +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -32,7 +32,6 @@ import com.mapbox.mapboxsdk.style.layers.PropertyFactory; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.DcHelper; @@ -41,10 +40,12 @@ import org.thoughtcrime.securesms.map.model.MapSource; import org.thoughtcrime.securesms.map.model.TimeComparableFeature; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static com.b44t.messenger.DcContext.DC_EVENT_LOCATION_CHANGED; +import static com.b44t.messenger.DcContext.DC_GCL_ADD_SELF; import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.expressions.Expression.get; import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; @@ -69,12 +70,15 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn public static final String TIMESTAMP = "TIMESTAMP"; public static final String MESSAGE_ID = "MESSAGE_ID"; public static final String ACCURACY = "ACCURACY"; + public static final int ALL_CHATS_GLOBAL_MAP = 0; + public static final long TIMESTAMP_NOW = 0L; + public static final long TIMEOUT = 3 * 60 * 60 * 1000; private static final String TAG = MapDataManager.class.getSimpleName(); private Style mapboxStyle; private HashMap contactMapSources; private HashMap featureCollections; private Feature selectedFeature; - private int[] chatIds = new int[1]; + private int chatId; private Context context; private ApplicationDcContext dcContext; private boolean isInitial = true; @@ -83,38 +87,29 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn void onDataInitialized(LatLngBounds bounds); } - public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int[] chatIds, MapDataState updateCallback) { + public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int chatId, MapDataState updateCallback) { this.mapboxStyle = mapboxMapStyle; this.context = context; this.dcContext = DcHelper.getContext(context); - this.chatIds = chatIds; + this.chatId = chatId; contactMapSources = new HashMap<>(); featureCollections = new HashMap<>(); LatLngBounds.Builder boundingBuilder = new LatLngBounds.Builder(); - for (int chatId : chatIds) { - int[] contactIds = dcContext.getChatContacts(chatId); - for (int contactId : contactIds) { - if (contactId == 1) { - //skip self, it is explicitely added as 1:1 don't include self whereas groups and selftalk do - continue; - } - addContactMapSource(contactId); - updateSource(chatId, contactId, boundingBuilder); - generateInfoWindows(contactId); - } + int[] contactIds = getContactIds(chatId); - addContactMapSource(1); - updateSource(chatId, 1, boundingBuilder); - generateInfoWindows(1); - - try { - updateCallback.onDataInitialized(boundingBuilder.build()); - } catch (InvalidLatLngBoundsException e) { - e.printStackTrace(); - } + for (int contactId : contactIds) { + updateSource(chatId, contactId, boundingBuilder); + generateInfoWindows(contactId); } + dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); + + try { + updateCallback.onDataInitialized(boundingBuilder.build()); + } catch (InvalidLatLngBoundsException e) { + e.printStackTrace(); + } } public void onResume() { @@ -129,60 +124,6 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn dcContext.eventCenter.removeObserver(DC_EVENT_LOCATION_CHANGED, this); } - public void addContactMapSource(int contactId) { - if (contactMapSources.get(contactId) != null) { - return; - } - - DcContact contact = dcContext.getContact(contactId); - MapSource contactMapSource = new MapSource(contactId); - contactMapSource.setColor(contact.getColor()); - contactMapSources.put(contactId, contactMapSource); - initGeoJsonSources(contactMapSource); - initLayers(contactMapSource); - } - - private Bitmap generateColoredLastPositionIcon(int colorFilter) { - return generateColoredBitmap(colorFilter, R.drawable.ic_location_on_white_24dp); - } - - private Bitmap generateColoredLocationIcon(int colorFilter) { - return generateColoredBitmap(colorFilter, R.drawable.ic_location_dot); - } - - private Bitmap generateColoredBitmap(int colorFilter, @DrawableRes int res) { - Bitmap icon = getBitmap(res); - Paint paint = new Paint(); - ColorFilter filter = new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); - paint.setColorFilter(filter); - Canvas canvas = new Canvas(icon); - canvas.drawBitmap(icon, 0, 0, paint); - return icon; - } - - private Bitmap getBitmap(@DrawableRes int res) { - Drawable drawable = ContextCompat.getDrawable(context, res); - Canvas canvas = new Canvas(); - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - canvas.setBitmap(bitmap); - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - drawable.draw(canvas); - return bitmap; - } - - private void updateSources() { - for (int chatId : chatIds) { - int[] contacts = dcContext.getChatContacts(chatId); - for (int contactId : contacts) { - if (!contactMapSources.containsKey(contactId)) { - addContactMapSource(contactId); - } - updateSource(chatId, contactId); - generateMissingInfoWindows(contactId); - } - } - } - @Override public Context getContext() { return context; @@ -208,145 +149,12 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn Log.d(TAG, "refreshSource finished"); } - private void updateSource(int chatId, int contactId) { - updateSource(chatId, contactId, null); - } - - private void updateSource(int chatId, int contactId, LatLngBounds.Builder boundingBuilder) { - DcArray locations = ApplicationContext.getInstance(context).dcContext.getLocations(chatId, contactId, System.currentTimeMillis()-3*60*60*1000, 0); - MapSource contactMapMetadata = contactMapSources.get(contactId); - - FeatureTreeSet sortedPointFeatures = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); - if (sortedPointFeatures == null) { - sortedPointFeatures = new FeatureTreeSet(); - } - - int count = locations.getCnt(); - for (int i = 0; i < count; i++) { - Point p = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i)); - Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), String.valueOf(locations.getLocationId(i))); - pointFeature.addBooleanProperty(MARKER_SELECTED, false); - pointFeature.addBooleanProperty(LAST_LOCATION, false); - pointFeature.addNumberProperty(CONTACT_ID, contactId); - pointFeature.addStringProperty(INFO_WINDOW_ID, locations.getLocationId(i) + "_info_" + locations.getMsgId(i)); - pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i)); - pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i)); - pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i)); - sortedPointFeatures.add(new TimeComparableFeature(pointFeature)); - - if (boundingBuilder != null) { - boundingBuilder.include(new LatLng(locations.getLatitude(i), locations.getLongitude(i))); - } - } - - if (sortedPointFeatures.size() > 0) { - sortedPointFeatures.first().getFeature().addBooleanProperty(LAST_LOCATION, true); - } - - FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(sortedPointFeatures.getFeatureList()); - FeatureCollection lineFeatureCollection = FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry( - LineString.fromLngLats(sortedPointFeatures.getPointList()) - )}); - - GeoJsonSource lineSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getLineSource()); - lineSource.setGeoJson(lineFeatureCollection); - GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getMarkerSource()); - pointSource.setGeoJson(pointFeatureCollection); - featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), sortedPointFeatures); - } - - private void generateMissingInfoWindows(int contactId) { - MapSource contactMapMetadata = contactMapSources.get(contactId); - FeatureTreeSet featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); - - ArrayList missingWindows = new ArrayList<>(); - - for (TimeComparableFeature tcf : featureList) { - String infoWindowId = tcf.getFeature().getStringProperty(INFO_WINDOW_ID); - if (mapboxStyle.getImage(infoWindowId) == null) { - Log.d(TAG, "create new infoWindow for " + infoWindowId); - missingWindows.add(tcf.getFeature()); - } else { - // the list is ordered and thus, all older features should already have an info window - break; - } - } - - if (missingWindows.size() > 0) { - new GenerateInfoWindowTask(this, contactId).execute(missingWindows); - } - } - - - private void generateInfoWindows(int contactId) { - MapSource contactMapMetadata = contactMapSources.get(contactId); - FeatureTreeSet collection = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); - new GenerateInfoWindowTask(this, contactId).execute(collection.getFeatureList()); - } - - private void initGeoJsonSources(MapSource source) { - GeoJsonSource markerPositionSource = new GeoJsonSource(source.getMarkerSource()); - GeoJsonSource linePositionSource = new GeoJsonSource(source.getLineSource()); - - try { - mapboxStyle.addSource(markerPositionSource); - mapboxStyle.addSource(linePositionSource); - } catch (RuntimeException e) { - //TODO: specify exception more - Log.e(TAG, "Unable to init GeoJsonSources. Already added to mapBoxMap? " + e.getMessage()); - } - - } - - private void initLayers(MapSource source) { - mapboxStyle.addImage(source.getMarkerLastPositon(), - generateColoredLastPositionIcon(source.getColorArgb())); - mapboxStyle.addImage(source.getMarkerIcon(), - generateColoredLocationIcon(source.getColorArgb())); - - Expression markerSize = - switchCase(toBool(get(MARKER_SELECTED)), literal(1.5f), - switchCase(toBool(get(LAST_LOCATION)), literal(1.0f), - switchCase(eq(get(MESSAGE_ID), literal(0)), literal(0.7f), literal(1.1f)))); - Expression markerIcon = switchCase(toBool(get(LAST_LOCATION)), literal(source.getMarkerLastPositon()), literal(source.getMarkerIcon())); - Expression allowOverlap = eq(get(LAST_LOCATION), literal(true)); - mapboxStyle.addLayer(new SymbolLayer(source.getMarkerLayer(), source.getMarkerSource()) - .withProperties( - iconImage(markerIcon), - iconSize(markerSize), - iconAllowOverlap(allowOverlap)) - ); - - mapboxStyle.addLayer(new LineLayer(source.getLineLayer(), source.getLineSource()) - .withProperties(PropertyFactory.lineCap(Property.LINE_CAP_ROUND), - PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND), - PropertyFactory.lineWidth(3f), - PropertyFactory.lineOpacity(0.5f), - PropertyFactory.lineColor(source.getColorArgb()))); - - Expression filterInfoWindow = eq((get(MARKER_SELECTED)), literal(true)); - Expression iconOffset = switchCase(toBool(get(LAST_LOCATION)), literal(new Float[] {-2f, -25f}), literal(new Float[] {-2f, -15f})); - mapboxStyle.addLayer(new SymbolLayer(source.getInfoWindowLayer(), source.getMarkerSource()). - withProperties( - iconImage("{"+INFO_WINDOW_ID+"}"), - iconAnchor(ICON_ANCHOR_BOTTOM_LEFT), - /* all info window and marker image to appear at the same time*/ - iconAllowOverlap(true), - /* offset the info window to be above the marker */ - iconOffset(iconOffset) - ).withFilter(filterInfoWindow)); - } - - //FIXME: consider to use data2 parameter to send chatID from core to Android @Override public void handleEvent(int eventId, Object data1, Object data2) { Log.d(TAG, "updateEvent in MapDataManager called. eventId: " + eventId); int contactId = ((Long) data1).intValue(); if (contactMapSources.containsKey(contactId)) { - //FIXME: ---------v this is wrong, but there's no other opportunity for now - updateSource(chatIds[0], contactId); - generateMissingInfoWindows(contactId); - refreshSource(contactId); + updateSource(chatId, contactId); } } @@ -396,6 +204,208 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn } } + private void updateSource(int chatId, int contactId) { + updateSource(chatId, contactId, null); + } + + private void updateSource(int chatId, int contactId, LatLngBounds.Builder boundingBuilder) { + DcArray locations = dcContext.getLocations(chatId, contactId, System.currentTimeMillis() - TIMEOUT, TIMESTAMP_NOW); + MapSource contactMapMetadata = contactMapSources.get(contactId); + if (contactMapMetadata == null) { + contactMapMetadata = addContactMapSource(contactId); + } + + FeatureTreeSet sortedPointFeatures = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + if (sortedPointFeatures == null) { + sortedPointFeatures = new FeatureTreeSet(); + } + + int count = locations.getCnt(); + for (int i = 0; i < count; i++) { + Point p = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i)); + Feature pointFeature = Feature.fromGeometry(p, new JsonObject(), String.valueOf(locations.getLocationId(i))); + pointFeature.addBooleanProperty(MARKER_SELECTED, false); + pointFeature.addBooleanProperty(LAST_LOCATION, false); + pointFeature.addNumberProperty(CONTACT_ID, contactId); + pointFeature.addStringProperty(INFO_WINDOW_ID, locations.getLocationId(i) + "_info_" + locations.getMsgId(i)); + pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i)); + pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i)); + pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i)); + sortedPointFeatures.add(new TimeComparableFeature(pointFeature)); + + if (boundingBuilder != null) { + boundingBuilder.include(new LatLng(locations.getLatitude(i), locations.getLongitude(i))); + } + } + + if (sortedPointFeatures.size() > 0) { + sortedPointFeatures.first().getFeature().addBooleanProperty(LAST_LOCATION, true); + } + + FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(sortedPointFeatures.getFeatureList()); + FeatureCollection lineFeatureCollection = FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry( + LineString.fromLngLats(sortedPointFeatures.getPointList()) + )}); + + GeoJsonSource lineSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getLineSource()); + lineSource.setGeoJson(lineFeatureCollection); + GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getMarkerSource()); + pointSource.setGeoJson(pointFeatureCollection); + featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), sortedPointFeatures); + + generateMissingInfoWindows(contactId); + } + + private void generateMissingInfoWindows(int contactId) { + MapSource contactMapMetadata = contactMapSources.get(contactId); + FeatureTreeSet featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + + ArrayList missingWindows = new ArrayList<>(); + for (TimeComparableFeature tcf : featureList) { + String infoWindowId = tcf.getFeature().getStringProperty(INFO_WINDOW_ID); + if (mapboxStyle.getImage(infoWindowId) == null) { + Log.d(TAG, "create new infoWindow for " + infoWindowId); + missingWindows.add(tcf.getFeature()); + } else { + // the list is ordered and thus, all older features should already have an info window + break; + } + } + + if (missingWindows.size() > 0) { + new GenerateInfoWindowTask(this, contactId).execute(missingWindows); + } + } + + + private void generateInfoWindows(int contactId) { + MapSource contactMapMetadata = contactMapSources.get(contactId); + FeatureTreeSet collection = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection()); + new GenerateInfoWindowTask(this, contactId).execute(collection.getFeatureList()); + } + + private void initGeoJsonSources(MapSource source) { + GeoJsonSource markerPositionSource = new GeoJsonSource(source.getMarkerSource()); + GeoJsonSource linePositionSource = new GeoJsonSource(source.getLineSource()); + + try { + mapboxStyle.addSource(markerPositionSource); + mapboxStyle.addSource(linePositionSource); + } catch (RuntimeException e) { + //TODO: specify exception more + Log.e(TAG, "Unable to init GeoJsonSources. Already added to mapBoxMap? " + e.getMessage()); + } + } + + private int[] getContactIds(int chatId) { + if (chatId == ALL_CHATS_GLOBAL_MAP) { + return dcContext.getContacts(DC_GCL_ADD_SELF, ""); + } else { + int[] contactIds = dcContext.getChatContacts(chatId); + boolean hasSelf = false; + for (int contact : contactIds) { + if (contact == 1) { + hasSelf = true; + break; + } + } + if (!hasSelf) { + contactIds = Arrays.copyOf(contactIds, contactIds.length + 1); + contactIds[contactIds.length - 1] = 1; + } + return contactIds; + } + } + + private void initLayers(MapSource source) { + mapboxStyle.addImage(source.getMarkerLastPositon(), + generateColoredLastPositionIcon(source.getColorArgb())); + mapboxStyle.addImage(source.getMarkerIcon(), + generateColoredLocationIcon(source.getColorArgb())); + + Expression markerSize = + switchCase(toBool(get(MARKER_SELECTED)), literal(1.5f), + switchCase(toBool(get(LAST_LOCATION)), literal(1.0f), + switchCase(eq(get(MESSAGE_ID), literal(0)), literal(0.7f), literal(1.1f)))); + Expression markerIcon = switchCase(toBool(get(LAST_LOCATION)), literal(source.getMarkerLastPositon()), literal(source.getMarkerIcon())); + Expression allowOverlap = eq(get(LAST_LOCATION), literal(true)); + mapboxStyle.addLayer(new SymbolLayer(source.getMarkerLayer(), source.getMarkerSource()) + .withProperties( + iconImage(markerIcon), + iconSize(markerSize), + iconAllowOverlap(allowOverlap)) + ); + + mapboxStyle.addLayer(new LineLayer(source.getLineLayer(), source.getLineSource()) + .withProperties(PropertyFactory.lineCap(Property.LINE_CAP_ROUND), + PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND), + PropertyFactory.lineWidth(3f), + PropertyFactory.lineOpacity(0.5f), + PropertyFactory.lineColor(source.getColorArgb()))); + + Expression filterInfoWindow = eq((get(MARKER_SELECTED)), literal(true)); + Expression iconOffset = switchCase(toBool(get(LAST_LOCATION)), literal(new Float[] {-2f, -25f}), literal(new Float[] {-2f, -15f})); + mapboxStyle.addLayer(new SymbolLayer(source.getInfoWindowLayer(), source.getMarkerSource()). + withProperties( + iconImage("{"+INFO_WINDOW_ID+"}"), + iconAnchor(ICON_ANCHOR_BOTTOM_LEFT), + /* all info window and marker image to appear at the same time*/ + iconAllowOverlap(true), + /* offset the info window to be above the marker */ + iconOffset(iconOffset) + ).withFilter(filterInfoWindow)); + } + + private MapSource addContactMapSource(int contactId) { + if (contactMapSources.get(contactId) != null) { + return contactMapSources.get(contactId); + } + + DcContact contact = dcContext.getContact(contactId); + MapSource contactMapSource = new MapSource(contactId); + contactMapSource.setColor(contact.getColor()); + contactMapSources.put(contactId, contactMapSource); + initGeoJsonSources(contactMapSource); + initLayers(contactMapSource); + return contactMapSource; + } + + private Bitmap generateColoredLastPositionIcon(int colorFilter) { + return generateColoredBitmap(colorFilter, R.drawable.ic_location_on_white_24dp); + } + + private Bitmap generateColoredLocationIcon(int colorFilter) { + return generateColoredBitmap(colorFilter, R.drawable.ic_location_dot); + } + + private Bitmap generateColoredBitmap(int colorFilter, @DrawableRes int res) { + Bitmap icon = getBitmap(res); + Paint paint = new Paint(); + ColorFilter filter = new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); + paint.setColorFilter(filter); + Canvas canvas = new Canvas(icon); + canvas.drawBitmap(icon, 0, 0, paint); + return icon; + } + + private Bitmap getBitmap(@DrawableRes int res) { + Drawable drawable = ContextCompat.getDrawable(context, res); + Canvas canvas = new Canvas(); + Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + canvas.setBitmap(bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + drawable.draw(canvas); + return bitmap; + } + + private void updateSources() { + int[] contactIds = getContactIds(chatId); + for (int contactId : contactIds) { + updateSource(chatId, contactId); + } + } + + private void replaceSelectedMarker(String featureId) { Feature feature = getFeatureWithId(featureId); feature.addBooleanProperty(MARKER_SELECTED, true); @@ -425,7 +435,6 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn refreshSource(selectedFeature.getNumberProperty(CONTACT_ID).intValue()); } - private Feature getFeatureWithId(String id) { for (String key : featureCollections.keySet()) { ArrayList featureCollection = featureCollections.get(key).getFeatureList();