mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Merge pull request #777 from deltachat/global_map_view
Initial implementation of a global map view
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -17,33 +17,6 @@
|
||||
android:background="@color/white"
|
||||
tools:backgroundTint="@color/core_light_10">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/group_sender_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/group_message_sender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="4sp"
|
||||
android:layout_marginEnd="4sp"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
tools:visibility="visible"
|
||||
tools:text="Bob"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -79,19 +52,44 @@
|
||||
tools:text="Mango pickle lorem ipsum"/>
|
||||
</LinearLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationItemFooter
|
||||
android:id="@+id/conversation_item_footer"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||
android:layout_gravity="end"
|
||||
android:gravity="end"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:visibility="gone"
|
||||
app:footer_text_color="?attr/conversation_item_outgoing_text_secondary_color"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_sender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||
android:layout_gravity="start"
|
||||
android:gravity="start"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
tools:visibility="visible"
|
||||
tools:text="Bob"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationItemFooter
|
||||
android:id="@+id/conversation_item_footer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||
android:layout_gravity="end"
|
||||
android:gravity="end"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:visibility="gone"
|
||||
app:footer_text_color="?attr/conversation_item_outgoing_text_secondary_color"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -19,4 +19,7 @@
|
||||
<item android:title="@string/menu_settings"
|
||||
android:id="@+id/menu_settings" />
|
||||
|
||||
<item android:title="@string/menu_show_global_map"
|
||||
android:id="@+id/menu_global_map" />
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<string name="menu_toggle_keyboard">Toggle emoji keyboard</string>
|
||||
<string name="menu_edit_group">Edit group</string>
|
||||
<string name="menu_show_map">Show map</string>
|
||||
<string name="menu_show_global_map">Show all locations</string>
|
||||
<string name="menu_archive_chat">Archive chat</string>
|
||||
<string name="menu_unarchive_chat">Unarchive chat</string>
|
||||
<string name="menu_add_attachment">Add attachment</string>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 static org.thoughtcrime.securesms.map.MapDataManager.ALL_CHATS_GLOBAL_MAP;
|
||||
|
||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationListFragment.ConversationSelectedListener
|
||||
{
|
||||
@@ -144,11 +145,18 @@ 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);
|
||||
intent.putExtra(MapActivity.CHAT_IDS, ALL_CHATS_GLOBAL_MAP);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void handleQrScan() {
|
||||
new IntentIntegrator(this).setCaptureActivity(QrScanActivity.class).initiateScan();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -36,12 +37,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;
|
||||
@@ -720,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,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 +41,7 @@ public class DcLocationManager implements Observer {
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.d(TAG, "background service disconnected");
|
||||
serviceBinder = null;
|
||||
}
|
||||
};
|
||||
@@ -49,7 +51,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 +104,7 @@ public class DcLocationManager implements Observer {
|
||||
DcHelper.getContext(context).deleteAllLocations();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
if (o instanceof DcLocation) {
|
||||
|
||||
@@ -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<ArrayList<Feature>, 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.getFirstName());
|
||||
|
||||
String msgText;
|
||||
if (messageId != 0) {
|
||||
DcContext dcContext = DcHelper.getContext(callbackRef.get().getContext());
|
||||
|
||||
@@ -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,7 +46,8 @@ public class MapActivity extends BaseActivity implements Observer {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_map);
|
||||
int chatId = getIntent().getIntExtra(CHAT_ID, -1);
|
||||
final int chatId = getIntent().getIntExtra(CHAT_ID, -1);
|
||||
|
||||
if (chatId == -1) {
|
||||
finish();
|
||||
return;
|
||||
@@ -80,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.");
|
||||
@@ -99,15 +103,21 @@ 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<Feature> 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 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) {
|
||||
@@ -171,4 +181,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ 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;
|
||||
|
||||
@@ -33,14 +32,20 @@ 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;
|
||||
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;
|
||||
@@ -65,13 +70,18 @@ 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<Integer, MapSource> contactMapSources;
|
||||
private HashMap<String, ArrayList<Feature>> featureCollections;
|
||||
private HashMap<String, FeatureTreeSet> featureCollections;
|
||||
private Feature selectedFeature;
|
||||
private int chatId;
|
||||
private Context context;
|
||||
private ApplicationDcContext dcContext;
|
||||
private boolean isInitial = true;
|
||||
|
||||
public interface MapDataState {
|
||||
void onDataInitialized(LatLngBounds bounds);
|
||||
@@ -80,26 +90,20 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
|
||||
public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int chatId, MapDataState updateCallback) {
|
||||
this.mapboxStyle = mapboxMapStyle;
|
||||
this.context = context;
|
||||
this.dcContext = DcHelper.getContext(context);
|
||||
this.chatId = chatId;
|
||||
contactMapSources = new HashMap<>();
|
||||
featureCollections = new HashMap<>();
|
||||
LatLngBounds.Builder boundingBuilder = new LatLngBounds.Builder();
|
||||
int[] contactIds = ApplicationContext.getInstance(context).dcContext.getChatContacts(chatId);
|
||||
|
||||
int[] contactIds = getContactIds(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(contactId, boundingBuilder);
|
||||
updateSource(chatId, contactId, boundingBuilder);
|
||||
generateInfoWindows(contactId);
|
||||
}
|
||||
|
||||
addContactMapSource(1);
|
||||
updateSource(1, boundingBuilder);
|
||||
generateMissingInfoWindows(1);
|
||||
|
||||
dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this);
|
||||
|
||||
try {
|
||||
updateCallback.onDataInitialized(boundingBuilder.build());
|
||||
@@ -109,57 +113,15 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
ApplicationContext.getInstance(context).dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this);
|
||||
updateSources();
|
||||
dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this);
|
||||
if (!isInitial) {
|
||||
updateSources();
|
||||
}
|
||||
isInitial = false;
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
ApplicationContext.getInstance(context).dcContext.eventCenter.removeObserver(DC_EVENT_LOCATION_CHANGED, this);
|
||||
}
|
||||
|
||||
public void addContactMapSource(int contactId) {
|
||||
DcContact contact = ApplicationContext.getInstance(context).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 (Integer contactId : contactMapSources.keySet()) {
|
||||
updateSource(contactId);
|
||||
generateMissingInfoWindows(contactId);
|
||||
}
|
||||
dcContext.eventCenter.removeObserver(DC_EVENT_LOCATION_CHANGED, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,151 +143,24 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
|
||||
public void refreshSource(int contactId) {
|
||||
Log.d(TAG, "refreshSource start");
|
||||
MapSource source = contactMapSources.get(contactId);
|
||||
ArrayList<Feature> 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 contactId, @Nullable LatLngBounds.Builder boundingBuilder) {
|
||||
DcArray locations = ApplicationContext.getInstance(context).dcContext.getLocations(chatId, contactId, System.currentTimeMillis()-3*60*60*1000, 0);
|
||||
int count = locations.getCnt();
|
||||
ArrayList<Feature> pointFeatureList = new ArrayList<>();
|
||||
MapSource contactMapMetadata = contactMapSources.get(contactId);
|
||||
|
||||
if (count == 0) {
|
||||
featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), pointFeatureList);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Point> coordinateList = new ArrayList<>();
|
||||
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);
|
||||
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.addNumberProperty(TIMESTAMP, locations.getTimestamp(i));
|
||||
pointFeature.addNumberProperty(MESSAGE_ID, locations.getMsgId(i));
|
||||
pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i));
|
||||
pointFeatureList.add(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);
|
||||
}
|
||||
|
||||
FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(pointFeatureList);
|
||||
FeatureCollection lineFeatureCollection = FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry(
|
||||
LineString.fromLngLats(coordinateList)
|
||||
)});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void generateMissingInfoWindows(int contactId) {
|
||||
MapSource contactMapMetadata = contactMapSources.get(contactId);
|
||||
ArrayList<Feature> featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection());
|
||||
ArrayList<Feature> missingWindows = new ArrayList<>();
|
||||
|
||||
for (Feature f : featureList) {
|
||||
String infoWindowId = f.getStringProperty(INFO_WINDOW_ID);
|
||||
if (mapboxStyle.getImage(infoWindowId) == null) {
|
||||
Log.d(TAG, "create new infoWindow for " + infoWindowId);
|
||||
missingWindows.add(f);
|
||||
} 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);
|
||||
ArrayList<Feature> featureList = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection());
|
||||
new GenerateInfoWindowTask(this, contactId).execute(featureList);
|
||||
}
|
||||
|
||||
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), literal(1.0f));
|
||||
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.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));
|
||||
}
|
||||
|
||||
@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)) {
|
||||
updateSource(contactId);
|
||||
generateMissingInfoWindows(contactId);
|
||||
updateSource(chatId, contactId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runOnMain() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,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<Feature> 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);
|
||||
@@ -398,10 +435,9 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
|
||||
refreshSource(selectedFeature.getNumberProperty(CONTACT_ID).intValue());
|
||||
}
|
||||
|
||||
|
||||
private Feature getFeatureWithId(String id) {
|
||||
for (String key : featureCollections.keySet()) {
|
||||
ArrayList<Feature> featureCollection = featureCollections.get(key);
|
||||
ArrayList<Feature> featureCollection = featureCollections.get(key).getFeatureList();
|
||||
for (Feature f : featureCollection) {
|
||||
if (f.id().equals(id)) {
|
||||
return f;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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<TimeComparableFeature> {
|
||||
|
||||
/**
|
||||
*
|
||||
* @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);
|
||||
super.add(obj);
|
||||
return !existed;
|
||||
}
|
||||
|
||||
public ArrayList<Feature> getFeatureList() {
|
||||
ArrayList<Feature> featureList = new ArrayList<>();
|
||||
Iterator<TimeComparableFeature> iterator = this.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
TimeComparableFeature timeComparableFeature = iterator.next();
|
||||
featureList.add(timeComparableFeature.getFeature());
|
||||
}
|
||||
return featureList;
|
||||
}
|
||||
|
||||
public ArrayList<Point> getPointList() {
|
||||
ArrayList<Point> points = new ArrayList<>();
|
||||
Iterator<TimeComparableFeature> 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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.map;
|
||||
package org.thoughtcrime.securesms.map.model;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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<TimeComparableFeature> {
|
||||
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;
|
||||
}
|
||||
|
||||
public Feature getFeature() {
|
||||
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.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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.feature.id().hashCode();
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<TimeComparableFeature> 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<TimeComparableFeature> 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<Feature> 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<Point> 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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user