235 lines
7.8 KiB
Java
235 lines
7.8 KiB
Java
package org.thoughtcrime.securesms.maps;
|
|
|
|
import android.Manifest;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.location.Address;
|
|
import android.location.Geocoder;
|
|
import android.os.AsyncTask;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.view.View;
|
|
import android.view.animation.OvershootInterpolator;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import com.google.android.gms.maps.CameraUpdateFactory;
|
|
import com.google.android.gms.maps.GoogleMap;
|
|
import com.google.android.gms.maps.SupportMapFragment;
|
|
import com.google.android.gms.maps.model.LatLng;
|
|
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* Allows selection of an address from a google map.
|
|
* <p>
|
|
* Based on https://github.com/suchoX/PlacePicker
|
|
*/
|
|
public final class PlacePickerActivity extends AppCompatActivity {
|
|
|
|
private static final String TAG = Log.tag(PlacePickerActivity.class);
|
|
|
|
// If it cannot load location for any reason, it defaults to the prime meridian.
|
|
private static final LatLng PRIME_MERIDIAN = new LatLng(51.4779, -0.0015);
|
|
private static final String ADDRESS_INTENT = "ADDRESS";
|
|
private static final float ZOOM = 17.0f;
|
|
|
|
private static final int ANIMATION_DURATION = 250;
|
|
private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator();
|
|
|
|
private SingleAddressBottomSheet bottomSheet;
|
|
private Address currentAddress;
|
|
private LatLng initialLocation;
|
|
private LatLng currentLocation = new LatLng(0, 0);
|
|
private AddressLookup addressLookup;
|
|
private GoogleMap googleMap;
|
|
|
|
public static void startActivityForResultAtCurrentLocation(@NonNull Activity activity, int requestCode) {
|
|
activity.startActivityForResult(new Intent(activity, PlacePickerActivity.class), requestCode);
|
|
}
|
|
|
|
public static AddressData addressFromData(@NonNull Intent data) {
|
|
return data.getParcelableExtra(ADDRESS_INTENT);
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_place_picker);
|
|
|
|
bottomSheet = findViewById(R.id.bottom_sheet);
|
|
View markerImage = findViewById(R.id.marker_image_view);
|
|
View fab = findViewById(R.id.place_chosen_button);
|
|
|
|
fab.setOnClickListener(v -> finishWithAddress());
|
|
|
|
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
|
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED)
|
|
{
|
|
new LocationRetriever(this, this, location -> {
|
|
setInitialLocation(new LatLng(location.getLatitude(), location.getLongitude()));
|
|
}, () -> {
|
|
Log.w(TAG, "Failed to get location.");
|
|
setInitialLocation(PRIME_MERIDIAN);
|
|
});
|
|
} else {
|
|
Log.w(TAG, "No location permissions");
|
|
setInitialLocation(PRIME_MERIDIAN);
|
|
}
|
|
|
|
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
|
|
if (mapFragment == null) throw new AssertionError("No map fragment");
|
|
|
|
mapFragment.getMapAsync(googleMap -> {
|
|
|
|
setMap(googleMap);
|
|
|
|
enableMyLocationButtonIfHaveThePermission(googleMap);
|
|
|
|
googleMap.setOnCameraMoveStartedListener(i -> {
|
|
markerImage.animate()
|
|
.translationY(-75f)
|
|
.setInterpolator(OVERSHOOT_INTERPOLATOR)
|
|
.setDuration(ANIMATION_DURATION)
|
|
.start();
|
|
|
|
bottomSheet.hide();
|
|
});
|
|
|
|
googleMap.setOnCameraIdleListener(() -> {
|
|
markerImage.animate()
|
|
.translationY(0f)
|
|
.setInterpolator(OVERSHOOT_INTERPOLATOR)
|
|
.setDuration(ANIMATION_DURATION)
|
|
.start();
|
|
|
|
setCurrentLocation(googleMap.getCameraPosition().target);
|
|
});
|
|
});
|
|
}
|
|
|
|
private void setInitialLocation(@NonNull LatLng latLng) {
|
|
initialLocation = latLng;
|
|
|
|
moveMapToInitialIfPossible();
|
|
}
|
|
|
|
private void setMap(GoogleMap googleMap) {
|
|
this.googleMap = googleMap;
|
|
|
|
moveMapToInitialIfPossible();
|
|
}
|
|
|
|
private void moveMapToInitialIfPossible() {
|
|
if (initialLocation != null && googleMap != null) {
|
|
Log.d(TAG, "Moving map to initial location");
|
|
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(initialLocation, ZOOM));
|
|
setCurrentLocation(initialLocation);
|
|
}
|
|
}
|
|
|
|
private void setCurrentLocation(LatLng location) {
|
|
currentLocation = location;
|
|
bottomSheet.showLoading();
|
|
lookupAddress(location);
|
|
}
|
|
|
|
private void finishWithAddress() {
|
|
Intent returnIntent = new Intent();
|
|
String address = currentAddress != null && currentAddress.getAddressLine(0) != null ? currentAddress.getAddressLine(0) : "";
|
|
AddressData addressData = new AddressData(currentLocation.latitude, currentLocation.longitude, address);
|
|
|
|
returnIntent.putExtra(ADDRESS_INTENT, addressData);
|
|
setResult(RESULT_OK, returnIntent);
|
|
finish();
|
|
}
|
|
|
|
private void enableMyLocationButtonIfHaveThePermission(GoogleMap googleMap) {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
|
checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
|
checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED)
|
|
{
|
|
googleMap.setMyLocationEnabled(true);
|
|
}
|
|
}
|
|
|
|
private void lookupAddress(@Nullable LatLng target) {
|
|
if (addressLookup != null) {
|
|
addressLookup.cancel(true);
|
|
}
|
|
addressLookup = new AddressLookup();
|
|
addressLookup.execute(target);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
if (addressLookup != null) {
|
|
addressLookup.cancel(true);
|
|
}
|
|
}
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
private class AddressLookup extends AsyncTask<LatLng, Void, Address> {
|
|
|
|
private final String TAG = Log.tag(AddressLookup.class);
|
|
private final Geocoder geocoder;
|
|
|
|
AddressLookup() {
|
|
geocoder = new Geocoder(getApplicationContext(), Locale.getDefault());
|
|
}
|
|
|
|
@Override
|
|
protected Address doInBackground(LatLng... latLngs) {
|
|
if (latLngs.length == 0) return null;
|
|
LatLng latLng = latLngs[0];
|
|
if (latLng == null) return null;
|
|
try {
|
|
List<Address> result = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1);
|
|
return !result.isEmpty() ? result.get(0) : null;
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Failed to get address from location", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(@Nullable Address address) {
|
|
currentAddress = address;
|
|
if (address != null) {
|
|
bottomSheet.showResult(address.getLatitude(), address.getLongitude(), addressToShortString(address), addressToString(address));
|
|
} else {
|
|
bottomSheet.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static @NonNull String addressToString(@Nullable Address address) {
|
|
return address != null ? address.getAddressLine(0) : "";
|
|
}
|
|
|
|
private static @NonNull String addressToShortString(@Nullable Address address) {
|
|
if (address == null) return "";
|
|
|
|
String addressLine = address.getAddressLine(0);
|
|
String[] split = addressLine.split(",");
|
|
|
|
if (split.length >= 3) {
|
|
return split[1].trim() + ", " + split[2].trim();
|
|
} else if (split.length == 2) {
|
|
return split[1].trim();
|
|
} else return split[0].trim();
|
|
}
|
|
}
|