package org.tm.archive.maps; import android.Manifest; import android.annotation.SuppressLint; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; import android.location.Address; import android.location.Geocoder; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.animation.OvershootInterpolator; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MapStyleOptions; import org.signal.core.util.concurrent.ListenableFuture; import org.signal.core.util.logging.Log; import org.tm.archive.R; import org.tm.archive.components.location.SignalMapView; import org.tm.archive.providers.BlobProvider; import org.tm.archive.util.BitmapUtil; import org.tm.archive.util.DynamicNoActionBarTheme; import org.tm.archive.util.DynamicTheme; import org.tm.archive.util.MediaUtil; import org.tm.archive.util.views.SimpleProgressDialog; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutionException; /** * Allows selection of an address from a google map. *
* 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();
public static final String KEY_CHAT_COLOR = "chat_color";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
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 Fragment fragment, int requestCode, @ColorInt int chatColor) {
fragment.startActivityForResult(new Intent(fragment.requireActivity(), PlacePickerActivity.class).putExtra(KEY_CHAT_COLOR, chatColor), requestCode);
}
public static AddressData addressFromData(@NonNull Intent data) {
return data.getParcelableExtra(ADDRESS_INTENT);
}
@SuppressLint("MissingInflatedId")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dynamicTheme.onCreate(this);
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);
ViewCompat.setBackgroundTintList(fab, ColorStateList.valueOf(getIntent().getIntExtra(KEY_CHAT_COLOR, Color.RED)));
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);
if (DynamicTheme.isDarkTheme(this)) {
try {
boolean success = googleMap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style));
if (!success) {
Log.e(TAG, "Style parsing failed.");
}
} catch (Resources.NotFoundException e) {
Log.e(TAG, "Can't find style. Error: ", e);
}
}
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);
});
});
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
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);
SimpleProgressDialog.DismissibleDialog dismissibleDialog = SimpleProgressDialog.showDelayed(this);
MapView mapView = findViewById(R.id.map_view);
SignalMapView.snapshot(currentLocation, mapView).addListener(new ListenableFuture.Listener<>() {
@Override
public void onSuccess(Bitmap result) {
dismissibleDialog.dismiss();
byte[] blob = BitmapUtil.toByteArray(result);
Uri uri = BlobProvider.getInstance()
.forData(blob)
.withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleSessionInMemory();
returnIntent.putExtra(ADDRESS_INTENT, addressData);
returnIntent.setData(uri);
setResult(RESULT_OK, returnIntent);
finish();
}
@Override
public void onFailure(ExecutionException e) {
dismissibleDialog.dismiss();
Log.e(TAG, "Failed to generate snapshot", e);
}
});
}
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