mirror of https://github.com/tasks/tasks
New place picker with google and mapbox support
parent
1cc5bf2e0e
commit
54fcad1aae
@ -0,0 +1,95 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import com.google.android.gms.maps.CameraUpdate;
|
||||
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
|
||||
import com.google.android.gms.maps.OnMapReadyCallback;
|
||||
import com.google.android.gms.maps.SupportMapFragment;
|
||||
import com.google.android.gms.maps.UiSettings;
|
||||
import com.google.android.gms.maps.model.CameraPosition;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.MapStyleOptions;
|
||||
import com.google.android.gms.maps.model.Marker;
|
||||
import com.google.android.gms.maps.model.MarkerOptions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.tasks.R;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public class GoogleMapFragment implements MapFragment, OnMapReadyCallback, OnMarkerClickListener {
|
||||
|
||||
private final Context context;
|
||||
private final MapFragmentCallback callbacks;
|
||||
private final boolean dark;
|
||||
private final List<Marker> markers = new ArrayList<>();
|
||||
private GoogleMap map;
|
||||
|
||||
GoogleMapFragment(
|
||||
Context context, SupportMapFragment fragment, MapFragmentCallback callbacks, boolean dark) {
|
||||
this.context = context;
|
||||
this.callbacks = callbacks;
|
||||
this.dark = dark;
|
||||
fragment.getMapAsync(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapPosition getMapPosition() {
|
||||
CameraPosition cameraPosition = map.getCameraPosition();
|
||||
LatLng target = cameraPosition.target;
|
||||
return new MapPosition(target.latitude, target.longitude, cameraPosition.zoom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void movePosition(MapPosition mapPosition, boolean animate) {
|
||||
CameraUpdate cameraUpdate =
|
||||
CameraUpdateFactory.newCameraPosition(
|
||||
CameraPosition.builder()
|
||||
.target(new LatLng(mapPosition.getLatitude(), mapPosition.getLongitude()))
|
||||
.zoom(mapPosition.getZoom())
|
||||
.build());
|
||||
if (animate) {
|
||||
map.animateCamera(cameraUpdate);
|
||||
} else {
|
||||
map.moveCamera(cameraUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMarkers(List<Place> places) {
|
||||
for (Marker marker : markers) {
|
||||
marker.remove();
|
||||
}
|
||||
markers.clear();
|
||||
for (Place place : places) {
|
||||
Marker marker =
|
||||
map.addMarker(
|
||||
new MarkerOptions().position(new LatLng(place.getLatitude(), place.getLongitude())));
|
||||
marker.setTag(place);
|
||||
markers.add(marker);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public void onMapReady(GoogleMap googleMap) {
|
||||
map = googleMap;
|
||||
map.setMyLocationEnabled(true);
|
||||
if (dark) {
|
||||
map.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.mapstyle_night));
|
||||
}
|
||||
UiSettings uiSettings = map.getUiSettings();
|
||||
uiSettings.setMyLocationButtonEnabled(false);
|
||||
uiSettings.setRotateGesturesEnabled(false);
|
||||
map.setOnMarkerClickListener(this);
|
||||
callbacks.onMapReady(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMarkerClick(Marker marker) {
|
||||
callbacks.onPlaceSelected((Place) marker.getTag());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,523 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.collect.Lists.transform;
|
||||
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.tasks.data.Place.newPlace;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.location.Address;
|
||||
import android.location.Geocoder;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnLayoutChangeListener;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.core.widget.ContentLoadingProgressBar;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||
import com.google.android.gms.location.LocationServices;
|
||||
import com.google.android.gms.maps.SupportMapFragment;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.libraries.places.api.Places;
|
||||
import com.google.android.libraries.places.api.model.Place;
|
||||
import com.google.android.libraries.places.api.model.Place.Field;
|
||||
import com.google.android.libraries.places.widget.Autocomplete;
|
||||
import com.google.android.libraries.places.widget.AutocompleteActivity;
|
||||
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.appbar.AppBarLayout.Behavior;
|
||||
import com.google.common.base.Strings;
|
||||
import com.mapbox.api.geocoding.v5.models.CarmenFeature;
|
||||
import com.mapbox.geojson.Point;
|
||||
import com.mapbox.mapboxsdk.Mapbox;
|
||||
import com.mapbox.mapboxsdk.plugins.places.autocomplete.PlaceAutocomplete;
|
||||
import com.mapbox.mapboxsdk.plugins.places.autocomplete.model.PlaceOptions;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import org.tasks.R;
|
||||
import org.tasks.billing.Inventory;
|
||||
import org.tasks.data.LocationDao;
|
||||
import org.tasks.data.PlaceUsage;
|
||||
import org.tasks.gtasks.PlayServices;
|
||||
import org.tasks.injection.ActivityComponent;
|
||||
import org.tasks.injection.ForApplication;
|
||||
import org.tasks.injection.InjectingAppCompatActivity;
|
||||
import org.tasks.location.LocationPickerAdapter.OnLocationPicked;
|
||||
import org.tasks.location.MapFragment.MapFragmentCallback;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.themes.Theme;
|
||||
import org.tasks.themes.ThemeColor;
|
||||
import org.tasks.ui.MenuColorizer;
|
||||
import org.tasks.ui.Toaster;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LocationPicker extends InjectingAppCompatActivity
|
||||
implements OnMenuItemClickListener, MapFragmentCallback, OnLocationPicked {
|
||||
|
||||
private static final String EXTRA_MAP_POSITION = "extra_map_position";
|
||||
private static final String EXTRA_APPBAR_OFFSET = "extra_appbar_offset";
|
||||
private static final String FRAG_TAG_MAP = "frag_tag_map";
|
||||
private static final int REQUEST_GOOGLE_AUTOCOMPLETE = 10101;
|
||||
private static final int REQUEST_MAPBOX_AUTOCOMPLETE = 10102;
|
||||
private static final Pattern pattern = Pattern.compile("(\\d+):(\\d+):(\\d+\\.\\d+)");
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@BindView(R.id.app_bar_layout)
|
||||
AppBarLayout appBarLayout;
|
||||
|
||||
@BindView(R.id.coordinator)
|
||||
CoordinatorLayout coordinatorLayout;
|
||||
|
||||
@BindView(R.id.search)
|
||||
View searchView;
|
||||
|
||||
@BindView(R.id.loading_indicator)
|
||||
ContentLoadingProgressBar loadingIndicator;
|
||||
|
||||
@BindView(R.id.choose_recent_location)
|
||||
View chooseRecentLocation;
|
||||
|
||||
@BindView(R.id.recent_locations)
|
||||
RecyclerView recentLocations;
|
||||
|
||||
@Inject @ForApplication Context context;
|
||||
@Inject Theme theme;
|
||||
@Inject Toaster toaster;
|
||||
@Inject Inventory inventory;
|
||||
@Inject PlayServices playServices;
|
||||
@Inject Preferences preferences;
|
||||
@Inject LocationDao locationDao;
|
||||
|
||||
private MapFragment map;
|
||||
private FusedLocationProviderClient fusedLocationProviderClient;
|
||||
private CompositeDisposable disposables;
|
||||
private MapPosition mapPosition;
|
||||
private LocationPickerAdapter adapter = new LocationPickerAdapter(this);
|
||||
private List<PlaceUsage> places = Collections.emptyList();
|
||||
private int offset;
|
||||
|
||||
private static String formatCoordinates(org.tasks.data.Place place) {
|
||||
return String.format(
|
||||
"%s %s",
|
||||
formatCoordinate(place.getLatitude(), true), formatCoordinate(place.getLongitude(), false));
|
||||
}
|
||||
|
||||
private static String formatCoordinate(double coordinates, boolean latitude) {
|
||||
String output = Location.convert(Math.abs(coordinates), Location.FORMAT_SECONDS);
|
||||
Matcher matcher = pattern.matcher(output);
|
||||
if (matcher.matches()) {
|
||||
return String.format(
|
||||
"%s°%s'%s\"%s",
|
||||
matcher.group(1),
|
||||
matcher.group(2),
|
||||
matcher.group(3),
|
||||
latitude ? (coordinates > 0 ? "N" : "S") : (coordinates > 0 ? "E" : "W"));
|
||||
} else {
|
||||
return Double.toString(coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canSearch() {
|
||||
return atLeastLollipop() || inventory.hasPro();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
theme.applyTheme(this);
|
||||
setContentView(R.layout.activity_location_picker);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
Configuration configuration = getResources().getConfiguration();
|
||||
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.smallestScreenWidthDp < 480) {
|
||||
searchView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mapPosition = savedInstanceState.getParcelable(EXTRA_MAP_POSITION);
|
||||
offset = savedInstanceState.getInt(EXTRA_APPBAR_OFFSET);
|
||||
}
|
||||
|
||||
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px);
|
||||
toolbar.setNavigationOnClickListener(v -> collapseToolbar());
|
||||
if (canSearch()) {
|
||||
toolbar.inflateMenu(R.menu.menu_location_picker);
|
||||
toolbar.setOnMenuItemClickListener(this);
|
||||
} else {
|
||||
searchView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
MenuColorizer.colorToolbar(this, toolbar);
|
||||
ThemeColor themeColor = theme.getThemeColor();
|
||||
themeColor.applyToStatusBarIcons(this);
|
||||
themeColor.applyToNavigationBar(this);
|
||||
|
||||
if (preferences.useGoogleMaps()) {
|
||||
initGoogleMaps();
|
||||
} else {
|
||||
initMapboxMaps();
|
||||
}
|
||||
|
||||
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
|
||||
|
||||
CoordinatorLayout.LayoutParams params =
|
||||
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
|
||||
Behavior behavior = new Behavior();
|
||||
behavior.setDragCallback(
|
||||
new AppBarLayout.Behavior.DragCallback() {
|
||||
@Override
|
||||
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
params.setBehavior(behavior);
|
||||
|
||||
appBarLayout.addOnOffsetChangedListener(
|
||||
(appBarLayout, offset) -> {
|
||||
this.offset = offset;
|
||||
toolbar.setAlpha(Math.abs(offset / (float) appBarLayout.getTotalScrollRange()));
|
||||
});
|
||||
|
||||
coordinatorLayout.addOnLayoutChangeListener(
|
||||
new OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(
|
||||
View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) {
|
||||
coordinatorLayout.removeOnLayoutChangeListener(this);
|
||||
updateAppbarLayout();
|
||||
}
|
||||
});
|
||||
|
||||
if (offset != 0) {
|
||||
appBarLayout.post(this::expandToolbar);
|
||||
}
|
||||
|
||||
adapter.setHasStableIds(true);
|
||||
((DefaultItemAnimator) recentLocations.getItemAnimator()).setSupportsChangeAnimations(false);
|
||||
recentLocations.setLayoutManager(new LinearLayoutManager(this));
|
||||
recentLocations.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void initGoogleMaps() {
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
SupportMapFragment mapFragment =
|
||||
(SupportMapFragment) supportFragmentManager.findFragmentByTag(FRAG_TAG_MAP);
|
||||
if (mapFragment == null) {
|
||||
mapFragment = new SupportMapFragment();
|
||||
supportFragmentManager.beginTransaction().replace(R.id.map, mapFragment).commit();
|
||||
}
|
||||
new GoogleMapFragment(context, mapFragment, this, theme.getThemeBase().isDarkTheme(this));
|
||||
}
|
||||
|
||||
private void initMapboxMaps() {
|
||||
Mapbox.getInstance(this, getString(R.string.mapbox_key));
|
||||
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
com.mapbox.mapboxsdk.maps.SupportMapFragment mapFragment =
|
||||
(com.mapbox.mapboxsdk.maps.SupportMapFragment)
|
||||
supportFragmentManager.findFragmentByTag(FRAG_TAG_MAP);
|
||||
if (mapFragment == null) {
|
||||
mapFragment = new com.mapbox.mapboxsdk.maps.SupportMapFragment();
|
||||
supportFragmentManager.beginTransaction().replace(R.id.map, mapFragment).commit();
|
||||
}
|
||||
new MapboxMapFragment(context, mapFragment, this, theme.getThemeBase().isDarkTheme(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapReady(MapFragment mapFragment) {
|
||||
map = mapFragment;
|
||||
if (mapPosition != null) {
|
||||
map.movePosition(mapPosition, false);
|
||||
} else {
|
||||
moveToCurrentLocation(false);
|
||||
}
|
||||
updateMarkers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaceSelected(org.tasks.data.Place place) {
|
||||
returnPlace(place);
|
||||
}
|
||||
|
||||
@OnClick(R.id.current_location)
|
||||
void onClick() {
|
||||
moveToCurrentLocation(true);
|
||||
}
|
||||
|
||||
@OnClick(R.id.select_this_location)
|
||||
void selectLocation() {
|
||||
loadingIndicator.setVisibility(View.VISIBLE);
|
||||
|
||||
MapPosition mapPosition = map.getMapPosition();
|
||||
disposables.add(
|
||||
Single.fromCallable(
|
||||
() -> {
|
||||
Geocoder geocoder = new Geocoder(this);
|
||||
return geocoder.getFromLocation(
|
||||
mapPosition.getLatitude(), mapPosition.getLongitude(), 1);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnError(e -> toaster.longToast(e.getMessage()))
|
||||
.doFinally(() -> loadingIndicator.setVisibility(View.GONE))
|
||||
.subscribe(
|
||||
addresses -> {
|
||||
org.tasks.data.Place place = newPlace();
|
||||
if (addresses.isEmpty()) {
|
||||
place.setLatitude(mapPosition.getLatitude());
|
||||
place.setLongitude(mapPosition.getLongitude());
|
||||
} else {
|
||||
Address address = addresses.get(0);
|
||||
place.setLatitude(address.getLatitude());
|
||||
place.setLongitude(address.getLongitude());
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
|
||||
stringBuilder.append(address.getAddressLine(i)).append("\n");
|
||||
}
|
||||
place.setPhone(address.getPhone());
|
||||
place.setAddress(stringBuilder.toString().trim());
|
||||
String url = address.getUrl();
|
||||
if (!Strings.isNullOrEmpty(url)) {
|
||||
place.setUrl(url);
|
||||
}
|
||||
}
|
||||
place.setName(formatCoordinates(place));
|
||||
returnPlace(place);
|
||||
}));
|
||||
}
|
||||
|
||||
@OnClick(R.id.search)
|
||||
void searchPlace() {
|
||||
if (preferences.useGooglePlaces() && inventory.hasPro()) {
|
||||
if (!Places.isInitialized()) {
|
||||
Places.initialize(this, getString(R.string.google_key));
|
||||
}
|
||||
|
||||
startActivityForResult(
|
||||
new Autocomplete.IntentBuilder(
|
||||
AutocompleteActivityMode.FULLSCREEN,
|
||||
asList(
|
||||
Field.ID,
|
||||
Field.LAT_LNG,
|
||||
Field.ADDRESS,
|
||||
Field.WEBSITE_URI,
|
||||
Field.NAME,
|
||||
Field.PHONE_NUMBER))
|
||||
.build(this),
|
||||
REQUEST_GOOGLE_AUTOCOMPLETE);
|
||||
} else {
|
||||
String token = getString(R.string.mapbox_key);
|
||||
Mapbox.getInstance(this, token);
|
||||
MapPosition mapPosition = map.getMapPosition();
|
||||
startActivityForResult(
|
||||
new PlaceAutocomplete.IntentBuilder()
|
||||
.accessToken(token)
|
||||
.placeOptions(
|
||||
PlaceOptions.builder()
|
||||
.backgroundColor(getResources().getColor(R.color.white_100))
|
||||
.proximity(
|
||||
Point.fromLngLat(mapPosition.getLongitude(), mapPosition.getLatitude()))
|
||||
.build())
|
||||
.build(this),
|
||||
REQUEST_MAPBOX_AUTOCOMPLETE);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private void moveToCurrentLocation(boolean animate) {
|
||||
fusedLocationProviderClient
|
||||
.getLastLocation()
|
||||
.addOnSuccessListener(
|
||||
location -> {
|
||||
if (location != null) {
|
||||
map.movePosition(
|
||||
new MapPosition(location.getLatitude(), location.getLongitude(), 15f), animate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == REQUEST_GOOGLE_AUTOCOMPLETE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
returnPlace(Autocomplete.getPlaceFromIntent(data));
|
||||
} else if (resultCode == AutocompleteActivity.RESULT_ERROR && data != null) {
|
||||
Status status = Autocomplete.getStatusFromIntent(data);
|
||||
toaster.longToast(status.getStatusMessage());
|
||||
}
|
||||
} else if (requestCode == REQUEST_MAPBOX_AUTOCOMPLETE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
returnPlace(PlaceAutocomplete.getPlace(data));
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void returnPlace(CarmenFeature place) {
|
||||
org.tasks.data.Place result = newPlace();
|
||||
result.setName(place.placeName());
|
||||
result.setAddress(place.address());
|
||||
result.setLatitude(place.center().latitude());
|
||||
result.setLongitude(place.center().longitude());
|
||||
returnPlace(result);
|
||||
}
|
||||
|
||||
private void returnPlace(Place place) {
|
||||
LatLng latLng = place.getLatLng();
|
||||
org.tasks.data.Place result = newPlace();
|
||||
result.setName(place.getName());
|
||||
CharSequence address = place.getAddress();
|
||||
if (address != null) {
|
||||
result.setAddress(place.getAddress());
|
||||
}
|
||||
CharSequence phoneNumber = place.getPhoneNumber();
|
||||
if (phoneNumber != null) {
|
||||
result.setPhone(phoneNumber.toString());
|
||||
}
|
||||
Uri uri = place.getWebsiteUri();
|
||||
if (uri != null) {
|
||||
result.setUrl(uri.toString());
|
||||
}
|
||||
result.setLatitude(latLng.latitude);
|
||||
result.setLongitude(latLng.longitude);
|
||||
returnPlace(result);
|
||||
}
|
||||
|
||||
private void returnPlace(org.tasks.data.Place place) {
|
||||
if (place == null) {
|
||||
Timber.e("Place is null");
|
||||
return;
|
||||
}
|
||||
if (place.getId() <= 0) {
|
||||
org.tasks.data.Place existing =
|
||||
locationDao.findPlace(place.getLatitude(), place.getLongitude());
|
||||
if (existing == null) {
|
||||
long placeId = locationDao.insert(place);
|
||||
place.setId(placeId);
|
||||
} else {
|
||||
existing.apply(place);
|
||||
locationDao.update(existing);
|
||||
place = existing;
|
||||
}
|
||||
}
|
||||
setResult(RESULT_OK, new Intent().putExtra(PlacePicker.EXTRA_PLACE, (Parcelable) place));
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
disposables = new CompositeDisposable(playServices.checkMaps(this));
|
||||
|
||||
locationDao.getPlaceUsage().observe(this, this::updatePlaces);
|
||||
}
|
||||
|
||||
private void updatePlaces(List<PlaceUsage> places) {
|
||||
this.places = places;
|
||||
updateMarkers();
|
||||
adapter.submitList(places);
|
||||
updateAppbarLayout();
|
||||
if (places.isEmpty()) {
|
||||
collapseToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMarkers() {
|
||||
if (map != null) {
|
||||
map.setMarkers(newArrayList(transform(places, PlaceUsage::getPlace)));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAppbarLayout() {
|
||||
CoordinatorLayout.LayoutParams params =
|
||||
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
|
||||
|
||||
if (places.isEmpty()) {
|
||||
params.height = coordinatorLayout.getHeight();
|
||||
chooseRecentLocation.setVisibility(View.GONE);
|
||||
} else {
|
||||
params.height = (coordinatorLayout.getHeight() * 75) / 100;
|
||||
chooseRecentLocation.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void collapseToolbar() {
|
||||
appBarLayout.setExpanded(true, true);
|
||||
}
|
||||
|
||||
private void expandToolbar() {
|
||||
appBarLayout.setExpanded(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
disposables.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putParcelable(EXTRA_MAP_POSITION, map.getMapPosition());
|
||||
outState.putInt(EXTRA_APPBAR_OFFSET, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_search) {
|
||||
searchPlace();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void picked(org.tasks.data.Place place) {
|
||||
returnPlace(place);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(org.tasks.data.Place place) {
|
||||
locationDao.delete(place);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.common.base.Strings;
|
||||
import org.tasks.R;
|
||||
import org.tasks.data.Place;
|
||||
import org.tasks.data.PlaceUsage;
|
||||
import org.tasks.location.LocationPickerAdapter.PlaceViewHolder;
|
||||
|
||||
public class LocationPickerAdapter extends ListAdapter<PlaceUsage, PlaceViewHolder> {
|
||||
|
||||
private final OnLocationPicked callback;
|
||||
|
||||
LocationPickerAdapter(OnLocationPicked callback) {
|
||||
super(new DiffCallback());
|
||||
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).place.getId();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PlaceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new PlaceViewHolder(
|
||||
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_place, parent, false),
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PlaceViewHolder holder, int position) {
|
||||
holder.bind(getItem(position));
|
||||
}
|
||||
|
||||
public interface OnLocationPicked {
|
||||
void picked(Place place);
|
||||
|
||||
void delete(Place place);
|
||||
}
|
||||
|
||||
public static class PlaceViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView name;
|
||||
private final TextView address;
|
||||
private final View delete;
|
||||
private Place place;
|
||||
|
||||
PlaceViewHolder(@NonNull View itemView, OnLocationPicked onLocationPicked) {
|
||||
super(itemView);
|
||||
itemView.setOnClickListener(v -> onLocationPicked.picked(place));
|
||||
name = itemView.findViewById(R.id.name);
|
||||
address = itemView.findViewById(R.id.address);
|
||||
delete = itemView.findViewById(R.id.delete);
|
||||
delete.setOnClickListener(v -> onLocationPicked.delete(place));
|
||||
}
|
||||
|
||||
public void bind(PlaceUsage placeUsage) {
|
||||
this.place = placeUsage.place;
|
||||
String name = place.getDisplayName();
|
||||
String address = place.getAddress();
|
||||
this.name.setText(name);
|
||||
if (Strings.isNullOrEmpty(address) || address.equals(name)) {
|
||||
this.address.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.address.setText(address);
|
||||
this.address.setVisibility(View.VISIBLE);
|
||||
}
|
||||
delete.setVisibility(placeUsage.count > 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiffCallback extends ItemCallback<PlaceUsage> {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull PlaceUsage oldItem, @NonNull PlaceUsage newItem) {
|
||||
return oldItem.place.getUid().equals(newItem.place.getUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull PlaceUsage oldItem, @NonNull PlaceUsage newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdate;
|
||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponent;
|
||||
import com.mapbox.mapboxsdk.location.modes.CameraMode;
|
||||
import com.mapbox.mapboxsdk.location.modes.RenderMode;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap.OnMarkerClickListener;
|
||||
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
import com.mapbox.mapboxsdk.maps.SupportMapFragment;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public class MapboxMapFragment implements MapFragment, OnMapReadyCallback, OnMarkerClickListener {
|
||||
|
||||
private final Context context;
|
||||
private final MapFragmentCallback callbacks;
|
||||
private final boolean dark;
|
||||
private MapboxMap map;
|
||||
private Map<Marker, Place> markers = new HashMap<>();
|
||||
|
||||
MapboxMapFragment(
|
||||
Context context, SupportMapFragment fragment, MapFragmentCallback callbacks, boolean dark) {
|
||||
this.context = context;
|
||||
this.callbacks = callbacks;
|
||||
this.dark = dark;
|
||||
fragment.getMapAsync(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapPosition getMapPosition() {
|
||||
CameraPosition cameraPosition = map.getCameraPosition();
|
||||
LatLng target = cameraPosition.target;
|
||||
return new MapPosition(
|
||||
target.getLatitude(), target.getLongitude(), (float) cameraPosition.zoom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void movePosition(MapPosition mapPosition, boolean animate) {
|
||||
CameraUpdate cameraUpdate =
|
||||
CameraUpdateFactory.newCameraPosition(
|
||||
new CameraPosition.Builder()
|
||||
.target(new LatLng(mapPosition.getLatitude(), mapPosition.getLongitude()))
|
||||
.zoom(mapPosition.getZoom())
|
||||
.build());
|
||||
if (animate) {
|
||||
map.animateCamera(cameraUpdate);
|
||||
} else {
|
||||
map.moveCamera(cameraUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMarkers(List<Place> places) {
|
||||
for (Marker marker : map.getMarkers()) {
|
||||
map.removeMarker(marker);
|
||||
}
|
||||
markers.clear();
|
||||
for (Place place : places) {
|
||||
Marker marker =
|
||||
map.addMarker(
|
||||
new MarkerOptions()
|
||||
.setPosition(new LatLng(place.getLatitude(), place.getLongitude())));
|
||||
markers.put(marker, place);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public void onMapReady(@NonNull MapboxMap mapboxMap) {
|
||||
map = mapboxMap;
|
||||
map.getUiSettings().setRotateGesturesEnabled(false);
|
||||
map.setStyle(
|
||||
dark ? Style.DARK : Style.MAPBOX_STREETS,
|
||||
style -> {
|
||||
LocationComponent locationComponent = map.getLocationComponent();
|
||||
locationComponent.activateLocationComponent(context, style);
|
||||
locationComponent.setLocationComponentEnabled(true);
|
||||
locationComponent.setCameraMode(CameraMode.NONE);
|
||||
locationComponent.setRenderMode(RenderMode.NORMAL);
|
||||
});
|
||||
map.setOnMarkerClickListener(this);
|
||||
callbacks.onMapReady(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMarkerClick(@NonNull Marker marker) {
|
||||
Place place = markers.get(marker);
|
||||
callbacks.onPlaceSelected(place);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#EF5350"
|
||||
android:pathData="M38.2558,14.5577C38.2558,7.0849 31.944,1.026 24.1527,1.0019V1C24.1454,1 24.1372,1.0009 24.1289,1.0009C24.1217,1.0009 24.1135,1 24.1062,1V1.0019C16.3143,1.026 10.0031,7.0849 10.0031,14.5577C10.0031,14.5577 9.8545,17.9323 11.6149,21.4284C12.9266,24.0362 14.5912,25.7794 16.3003,28.1506C18.9353,31.8058 20.187,33.7253 21.5007,37.2636C22.4339,39.7745 23.3293,42.7376 24.1285,47C24.9286,42.7376 25.8245,39.7745 26.7577,37.2636C28.0728,33.7248 29.3245,31.8054 31.9581,28.1506C33.6672,25.7798 35.3308,24.0362 36.644,21.4284C38.4039,17.9323 38.2558,14.5577 38.2558,14.5577ZM24.1193,19.463C21.2291,19.463 18.8874,17.2184 18.8874,14.4482C18.8874,11.6794 21.2287,9.4358 24.1193,9.4358C27.0104,9.4358 29.3516,11.6794 29.3516,14.4482C29.3516,17.2179 27.0104,19.463 24.1193,19.463Z"
|
||||
android:strokeWidth="1.0"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#952F2D"
|
||||
android:pathData="M18,14.4166a6,5.75 0,1 0,12 0a6,5.75 0,1 0,-12 0z" />
|
||||
</vector>
|
||||
@ -0,0 +1,215 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="?attr/overlay_theme">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
app:contentScrim="?attr/colorPrimary"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
app:statusBarScrim="?attr/colorPrimaryDark"
|
||||
app:titleEnabled="false">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraint_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/window_background"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:layout_collapseParallaxMultiplier="0.4">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/map"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/select_this_location"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:paddingBottom="48dp"
|
||||
android:src="@drawable/ic_map_marker_select_red_48dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/map"
|
||||
app:layout_constraintLeft_toLeftOf="@id/map"
|
||||
app:layout_constraintRight_toRightOf="@id/map"
|
||||
app:layout_constraintTop_toTopOf="@id/map"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/current_location"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/task_list_fab_margin"
|
||||
android:layout_marginBottom="@dimen/keyline_first"
|
||||
android:layout_marginEnd="@dimen/keyline_first"
|
||||
android:layout_marginRight="@dimen/keyline_first"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:padding="0dp"
|
||||
android:src="@drawable/ic_outline_gps_fixed_24px"
|
||||
android:tint="?attr/fab_text"
|
||||
app:borderWidth="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/map"
|
||||
app:layout_constraintRight_toRightOf="@id/map"/>
|
||||
|
||||
<!--
|
||||
This view is a hack, when using a Mapbox fragment for some reason other views
|
||||
dont collapse under the status bar without this
|
||||
-->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0px"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/select_this_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/keyline_first"
|
||||
android:paddingBottom="@dimen/keyline_first"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toTopOf="@id/choose_recent_location"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/map">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/place_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/keyline_first"
|
||||
android:layout_marginLeft="@dimen/keyline_first"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:contentDescription="@string/pick_this_location"
|
||||
android:src="@drawable/ic_outline_place_24px"
|
||||
android:tint="@color/icon_tint"/>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/loading_indicator"
|
||||
style="@style/Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="@dimen/keyline_first"
|
||||
android:layout_marginRight="@dimen/keyline_first"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/keyline_second"
|
||||
android:layout_marginLeft="@dimen/keyline_second"
|
||||
android:layout_toEndOf="@id/place_icon"
|
||||
android:layout_toLeftOf="@id/loading_indicator"
|
||||
android:layout_toRightOf="@id/place_icon"
|
||||
android:layout_toStartOf="@id/loading_indicator"
|
||||
android:gravity="start|center_vertical"
|
||||
android:text="@string/pick_this_location"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/choose_recent_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="@color/window_background"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/select_this_location">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/keyline_first"
|
||||
android:layout_marginLeft="@dimen/keyline_first"
|
||||
android:text="@string/or_choose_a_location"
|
||||
android:textAppearance="@style/TextAppearance"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="?attr/overlay_theme"
|
||||
app:layout_collapseMode="pin"
|
||||
app:popupTheme="@style/popup_overlay"
|
||||
app:title="@string/choose_a_location"/>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="@dimen/keyline_first"
|
||||
android:layout_marginStart="@dimen/card_view_margin"
|
||||
android:layout_marginEnd="@dimen/card_view_margin"
|
||||
app:cardBackgroundColor="@color/window_background"
|
||||
app:cardElevation="@dimen/elevation_app_bar"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:layout_collapseParallaxMultiplier=".7">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/card_view_margin"
|
||||
android:layout_marginLeft="@dimen/card_view_margin"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@android:string/search_go"
|
||||
android:src="@drawable/ic_outline_search_24px"
|
||||
android:tint="@color/icon_tint"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginLeft="64dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@android:string/search_go"
|
||||
android:textAppearance="@style/TextAppearance"
|
||||
android:textColor="@color/text_primary"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recent_locations"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/window_background"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/row_place"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/place_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:paddingStart="@dimen/keyline_first"
|
||||
android:paddingEnd="@dimen/keyline_second"
|
||||
android:paddingLeft="@dimen/keyline_first"
|
||||
android:paddingRight="@dimen/keyline_second"
|
||||
android:src="@drawable/ic_outline_place_24px"
|
||||
android:tint="@color/icon_tint"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:paddingStart="@dimen/keyline_first"
|
||||
android:paddingEnd="@dimen/keyline_first"
|
||||
android:paddingLeft="@dimen/keyline_first"
|
||||
android:paddingRight="@dimen/keyline_first"
|
||||
android:src="@drawable/ic_outline_delete_24px"
|
||||
android:tint="@color/icon_tint"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/place_icon"
|
||||
android:layout_toRightOf="@id/place_icon"
|
||||
android:layout_toLeftOf="@id/delete"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="@dimen/keyline_first"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingRight="@dimen/keyline_first"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|top"
|
||||
android:maxLines="2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/keyline_content_inset"
|
||||
android:paddingEnd="@dimen/keyline_first"
|
||||
android:paddingLeft="@dimen/keyline_content_inset"
|
||||
android:paddingRight="@dimen/keyline_first"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,193 @@
|
||||
// https://github.com/googlemaps/android-samples/blob/816307c077060b30a27adc125071d0debd478a98/ApiDemos/java/app/src/main/res/raw/mapstyle_night.json
|
||||
|
||||
[
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#242f3e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"lightness": -80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative.locality",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#263c3f"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#6b9a76"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#2b3544"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#9ca5b3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#38414e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#212a37"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#1f2835"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#f3d19c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.local",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#38414e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.local",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#212a37"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#2f3948"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit.station",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#17263c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#515c6d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"lightness": -20
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,41 @@
|
||||
package org.tasks.data;
|
||||
|
||||
import androidx.room.Embedded;
|
||||
|
||||
public class PlaceUsage {
|
||||
@Embedded public Place place;
|
||||
public int count;
|
||||
|
||||
public Place getPlace() {
|
||||
return place;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlaceUsage that = (PlaceUsage) o;
|
||||
|
||||
if (count != that.count) {
|
||||
return false;
|
||||
}
|
||||
return place != null ? place.equals(that.place) : that.place == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = place != null ? place.hashCode() : 0;
|
||||
result = 31 * result + count;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlaceUsage{" + "place=" + place + ", count=" + count + '}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import java.util.List;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public interface MapFragment {
|
||||
|
||||
MapPosition getMapPosition();
|
||||
|
||||
void movePosition(MapPosition mapPosition, boolean animate);
|
||||
|
||||
void setMarkers(List<Place> places);
|
||||
|
||||
interface MapFragmentCallback {
|
||||
void onMapReady(MapFragment mapFragment);
|
||||
|
||||
void onPlaceSelected(Place place);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class MapPosition implements Parcelable {
|
||||
|
||||
public static final Creator<MapPosition> CREATOR =
|
||||
new Creator<MapPosition>() {
|
||||
@Override
|
||||
public MapPosition createFromParcel(Parcel in) {
|
||||
return new MapPosition(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapPosition[] newArray(int size) {
|
||||
return new MapPosition[size];
|
||||
}
|
||||
};
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
private final float zoom;
|
||||
|
||||
public MapPosition(double latitude, double longitude, float zoom) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
protected MapPosition(Parcel in) {
|
||||
latitude = in.readDouble();
|
||||
longitude = in.readDouble();
|
||||
zoom = in.readFloat();
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public float getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeDouble(latitude);
|
||||
dest.writeDouble(longitude);
|
||||
dest.writeFloat(zoom);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_search"
|
||||
android:icon="@drawable/ic_outline_search_24px"
|
||||
android:title="@string/TLA_menu_search"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
Loading…
Reference in New Issue