mirror of https://github.com/tasks/tasks
Convert LocationPickerActivity to Kotlin
parent
bbf71bae38
commit
0f27915f82
@ -1,20 +0,0 @@
|
|||||||
package org.tasks.data
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@Deprecated("use coroutines")
|
|
||||||
class LocationDaoBlocking @Inject constructor(private val dao: LocationDao) {
|
|
||||||
fun insert(place: Place): Long = runBlocking {
|
|
||||||
dao.insert(place)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPlaceUsage(): LiveData<List<PlaceUsage>> {
|
|
||||||
return dao.getPlaceUsage()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findPlace(latitude: String, longitude: String): Place? = runBlocking {
|
|
||||||
dao.findPlace(latitude, longitude)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,472 +0,0 @@
|
|||||||
package org.tasks.location;
|
|
||||||
|
|
||||||
import static com.google.common.collect.Lists.transform;
|
|
||||||
import static com.todoroo.andlib.utility.AndroidUtilities.hideKeyboard;
|
|
||||||
import static org.tasks.PermissionUtil.verifyPermissions;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MenuItem.OnActionExpandListener;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnLayoutChangeListener;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import androidx.appcompat.widget.SearchView.OnQueryTextListener;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
import androidx.core.widget.ContentLoadingProgressBar;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
|
||||||
import com.google.android.material.appbar.AppBarLayout.Behavior;
|
|
||||||
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
|
||||||
import com.mapbox.android.core.location.LocationEngineCallback;
|
|
||||||
import com.mapbox.android.core.location.LocationEngineProvider;
|
|
||||||
import com.mapbox.android.core.location.LocationEngineResult;
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.Event;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.activities.PlaceSettingsActivity;
|
|
||||||
import org.tasks.billing.Inventory;
|
|
||||||
import org.tasks.caldav.GeoUtils;
|
|
||||||
import org.tasks.data.LocationDaoBlocking;
|
|
||||||
import org.tasks.data.Place;
|
|
||||||
import org.tasks.data.PlaceUsage;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.injection.InjectingAppCompatActivity;
|
|
||||||
import org.tasks.location.LocationPickerAdapter.OnLocationPicked;
|
|
||||||
import org.tasks.location.LocationSearchAdapter.OnPredictionPicked;
|
|
||||||
import org.tasks.location.MapFragment.MapFragmentCallback;
|
|
||||||
import org.tasks.preferences.ActivityPermissionRequestor;
|
|
||||||
import org.tasks.preferences.PermissionChecker;
|
|
||||||
import org.tasks.preferences.PermissionRequestor;
|
|
||||||
import org.tasks.themes.ColorProvider;
|
|
||||||
import org.tasks.themes.Theme;
|
|
||||||
import org.tasks.themes.ThemeColor;
|
|
||||||
import org.tasks.ui.Toaster;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
public class LocationPickerActivity extends InjectingAppCompatActivity
|
|
||||||
implements OnMenuItemClickListener,
|
|
||||||
MapFragmentCallback,
|
|
||||||
OnLocationPicked,
|
|
||||||
OnQueryTextListener,
|
|
||||||
OnPredictionPicked,
|
|
||||||
OnActionExpandListener {
|
|
||||||
|
|
||||||
public static final String EXTRA_PLACE = "extra_place";
|
|
||||||
private static final String EXTRA_MAP_POSITION = "extra_map_position";
|
|
||||||
private static final String EXTRA_APPBAR_OFFSET = "extra_appbar_offset";
|
|
||||||
private static final int SEARCH_DEBOUNCE_TIMEOUT = 300;
|
|
||||||
|
|
||||||
@BindView(R.id.toolbar)
|
|
||||||
Toolbar toolbar;
|
|
||||||
|
|
||||||
@BindView(R.id.app_bar_layout)
|
|
||||||
AppBarLayout appBarLayout;
|
|
||||||
|
|
||||||
@BindView(R.id.collapsing_toolbar_layout)
|
|
||||||
CollapsingToolbarLayout toolbarLayout;
|
|
||||||
|
|
||||||
@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 recyclerView;
|
|
||||||
|
|
||||||
@Inject Theme theme;
|
|
||||||
@Inject Toaster toaster;
|
|
||||||
@Inject LocationDaoBlocking locationDao;
|
|
||||||
@Inject PlaceSearchProvider searchProvider;
|
|
||||||
@Inject PermissionChecker permissionChecker;
|
|
||||||
@Inject ActivityPermissionRequestor permissionRequestor;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject MapFragment map;
|
|
||||||
@Inject Geocoder geocoder;
|
|
||||||
@Inject Inventory inventory;
|
|
||||||
@Inject ColorProvider colorProvider;
|
|
||||||
|
|
||||||
private CompositeDisposable disposables;
|
|
||||||
@Nullable private MapPosition mapPosition;
|
|
||||||
private LocationPickerAdapter recentsAdapter;
|
|
||||||
private LocationSearchAdapter searchAdapter;
|
|
||||||
private List<PlaceUsage> places = Collections.emptyList();
|
|
||||||
private int offset;
|
|
||||||
private MenuItem search;
|
|
||||||
private final PublishSubject<String> searchSubject = PublishSubject.create();
|
|
||||||
private PlaceSearchViewModel viewModel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
theme.applyTheme(this);
|
|
||||||
setContentView(R.layout.activity_location_picker);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this).get(PlaceSearchViewModel.class);
|
|
||||||
viewModel.setSearchProvider(searchProvider);
|
|
||||||
|
|
||||||
Configuration configuration = getResources().getConfiguration();
|
|
||||||
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
|
||||||
&& configuration.smallestScreenWidthDp < 480) {
|
|
||||||
searchView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
Place currentPlace = getIntent().getParcelableExtra(EXTRA_PLACE);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
mapPosition =
|
|
||||||
currentPlace == null
|
|
||||||
? getIntent().getParcelableExtra(EXTRA_MAP_POSITION)
|
|
||||||
: currentPlace.getMapPosition();
|
|
||||||
} else {
|
|
||||||
mapPosition = savedInstanceState.getParcelable(EXTRA_MAP_POSITION);
|
|
||||||
offset = savedInstanceState.getInt(EXTRA_APPBAR_OFFSET);
|
|
||||||
viewModel.restoreState(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px);
|
|
||||||
toolbar.setNavigationOnClickListener(v -> collapseToolbar());
|
|
||||||
toolbar.inflateMenu(R.menu.menu_location_picker);
|
|
||||||
Menu menu = toolbar.getMenu();
|
|
||||||
search = menu.findItem(R.id.menu_search);
|
|
||||||
search.setOnActionExpandListener(this);
|
|
||||||
((SearchView) search.getActionView()).setOnQueryTextListener(this);
|
|
||||||
toolbar.setOnMenuItemClickListener(this);
|
|
||||||
|
|
||||||
ThemeColor themeColor = theme.getThemeColor();
|
|
||||||
themeColor.applyToStatusBarIcons(this);
|
|
||||||
themeColor.applyToNavigationBar(this);
|
|
||||||
themeColor.setStatusBarColor(toolbarLayout);
|
|
||||||
themeColor.apply(toolbar);
|
|
||||||
|
|
||||||
boolean dark = theme.getThemeBase().isDarkTheme(this);
|
|
||||||
map.init(getSupportFragmentManager(), this, dark);
|
|
||||||
|
|
||||||
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) -> {
|
|
||||||
if (offset == 0 && this.offset != 0) {
|
|
||||||
closeSearch();
|
|
||||||
hideKeyboard(this);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
locationDao
|
|
||||||
.getPlaceUsage()
|
|
||||||
.observe(LocationPickerActivity.this, LocationPickerActivity.this::updatePlaces);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (offset != 0) {
|
|
||||||
appBarLayout.post(() -> expandToolbar(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
findViewById(map.getMarkerId()).setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
searchAdapter = new LocationSearchAdapter(searchProvider.getAttributionRes(dark), this);
|
|
||||||
recentsAdapter = new LocationPickerAdapter(this, inventory, colorProvider, this);
|
|
||||||
recentsAdapter.setHasStableIds(true);
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
recyclerView.setAdapter(
|
|
||||||
search != null && search.isActionViewExpanded() ? searchAdapter : recentsAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMapReady(MapFragment mapFragment) {
|
|
||||||
map = mapFragment;
|
|
||||||
updateMarkers();
|
|
||||||
if (permissionChecker.canAccessLocation()) {
|
|
||||||
mapFragment.showMyLocation();
|
|
||||||
}
|
|
||||||
if (mapPosition != null) {
|
|
||||||
map.movePosition(mapPosition, false);
|
|
||||||
} else if (permissionRequestor.requestFineLocation()) {
|
|
||||||
moveToCurrentLocation(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (closeSearch()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset != 0) {
|
|
||||||
collapseToolbar();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean closeSearch() {
|
|
||||||
return search != null && search.isActionViewExpanded() && search.collapseActionView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaceSelected(org.tasks.data.Place place) {
|
|
||||||
returnPlace(place);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.current_location)
|
|
||||||
void onClick() {
|
|
||||||
if (permissionRequestor.requestFineLocation()) {
|
|
||||||
moveToCurrentLocation(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(
|
|
||||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
if (requestCode == PermissionRequestor.REQUEST_LOCATION) {
|
|
||||||
if (verifyPermissions(grantResults)) {
|
|
||||||
map.showMyLocation();
|
|
||||||
moveToCurrentLocation(true);
|
|
||||||
} else {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.missing_permissions)
|
|
||||||
.setMessage(R.string.location_permission_required_location)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.select_this_location)
|
|
||||||
void selectLocation() {
|
|
||||||
loadingIndicator.setVisibility(View.VISIBLE);
|
|
||||||
MapPosition mapPosition = map.getMapPosition();
|
|
||||||
disposables.add(
|
|
||||||
Single.fromCallable(() -> geocoder.reverseGeocode(mapPosition))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doFinally(() -> loadingIndicator.setVisibility(View.GONE))
|
|
||||||
.subscribe(this::returnPlace, e -> toaster.longToast(e.getMessage())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.search)
|
|
||||||
void searchPlace() {
|
|
||||||
mapPosition = map.getMapPosition();
|
|
||||||
expandToolbar(true);
|
|
||||||
search.expandActionView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
private void moveToCurrentLocation(boolean animate) {
|
|
||||||
LocationEngineProvider.getBestLocationEngine(this)
|
|
||||||
.getLastLocation(
|
|
||||||
new LocationEngineCallback<LocationEngineResult>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(LocationEngineResult result) {
|
|
||||||
Location location = result.getLastLocation();
|
|
||||||
if (location != null) {
|
|
||||||
map.movePosition(
|
|
||||||
new MapPosition(location.getLatitude(), location.getLongitude()), animate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Exception exception) {
|
|
||||||
toaster.longToast(exception.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void returnPlace(@Nullable 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(
|
|
||||||
GeoUtils.INSTANCE.toLikeString(place.getLatitude()),
|
|
||||||
GeoUtils.INSTANCE.toLikeString(place.getLongitude()));
|
|
||||||
if (existing == null) {
|
|
||||||
place.setId(locationDao.insert(place));
|
|
||||||
} else {
|
|
||||||
place = existing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hideKeyboard(this);
|
|
||||||
setResult(RESULT_OK, new Intent().putExtra(EXTRA_PLACE, (Parcelable) place));
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
viewModel.observe(this, searchAdapter::submitList, this::returnPlace, this::handleError);
|
|
||||||
|
|
||||||
disposables =
|
|
||||||
new CompositeDisposable(
|
|
||||||
searchSubject
|
|
||||||
.debounce(SEARCH_DEBOUNCE_TIMEOUT, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(query -> viewModel.query(query, mapPosition)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleError(Event<String> error) {
|
|
||||||
String message = error.getIfUnhandled();
|
|
||||||
if (!isNullOrEmpty(message)) {
|
|
||||||
toaster.longToast(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePlaces(List<PlaceUsage> places) {
|
|
||||||
this.places = places;
|
|
||||||
updateMarkers();
|
|
||||||
recentsAdapter.submitList(places);
|
|
||||||
|
|
||||||
CoordinatorLayout.LayoutParams params =
|
|
||||||
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
|
|
||||||
|
|
||||||
int height = coordinatorLayout.getHeight();
|
|
||||||
if (this.places.isEmpty()) {
|
|
||||||
params.height = height;
|
|
||||||
chooseRecentLocation.setVisibility(View.GONE);
|
|
||||||
collapseToolbar();
|
|
||||||
} else {
|
|
||||||
params.height = (height * 75) / 100;
|
|
||||||
chooseRecentLocation.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMarkers() {
|
|
||||||
if (map != null) {
|
|
||||||
map.setMarkers(transform(places, PlaceUsage::getPlace));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collapseToolbar() {
|
|
||||||
appBarLayout.setExpanded(true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expandToolbar(boolean animate) {
|
|
||||||
appBarLayout.setExpanded(false, animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
viewModel.saveState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 settings(org.tasks.data.Place place) {
|
|
||||||
Intent intent = new Intent(this, PlaceSettingsActivity.class);
|
|
||||||
intent.putExtra(PlaceSettingsActivity.EXTRA_PLACE, (Parcelable) place);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String query) {
|
|
||||||
searchSubject.onNext(query);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void picked(PlaceSearchResult prediction) {
|
|
||||||
viewModel.fetch(prediction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
|
||||||
searchAdapter.submitList(Collections.emptyList());
|
|
||||||
recyclerView.setAdapter(searchAdapter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
|
||||||
recyclerView.setAdapter(recentsAdapter);
|
|
||||||
if (places.isEmpty()) {
|
|
||||||
collapseToolbar();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,411 @@
|
|||||||
|
package org.tasks.location
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
|
||||||
|
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
import com.mapbox.android.core.location.LocationEngineCallback
|
||||||
|
import com.mapbox.android.core.location.LocationEngineProvider
|
||||||
|
import com.mapbox.android.core.location.LocationEngineResult
|
||||||
|
import com.todoroo.andlib.utility.AndroidUtilities
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.Event
|
||||||
|
import org.tasks.PermissionUtil.verifyPermissions
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.activities.PlaceSettingsActivity
|
||||||
|
import org.tasks.billing.Inventory
|
||||||
|
import org.tasks.caldav.GeoUtils.toLikeString
|
||||||
|
import org.tasks.data.LocationDao
|
||||||
|
import org.tasks.data.Place
|
||||||
|
import org.tasks.data.PlaceUsage
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.injection.InjectingAppCompatActivity
|
||||||
|
import org.tasks.location.LocationPickerAdapter.OnLocationPicked
|
||||||
|
import org.tasks.location.LocationSearchAdapter.OnPredictionPicked
|
||||||
|
import org.tasks.location.MapFragment.MapFragmentCallback
|
||||||
|
import org.tasks.preferences.ActivityPermissionRequestor
|
||||||
|
import org.tasks.preferences.PermissionChecker
|
||||||
|
import org.tasks.preferences.PermissionRequestor
|
||||||
|
import org.tasks.themes.ColorProvider
|
||||||
|
import org.tasks.themes.Theme
|
||||||
|
import org.tasks.ui.Toaster
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LocationPickerActivity : InjectingAppCompatActivity(), Toolbar.OnMenuItemClickListener, MapFragmentCallback, OnLocationPicked, SearchView.OnQueryTextListener, OnPredictionPicked, MenuItem.OnActionExpandListener {
|
||||||
|
@BindView(R.id.toolbar)
|
||||||
|
lateinit var toolbar: Toolbar
|
||||||
|
|
||||||
|
@BindView(R.id.app_bar_layout)
|
||||||
|
lateinit var appBarLayout: AppBarLayout
|
||||||
|
|
||||||
|
@BindView(R.id.collapsing_toolbar_layout)
|
||||||
|
lateinit var toolbarLayout: CollapsingToolbarLayout
|
||||||
|
|
||||||
|
@BindView(R.id.coordinator)
|
||||||
|
lateinit var coordinatorLayout: CoordinatorLayout
|
||||||
|
|
||||||
|
@BindView(R.id.search)
|
||||||
|
lateinit var searchView: View
|
||||||
|
|
||||||
|
@BindView(R.id.loading_indicator)
|
||||||
|
lateinit var loadingIndicator: ContentLoadingProgressBar
|
||||||
|
|
||||||
|
@BindView(R.id.choose_recent_location)
|
||||||
|
lateinit var chooseRecentLocation: View
|
||||||
|
|
||||||
|
@BindView(R.id.recent_locations)
|
||||||
|
lateinit var recyclerView: RecyclerView
|
||||||
|
|
||||||
|
@Inject lateinit var theme: Theme
|
||||||
|
@Inject lateinit var toaster: Toaster
|
||||||
|
@Inject lateinit var locationDao: LocationDao
|
||||||
|
@Inject lateinit var searchProvider: PlaceSearchProvider
|
||||||
|
@Inject lateinit var permissionChecker: PermissionChecker
|
||||||
|
@Inject lateinit var permissionRequestor: ActivityPermissionRequestor
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var map: MapFragment
|
||||||
|
@Inject lateinit var geocoder: Geocoder
|
||||||
|
@Inject lateinit var inventory: Inventory
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
|
private var disposables: CompositeDisposable? = null
|
||||||
|
private var mapPosition: MapPosition? = null
|
||||||
|
private var recentsAdapter: LocationPickerAdapter? = null
|
||||||
|
private var searchAdapter: LocationSearchAdapter? = null
|
||||||
|
private var places: List<PlaceUsage> = emptyList()
|
||||||
|
private var offset = 0
|
||||||
|
private lateinit var search: MenuItem
|
||||||
|
private val searchSubject = PublishSubject.create<String>()
|
||||||
|
private val viewModel: PlaceSearchViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
theme.applyTheme(this)
|
||||||
|
setContentView(R.layout.activity_location_picker)
|
||||||
|
ButterKnife.bind(this)
|
||||||
|
val configuration = resources.configuration
|
||||||
|
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
&& configuration.smallestScreenWidthDp < 480) {
|
||||||
|
searchView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
val currentPlace: Place? = intent.getParcelableExtra(EXTRA_PLACE)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
mapPosition = currentPlace?.mapPosition ?: intent.getParcelableExtra(EXTRA_MAP_POSITION)
|
||||||
|
} else {
|
||||||
|
mapPosition = savedInstanceState.getParcelable(EXTRA_MAP_POSITION)
|
||||||
|
offset = savedInstanceState.getInt(EXTRA_APPBAR_OFFSET)
|
||||||
|
viewModel.restoreState(savedInstanceState)
|
||||||
|
}
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px)
|
||||||
|
toolbar.setNavigationOnClickListener { collapseToolbar() }
|
||||||
|
toolbar.inflateMenu(R.menu.menu_location_picker)
|
||||||
|
val menu = toolbar.menu
|
||||||
|
search = menu.findItem(R.id.menu_search)
|
||||||
|
search.setOnActionExpandListener(this)
|
||||||
|
(search.actionView as SearchView).setOnQueryTextListener(this)
|
||||||
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
val themeColor = theme.themeColor
|
||||||
|
themeColor.applyToStatusBarIcons(this)
|
||||||
|
themeColor.applyToNavigationBar(this)
|
||||||
|
themeColor.setStatusBarColor(toolbarLayout)
|
||||||
|
themeColor.apply(toolbar)
|
||||||
|
val dark = theme.themeBase.isDarkTheme(this)
|
||||||
|
map.init(supportFragmentManager, this, dark)
|
||||||
|
val params = appBarLayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
val behavior = AppBarLayout.Behavior()
|
||||||
|
behavior.setDragCallback(
|
||||||
|
object : DragCallback() {
|
||||||
|
override fun canDrag(appBarLayout: AppBarLayout): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.behavior = behavior
|
||||||
|
appBarLayout.addOnOffsetChangedListener(
|
||||||
|
OnOffsetChangedListener { appBarLayout: AppBarLayout, offset: Int ->
|
||||||
|
if (offset == 0 && this.offset != 0) {
|
||||||
|
closeSearch()
|
||||||
|
AndroidUtilities.hideKeyboard(this)
|
||||||
|
}
|
||||||
|
this.offset = offset
|
||||||
|
toolbar.alpha = abs(offset / appBarLayout.totalScrollRange.toFloat())
|
||||||
|
})
|
||||||
|
coordinatorLayout.addOnLayoutChangeListener(
|
||||||
|
object : View.OnLayoutChangeListener {
|
||||||
|
override fun onLayoutChange(
|
||||||
|
v: View, l: Int, t: Int, r: Int, b: Int, ol: Int, ot: Int, or: Int, ob: Int) {
|
||||||
|
coordinatorLayout.removeOnLayoutChangeListener(this)
|
||||||
|
locationDao
|
||||||
|
.getPlaceUsage()
|
||||||
|
.observe(this@LocationPickerActivity, Observer { places: List<PlaceUsage> -> updatePlaces(places) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (offset != 0) {
|
||||||
|
appBarLayout.post { expandToolbar(false) }
|
||||||
|
}
|
||||||
|
findViewById<View>(map.markerId).visibility = View.VISIBLE
|
||||||
|
searchAdapter = LocationSearchAdapter(searchProvider.getAttributionRes(dark), this)
|
||||||
|
recentsAdapter = LocationPickerAdapter(this, inventory, colorProvider, this)
|
||||||
|
recentsAdapter!!.setHasStableIds(true)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
recyclerView.adapter = if (search.isActionViewExpanded) searchAdapter else recentsAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMapReady(mapFragment: MapFragment) {
|
||||||
|
map = mapFragment
|
||||||
|
updateMarkers()
|
||||||
|
if (permissionChecker.canAccessLocation()) {
|
||||||
|
mapFragment.showMyLocation()
|
||||||
|
}
|
||||||
|
if (mapPosition != null) {
|
||||||
|
map.movePosition(mapPosition, false)
|
||||||
|
} else if (permissionRequestor.requestFineLocation()) {
|
||||||
|
moveToCurrentLocation(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (closeSearch()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (offset != 0) {
|
||||||
|
collapseToolbar()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun closeSearch(): Boolean {
|
||||||
|
return search.isActionViewExpanded && search.collapseActionView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaceSelected(place: Place) {
|
||||||
|
returnPlace(place)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.current_location)
|
||||||
|
fun onClick() {
|
||||||
|
if (permissionRequestor.requestFineLocation()) {
|
||||||
|
moveToCurrentLocation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
if (requestCode == PermissionRequestor.REQUEST_LOCATION) {
|
||||||
|
if (verifyPermissions(grantResults)) {
|
||||||
|
map.showMyLocation()
|
||||||
|
moveToCurrentLocation(true)
|
||||||
|
} else {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.missing_permissions)
|
||||||
|
.setMessage(R.string.location_permission_required_location)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.select_this_location)
|
||||||
|
fun selectLocation() {
|
||||||
|
loadingIndicator.visibility = View.VISIBLE
|
||||||
|
val mapPosition = map.mapPosition
|
||||||
|
disposables!!.add(
|
||||||
|
Single.fromCallable { geocoder.reverseGeocode(mapPosition) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doFinally { loadingIndicator.visibility = View.GONE }
|
||||||
|
.subscribe({ place: Place? -> returnPlace(place) }) { e: Throwable -> toaster.longToast(e.message) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.search)
|
||||||
|
fun searchPlace() {
|
||||||
|
mapPosition = map.mapPosition
|
||||||
|
expandToolbar(true)
|
||||||
|
search.expandActionView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun moveToCurrentLocation(animate: Boolean) {
|
||||||
|
LocationEngineProvider.getBestLocationEngine(this)
|
||||||
|
.getLastLocation(
|
||||||
|
object : LocationEngineCallback<LocationEngineResult> {
|
||||||
|
override fun onSuccess(result: LocationEngineResult) {
|
||||||
|
val location = result.lastLocation
|
||||||
|
if (location != null) {
|
||||||
|
map.movePosition(
|
||||||
|
MapPosition(location.latitude, location.longitude), animate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(exception: Exception) {
|
||||||
|
toaster.longToast(exception.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnPlace(place: Place?) {
|
||||||
|
if (place == null) {
|
||||||
|
Timber.e("Place is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
AndroidUtilities.hideKeyboard(this)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var place = place
|
||||||
|
if (place.id <= 0) {
|
||||||
|
val existing = locationDao.findPlace(
|
||||||
|
place.latitude.toLikeString(),
|
||||||
|
place.longitude.toLikeString())
|
||||||
|
if (existing == null) {
|
||||||
|
place.id = locationDao.insert(place)
|
||||||
|
} else {
|
||||||
|
place = existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_PLACE, place as Parcelable?))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
viewModel.observe(this, Observer { list: List<PlaceSearchResult?>? -> searchAdapter!!.submitList(list) }, Observer { place: Place? -> returnPlace(place) }, Observer { error: Event<String> -> handleError(error) })
|
||||||
|
disposables = CompositeDisposable(
|
||||||
|
searchSubject
|
||||||
|
.debounce(SEARCH_DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { query: String? -> viewModel.query(query, mapPosition) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleError(error: Event<String>) {
|
||||||
|
val message = error.ifUnhandled
|
||||||
|
if (!isNullOrEmpty(message)) {
|
||||||
|
toaster.longToast(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePlaces(places: List<PlaceUsage>) {
|
||||||
|
this.places = places
|
||||||
|
updateMarkers()
|
||||||
|
recentsAdapter!!.submitList(places)
|
||||||
|
val params = appBarLayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
val height = coordinatorLayout.height
|
||||||
|
if (this.places.isEmpty()) {
|
||||||
|
params.height = height
|
||||||
|
chooseRecentLocation.visibility = View.GONE
|
||||||
|
collapseToolbar()
|
||||||
|
} else {
|
||||||
|
params.height = height * 75 / 100
|
||||||
|
chooseRecentLocation.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateMarkers() {
|
||||||
|
map.setMarkers(places.map(PlaceUsage::place))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collapseToolbar() {
|
||||||
|
appBarLayout.setExpanded(true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expandToolbar(animate: Boolean) {
|
||||||
|
appBarLayout.setExpanded(false, animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
disposables!!.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelable(EXTRA_MAP_POSITION, map.mapPosition)
|
||||||
|
outState.putInt(EXTRA_APPBAR_OFFSET, offset)
|
||||||
|
viewModel.saveState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
|
return if (item.itemId == R.id.menu_search) {
|
||||||
|
searchPlace()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun picked(place: Place) {
|
||||||
|
returnPlace(place)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun settings(place: Place) {
|
||||||
|
val intent = Intent(this, PlaceSettingsActivity::class.java)
|
||||||
|
intent.putExtra(PlaceSettingsActivity.EXTRA_PLACE, place as Parcelable)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(query: String): Boolean {
|
||||||
|
searchSubject.onNext(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun picked(prediction: PlaceSearchResult) {
|
||||||
|
viewModel.fetch(prediction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
|
searchAdapter!!.submitList(emptyList())
|
||||||
|
recyclerView.adapter = searchAdapter
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
|
recyclerView.adapter = recentsAdapter
|
||||||
|
if (places.isEmpty()) {
|
||||||
|
collapseToolbar()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_PLACE = "extra_place"
|
||||||
|
private const val EXTRA_MAP_POSITION = "extra_map_position"
|
||||||
|
private const val EXTRA_APPBAR_OFFSET = "extra_appbar_offset"
|
||||||
|
private const val SEARCH_DEBOUNCE_TIMEOUT = 300
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
package org.tasks.location;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import org.tasks.Event;
|
|
||||||
import org.tasks.data.Place;
|
|
||||||
|
|
||||||
@SuppressWarnings({"WeakerAccess", "RedundantSuppression"})
|
|
||||||
public class PlaceSearchViewModel extends ViewModel {
|
|
||||||
private PlaceSearchProvider searchProvider;
|
|
||||||
|
|
||||||
private final MutableLiveData<List<PlaceSearchResult>> searchResults = new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<Event<String>> error = new MutableLiveData<>();
|
|
||||||
private final 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, @Nullable MapPosition bias) {
|
|
||||||
assertMainThread();
|
|
||||||
|
|
||||||
if (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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.tasks.location
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.todoroo.andlib.utility.AndroidUtilities
|
||||||
|
import org.tasks.Event
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.data.Place
|
||||||
|
|
||||||
|
class PlaceSearchViewModel @ViewModelInject constructor(
|
||||||
|
private val searchProvider: PlaceSearchProvider
|
||||||
|
): ViewModel() {
|
||||||
|
private val searchResults = MutableLiveData<List<PlaceSearchResult>>()
|
||||||
|
private val error = MutableLiveData<Event<String>>()
|
||||||
|
private val selection = MutableLiveData<Place>()
|
||||||
|
|
||||||
|
fun observe(
|
||||||
|
owner: LifecycleOwner?,
|
||||||
|
onResults: Observer<List<PlaceSearchResult>>?,
|
||||||
|
onSelection: Observer<Place>?,
|
||||||
|
onError: Observer<Event<String>>?) {
|
||||||
|
searchResults.observe(owner!!, onResults!!)
|
||||||
|
selection.observe(owner, onSelection!!)
|
||||||
|
error.observe(owner, onError!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveState(outState: Bundle?) {
|
||||||
|
searchProvider.saveState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreState(savedInstanceState: Bundle?) {
|
||||||
|
searchProvider.restoreState(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun query(query: String?, bias: MapPosition?) {
|
||||||
|
AndroidUtilities.assertMainThread()
|
||||||
|
if (isNullOrEmpty(query)) {
|
||||||
|
searchResults.postValue(emptyList())
|
||||||
|
} else {
|
||||||
|
searchProvider.search(query, bias, { value: List<PlaceSearchResult> -> searchResults.setValue(value) }) { message: String -> setError(message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetch(result: PlaceSearchResult?) {
|
||||||
|
searchProvider.fetch(result, { value: Place -> selection.setValue(value) }) { message: String -> setError(message) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setError(message: String) {
|
||||||
|
error.value = Event(message)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue