Simplify LocationControlSet

pull/795/head
Alex Baker 6 years ago
parent be0432c86c
commit c7401762b2

@ -18,26 +18,36 @@ import com.google.android.gms.location.LocationServices;
import java.util.List;
import javax.inject.Inject;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
public class GeofenceApi {
private final Context context;
private final PermissionChecker permissionChecker;
private final LocationDao locationDao;
@Inject
public GeofenceApi(
@ForApplication Context context,
Preferences preferences,
PermissionChecker permissionChecker) {
PermissionChecker permissionChecker,
LocationDao locationDao) {
this.context = context;
this.permissionChecker = permissionChecker;
this.locationDao = locationDao;
}
public void registerAll() {
register(locationDao.getActiveGeofences());
}
public void register(long taskId) {
register(locationDao.getActiveGeofences(taskId));
}
@SuppressLint("MissingPermission")
void register(final List<Location> locations) {
public void register(final List<Location> locations) {
if (!permissionChecker.canAccessLocation()) {
return;
}
@ -55,8 +65,14 @@ public class GeofenceApi {
}
}
public void cancel(long taskId) {
cancel(locationDao.getGeofences(taskId));
}
public void cancel(final Location location) {
cancel(singletonList(location));
if (location != null) {
cancel(singletonList(location));
}
}
public void cancel(final List<Location> locations) {

@ -17,7 +17,7 @@ import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingApplication;
import org.tasks.injection.InjectingJobIntentService;
import org.tasks.jobs.WorkManager;
import org.tasks.location.GeofenceService;
import org.tasks.location.GeofenceApi;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.scheduling.CalendarNotificationIntentService;
@ -36,7 +36,7 @@ public class Tasks extends InjectingApplication {
@Inject ThemeCache themeCache;
@Inject WorkManager workManager;
@Inject RefreshScheduler refreshScheduler;
@Inject GeofenceService geofenceService;
@Inject GeofenceApi geofenceApi;
@Inject LocalBroadcastManager localBroadcastManager;
@Override
@ -84,7 +84,7 @@ public class Tasks extends InjectingApplication {
workManager.updateBackgroundSync();
workManager.scheduleMidnightRefresh();
workManager.scheduleBackup();
geofenceService.setupGeofences();
geofenceApi.registerAll();
FileHelper.delete(context, preferences.getCacheDirectory());
}

@ -18,8 +18,8 @@ public interface LocationDao {
Location getGeofence(Long id);
@Query(
"SELECT * FROM geofences INNER JOIN places ON geofences.place = places.uid WHERE task = :taskId ORDER BY name ASC")
List<Location> getGeofences(long taskId);
"SELECT * FROM geofences INNER JOIN places ON geofences.place = places.uid WHERE task = :taskId ORDER BY name ASC LIMIT 1")
Location getGeofences(long taskId);
@Query(
"SELECT geofences.*, places.* FROM geofences INNER JOIN places ON geofences.place = places.uid INNER JOIN tasks ON tasks._id = geofences.task WHERE tasks._id = :taskId AND tasks.deleted = 0 AND tasks.completed = 0")
@ -39,7 +39,7 @@ public interface LocationDao {
void delete(Place place);
@Insert
void insert(Geofence location);
long insert(Geofence location);
@Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(Place place);

@ -142,7 +142,6 @@ public class LocationDialog extends InjectingDialogFragment {
private void sendResult(Location result) {
Intent data = new Intent();
data.putExtra(EXTRA_ORIGINAL, (Parcelable) getOriginal());
data.putExtra(EXTRA_LOCATION, (Parcelable) result);
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, data);
dismiss();

@ -24,7 +24,7 @@ import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingWorker;
import org.tasks.injection.JobComponent;
import org.tasks.location.GeofenceService;
import org.tasks.location.GeofenceApi;
import org.tasks.notifications.NotificationManager;
import org.tasks.scheduling.RefreshScheduler;
import org.tasks.sync.SyncAdapters;
@ -41,7 +41,7 @@ public class AfterSaveWork extends InjectingWorker {
@Inject RepeatTaskHelper repeatTaskHelper;
@Inject @ForApplication Context context;
@Inject NotificationManager notificationManager;
@Inject GeofenceService geofenceService;
@Inject GeofenceApi geofenceApi;
@Inject TimerPlugin timerPlugin;
@Inject ReminderService reminderService;
@Inject RefreshScheduler refreshScheduler;
@ -94,9 +94,9 @@ public class AfterSaveWork extends InjectingWorker {
if (justCompleted || justDeleted) {
notificationManager.cancel(taskId);
geofenceService.cancelGeofences(taskId);
geofenceApi.cancel(taskId);
} else if (completionDateModified || deletionDateModified) {
geofenceService.setupGeofences(taskId);
geofenceApi.register(taskId);
}
if (justCompleted) {

@ -14,7 +14,7 @@ import org.tasks.data.UserActivityDao;
import org.tasks.files.FileHelper;
import org.tasks.injection.InjectingWorker;
import org.tasks.injection.JobComponent;
import org.tasks.location.GeofenceService;
import org.tasks.location.GeofenceApi;
import org.tasks.notifications.NotificationManager;
import timber.log.Timber;
@ -23,7 +23,7 @@ public class CleanupWork extends InjectingWorker {
static final String EXTRA_TASK_IDS = "extra_task_ids";
private final Context context;
@Inject NotificationManager notificationManager;
@Inject GeofenceService geofenceService;
@Inject GeofenceApi geofenceApi;
@Inject TimerPlugin timerPlugin;
@Inject ReminderService reminderService;
@Inject AlarmService alarmService;
@ -47,7 +47,7 @@ public class CleanupWork extends InjectingWorker {
alarmService.cancelAlarms(task);
reminderService.cancelReminder(task);
notificationManager.cancel(task);
geofenceService.cancelGeofences(task);
geofenceApi.cancel(task);
for (TaskAttachment attachment : taskAttachmentDao.getAttachments(task)) {
FileHelper.delete(context, attachment.parseUri());
taskAttachmentDao.delete(attachment);

@ -1,102 +0,0 @@
package org.tasks.location;
import static com.google.common.collect.Lists.newArrayList;
import static com.todoroo.astrid.data.Task.NO_ID;
import static java.util.Collections.emptyList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.tasks.data.Geofence;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Place;
public class GeofenceService {
private final GeofenceApi geofenceApi;
private final LocationDao locationDao;
@Inject
public GeofenceService(GeofenceApi geofenceApi, LocationDao locationDao) {
this.geofenceApi = geofenceApi;
this.locationDao = locationDao;
}
public List<Location> getGeofences(long taskId) {
return taskId == NO_ID ? emptyList() : locationDao.getGeofences(taskId);
}
public void setupGeofences() {
geofenceApi.register(locationDao.getActiveGeofences());
}
public void setupGeofences(long taskId) {
geofenceApi.register(getGeofencesForTask(taskId));
}
public void cancelGeofences(long taskId) {
for (Location location : getGeofences(taskId)) {
geofenceApi.cancel(location);
}
}
public boolean synchronizeGeofences(final long taskId, Set<Location> locations) {
boolean changed = synchronizeMetadata(taskId, newArrayList(locations), geofenceApi::cancel);
if (changed) {
setupGeofences(taskId);
}
return changed;
}
private boolean synchronizeMetadata(
long taskId, List<Location> locations, final SynchronizeGeofenceCallback callback) {
boolean dirty = false;
for (Location metadatum : locations) {
metadatum.setTask(taskId);
metadatum.setId(0L);
}
for (Location item : locationDao.getGeofences(taskId)) {
long id = item.getId();
// clear item id when matching with incoming values
item.setId(0L);
if (locations.contains(item)) {
locations.remove(item);
} else {
// not matched. cut it
item.setId(id);
if (callback != null) {
callback.beforeDelete(item);
}
locationDao.delete(item.geofence);
dirty = true;
}
}
// everything that remains shall be written
for (Location location : locations) {
Place place = location.place;
Geofence geofence = location.geofence;
geofence.setTask(taskId);
geofence.setPlace(place.getUid());
locationDao.insert(geofence);
dirty = true;
}
return dirty;
}
private List<Location> getGeofencesForTask(long taskId) {
return locationDao.getActiveGeofences(taskId);
}
interface SynchronizeGeofenceCallback {
void beforeDelete(Location location);
}
}

@ -1,14 +1,11 @@
package org.tasks.ui;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static org.tasks.dialogs.LocationDialog.newLocationDialog;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@ -16,7 +13,6 @@ import android.text.style.ClickableSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -25,43 +21,46 @@ import butterknife.OnClick;
import com.google.common.base.Strings;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Collections;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.Geofence;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Place;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.LocationDialog;
import org.tasks.injection.FragmentComponent;
import org.tasks.location.GeofenceService;
import org.tasks.location.GeofenceApi;
import org.tasks.location.PlacePicker;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class LocationControlSet extends TaskEditControlFragment {
public static final int TAG = R.string.TEA_ctrl_locations_pref;
private static final int REQUEST_LOCATION_REMINDER = 12153;
private static final int REQUEST_LOCATION_DETAILS = 12154;
private static final String FRAG_TAG_LOCATION_DIALOG = "location_dialog";
private static final String EXTRA_ORIGINAL = "extra_original_location";
private static final String EXTRA_LOCATION = "extra_new_location";
private static final String EXTRA_GEOFENCES = "extra_geofences";
private final Set<Location> locations = new LinkedHashSet<>();
@Inject GeofenceService geofenceService;
@Inject Preferences preferences;
@Inject DialogBuilder dialogBuilder;
@Inject GeofenceApi geofenceApi;
@Inject LocationDao locationDao;
@BindView(R.id.location_name)
TextView locationName;
@BindView(R.id.alert_container)
LinearLayout alertContainer;
@BindView(R.id.location_address)
TextView locationAddress;
@BindView(R.id.alarms_add)
View addLocation;
@BindView(R.id.location_more)
View locationOptions;
private long taskId;
private Location original;
private Location location;
@Nullable
@Override
@ -70,27 +69,39 @@ public class LocationControlSet extends TaskEditControlFragment {
View view = super.onCreateView(inflater, container, savedInstanceState);
taskId = task.getId();
if (savedInstanceState == null) {
locations.addAll(geofenceService.getGeofences(taskId));
original = locationDao.getGeofences(taskId);
location = original;
} else {
List<Parcelable> geofenceArray = savedInstanceState.getParcelableArrayList(EXTRA_GEOFENCES);
for (Parcelable geofence : geofenceArray) {
locations.add((Location) geofence);
}
original = savedInstanceState.getParcelable(EXTRA_ORIGINAL);
location = savedInstanceState.getParcelable(EXTRA_LOCATION);
}
setup(locations);
updateUI();
return view;
}
@OnClick(R.id.alarms_add)
@OnClick({R.id.location_name, R.id.location_address})
void addAlarm(View view) {
pickLocation();
if (location == null) {
startActivityForResult(PlacePicker.getIntent(getActivity()), REQUEST_LOCATION_REMINDER);
} else {
openMap();
}
}
@OnClick(R.id.location_more)
void locationOptions(View view) {
LocationDialog dialog = newLocationDialog(location);
dialog.setTargetFragment(this, REQUEST_LOCATION_DETAILS);
dialog.show(getFragmentManager(), FRAG_TAG_LOCATION_DIALOG);
}
@Override
protected int getLayout() {
return R.layout.control_set_locations;
return R.layout.location_row;
}
@Override
@ -103,104 +114,90 @@ public class LocationControlSet extends TaskEditControlFragment {
return TAG;
}
private void setup(Collection<Location> locations) {
if (locations.isEmpty()) {
alertContainer.setVisibility(View.GONE);
addLocation.setVisibility(View.VISIBLE);
private void updateUI() {
if (location == null) {
locationName.setText("");
locationOptions.setVisibility(View.GONE);
locationAddress.setVisibility(View.GONE);
} else {
addLocation.setVisibility(View.GONE);
alertContainer.setVisibility(View.VISIBLE);
alertContainer.removeAllViews();
for (Location location : locations) {
addGeolocationReminder(location);
locationOptions.setVisibility(View.VISIBLE);
String name = location.getDisplayName();
String address = location.getAddress();
if (!Strings.isNullOrEmpty(address) && !address.equals(name)) {
locationAddress.setText(address);
locationAddress.setVisibility(View.VISIBLE);
} else {
locationAddress.setVisibility(View.GONE);
}
SpannableString spannableString = new SpannableString(name);
spannableString.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
openMap();
}
},
0,
name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
locationName.setText(spannableString);
locationName.setMovementMethod(LinkMovementMethod.getInstance());
}
}
private void openMap() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(location.getGeoUri()));
startActivity(intent);
}
@Override
public boolean hasChanges(Task original) {
return !newHashSet(geofenceService.getGeofences(taskId)).equals(locations);
public boolean hasChanges(Task task) {
return original == null ? location == null : !original.equals(location);
}
@Override
public void apply(Task task) {
if (geofenceService.synchronizeGeofences(task.getId(), locations)) {
task.setModificationDate(DateUtilities.now());
if (original != null) {
geofenceApi.cancel(original);
locationDao.delete(original.geofence);
}
if (location != null) {
Place place = location.place;
Geofence geofence = location.geofence;
geofence.setTask(taskId);
geofence.setPlace(place.getUid());
geofence.setId(locationDao.insert(geofence));
geofenceApi.register(Collections.singletonList(location));
}
task.setModificationDate(DateUtilities.now());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(EXTRA_GEOFENCES, newArrayList(locations));
outState.putParcelable(EXTRA_ORIGINAL, original);
outState.putParcelable(EXTRA_LOCATION, location);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LOCATION_REMINDER) {
if (resultCode == Activity.RESULT_OK) {
locations.clear();
locations.add(PlacePicker.getPlace(data, preferences));
setup(locations);
location = PlacePicker.getPlace(data, preferences);
updateUI();
}
} else if (requestCode == REQUEST_LOCATION_DETAILS) {
if (resultCode == Activity.RESULT_OK) {
Location original = data.getParcelableExtra(LocationDialog.EXTRA_ORIGINAL);
Location location = data.getParcelableExtra(LocationDialog.EXTRA_LOCATION);
Timber.d("original: %s, updated: %s", original, location);
locations.remove(original);
if (location != null) {
locations.add(location);
}
setup(locations);
location = data.getParcelableExtra(LocationDialog.EXTRA_LOCATION);
updateUI();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void pickLocation() {
startActivityForResult(PlacePicker.getIntent(getActivity()), REQUEST_LOCATION_REMINDER);
}
private void addGeolocationReminder(final Location location) {
final View alertItem = getActivity().getLayoutInflater().inflate(R.layout.location_row, null);
alertContainer.addView(alertItem);
String name = location.getDisplayName();
String address = location.getAddress();
if (!Strings.isNullOrEmpty(address) && !address.equals(name)) {
TextView addressView = alertItem.findViewById(R.id.location_address);
addressView.setText(address);
addressView.setVisibility(View.VISIBLE);
}
SpannableString spannableString = new SpannableString(name);
spannableString.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(location.getGeoUri()));
startActivity(intent);
}
},
0,
name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView nameView = alertItem.findViewById(R.id.location_name);
nameView.setText(spannableString);
nameView.setMovementMethod(LinkMovementMethod.getInstance());
alertItem
.findViewById(R.id.location_more)
.setOnClickListener(
v -> {
LocationDialog dialog = newLocationDialog(location);
dialog.setTargetFragment(this, REQUEST_LOCATION_DETAILS);
dialog.show(getFragmentManager(), FRAG_TAG_LOCATION_DIALOG);
});
}
@Override
protected void inject(FragmentComponent component) {
component.inject(this);

@ -1,48 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="100"
android:orientation="vertical">
<TextView
android:id="@+id/location_name"
style="@style/TaskEditTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/asTextColor"/>
<TextView
android:id="@+id/location_address"
style="@style/TaskEditTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/asTextColorHint"
android:visibility="gone"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"/>
</LinearLayout>
android:baselineAligned="false">
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/location_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_gravity="top|center"
android:alpha="?attr/alpha_secondary"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_outline_more_vert_24px"
android:tint="?attr/icon_tint"/>
</LinearLayout>
<TextView
android:id="@+id/location_name"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@id/location_more"
android:layout_toStartOf="@id/location_more"
android:clickable="true"
android:focusable="true"
android:hint="@string/add_location"
android:textColor="?attr/asTextColor"
android:textColorHint="@color/text_secondary"/>
<TextView
android:id="@+id/location_address"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/location_name"
android:layout_toLeftOf="@id/location_more"
android:layout_toStartOf="@id/location_more"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="1"
android:singleLine="true"
android:textColor="?attr/asTextColorHint"
android:visibility="gone"/>
</RelativeLayout>

Loading…
Cancel
Save