That fuck shit the fascists are using
at master 294 lines 10 kB view raw
1package org.tm.archive.maps; 2 3import android.Manifest; 4import android.annotation.SuppressLint; 5import android.content.Intent; 6import android.content.pm.PackageManager; 7import android.content.res.ColorStateList; 8import android.content.res.Resources; 9import android.graphics.Bitmap; 10import android.graphics.Color; 11import android.location.Address; 12import android.location.Geocoder; 13import android.net.Uri; 14import android.os.AsyncTask; 15import android.os.Build; 16import android.os.Bundle; 17import android.view.View; 18import android.view.animation.OvershootInterpolator; 19 20import androidx.annotation.ColorInt; 21import androidx.annotation.NonNull; 22import androidx.annotation.Nullable; 23import androidx.appcompat.app.AppCompatActivity; 24import androidx.core.content.ContextCompat; 25import androidx.core.view.ViewCompat; 26import androidx.fragment.app.Fragment; 27 28import com.google.android.gms.maps.CameraUpdateFactory; 29import com.google.android.gms.maps.GoogleMap; 30import com.google.android.gms.maps.MapView; 31import com.google.android.gms.maps.SupportMapFragment; 32import com.google.android.gms.maps.model.LatLng; 33import com.google.android.gms.maps.model.MapStyleOptions; 34 35import org.signal.core.util.concurrent.ListenableFuture; 36import org.signal.core.util.logging.Log; 37import org.tm.archive.R; 38import org.tm.archive.components.location.SignalMapView; 39import org.tm.archive.providers.BlobProvider; 40import org.tm.archive.util.BitmapUtil; 41import org.tm.archive.util.DynamicNoActionBarTheme; 42import org.tm.archive.util.DynamicTheme; 43import org.tm.archive.util.MediaUtil; 44import org.tm.archive.util.views.SimpleProgressDialog; 45 46import java.io.IOException; 47import java.util.List; 48import java.util.Locale; 49import java.util.concurrent.ExecutionException; 50 51/** 52 * Allows selection of an address from a google map. 53 * <p> 54 * Based on https://github.com/suchoX/PlacePicker 55 */ 56public final class PlacePickerActivity extends AppCompatActivity { 57 58 private static final String TAG = Log.tag(PlacePickerActivity.class); 59 60 // If it cannot load location for any reason, it defaults to the prime meridian. 61 private static final LatLng PRIME_MERIDIAN = new LatLng(51.4779, -0.0015); 62 private static final String ADDRESS_INTENT = "ADDRESS"; 63 private static final float ZOOM = 17.0f; 64 65 private static final int ANIMATION_DURATION = 250; 66 private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(); 67 public static final String KEY_CHAT_COLOR = "chat_color"; 68 69 private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); 70 71 private SingleAddressBottomSheet bottomSheet; 72 private Address currentAddress; 73 private LatLng initialLocation; 74 private LatLng currentLocation = new LatLng(0, 0); 75 private AddressLookup addressLookup; 76 private GoogleMap googleMap; 77 78 public static void startActivityForResultAtCurrentLocation(@NonNull Fragment fragment, int requestCode, @ColorInt int chatColor) { 79 fragment.startActivityForResult(new Intent(fragment.requireActivity(), PlacePickerActivity.class).putExtra(KEY_CHAT_COLOR, chatColor), requestCode); 80 } 81 82 public static AddressData addressFromData(@NonNull Intent data) { 83 return data.getParcelableExtra(ADDRESS_INTENT); 84 } 85 86 @SuppressLint("MissingInflatedId") 87 @Override 88 public void onCreate(@Nullable Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 dynamicTheme.onCreate(this); 91 92 setContentView(R.layout.activity_place_picker); 93 94 bottomSheet = findViewById(R.id.bottom_sheet); 95 View markerImage = findViewById(R.id.marker_image_view); 96 View fab = findViewById(R.id.place_chosen_button); 97 98 ViewCompat.setBackgroundTintList(fab, ColorStateList.valueOf(getIntent().getIntExtra(KEY_CHAT_COLOR, Color.RED))); 99 fab.setOnClickListener(v -> finishWithAddress()); 100 101 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || 102 ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) 103 { 104 new LocationRetriever(this, this, location -> { 105 setInitialLocation(new LatLng(location.getLatitude(), location.getLongitude())); 106 }, () -> { 107 Log.w(TAG, "Failed to get location."); 108 setInitialLocation(PRIME_MERIDIAN); 109 }); 110 } else { 111 Log.w(TAG, "No location permissions"); 112 setInitialLocation(PRIME_MERIDIAN); 113 } 114 115 SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); 116 if (mapFragment == null) throw new AssertionError("No map fragment"); 117 118 mapFragment.getMapAsync(googleMap -> { 119 setMap(googleMap); 120 if (DynamicTheme.isDarkTheme(this)) { 121 try { 122 boolean success = googleMap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style)); 123 124 if (!success) { 125 Log.e(TAG, "Style parsing failed."); 126 } 127 } catch (Resources.NotFoundException e) { 128 Log.e(TAG, "Can't find style. Error: ", e); 129 } 130 } 131 132 enableMyLocationButtonIfHaveThePermission(googleMap); 133 134 googleMap.setOnCameraMoveStartedListener(i -> { 135 markerImage.animate() 136 .translationY(-75f) 137 .setInterpolator(OVERSHOOT_INTERPOLATOR) 138 .setDuration(ANIMATION_DURATION) 139 .start(); 140 141 bottomSheet.hide(); 142 }); 143 144 googleMap.setOnCameraIdleListener(() -> { 145 markerImage.animate() 146 .translationY(0f) 147 .setInterpolator(OVERSHOOT_INTERPOLATOR) 148 .setDuration(ANIMATION_DURATION) 149 .start(); 150 151 setCurrentLocation(googleMap.getCameraPosition().target); 152 }); 153 }); 154 } 155 156 @Override 157 protected void onResume() { 158 super.onResume(); 159 dynamicTheme.onResume(this); 160 } 161 162 private void setInitialLocation(@NonNull LatLng latLng) { 163 initialLocation = latLng; 164 165 moveMapToInitialIfPossible(); 166 } 167 168 private void setMap(GoogleMap googleMap) { 169 this.googleMap = googleMap; 170 171 moveMapToInitialIfPossible(); 172 } 173 174 private void moveMapToInitialIfPossible() { 175 if (initialLocation != null && googleMap != null) { 176 Log.d(TAG, "Moving map to initial location"); 177 googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(initialLocation, ZOOM)); 178 setCurrentLocation(initialLocation); 179 } 180 } 181 182 private void setCurrentLocation(LatLng location) { 183 currentLocation = location; 184 bottomSheet.showLoading(); 185 lookupAddress(location); 186 } 187 188 private void finishWithAddress() { 189 Intent returnIntent = new Intent(); 190 String address = currentAddress != null && currentAddress.getAddressLine(0) != null ? currentAddress.getAddressLine(0) : ""; 191 AddressData addressData = new AddressData(currentLocation.latitude, currentLocation.longitude, address); 192 193 SimpleProgressDialog.DismissibleDialog dismissibleDialog = SimpleProgressDialog.showDelayed(this); 194 MapView mapView = findViewById(R.id.map_view); 195 SignalMapView.snapshot(currentLocation, mapView).addListener(new ListenableFuture.Listener<>() { 196 @Override 197 public void onSuccess(Bitmap result) { 198 dismissibleDialog.dismiss(); 199 byte[] blob = BitmapUtil.toByteArray(result); 200 Uri uri = BlobProvider.getInstance() 201 .forData(blob) 202 .withMimeType(MediaUtil.IMAGE_JPEG) 203 .createForSingleSessionInMemory(); 204 returnIntent.putExtra(ADDRESS_INTENT, addressData); 205 returnIntent.setData(uri); 206 setResult(RESULT_OK, returnIntent); 207 finish(); 208 } 209 210 @Override 211 public void onFailure(ExecutionException e) { 212 dismissibleDialog.dismiss(); 213 Log.e(TAG, "Failed to generate snapshot", e); 214 } 215 }); 216 } 217 218 private void enableMyLocationButtonIfHaveThePermission(GoogleMap googleMap) { 219 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || 220 checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || 221 checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) 222 { 223 googleMap.setMyLocationEnabled(true); 224 } 225 } 226 227 private void lookupAddress(@Nullable LatLng target) { 228 if (addressLookup != null) { 229 addressLookup.cancel(true); 230 } 231 addressLookup = new AddressLookup(); 232 addressLookup.execute(target); 233 } 234 235 @Override 236 protected void onPause() { 237 super.onPause(); 238 if (addressLookup != null) { 239 addressLookup.cancel(true); 240 } 241 } 242 243 @SuppressLint("StaticFieldLeak") 244 private class AddressLookup extends AsyncTask<LatLng, Void, Address> { 245 246 private final String TAG = Log.tag(AddressLookup.class); 247 private final Geocoder geocoder; 248 249 AddressLookup() { 250 geocoder = new Geocoder(getApplicationContext(), Locale.getDefault()); 251 } 252 253 @Override 254 protected Address doInBackground(LatLng... latLngs) { 255 if (latLngs.length == 0) return null; 256 LatLng latLng = latLngs[0]; 257 if (latLng == null) return null; 258 try { 259 List<Address> result = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1); 260 return !result.isEmpty() ? result.get(0) : null; 261 } catch (IOException e) { 262 Log.w(TAG, "Failed to get address from location", e); 263 return null; 264 } 265 } 266 267 @Override 268 protected void onPostExecute(@Nullable Address address) { 269 currentAddress = address; 270 if (address != null) { 271 bottomSheet.showResult(address.getLatitude(), address.getLongitude(), addressToShortString(address), addressToString(address)); 272 } else { 273 bottomSheet.hide(); 274 } 275 } 276 } 277 278 private static @NonNull String addressToString(@Nullable Address address) { 279 return address != null ? address.getAddressLine(0) : ""; 280 } 281 282 private static @NonNull String addressToShortString(@Nullable Address address) { 283 if (address == null) return ""; 284 285 String addressLine = address.getAddressLine(0); 286 String[] split = addressLine.split(","); 287 288 if (split.length >= 3) { 289 return split[1].trim() + ", " + split[2].trim(); 290 } else if (split.length == 2) { 291 return split[1].trim(); 292 } else return split[0].trim(); 293 } 294}