That fuck shit the fascists are using
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}