diff --git a/res/layout/attachment_type_selector.xml b/res/layout/attachment_type_selector.xml index 7bbf603fb..0a72b1665 100644 --- a/res/layout/attachment_type_selector.xml +++ b/res/layout/attachment_type_selector.xml @@ -162,6 +162,32 @@ + + + + + + + + + + display(final SignalPlace place) { + final SettableFuture future = new SettableFuture<>(); + + this.mapView.onCreate(null); + this.mapView.onResume(); + + this.mapView.setVisibility(View.VISIBLE); + this.imageView.setVisibility(View.GONE); + + this.mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(final GoogleMap googleMap) { + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(place.getLatLong(), 13)); + googleMap.addMarker(new MarkerOptions().position(place.getLatLong())); + googleMap.setBuildingsEnabled(true); + googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + googleMap.getUiSettings().setAllGesturesEnabled(false); + googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + googleMap.snapshot(new GoogleMap.SnapshotReadyCallback() { + @Override + public void onSnapshotReady(Bitmap bitmap) { + future.set(bitmap); + imageView.setImageBitmap(bitmap); + imageView.setVisibility(View.VISIBLE); + mapView.setVisibility(View.GONE); + mapView.onPause(); + mapView.onDestroy(); + } + }); + } + }); + } + }); + + this.textView.setText(place.getDescription()); + + return future; + } + +} diff --git a/src/org/thoughtcrime/securesms/components/location/SignalPlace.java b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java new file mode 100644 index 000000000..3b8566cdf --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java @@ -0,0 +1,81 @@ +package org.thoughtcrime.securesms.components.location; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.android.gms.location.places.Place; +import com.google.android.gms.maps.model.LatLng; + +import org.thoughtcrime.securesms.util.JsonUtils; + +import java.io.IOException; + +public class SignalPlace { + + private static final String URL = "https://maps.google.com/maps"; + private static final String TAG = SignalPlace.class.getSimpleName(); + + @JsonProperty + private CharSequence name; + + @JsonProperty + private CharSequence address; + + @JsonProperty + private double latitude; + + @JsonProperty + private double longitude; + + public SignalPlace(Place place) { + this.name = place.getName(); + this.address = place.getAddress(); + this.latitude = place.getLatLng().latitude; + this.longitude = place.getLatLng().longitude; + } + + public SignalPlace() {} + + @JsonIgnore + public LatLng getLatLong() { + return new LatLng(latitude, longitude); + } + + @JsonIgnore + public String getDescription() { + String description = ""; + + if (!TextUtils.isEmpty(name)) { + description += (name + "\n"); + } + + if (!TextUtils.isEmpty(address)) { + description += (address + "\n"); + } + + description += Uri.parse(URL) + .buildUpon() + .appendQueryParameter("q", String.format("%s,%s", latitude, longitude)) + .build().toString(); + + return description; + } + + public @Nullable String serialize() { + try { + return JsonUtils.toJson(this); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } + + public static SignalPlace deserialize(@NonNull String serialized) throws IOException { + return JsonUtils.fromJson(serialized, SignalPlace.class); + } +} diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 29560817c..b4b33e297 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -49,6 +49,8 @@ import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.DocumentView; import org.thoughtcrime.securesms.components.RemovableEditableMediaView; import org.thoughtcrime.securesms.components.ThumbnailView; +import org.thoughtcrime.securesms.components.location.SignalMapView; +import org.thoughtcrime.securesms.components.location.SignalPlace; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; @@ -82,6 +84,7 @@ public class AttachmentManager { private ThumbnailView thumbnail; private AudioView audioView; private DocumentView documentView; + private SignalMapView mapView; private @NonNull List garbage = new LinkedList<>(); private @NonNull Optional slide = Optional.absent(); @@ -100,6 +103,7 @@ public class AttachmentManager { this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail); this.audioView = ViewUtil.findById(root, R.id.attachment_audio); this.documentView = ViewUtil.findById(root, R.id.attachment_document); + this.mapView = ViewUtil.findById(root, R.id.attachment_location); this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view); removableMediaView.setRemoveClickListener(new RemoveButtonListener()); @@ -176,6 +180,34 @@ public class AttachmentManager { this.slide = Optional.of(slide); } + public ListenableFuture setLocation(@NonNull final SignalPlace place, + @NonNull final MediaConstraints constraints) + { + inflateStub(); + + SettableFuture returnResult = new SettableFuture<>(); + ListenableFuture future = mapView.display(place); + + attachmentViewStub.get().setVisibility(View.VISIBLE); + removableMediaView.display(mapView, false); + + future.addListener(new AssertedSuccessListener() { + @Override + public void onSuccess(@NonNull Bitmap result) { + byte[] blob = BitmapUtil.toByteArray(result); + Uri uri = PersistentBlobProvider.getInstance(context) + .create(context, blob, MediaUtil.IMAGE_PNG, null); + LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place); + + setSlide(locationSlide); + attachmentListener.onAttachmentChanged(); + returnResult.set(true); + } + }); + + return returnResult; + } + @SuppressLint("StaticFieldLeak") public ListenableFuture setMedia(@NonNull final GlideRequests glideRequests, @NonNull final Uri uri, @@ -341,6 +373,21 @@ public class AttachmentManager { .execute(); } + public static void selectLocation(Activity activity, int requestCode) { + Permissions.with(activity) + .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) + .ifNecessary() + .withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_location_denied)) + .onAllGranted(() -> { + try { + activity.startActivityForResult(new PlacePicker.IntentBuilder().build(activity), requestCode); + } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) { + Log.w(TAG, e); + } + }) + .execute(); + } + private @Nullable Uri getSlideUri() { return slide.isPresent() ? slide.get().getUri() : null; } diff --git a/src/org/thoughtcrime/securesms/mms/LocationSlide.java b/src/org/thoughtcrime/securesms/mms/LocationSlide.java new file mode 100644 index 000000000..c07c4b3da --- /dev/null +++ b/src/org/thoughtcrime/securesms/mms/LocationSlide.java @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.mms; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.components.location.SignalPlace; +import org.thoughtcrime.securesms.util.guava.Optional; + +public class LocationSlide extends ImageSlide { + + @NonNull + private final SignalPlace place; + + public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull SignalPlace place) + { + super(context, uri, size, 0, 0); + this.place = place; + } + + @Override + @NonNull + public Optional getBody() { + return Optional.of(place.getDescription()); + } + + @NonNull + public SignalPlace getPlace() { + return place; + } + + @Override + public boolean hasLocation() { + return true; + } + +}