Add LocationFilter

pull/996/head
Alex Baker 5 years ago
parent d6aa21c4f9
commit 0b95b11b68

@ -89,6 +89,7 @@ import org.tasks.data.TagDataDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.SortDialog;
import org.tasks.etesync.EteSyncCalendarSettingsActivity;
import org.tasks.filters.LocationFilter;
import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent;
import org.tasks.injection.InjectingFragment;
@ -331,6 +332,9 @@ public final class TaskListFragment extends InjectingFragment
private void setupMenu() {
Menu menu = toolbar.getMenu();
menu.clear();
if (filter.hasBeginningMenu()) {
toolbar.inflateMenu(filter.getBeginningMenu());
}
toolbar.inflateMenu(R.menu.menu_task_list_fragment);
if (filter.hasMenu()) {
toolbar.inflateMenu(filter.getMenu());
@ -459,6 +463,9 @@ public final class TaskListFragment extends InjectingFragment
taskDao.setCollapsed(taskListViewModel.getValue(), true);
localBroadcastManager.broadcastRefresh();
return true;
case R.id.menu_open_map:
((LocationFilter) filter).openMap(context);
return true;
default:
return onOptionsItemSelected(item);
}

@ -25,6 +25,7 @@ import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.api.TagFilter;
import org.tasks.R;
import org.tasks.billing.Inventory;
import org.tasks.filters.LocationFilter;
import org.tasks.locale.Locale;
import org.tasks.preferences.SyncPreferences;
import org.tasks.themes.ColorProvider;
@ -158,6 +159,8 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
return R.drawable.ic_outline_cloud_24px;
} else if (filter instanceof CustomFilter) {
return R.drawable.ic_outline_filter_list_24px;
} else if (filter instanceof LocationFilter) {
return R.drawable.ic_outline_place_24px;
} else {
return filter.icon;
}

@ -55,7 +55,7 @@ public class CaldavFilter extends Filter {
.where(getCriterion(caldavCalendar));
}
public static Criterion getCriterion(CaldavCalendar caldavCalendar) {
private static Criterion getCriterion(CaldavCalendar caldavCalendar) {
return Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
CaldavTask.DELETED.eq(0),

@ -197,6 +197,14 @@ public class Filter extends FilterListItem {
return true;
}
public boolean hasBeginningMenu() {
return getBeginningMenu() > 0;
}
public @MenuRes int getBeginningMenu() {
return 0;
}
public boolean hasMenu() {
return getMenu() != 0;
}

@ -22,8 +22,11 @@ import javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.data.Geofence;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.LocationDao;
import org.tasks.data.Place;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.data.TagData;
@ -40,6 +43,7 @@ public class TaskCreator {
private final GoogleTaskDao googleTaskDao;
private final DefaultFilterProvider defaultFilterProvider;
private final CaldavDao caldavDao;
private final LocationDao locationDao;
private final TagDataDao tagDataDao;
private final TaskDao taskDao;
@ -52,7 +56,8 @@ public class TaskCreator {
TagDao tagDao,
GoogleTaskDao googleTaskDao,
DefaultFilterProvider defaultFilterProvider,
CaldavDao caldavDao) {
CaldavDao caldavDao,
LocationDao locationDao) {
this.gcalHelper = gcalHelper;
this.preferences = preferences;
this.tagDataDao = tagDataDao;
@ -61,6 +66,7 @@ public class TaskCreator {
this.googleTaskDao = googleTaskDao;
this.defaultFilterProvider = defaultFilterProvider;
this.caldavDao = caldavDao;
this.locationDao = locationDao;
}
private static void setDefaultReminders(Preferences preferences, Task task) {
@ -105,6 +111,13 @@ public class TaskCreator {
}
}
if (task.hasTransitory(Place.KEY)) {
Place place = locationDao.getPlace(task.getTransitory(Place.KEY));
if (place != null) {
locationDao.insert(new Geofence(place.getUid(), preferences));
}
}
taskDao.save(task, null);
return task;
}
@ -152,6 +165,7 @@ public class TaskCreator {
break;
case GoogleTask.KEY:
case CaldavTask.KEY:
case Place.KEY:
task.putTransitory(key, value);
break;
default:

@ -1,19 +1,12 @@
package org.tasks.data;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.room.Embedded;
import androidx.room.Ignore;
import java.io.Serializable;
import java.util.List;
import org.tasks.R;
public class Location implements Serializable, Parcelable {
@ -140,18 +133,7 @@ public class Location implements Serializable, Parcelable {
}
public void open(@Nullable Context context) {
if (context == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(place.getGeoUri()));
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
if (resolveInfos.isEmpty()) {
Toast.makeText(context, R.string.no_application_found_link, Toast.LENGTH_SHORT).show();
} else {
context.startActivity(intent);
}
place.open(context);
}
@Override
@ -193,4 +175,8 @@ public class Location implements Serializable, Parcelable {
dest.writeParcelable(geofence, flags);
dest.writeParcelable(place, flags);
}
public Place getPlace() {
return place;
}
}

@ -9,6 +9,7 @@ import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Single;
import java.util.List;
import org.tasks.filters.LocationFilters;
@Dao
public interface LocationDao {
@ -62,10 +63,22 @@ public interface LocationDao {
@Query("SELECT * FROM places WHERE place_id = :id")
Place getPlace(long id);
@Query("SELECT * FROM places WHERE uid = :uid")
Place getPlace(String uid);
@Query(
"SELECT places.*, IFNULL(COUNT(geofence_id),0) AS count FROM places LEFT OUTER JOIN geofences ON geofences.place = places.uid GROUP BY uid ORDER BY COUNT(geofence_id) DESC")
LiveData<List<PlaceUsage>> getPlaceUsage();
@Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude")
Place findPlace(String latitude, String longitude);
@Query("SELECT places.*, COUNT(tasks._id) AS count FROM places "
+ " INNER JOIN geofences ON geofences.place = places.uid "
+ " INNER JOIN tasks ON geofences.task = tasks._id"
+ " WHERE tasks.completed = 0 AND tasks.deleted = 0"
+ " AND tasks.hideUntil < :now"
+ " GROUP BY places.uid"
+ " ORDER BY count DESC")
List<LocationFilters> getPlaceFilters(long now);
}

@ -3,10 +3,15 @@ package org.tasks.data;
import static com.mapbox.api.geocoding.v5.GeocodingCriteria.TYPE_ADDRESS;
import static org.tasks.data.Place.TABLE_NAME;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
@ -23,11 +28,13 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.fortuna.ical4j.model.property.Geo;
import org.tasks.R;
import org.tasks.location.MapPosition;
@Entity(tableName = TABLE_NAME, indices = @Index(name = "place_uid", value = "uid", unique = true))
public class Place implements Serializable, Parcelable {
public static final String KEY = "place";
public static final String TABLE_NAME = "places";
public static final Table TABLE = new Table(TABLE_NAME);
@ -233,7 +240,22 @@ public class Place implements Serializable, Parcelable {
return Strings.isNullOrEmpty(address) ? null : address.replace(String.format("%s, ", name), "");
}
String getGeoUri() {
public void open(Context context) {
if (context == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(getGeoUri()));
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
if (resolveInfos.isEmpty()) {
Toast.makeText(context, R.string.no_application_found_link, Toast.LENGTH_SHORT).show();
} else {
context.startActivity(intent);
}
}
private String getGeoUri() {
return String.format(
"geo:%s,%s?q=%s",
latitude, longitude, Uri.encode(getDisplayName()));

@ -2,6 +2,7 @@ package org.tasks.filters;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread;
@ -14,6 +15,7 @@ import static org.tasks.ui.NavigationDrawerFragment.REQUEST_SETTINGS;
import android.content.Context;
import android.content.Intent;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.core.BuiltInFilterExposer;
@ -40,6 +42,7 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.data.LocationDao;
import org.tasks.etesync.EteSyncCalendarSettingsActivity;
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType;
import org.tasks.injection.ForApplication;
@ -59,6 +62,7 @@ public class FilterProvider {
private final GoogleTaskListDao googleTaskListDao;
private final CaldavDao caldavDao;
private final Preferences preferences;
private final LocationDao locationDao;
@Inject
public FilterProvider(
@ -70,7 +74,8 @@ public class FilterProvider {
TagFilterExposer tagFilterExposer,
GoogleTaskListDao googleTaskListDao,
CaldavDao caldavDao,
Preferences preferences) {
Preferences preferences,
LocationDao locationDao) {
this.context = context;
this.inventory = inventory;
this.builtInFilterExposer = builtInFilterExposer;
@ -80,6 +85,7 @@ public class FilterProvider {
this.googleTaskListDao = googleTaskListDao;
this.caldavDao = caldavDao;
this.preferences = preferences;
this.locationDao = locationDao;
}
public List<FilterListItem> getRemoteListPickerItems() {
@ -97,7 +103,7 @@ public class FilterProvider {
getSubmenu(
account.getAccount(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
filters.getValue(),
true,
account.isCollapsed(),
SubheaderType.GOOGLE_TASKS,
@ -110,7 +116,7 @@ public class FilterProvider {
getSubmenu(
account.getName(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
filters.getValue(),
true,
account.isCollapsed(),
SubheaderType.CALDAV,
@ -141,6 +147,19 @@ public class FilterProvider {
items.addAll(
getSubmenu(R.string.tags, R.string.p_collapse_tags, tagFilterExposer::getFilters));
if (navigationDrawer) {
boolean collapsed = preferences.getBoolean(R.string.p_collapse_locations, false);
items.addAll(
getSubmenu(
context.getString(R.string.locations),
false,
collapsed ? Collections.emptyList() : getLocationFilters(),
true,
collapsed,
SubheaderType.PREFERENCE,
R.string.p_collapse_locations));
}
if (navigationDrawer && !preferences.getBoolean(R.string.p_collapse_tags, false)) {
items.add(
new NavigationDrawerAction(
@ -156,7 +175,7 @@ public class FilterProvider {
getSubmenu(
account.getAccount(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
filters.getValue(),
!navigationDrawer,
account.isCollapsed(),
SubheaderType.GOOGLE_TASKS,
@ -179,7 +198,7 @@ public class FilterProvider {
getSubmenu(
account.getName(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
filters.getValue(),
!navigationDrawer,
account.isCollapsed(),
SubheaderType.CALDAV,
@ -236,6 +255,12 @@ public class FilterProvider {
return items;
}
private List<Filter> getLocationFilters() {
List<LocationFilters> filters = locationDao.getPlaceFilters(now());
return newArrayList(
Iterables.transform(filter(filters, f -> f.count > 0), LocationFilters::toLocationFilter));
}
private List<Filter> getFilters() {
ArrayList<Filter> filters = new ArrayList<>();
filters.addAll(builtInFilterExposer.getFilters());

@ -0,0 +1,96 @@
package org.tasks.filters;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.HashMap;
import java.util.Map;
import org.tasks.R;
import org.tasks.data.Geofence;
import org.tasks.data.Place;
public class LocationFilter extends Filter {
public static final Parcelable.Creator<LocationFilter> CREATOR =
new Parcelable.Creator<LocationFilter>() {
@Override
public LocationFilter createFromParcel(Parcel source) {
return new LocationFilter(source);
}
/** {@inheritDoc} */
@Override
public LocationFilter[] newArray(int size) {
return new LocationFilter[size];
}
};
private Place place;
private LocationFilter(Parcel source) {
super();
readFromParcel(source);
place = source.readParcelable(getClass().getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(place, 0);
}
public LocationFilter(Place place) {
super(place.getDisplayName(), queryTemplate(place), getValuesForNewTask(place));
this.place = place;
}
public Place getPlace() {
return place;
}
private static QueryTemplate queryTemplate(Place place) {
return new QueryTemplate()
.join(Join.inner(Geofence.TABLE, Task.ID.eq(Geofence.TASK)))
.join(Join.inner(Place.TABLE, Place.UID.eq(Geofence.PLACE)))
.where(getCriterion(place));
}
private static Criterion getCriterion(Place place) {
return Criterion.and(TaskDao.TaskCriteria.activeAndVisible(), Place.UID.eq(place.getUid()));
}
private static Map<String, Object> getValuesForNewTask(Place place) {
Map<String, Object> result = new HashMap<>();
result.put(Place.KEY, place.getUid());
return result;
}
@Override
public int getBeginningMenu() {
return R.menu.menu_location_list_settings;
}
@Override
public boolean areItemsTheSame(@NonNull FilterListItem other) {
return other instanceof LocationFilter
&& place.getUid().equals(((LocationFilter) other).getPlace().getUid());
}
@Override
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return place.equals(((LocationFilter) other).getPlace()) && count == other.count;
}
public void openMap(Context context) {
place.open(context);
}
}

@ -0,0 +1,44 @@
package org.tasks.filters;
import androidx.room.Embedded;
import org.tasks.data.Place;
public class LocationFilters {
@Embedded public Place place;
public int count;
public LocationFilter toLocationFilter() {
LocationFilter filter = new LocationFilter(place);
filter.count = count;
return filter;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LocationFilters)) {
return false;
}
LocationFilters that = (LocationFilters) 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 "LocationFilters{" + "place=" + place + ", count=" + count + '}';
}
}

@ -25,7 +25,6 @@ import com.todoroo.astrid.service.TaskCompleter;
import com.todoroo.astrid.ui.CheckableImageView;
import java.util.List;
import org.tasks.R;
import org.tasks.data.Location;
import org.tasks.data.TaskContainer;
import org.tasks.dialogs.Linkify;
import org.tasks.preferences.Preferences;
@ -280,8 +279,6 @@ public class ViewHolder extends RecyclerView.ViewHolder {
Object tag = v.getTag();
if (tag instanceof Filter) {
callback.onClick((Filter) tag);
} else if (tag instanceof Location) {
((Location) tag).open(context);
} else if (tag instanceof TaskContainer) {
TaskContainer task = (TaskContainer) tag;
callback.toggleSubtasks(task, !task.isCollapsed());

@ -162,7 +162,8 @@ object CustomIcons {
1125 to R.drawable.ic_outline_info_24px,
1126 to R.drawable.ic_outline_palette_24px,
1127 to R.drawable.ic_outline_sd_storage_24px,
1128 to R.drawable.ic_baseline_lens_24px
1128 to R.drawable.ic_baseline_lens_24px,
1129 to R.drawable.ic_map_24px
)
@kotlin.jvm.JvmStatic

@ -26,8 +26,10 @@ import java.util.Set;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.billing.Inventory;
import org.tasks.data.Location;
import org.tasks.data.TagData;
import org.tasks.data.TaskContainer;
import org.tasks.filters.LocationFilter;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ColorProvider;
@ -111,9 +113,11 @@ public class ChipProvider {
if (!hideSubtaskChip && task.hasChildren()) {
chips.add(newSubtaskChip(task, !showText));
}
if (task.hasLocation()) {
Chip chip = newChip(task.getLocation());
apply(chip, R.drawable.ic_outline_place_24px, task.getLocation().getDisplayName(), 0, showText, showIcon);
if (task.hasLocation() && !(filter instanceof LocationFilter)) {
Location location = task.getLocation();
Chip chip = newChip(new LocationFilter(location.getPlace()));
apply(
chip, R.drawable.ic_outline_place_24px, location.getDisplayName(), 0, showText, showIcon);
chips.add(chip);
}
if (!isSubtask) {

@ -83,12 +83,19 @@ public class LocationControlSet extends TaskEditControlFragment {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (savedInstanceState == null) {
if (!task.isNew()) {
if (task.isNew()) {
if (task.hasTransitory(Place.KEY)) {
Place place = locationDao.getPlace(task.getTransitory(Place.KEY));
if (place != null) {
original = new Location(new Geofence(place.getUid(), preferences), place);
}
}
} else {
original = locationDao.getGeofences(task.getId());
}
if (original != null) {
location = new Location(original.geofence, original.place);
}
}
} else {
original = savedInstanceState.getParcelable(EXTRA_ORIGINAL);
location = savedInstanceState.getParcelable(EXTRA_LOCATION);

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/>
</vector>

@ -0,0 +1,9 @@
<?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_open_map"
android:title="@string/open_map"
android:icon="@drawable/ic_map_24px"
app:showAsAction="ifRoom" />
</menu>

@ -7,13 +7,13 @@
android:id="@+id/menu_voice_add"
android:icon="@drawable/ic_outline_mic_none_24px"
android:title="@string/EPr_voiceInputEnabled_title"
app:showAsAction="always"/>
app:showAsAction="ifRoom"/>
<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"/>
app:showAsAction="ifRoom|collapseActionView"/>
<item
android:id="@+id/menu_sort"
android:icon="@drawable/ic_outline_sort_24px"

@ -293,4 +293,5 @@
<string name="p_desaturate_colors">desaturate_colors</string>
<string name="p_collapse_filters">collapse_filters</string>
<string name="p_collapse_tags">collapse_tags</string>
<string name="p_collapse_locations">collapse_locations</string>
</resources>

@ -566,4 +566,5 @@ File %1$s contained %2$s.\n\n
<string name="desaturate_colors">Desaturate colors</string>
<string name="desaturate_colors_summary_on">Colors will be desaturated in dark themes</string>
<string name="desaturate_colors_summary_off">Colors will not be desaturated in dark themes</string>
<string name="locations">Locations</string>
</resources>

Loading…
Cancel
Save