diff --git a/res/drawable/ic_location_dot.xml b/res/drawable/ic_location_dot.xml new file mode 100644 index 000000000..15c532a57 --- /dev/null +++ b/res/drawable/ic_location_dot.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index c59791b55..13d8a4106 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -177,7 +177,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private Recipient recipient; private ApplicationDcContext dcContext; private DcChat dcChat = new DcChat(0); - private int chatId; + private int chatId; private boolean archived; private final boolean isSecureText = true; private boolean isDefaultSms = true; @@ -475,6 +475,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleShowMap() { Intent intent = new Intent(this, MapActivity.class); + intent.putExtra(MapActivity.CHAT_ID, chatId); startActivity(intent); } diff --git a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java index 1b8691647..b70afb6be 100644 --- a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java @@ -11,6 +11,7 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; +import android.support.v4.content.ContextCompat; import android.util.Pair; import android.view.Gravity; import android.view.LayoutInflater; @@ -163,11 +164,7 @@ public class AttachmentTypeSelector extends PopupWindow { resId = R.drawable.ic_location_on_white_24dp; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - this.locationButton.setImageDrawable(context.getDrawable(resId)); - } else { - this.locationButton.setImageDrawable(context.getResources().getDrawable(resId)); - } + this.locationButton.setImageDrawable(ContextCompat.getDrawable(context, resId)); } private void animateButtonIn(View button, int delay) { diff --git a/src/org/thoughtcrime/securesms/geolocation/DcLocation.java b/src/org/thoughtcrime/securesms/geolocation/DcLocation.java index 73cbd044e..948969b99 100644 --- a/src/org/thoughtcrime/securesms/geolocation/DcLocation.java +++ b/src/org/thoughtcrime/securesms/geolocation/DcLocation.java @@ -14,7 +14,7 @@ public class DcLocation extends Observable { private static DcLocation instance; private DcLocation() { - lastLocation = new Location("?"); + lastLocation = getDefault(); } public static DcLocation getInstance() { @@ -36,8 +36,15 @@ public class DcLocation extends Observable { } void reset() { - updateLocation(new Location("?")); + updateLocation(getDefault()); + } + private Location getDefault() { + Location location = new Location("?"); + location.setLatitude(52.52); + location.setLongitude(13.404); + return location; + } } diff --git a/src/org/thoughtcrime/securesms/map/MapActivity.java b/src/org/thoughtcrime/securesms/map/MapActivity.java index 907f58db1..b77956d58 100644 --- a/src/org/thoughtcrime/securesms/map/MapActivity.java +++ b/src/org/thoughtcrime/securesms/map/MapActivity.java @@ -31,23 +31,30 @@ import java.util.Observer; 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 LINE_LAYER = "line_layer"; public static final String LINE_SOURCE = "line_source"; public static final String MARKER_LAYER = "symbol_layer"; public static final String MARKER_ICON = "marker_icon_id"; public static final String MARKER_POSITION_SOURCE = "marker_position"; - private static final String TAG = MapActivity.class.getSimpleName(); private MapView mapView; private MapboxMap mapboxMap; - private PermissionsManager permissionsManager; private DcLocation dcLocation; + private MapDataManager mapDataManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_map); + int chatId = getIntent().getIntExtra(CHAT_ID, -1); + if (chatId == -1) { + finish(); + return; + } mapView = findViewById(R.id.mapView); mapView.onCreate(savedInstanceState); @@ -61,8 +68,12 @@ public class MapActivity extends BaseActivity implements Observer { mapboxMap.getUiSettings().setLogoEnabled(false); mapboxMap.getUiSettings().setAttributionEnabled(false); - initMapDrawings(); - showDeviceLocation(); + Style mapBoxStyle = mapboxMap.getStyle(); + if (mapBoxStyle == null) { + return; + } + + mapDataManager = new MapDataManager(this, mapBoxStyle, chatId); })); dcLocation = DcLocation.getInstance(); @@ -80,6 +91,9 @@ public class MapActivity extends BaseActivity implements Observer { super.onResume(); mapView.onResume(); DcLocation.getInstance().addObserver(this); + if (mapDataManager != null) { + mapDataManager.onResume(); + } } @Override @@ -87,6 +101,9 @@ public class MapActivity extends BaseActivity implements Observer { super.onPause(); mapView.onPause(); DcLocation.getInstance().deleteObserver(this); + if (mapDataManager != null) { + mapDataManager.onPause(); + } } @Override @@ -107,12 +124,6 @@ public class MapActivity extends BaseActivity implements Observer { mapView.onSaveInstanceState(outState); } - //Android SDK callback for the result from requesting permissions - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - private void initMapDrawings() { if (mapboxMap == null || mapboxMap.getStyle() == null) { return; @@ -144,45 +155,6 @@ public class MapActivity extends BaseActivity implements Observer { PropertyFactory.lineColor(Color.parseColor("#3bb2d0")))); } - private void showDeviceLocation() { - if (this.dcLocation.getLastLocation().getProvider().equals("?")) { - return; - } - - FeatureCollection featureCollection = FeatureCollection.fromFeature(Feature.fromGeometry( - Point.fromLngLat(dcLocation.getLastLocation().getLongitude(), - dcLocation.getLastLocation().getLatitude()))); - - drawPoints(featureCollection); - } - - private void drawPoints(@NonNull FeatureCollection featureCollection) { - if (mapboxMap == null || - mapboxMap.getStyle() == null || - featureCollection.features() == null || - featureCollection.features().size() == 0) { - return; - } - - Style style = mapboxMap.getStyle(); - GeoJsonSource source = (GeoJsonSource) style.getSource(MARKER_POSITION_SOURCE); - source.setGeoJson(featureCollection); - } - - private void drawLines(@NonNull FeatureCollection featureCollection) { - if (mapboxMap == null || - mapboxMap.getStyle() == null || - featureCollection.features() == null || - featureCollection.features().size() == 0) { - return; - } - - Style style = mapboxMap.getStyle(); - GeoJsonSource source = (GeoJsonSource) style.getSource(LINE_SOURCE); - source.setGeoJson(featureCollection); - } - - @Override public void update(Observable o, Object arg) { if (o instanceof DcLocation) { @@ -190,8 +162,7 @@ public class MapActivity extends BaseActivity implements Observer { Log.d(TAG, "show marker on map: " + dcLocation.getLastLocation().getLatitude() + ", " + dcLocation.getLastLocation().getLongitude()); - showDeviceLocation(); - + //TODO: consider implementing a button -> center map to current location } } } diff --git a/src/org/thoughtcrime/securesms/map/MapDataManager.java b/src/org/thoughtcrime/securesms/map/MapDataManager.java new file mode 100644 index 000000000..3df6e414a --- /dev/null +++ b/src/org/thoughtcrime/securesms/map/MapDataManager.java @@ -0,0 +1,194 @@ +package org.thoughtcrime.securesms.map; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.util.Log; + +import com.b44t.messenger.DcArray; +import com.b44t.messenger.DcContact; +import com.b44t.messenger.DcEventCenter; +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.FeatureCollection; +import com.mapbox.geojson.LineString; +import com.mapbox.geojson.Point; +import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.mapboxsdk.style.layers.LineLayer; +import com.mapbox.mapboxsdk.style.layers.Property; +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 java.util.ArrayList; +import java.util.HashMap; + +import static com.b44t.messenger.DcContext.DC_EVENT_LOCATION_CHANGED; +import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM; + +/** + * Created by cyberta on 07.03.19. + */ + +public class MapDataManager implements DcEventCenter.DcEventDelegate { + private static final String TAG = MapDataManager.class.getSimpleName(); + private Style mapboxStyle; + private HashMap contactMapSources; + private int chatId; + private Context context; + + public MapDataManager(Context context, @NonNull Style mapboxMapStyle, int chatId) { + this.mapboxStyle = mapboxMapStyle; + this.context = context; + this.chatId = chatId; + contactMapSources = new HashMap<>(); + int[] contactIds = ApplicationContext.getInstance(context).dcContext.getChatContacts(chatId); + for (int contactId : contactIds) { + addContactMapSource(contactId); + updateSource(contactId); + } + } + + public void onResume() { + ApplicationContext.getInstance(context).dcContext.eventCenter.addObserver(DC_EVENT_LOCATION_CHANGED, this); + updateSources(); + } + + 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); + } + } + + private void updateSource(int contactId) { + DcArray locations = ApplicationContext.getInstance(context).dcContext.getLocations(chatId, contactId); + int count = locations.getCnt(); + if (count == 0) { + return; + } + + ArrayList coordinateList = new ArrayList<>(); + ArrayList pointFeatureList = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + Point p = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i)); + coordinateList.add(p); + pointFeatureList.add(Feature.fromGeometry(p)); + } + + FeatureCollection pointFeatureCollection = FeatureCollection.fromFeatures(pointFeatureList); + FeatureCollection lineFeatureCollection = FeatureCollection.fromFeatures(new Feature[] {Feature.fromGeometry( + LineString.fromLngLats(coordinateList) + )}); + + MapSource contactMapMetadata = contactMapSources.get(contactId); + GeoJsonSource lineSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getLineSource()); + lineSource.setGeoJson(lineFeatureCollection); + GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getMarkerSource()); + pointSource.setGeoJson(pointFeatureCollection); + GeoJsonSource lastPostionSource = (GeoJsonSource) mapboxStyle.getSource(contactMapMetadata.getLastPositionSource()); + lastPostionSource.setGeoJson(pointFeatureList.get(0)); + } + + private void initGeoJsonSources(MapSource source) { + GeoJsonSource lastPositionSource = new GeoJsonSource(source.getLastPositionSource()); + GeoJsonSource markerPositionSource = new GeoJsonSource(source.getMarkerSource()); + GeoJsonSource linePositionSource = new GeoJsonSource(source.getLineSource()); + + try { + mapboxStyle.addSource(lastPositionSource); + 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())); + + + mapboxStyle.addLayer(new SymbolLayer(source.getMarkerLayer(), source.getMarkerSource()) + .withProperties(PropertyFactory.iconImage(source.getMarkerIcon()))); + mapboxStyle.addLayer(new SymbolLayer(source.getLastPositionLayer(), source.getLastPositionSource()) + .withProperties(PropertyFactory.iconImage(source.getMarkerLastPositon()), + PropertyFactory.iconAnchor(ICON_ANCHOR_BOTTOM))); + 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()))); + } + + + @Override + public void handleEvent(int eventId, Object data1, Object data2) { + int contactId = (Integer) data1; + if (contactMapSources.containsKey(contactId)) { + updateSource(contactId); + } + } + + @Override + public boolean runOnMain() { + return false; + } + +} diff --git a/src/org/thoughtcrime/securesms/map/MapSource.java b/src/org/thoughtcrime/securesms/map/MapSource.java new file mode 100644 index 000000000..e4cb40219 --- /dev/null +++ b/src/org/thoughtcrime/securesms/map/MapSource.java @@ -0,0 +1,92 @@ +package org.thoughtcrime.securesms.map; + +import android.graphics.Color; + +/** + * Created by cyberta on 07.03.19. + */ + +public class MapSource { + public static final String LINE_LAYER = "line_layer"; + public static final String MARKER_LAYER = "symbol_layer"; + public static final String LAST_POSITON_LAYER = "last_position_layer"; + public static final String LINE_SOURCE = "line_source"; + public static final String MARKER_POSITION_SOURCE = "marker_position"; + public static final String LAST_POSITION_SOURCE = "marker_last_position"; + public static final String MARKER_ICON = "marker_icon_id"; + public static final String MARKER_LAST_POSITON = "marker_last_position"; + + private final String markerSource; + private final String lineSource; + private final String lastPositionSource; + + private final String markerLayer; + private final String lineLayer; + private final String lastPositionLayer; + + private final String markerIcon; + private final String markerLastPositon; + + private int color; + private int colorArgb; + + public MapSource(int chatId) { + markerSource = MARKER_POSITION_SOURCE + "_" + chatId; + lineSource = LINE_SOURCE + "_" + chatId; + lastPositionSource = LAST_POSITION_SOURCE + "_" + chatId; + markerLayer = MARKER_LAYER + "_" + chatId; + lineLayer = LINE_LAYER + "_" + chatId; + lastPositionLayer = LAST_POSITON_LAYER + "_" + chatId; + markerIcon = MARKER_ICON + "_" + chatId; + markerLastPositon = MARKER_LAST_POSITON + "_" + chatId; + } + + public void setColor(int color) { + this.color = color; + colorArgb = Color.argb(0xFF, Color.red(color), Color.green(color), Color.blue(color)); + } + + public int getColorArgb() { + return colorArgb; + } + + public int getColor() { + return color; + } + + public String getMarkerSource() { + return markerSource; + } + + public String getLineSource() { + return lineSource; + } + + public String getLastPositionSourceName() { + return lastPositionSource; + } + + public String getMarkerLayer() { + return markerLayer; + } + + public String getLineLayer() { + return lineLayer; + } + + public String getLastPositionLayer() { + return lastPositionLayer; + } + + public String getMarkerIcon() { + return markerIcon; + } + + public String getMarkerLastPositon() { + return markerLastPositon; + } + + public String getLastPositionSource() { + return lastPositionSource; + } +}