mirror of https://github.com/tasks/tasks
Show location search in location picker activity
parent
54fcad1aae
commit
8cdbe66a19
@ -0,0 +1,130 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.tasks.data.Place.newPlace;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.LatLngBounds;
|
||||
import com.google.android.libraries.places.api.Places;
|
||||
import com.google.android.libraries.places.api.model.AutocompletePrediction;
|
||||
import com.google.android.libraries.places.api.model.AutocompleteSessionToken;
|
||||
import com.google.android.libraries.places.api.model.Place.Field;
|
||||
import com.google.android.libraries.places.api.model.RectangularBounds;
|
||||
import com.google.android.libraries.places.api.net.FetchPlaceRequest;
|
||||
import com.google.android.libraries.places.api.net.FetchPlaceResponse;
|
||||
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;
|
||||
import com.google.android.libraries.places.api.net.PlacesClient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.tasks.Callback;
|
||||
import org.tasks.R;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public class GooglePlacesSearchProvider implements PlaceSearchProvider {
|
||||
|
||||
private static final String EXTRA_SESSION_TOKEN = "extra_session_token";
|
||||
private final Context context;
|
||||
|
||||
private AutocompleteSessionToken token;
|
||||
private PlacesClient placesClient;
|
||||
|
||||
public GooglePlacesSearchProvider(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(Bundle savedInstanceState) {
|
||||
token = savedInstanceState.getParcelable(EXTRA_SESSION_TOKEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveState(Bundle outState) {}
|
||||
|
||||
@Override
|
||||
public void search(
|
||||
String query,
|
||||
MapPosition bias,
|
||||
Callback<List<PlaceSearchResult>> onSuccess,
|
||||
Callback<String> onError) {
|
||||
if (!Places.isInitialized()) {
|
||||
Places.initialize(context, context.getString(R.string.google_key));
|
||||
}
|
||||
if (placesClient == null) {
|
||||
placesClient = Places.createClient(context);
|
||||
}
|
||||
if (token == null) {
|
||||
token = AutocompleteSessionToken.newInstance();
|
||||
}
|
||||
placesClient
|
||||
.findAutocompletePredictions(
|
||||
FindAutocompletePredictionsRequest.builder()
|
||||
.setSessionToken(token)
|
||||
.setQuery(query)
|
||||
.setLocationBias(
|
||||
RectangularBounds.newInstance(
|
||||
LatLngBounds.builder()
|
||||
.include(new LatLng(bias.getLatitude(), bias.getLongitude()))
|
||||
.build()))
|
||||
.build())
|
||||
.addOnSuccessListener(
|
||||
response -> onSuccess.call(toSearchResults(response.getAutocompletePredictions())))
|
||||
.addOnFailureListener(e -> onError.call(e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(
|
||||
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError) {
|
||||
placesClient
|
||||
.fetchPlace(
|
||||
FetchPlaceRequest.builder(
|
||||
placeSearchResult.getId(),
|
||||
asList(
|
||||
Field.ID,
|
||||
Field.LAT_LNG,
|
||||
Field.ADDRESS,
|
||||
Field.WEBSITE_URI,
|
||||
Field.NAME,
|
||||
Field.PHONE_NUMBER))
|
||||
.setSessionToken(token)
|
||||
.build())
|
||||
.addOnSuccessListener(result -> onSuccess.call(toPlace(result)))
|
||||
.addOnFailureListener(e -> onError.call(e.getMessage()));
|
||||
}
|
||||
|
||||
private List<PlaceSearchResult> toSearchResults(List<AutocompletePrediction> predictions) {
|
||||
List<PlaceSearchResult> results = new ArrayList<>();
|
||||
for (AutocompletePrediction prediction : predictions) {
|
||||
results.add(
|
||||
new PlaceSearchResult(
|
||||
prediction.getPlaceId(),
|
||||
prediction.getPrimaryText(null).toString(),
|
||||
prediction.getSecondaryText(null).toString()));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private Place toPlace(FetchPlaceResponse fetchPlaceResponse) {
|
||||
com.google.android.libraries.places.api.model.Place place = fetchPlaceResponse.getPlace();
|
||||
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());
|
||||
}
|
||||
LatLng latLng = place.getLatLng();
|
||||
result.setLatitude(latLng.latitude);
|
||||
result.setLongitude(latLng.longitude);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
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.location.LocationSearchAdapter.SearchViewHolder;
|
||||
|
||||
public class LocationSearchAdapter extends ListAdapter<PlaceSearchResult, SearchViewHolder> {
|
||||
|
||||
private final OnPredictionPicked callback;
|
||||
|
||||
LocationSearchAdapter(OnPredictionPicked callback) {
|
||||
super(new DiffCallback());
|
||||
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new SearchViewHolder(
|
||||
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_place, parent, false),
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) {
|
||||
holder.bind(getItem(position));
|
||||
}
|
||||
|
||||
public interface OnPredictionPicked {
|
||||
void picked(PlaceSearchResult prediction);
|
||||
}
|
||||
|
||||
public static class SearchViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView name;
|
||||
private final TextView address;
|
||||
private PlaceSearchResult prediction;
|
||||
|
||||
SearchViewHolder(@NonNull View itemView, OnPredictionPicked onPredictionPicked) {
|
||||
super(itemView);
|
||||
itemView.setOnClickListener(v -> onPredictionPicked.picked(prediction));
|
||||
name = itemView.findViewById(R.id.name);
|
||||
address = itemView.findViewById(R.id.address);
|
||||
itemView.findViewById(R.id.place_icon).setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void bind(PlaceSearchResult prediction) {
|
||||
this.prediction = prediction;
|
||||
CharSequence name = prediction.getName();
|
||||
CharSequence address = prediction.getAddress();
|
||||
this.name.setText(name);
|
||||
if (address == null || Strings.isNullOrEmpty(address.toString()) || address.toString().equals(name.toString())) {
|
||||
this.address.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.address.setText(address);
|
||||
this.address.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiffCallback extends ItemCallback<PlaceSearchResult> {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(
|
||||
@NonNull PlaceSearchResult oldItem, @NonNull PlaceSearchResult newItem) {
|
||||
return oldItem.getId().equals(newItem.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(
|
||||
@NonNull PlaceSearchResult oldItem, @NonNull PlaceSearchResult newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import static com.mapbox.api.geocoding.v5.GeocodingCriteria.TYPE_ADDRESS;
|
||||
import static org.tasks.data.Place.newPlace;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import com.mapbox.api.geocoding.v5.MapboxGeocoding;
|
||||
import com.mapbox.api.geocoding.v5.models.CarmenFeature;
|
||||
import com.mapbox.api.geocoding.v5.models.GeocodingResponse;
|
||||
import com.mapbox.geojson.Point;
|
||||
import com.mapbox.mapboxsdk.Mapbox;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.tasks.Callback;
|
||||
import org.tasks.R;
|
||||
import org.tasks.data.Place;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class MapboxSearchProvider implements PlaceSearchProvider {
|
||||
|
||||
private final Context context;
|
||||
private MapboxGeocoding.Builder builder;
|
||||
|
||||
public MapboxSearchProvider(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(Bundle savedInstanceState) {}
|
||||
|
||||
@Override
|
||||
public void saveState(Bundle outState) {}
|
||||
|
||||
@Override
|
||||
public void search(
|
||||
String query,
|
||||
MapPosition bias,
|
||||
Callback<List<PlaceSearchResult>> onSuccess,
|
||||
Callback<String> onError) {
|
||||
if (builder == null) {
|
||||
String token = context.getString(R.string.mapbox_key);
|
||||
Mapbox.getInstance(context, token);
|
||||
builder =
|
||||
MapboxGeocoding.builder()
|
||||
.autocomplete(true)
|
||||
.accessToken(token)
|
||||
.proximity(Point.fromLngLat(bias.getLongitude(), bias.getLatitude()));
|
||||
}
|
||||
|
||||
builder
|
||||
.query(query)
|
||||
.build()
|
||||
.enqueueCall(
|
||||
new retrofit2.Callback<GeocodingResponse>() {
|
||||
@Override
|
||||
public void onResponse(
|
||||
Call<GeocodingResponse> call, Response<GeocodingResponse> response) {
|
||||
List<PlaceSearchResult> results = new ArrayList<>();
|
||||
results.clear();
|
||||
for (CarmenFeature feature : response.body().features()) {
|
||||
results.add(toSearchResult(feature));
|
||||
}
|
||||
onSuccess.call(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<GeocodingResponse> call, Throwable t) {
|
||||
onError.call(t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(
|
||||
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError) {
|
||||
CarmenFeature carmenFeature = (CarmenFeature) placeSearchResult.getTag();
|
||||
org.tasks.data.Place place = newPlace();
|
||||
place.setName(placeSearchResult.getName());
|
||||
place.setAddress(placeSearchResult.getAddress());
|
||||
place.setLatitude(carmenFeature.center().latitude());
|
||||
place.setLongitude(carmenFeature.center().longitude());
|
||||
onSuccess.call(place);
|
||||
}
|
||||
|
||||
private PlaceSearchResult toSearchResult(CarmenFeature feature) {
|
||||
String name = getName(feature);
|
||||
String address = feature.placeName();
|
||||
String replace = String.format("%s, ", name);
|
||||
if (address != null && address.startsWith(replace)) {
|
||||
address = address.replace(replace, "");
|
||||
}
|
||||
return new PlaceSearchResult(feature.id(), name, address, feature);
|
||||
}
|
||||
|
||||
private String getName(CarmenFeature feature) {
|
||||
List<String> types = feature.placeType();
|
||||
return types != null && types.contains(TYPE_ADDRESS)
|
||||
? String.format("%s %s", feature.address(), feature.text())
|
||||
: feature.text();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.os.Bundle;
|
||||
import java.util.List;
|
||||
import org.tasks.Callback;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public interface PlaceSearchProvider {
|
||||
void restoreState(Bundle savedInstanceState);
|
||||
|
||||
void saveState(Bundle outState);
|
||||
|
||||
void search(
|
||||
String query,
|
||||
MapPosition bias,
|
||||
Callback<List<PlaceSearchResult>> onSuccess,
|
||||
Callback<String> onError);
|
||||
|
||||
void fetch(
|
||||
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package org.tasks.location;
|
||||
|
||||
public class PlaceSearchResult {
|
||||
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final String address;
|
||||
private final Object tag;
|
||||
|
||||
PlaceSearchResult(String id, String name, String address) {
|
||||
this(id, name, address, null);
|
||||
}
|
||||
|
||||
PlaceSearchResult(String id, String name, String address, Object tag) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public Object getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlaceSearchResult that = (PlaceSearchResult) o;
|
||||
|
||||
if (id != null ? !id.equals(that.id) : that.id != null) {
|
||||
return false;
|
||||
}
|
||||
if (name != null ? !name.equals(that.name) : that.name != null) {
|
||||
return false;
|
||||
}
|
||||
if (address != null ? !address.equals(that.address) : that.address != null) {
|
||||
return false;
|
||||
}
|
||||
return tag != null ? tag.equals(that.tag) : that.tag == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id != null ? id.hashCode() : 0;
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
result = 31 * result + (address != null ? address.hashCode() : 0);
|
||||
result = 31 * result + (tag != null ? tag.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlaceSearchResult{"
|
||||
+ "id='"
|
||||
+ id
|
||||
+ '\''
|
||||
+ ", name='"
|
||||
+ name
|
||||
+ '\''
|
||||
+ ", address='"
|
||||
+ address
|
||||
+ '\''
|
||||
+ ", tag="
|
||||
+ tag
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.tasks.Event;
|
||||
import org.tasks.data.Place;
|
||||
|
||||
public class PlaceSearchViewModel extends ViewModel {
|
||||
private PlaceSearchProvider searchProvider;
|
||||
|
||||
private MutableLiveData<List<PlaceSearchResult>> searchResults = new MutableLiveData<>();
|
||||
private MutableLiveData<Event<String>> error = new MutableLiveData<>();
|
||||
private MutableLiveData<Place> selection = new MutableLiveData<>();
|
||||
|
||||
void setSearchProvider(PlaceSearchProvider searchProvider) {
|
||||
this.searchProvider = searchProvider;
|
||||
}
|
||||
|
||||
void observe(
|
||||
LifecycleOwner owner,
|
||||
Observer<List<PlaceSearchResult>> onResults,
|
||||
Observer<Place> onSelection,
|
||||
Observer<Event<String>> onError) {
|
||||
searchResults.observe(owner, onResults);
|
||||
selection.observe(owner, onSelection);
|
||||
error.observe(owner, onError);
|
||||
}
|
||||
|
||||
void saveState(Bundle outState) {
|
||||
searchProvider.saveState(outState);
|
||||
}
|
||||
|
||||
void restoreState(Bundle savedInstanceState) {
|
||||
searchProvider.restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
public void query(String query, MapPosition bias) {
|
||||
assertMainThread();
|
||||
|
||||
if (Strings.isNullOrEmpty(query)) {
|
||||
searchResults.postValue(Collections.emptyList());
|
||||
} else {
|
||||
searchProvider.search(query, bias, searchResults::setValue, this::setError);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetch(PlaceSearchResult result) {
|
||||
searchProvider.fetch(result, selection::setValue, this::setError);
|
||||
}
|
||||
|
||||
private void setError(String message) {
|
||||
error.setValue(new Event<>(message));
|
||||
}
|
||||
}
|
@ -1,76 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout 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">
|
||||
android:focusable="true">
|
||||
|
||||
<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"/>
|
||||
<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_history_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"/>
|
||||
<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:visibility="gone"
|
||||
android:src="@drawable/ic_outline_delete_24px"
|
||||
android:tint="@color/icon_tint"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/place_icon"
|
||||
android:layout_toLeftOf="@id/delete"
|
||||
android:layout_toRightOf="@id/place_icon"
|
||||
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"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/keyline_content_inset"
|
||||
android:layout_below="@+id/name"
|
||||
android:layout_toEndOf="@id/place_icon"
|
||||
android:layout_toLeftOf="@id/delete"
|
||||
android:layout_toRightOf="@id/place_icon"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="@dimen/keyline_first"
|
||||
android:paddingLeft="@dimen/keyline_content_inset"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingRight="@dimen/keyline_first"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -0,0 +1,5 @@
|
||||
package org.tasks;
|
||||
|
||||
public interface Callback<T> {
|
||||
void call(T item);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.tasks;
|
||||
|
||||
public class Event<T> {
|
||||
|
||||
private final T value;
|
||||
private boolean handled;
|
||||
|
||||
public Event(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public T getIfUnhandled() {
|
||||
if (handled) {
|
||||
return null;
|
||||
}
|
||||
handled = true;
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?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:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView"/>
|
||||
|
||||
</menu>
|
Loading…
Reference in New Issue