Load task values into fields

pull/618/head
Alex Baker 8 years ago
parent c93a1a8c21
commit d6a3484004

@ -2,7 +2,6 @@ package com.todoroo.astrid.model;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
@ -11,15 +10,11 @@ import org.junit.runner.RunWith;
import org.tasks.Snippet; import org.tasks.Snippet;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.tasks.Freeze.freezeClock; import static org.tasks.Freeze.freezeClock;
import static org.tasks.time.DateTimeUtils.currentTimeMillis; import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@ -27,11 +22,10 @@ import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class TaskTest extends InjectingTestCase { public class TaskTest extends InjectingTestCase {
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject Preferences preferences;
@Test @Test
public void testNewTaskHasNoCreationDate() { public void testNewTaskHasNoCreationDate() {
assertFalse(new Task().containsValue(Task.CREATION_DATE)); assertFalse(new Task().isModified(Task.CREATION_DATE));
} }
@Test @Test
@ -51,17 +45,6 @@ public class TaskTest extends InjectingTestCase {
assertEquals(task, fromDb); assertEquals(task, fromDb);
} }
@Test
public void testDefaults() {
preferences.setDefaults();
Map<String, AbstractModel.ValueReader<?>> defaults = new Task().getRoomGetters();
assertTrue(defaults.containsKey(Task.TITLE.name));
assertTrue(defaults.containsKey(Task.DUE_DATE.name));
assertTrue(defaults.containsKey(Task.HIDE_UNTIL.name));
assertTrue(defaults.containsKey(Task.COMPLETION_DATE.name));
assertTrue(defaults.containsKey(Task.IMPORTANCE.name));
}
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);

@ -59,7 +59,6 @@ public class GtasksSubtaskListFragment extends GtasksListFragment {
Property<?>[] baseProperties = TaskAdapter.PROPERTIES; Property<?>[] baseProperties = TaskAdapter.PROPERTIES;
ArrayList<Property<?>> properties = new ArrayList<>(Arrays.asList(baseProperties)); ArrayList<Property<?>> properties = new ArrayList<>(Arrays.asList(baseProperties));
properties.add(GoogleTask.INDENT); properties.add(GoogleTask.INDENT);
properties.add(GoogleTask.ORDER);
return properties.toArray(new Property<?>[properties.size()]); return properties.toArray(new Property<?>[properties.size()]);
} }

@ -72,13 +72,13 @@ class OrderedMetadataListFragmentHelper {
@Override @Override
public int getIndent(Task task) { public int getIndent(Task task) {
return task.getValue(GoogleTask.INDENT); return task.getGoogleTaskIndent();
} }
@Override @Override
public boolean canIndent(int position, Task task) { public boolean canIndent(int position, Task task) {
Task parent = taskAdapter.getTask(position - 1); Task parent = taskAdapter.getTask(position - 1);
return parent != null && getIndent(task) <= parent.getValue(GoogleTask.INDENT); return parent != null && getIndent(task) <= parent.getGoogleTaskIndent();
} }
@Override @Override
@ -134,10 +134,9 @@ class OrderedMetadataListFragmentHelper {
ArrayList<Long> chained = chainedCompletions.get(itemId); ArrayList<Long> chained = chainedCompletions.get(itemId);
if(chained != null) { if(chained != null) {
for(Long taskId : chained) { for(Long taskId : chained) {
Task model = new Task(); Task task = taskDao.fetch(taskId);
model.setId(taskId); task.setCompletionDate(completionDate);
model.setCompletionDate(completionDate); taskDao.save(task);
taskDao.save(model);
} }
taskAdapter.notifyDataSetInvalidated(); taskAdapter.notifyDataSetInvalidated();
} }
@ -145,7 +144,7 @@ class OrderedMetadataListFragmentHelper {
} }
final ArrayList<Long> chained = new ArrayList<>(); final ArrayList<Long> chained = new ArrayList<>();
final int parentIndent = item.getValue(GoogleTask.INDENT); final int parentIndent = item.getGoogleTaskIndent();
updater.applyToChildren(list, itemId, node -> { updater.applyToChildren(list, itemId, node -> {
Task childTask = taskDao.fetch(node.taskId); Task childTask = taskDao.fetch(node.taskId);
if(!TextUtils.isEmpty(childTask.getRecurrence())) { if(!TextUtils.isEmpty(childTask.getRecurrence())) {
@ -153,11 +152,8 @@ class OrderedMetadataListFragmentHelper {
googleTask.setIndent(parentIndent); googleTask.setIndent(parentIndent);
googleTaskDao.update(googleTask); googleTaskDao.update(googleTask);
} }
childTask.setCompletionDate(completionDate);
Task model = new Task(); taskDao.save(childTask);
model.setId(node.taskId);
model.setCompletionDate(completionDate);
taskDao.save(model);
chained.add(node.taskId); chained.add(node.taskId);
}); });

@ -249,26 +249,20 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
//If task was newly created but without a title, don't sync--we're in the middle of //If task was newly created but without a title, don't sync--we're in the middle of
//creating a task which may end up being cancelled. Also don't sync new but already //creating a task which may end up being cancelled. Also don't sync new but already
//deleted tasks //deleted tasks
if (newlyCreated && if (newlyCreated && (TextUtils.isEmpty(task.getTitle()) || task.getDeletionDate() > 0)) {
(!task.containsValue(Task.TITLE) || TextUtils.isEmpty(task.getTitle()) || task.getDeletionDate() > 0)) {
return; return;
} }
//Update the remote model's changed properties //Update the remote model's changed properties
if (task.containsValue(Task.DELETION_DATE) && task.isDeleted()) { if (task.isDeleted()) {
remoteModel.setDeleted(true); remoteModel.setDeleted(true);
} }
if (task.containsValue(Task.TITLE)) {
remoteModel.setTitle(task.getTitle()); remoteModel.setTitle(task.getTitle());
}
if (task.containsValue(Task.NOTES)) {
remoteModel.setNotes(task.getNotes()); remoteModel.setNotes(task.getNotes());
} if (task.hasDueDate()) {
if (task.containsValue(Task.DUE_DATE) && task.hasDueDate()) {
remoteModel.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(task.getDueDate())); remoteModel.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(task.getDueDate()));
} }
if (task.containsValue(Task.COMPLETION_DATE)) {
if (task.isCompleted()) { if (task.isCompleted()) {
remoteModel.setCompleted(GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.getCompletionDate())); remoteModel.setCompleted(GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.getCompletionDate()));
remoteModel.setStatus("completed"); //$NON-NLS-1$ remoteModel.setStatus("completed"); //$NON-NLS-1$
@ -276,7 +270,6 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
remoteModel.setCompleted(null); remoteModel.setCompleted(null);
remoteModel.setStatus("needsAction"); //$NON-NLS-1$ remoteModel.setStatus("needsAction"); //$NON-NLS-1$
} }
}
if (!newlyCreated) { if (!newlyCreated) {
try { try {
@ -382,8 +375,8 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
if(task.task.isSaved()) { if(task.task.isSaved()) {
Task local = taskDao.fetch(task.task.getId()); Task local = taskDao.fetch(task.task.getId());
if (local == null) { if (local == null) {
task.task.clearValue(Task.ID); task.task.setId(NO_ID);
task.task.clearValue(Task.UUID); task.task.setUuid(NO_UUID);
} else { } else {
mergeDates(task.task, local); mergeDates(task.task, local);
} }

@ -1,13 +1,13 @@
package org.tasks.receivers; package org.tasks.receivers;
import android.content.ContentValues;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.gtasks.SyncAdapterHelper; import org.tasks.gtasks.SyncAdapterHelper;
import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
public class GoogleTaskPusher { public class GoogleTaskPusher {
@ -22,7 +22,7 @@ public class GoogleTaskPusher {
this.syncAdapterHelper = syncAdapterHelper; this.syncAdapterHelper = syncAdapterHelper;
} }
void push(Task task, ContentValues modifiedValues) { void push(Task task, ArrayList<String> modifiedValues) {
if(!syncAdapterHelper.isEnabled()) { if(!syncAdapterHelper.isEnabled()) {
return; return;
} }
@ -36,12 +36,12 @@ public class GoogleTaskPusher {
} }
} }
private boolean checkValuesForProperties(ContentValues values, Property<?>[] properties) { private boolean checkValuesForProperties(ArrayList<String> values, Property<?>[] properties) {
if (values == null) { if (values == null) {
return false; return false;
} }
for (Property<?> property : properties) { for (Property<?> property : properties) {
if (property != Task.ID && values.containsKey(property.name)) { if (property != Task.ID && values.contains(property.name)) {
return true; return true;
} }
} }

@ -1,6 +1,5 @@
package org.tasks.receivers; package org.tasks.receivers;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -10,14 +9,16 @@ import com.todoroo.astrid.data.Task;
import org.tasks.injection.BroadcastComponent; import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver; import org.tasks.injection.InjectingBroadcastReceiver;
import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
public class PushReceiver extends InjectingBroadcastReceiver { public class PushReceiver extends InjectingBroadcastReceiver {
public static void broadcast(Context context, Task task, ContentValues values) { public static void broadcast(Context context, Task task, ArrayList<String> values) {
Intent intent = new Intent(context, PushReceiver.class); Intent intent = new Intent(context, PushReceiver.class);
intent.putExtra(AstridApiConstants.EXTRAS_TASK, task); intent.putExtra(AstridApiConstants.EXTRAS_TASK, task);
intent.putExtra(AstridApiConstants.EXTRAS_VALUES, values); intent.putStringArrayListExtra(AstridApiConstants.EXTRAS_VALUES, values);
context.sendBroadcast(intent); context.sendBroadcast(intent);
} }
@ -29,7 +30,7 @@ public class PushReceiver extends InjectingBroadcastReceiver {
googleTaskPusher.push( googleTaskPusher.push(
intent.getParcelableExtra(AstridApiConstants.EXTRAS_TASK), intent.getParcelableExtra(AstridApiConstants.EXTRAS_TASK),
intent.getParcelableExtra(AstridApiConstants.EXTRAS_VALUES)); intent.getStringArrayListExtra(AstridApiConstants.EXTRAS_VALUES));
} }
@Override @Override

@ -8,22 +8,18 @@ package com.todoroo.andlib.data;
import android.arch.persistence.room.Ignore; import android.arch.persistence.room.Ignore;
import android.content.ContentValues; import android.content.ContentValues;
import com.todoroo.andlib.data.Property.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.PropertyVisitor;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.data.Tag; import org.tasks.data.Tag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.HashSet;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import timber.log.Timber; import static com.google.common.collect.Sets.newHashSet;
import static com.todoroo.andlib.data.Property.DoubleProperty;
/** /**
* <code>AbstractModel</code> represents a row in a database. * <code>AbstractModel</code> represents a row in a database.
@ -37,8 +33,6 @@ import static com.todoroo.andlib.data.Property.DoubleProperty;
*/ */
public abstract class AbstractModel { public abstract class AbstractModel {
private static final ContentValuesSavingVisitor saver = new ContentValuesSavingVisitor();
/** id property common to all models */ /** id property common to all models */
protected static final String ID_PROPERTY_NAME = "_id"; //$NON-NLS-1$ protected static final String ID_PROPERTY_NAME = "_id"; //$NON-NLS-1$
@ -50,13 +44,10 @@ public abstract class AbstractModel {
// --- abstract methods // --- abstract methods
public interface ValueReader<TYPE> { public interface ValueWriter<TYPE> {
TYPE getValue(Task instance); void setValue(Task instance, TYPE value);
} }
/** Get the default values for this object */
abstract public Map<String, ValueReader<?>> getRoomGetters();
// --- data store variables and management // --- data store variables and management
/* Data Source Ordering: /* Data Source Ordering:
@ -68,46 +59,15 @@ public abstract class AbstractModel {
/** User set values */ /** User set values */
@Ignore @Ignore
protected ContentValues setValues = null; protected final Set<String> setValues = new HashSet<>();
/** Values from database */
@Ignore
protected ContentValues values = null;
/** Transitory Metadata (not saved in database) */ /** Transitory Metadata (not saved in database) */
@Ignore @Ignore
protected HashMap<String, Object> transitoryData = null; protected HashMap<String, Object> transitoryData = null;
protected AbstractModel() {
}
protected AbstractModel(TodorooCursor cursor) {
readPropertiesFromCursor(cursor);
}
/** Get the user-set values for this object */ /** Get the user-set values for this object */
public ContentValues getSetValues() { public Set<String> getSetValues() {
return setValues; return newHashSet(setValues);
}
/** Get a list of all field/value pairs merged across data sources */
public ContentValues getMergedValues() {
ContentValues mergedValues = new ContentValues();
for (Map.Entry<String, ValueReader<?>> entry : getRoomGetters().entrySet()) {
Object value = entry.getValue().getValue((Task) this);
if (value != null) {
AndroidUtilities.putInto(mergedValues, entry.getKey(), value);
}
}
if (values != null) {
mergedValues.putAll(values);
}
if(setValues != null) {
mergedValues.putAll(setValues);
}
return mergedValues;
} }
/** /**
@ -115,93 +75,18 @@ public abstract class AbstractModel {
* saved - future saves will not need to write all the data as before. * saved - future saves will not need to write all the data as before.
*/ */
public void markSaved() { public void markSaved() {
if(values == null) { setValues.clear();
values = setValues;
} else if(setValues != null) {
values.putAll(setValues);
}
setValues = null;
} }
/** private void setValue(String columnName, Object value) {
* Use merged values to compare two models to each other. Must be of ValueWriter<Object> writer = Task.roomSetters.get(columnName);
* exactly the same class. if (writer == null) {
*/ throw new RuntimeException();
@Override
public boolean equals(Object other) {
if(other == null || other.getClass() != getClass()) {
return false;
} }
writer.setValue((Task) this, value);
return getMergedValues().equals(((AbstractModel) other).getMergedValues()); setValues.add(columnName);
} }
@Override
public int hashCode() {
return getMergedValues().hashCode() ^ getClass().hashCode();
}
@Override
public String toString() {
return getClass().getSimpleName() + "\n" + "set values:\n" + setValues + "\n" + "values:\n" + values + "\n";
}
/**
* Reads all properties from the supplied cursor and store
*/
void readPropertiesFromCursor(TodorooCursor cursor) {
if (values == null) {
values = new ContentValues();
}
// clears user-set values
setValues = null;
transitoryData = null;
for (Property<?> property : cursor.getProperties()) {
try {
saver.save(property, values, cursor.get(property));
} catch (IllegalArgumentException e) {
// underlying cursor may have changed, suppress
Timber.e(e, e.getMessage());
}
}
}
/**
* Reads the given property. Make sure this model has this property!
*/
public synchronized <TYPE> TYPE getValue(Property<TYPE> property) {
Object value;
String columnName = property.getColumnName();
if(setValues != null && setValues.containsKey(columnName)) {
value = setValues.get(columnName);
} else if(values != null && values.containsKey(columnName)) {
value = values.get(columnName);
} else if(getRoomGetters().containsKey(columnName)) {
value = getRoomGetters().get(columnName).getValue((Task) this);
} else {
throw new UnsupportedOperationException(
"Model Error: Did not read property " + property.name); //$NON-NLS-1$
}
// resolve properties that were retrieved with a different type than accessed
try {
if(value instanceof String && property instanceof LongProperty) {
return (TYPE) Long.valueOf((String) value);
} else if(value instanceof String && property instanceof IntegerProperty) {
return (TYPE) Integer.valueOf((String) value);
} else if(value instanceof Integer && property instanceof LongProperty) {
return (TYPE) Long.valueOf(((Number) value).longValue());
} else if(value instanceof String && property instanceof DoubleProperty) {
return (TYPE) Double.valueOf((String) value);
}
return (TYPE) value;
} catch (NumberFormatException e) {
Timber.e(e, e.getMessage());
return (TYPE) getRoomGetters().get(property.name).getValue((Task) this);
}
}
/** /**
* Utility method to get the identifier of the model, if it exists. * Utility method to get the identifier of the model, if it exists.
@ -211,15 +96,7 @@ public abstract class AbstractModel {
abstract public long getId(); abstract public long getId();
public void setId(long id) { public void setId(long id) {
if (setValues == null) { setValue(ID_PROPERTY, id);
setValues = new ContentValues();
}
if(id == NO_ID) {
clearValue(ID_PROPERTY);
} else {
setValues.put(ID_PROPERTY_NAME, id);
}
} }
/** /**
@ -232,54 +109,17 @@ public abstract class AbstractModel {
/** /**
* @return true if setValues or values contains this property * @return true if setValues or values contains this property
*/ */
public boolean containsValue(Property<?> property) { public boolean isModified(Property<?> property) {
if(setValues != null && setValues.containsKey(property.getColumnName())) { return setValues.contains(property.getColumnName());
return true;
}
if(values != null && values.containsKey(property.getColumnName())) {
return true;
}
return false;
} }
// --- data storage // --- data storage
/**
* Check whether the user has changed this property value and it should be
* stored for saving in the database
*/
private synchronized <TYPE> boolean shouldSaveValue(Property<TYPE> property, TYPE newValue) {
// we've already decided to save it, so overwrite old value
if (setValues.containsKey(property.getColumnName())) {
return true;
}
TYPE value = getValue(property);
if (value == null) {
if (newValue == null) {
return false;
}
} else if (value.equals(newValue)) {
return false;
}
// otherwise, good to save
return true;
}
/** /**
* Sets the given property. Make sure this model has this property! * Sets the given property. Make sure this model has this property!
*/ */
public synchronized <TYPE> void setValue(Property<TYPE> property, public synchronized <TYPE> void setValue(Property<TYPE> property, TYPE value) {
TYPE value) { setValue(property.getColumnName(), value);
if (setValues == null) {
setValues = new ContentValues();
}
if (!shouldSaveValue(property, value)) {
return;
}
saver.save(property, setValues, value);
} }
/** /**
@ -287,26 +127,11 @@ public abstract class AbstractModel {
* keeping the existing value if one already exists * keeping the existing value if one already exists
*/ */
public synchronized void mergeWithoutReplacement(ContentValues other) { public synchronized void mergeWithoutReplacement(ContentValues other) {
if (setValues == null) {
setValues = new ContentValues();
}
for (Entry<String, Object> item : other.valueSet()) { for (Entry<String, Object> item : other.valueSet()) {
if (setValues.containsKey(item.getKey())) { String columnName = item.getKey();
continue; if (setValues.add(columnName)) {
setValue(columnName, item.getValue());
} }
AndroidUtilities.putInto(setValues, item.getKey(), item.getValue());
}
}
/**
* Clear the key for the given property
*/
public synchronized void clearValue(Property<?> property) {
if(setValues != null && setValues.containsKey(property.getColumnName())) {
setValues.remove(property.getColumnName());
}
if(values != null && values.containsKey(property.getColumnName())) {
values.remove(property.getColumnName());
} }
} }
@ -355,50 +180,4 @@ public abstract class AbstractModel {
Object trans = clearTransitory(flag); Object trans = clearTransitory(flag);
return trans != null; return trans != null;
} }
/**
* Visitor that saves a value into a content values store
*
* @author Tim Su <tim@todoroo.com>
*
*/
public static class ContentValuesSavingVisitor implements PropertyVisitor<Void, Object> {
private ContentValues store;
public synchronized void save(Property<?> property, ContentValues newStore, Object value) {
this.store = newStore;
// we don't allow null values, as they indicate unset properties
// when the database was written
if(value != null) {
property.accept(this, value);
}
}
@Override
public Void visitInteger(Property<Integer> property, Object value) {
store.put(property.getColumnName(), (Integer) value);
return null;
}
@Override
public Void visitLong(Property<Long> property, Object value) {
store.put(property.getColumnName(), (Long) value);
return null;
}
@Override
public Void visitDouble(Property<Double> property, Object value) {
store.put(property.getColumnName(), (Double) value);
return null;
}
@Override
public Void visitString(Property<String> property, Object value) {
store.put(property.getColumnName(), (String) value);
return null;
}
}
} }

@ -192,30 +192,6 @@ public abstract class Property<TYPE> extends Field implements Cloneable {
} }
} }
/**
* Double property type. See {@link Property}
*
* @author Tim Su <tim@todoroo.com>
*
*/
public static class DoubleProperty extends Property<Double> {
public DoubleProperty(Table table, String name) {
super(table, name);
}
@Override
public <RETURN, PARAMETER> RETURN accept(
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
return visitor.visitDouble(this, data);
}
@Override
public DoubleProperty cloneAs(String tableAlias, String columnAlias) {
return (DoubleProperty) super.cloneAs(tableAlias, columnAlias);
}
}
/** /**
* Long property type. See {@link Property} * Long property type. See {@link Property}
* *

@ -70,7 +70,7 @@ public class TodorooCursor extends CursorWrapper {
* @param <PROPERTY_TYPE> type to return * @param <PROPERTY_TYPE> type to return
* @param property to retrieve * @param property to retrieve
*/ */
public <PROPERTY_TYPE> PROPERTY_TYPE get(Property<PROPERTY_TYPE> property) { public <PROPERTY_TYPE> PROPERTY_TYPE get(Property<?> property) {
return (PROPERTY_TYPE)property.accept(reader, this); return (PROPERTY_TYPE)property.accept(reader, this);
} }

@ -95,7 +95,7 @@ public abstract class Database extends RoomDatabase {
return this; return this;
} }
private void onDatabaseUpdated() { public void onDatabaseUpdated() {
if (onDatabaseUpdated != null) { if (onDatabaseUpdated != null) {
onDatabaseUpdated.run(); onDatabaseUpdated.run();
} }

@ -7,11 +7,10 @@ package com.todoroo.astrid.dao;
import android.arch.persistence.room.Dao; import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert; import android.arch.persistence.room.Insert;
import android.content.ContentValues; import android.arch.persistence.room.Update;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
@ -28,7 +27,7 @@ import org.tasks.jobs.AfterSaveIntentService;
import org.tasks.receivers.PushReceiver; import org.tasks.receivers.PushReceiver;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.Set;
import timber.log.Timber; import timber.log.Timber;
@ -154,7 +153,7 @@ public abstract class TaskDao {
* *
*/ */
public void save(Task task) { public void save(Task task) {
ContentValues modifiedValues = saveExisting(task); Set<String> modifiedValues = saveExisting(task);
if (modifiedValues != null) { if (modifiedValues != null) {
AfterSaveIntentService.enqueue(context, task.getId(), modifiedValues); AfterSaveIntentService.enqueue(context, task.getId(), modifiedValues);
} else if (task.checkTransitory(SyncFlags.FORCE_SYNC)) { } else if (task.checkTransitory(SyncFlags.FORCE_SYNC)) {
@ -165,35 +164,30 @@ public abstract class TaskDao {
@Insert @Insert
abstract long insert(Task task); abstract long insert(Task task);
@Update
abstract int update(Task task);
public void createNew(Task task) { public void createNew(Task task) {
task.id = null; task.id = null;
task.remoteId = task.getUuid(); task.remoteId = task.getUuid();
task.setId(insert(task)); long insert = insert(task);
task.setId(insert);
} }
private ContentValues saveExisting(Task item) { private Set<String> saveExisting(Task item) {
ContentValues values = item.getSetValues(); Set<String> values = item.getSetValues();
if (values == null || values.size() == 0) { if (values == null || values.size() == 0) {
return null; return null;
} }
if (!TaskApiDao.insignificantChange(values)) { if (!TaskApiDao.insignificantChange(values)) {
if (!values.containsKey(Task.MODIFICATION_DATE.name)) { if (!values.contains(Task.MODIFICATION_DATE.name)) {
item.setModificationDate(now()); item.setModificationDate(now());
} }
} }
DatabaseChangeOp update = new DatabaseChangeOp() { int updated = update(item);
@Override if (updated == 1) {
public boolean makeChange() { item.markSaved();
return database.update(values, database.onDatabaseUpdated();
AbstractModel.ID_PROPERTY.eq(item.getId()).toString()) > 0;
}
@Override
public String toString() {
return "UPDATE";
}
};
if (updateAndRecordChanges(item, update)) {
return values; return values;
} }
return null; return null;
@ -229,23 +223,5 @@ public abstract class TaskDao {
Cursor cursor = database.rawQuery(queryString); Cursor cursor = database.rawQuery(queryString);
return new TodorooCursor(cursor, query.getFields()); return new TodorooCursor(cursor, query.getFields());
} }
private interface DatabaseChangeOp {
boolean makeChange();
}
private boolean updateAndRecordChanges(Task item, DatabaseChangeOp op) {
final AtomicBoolean result = new AtomicBoolean(false);
synchronized(database) {
result.set(op.makeChange());
if (result.get()) {
item.markSaved();
if (BuildConfig.DEBUG) {
Timber.v("%s %s", op, item.toString());
}
}
}
return result.get();
}
} }

@ -29,6 +29,7 @@ import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.backup.XmlReader; import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter; import org.tasks.backup.XmlWriter;
import org.tasks.data.GoogleTask;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.util.HashMap; import java.util.HashMap;
@ -36,6 +37,7 @@ import java.util.Map;
import timber.log.Timber; import timber.log.Timber;
import static com.google.common.collect.Lists.newArrayList;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
/** /**
@ -228,39 +230,35 @@ public class Task extends AbstractModel implements Parcelable {
public static final int IMPORTANCE_SHOULD_DO = 2; public static final int IMPORTANCE_SHOULD_DO = 2;
public static final int IMPORTANCE_NONE = 3; public static final int IMPORTANCE_NONE = 3;
// --- defaults public static final Map<String, ValueWriter<Object>> roomSetters = new HashMap<>();
/** Default values container */
private static final Map<String, ValueReader<?>> roomGetters = new HashMap<>();
static { static {
roomGetters.put(CALENDAR_URI.name, t -> t.calendarUri); roomSetters.put(CALENDAR_URI.name, (t, v) -> t.calendarUri = (String) v);
roomGetters.put(COMPLETION_DATE.name, t -> t.completed); roomSetters.put(COMPLETION_DATE.name, (t, v) -> t.completed = (Long) v);
roomGetters.put(CREATION_DATE.name, t -> t.created); roomSetters.put(CREATION_DATE.name, (t, v) -> t.created = (Long) v);
roomGetters.put(DELETION_DATE.name, t -> t.deleted); roomSetters.put(DELETION_DATE.name, (t, v) -> t.deleted = (Long) v);
roomGetters.put(DUE_DATE.name, t -> t.dueDate); roomSetters.put(DUE_DATE.name, (t, v) -> t.dueDate = v instanceof String ? Long.valueOf((String) v) : (Long) v);
roomGetters.put(ELAPSED_SECONDS.name, t -> t.elapsedSeconds); roomSetters.put(ELAPSED_SECONDS.name, (t, v) -> t.elapsedSeconds = (Integer) v);
roomGetters.put(ESTIMATED_SECONDS.name, t -> t.estimatedSeconds); roomSetters.put(ESTIMATED_SECONDS.name, (t, v) -> t.estimatedSeconds = (Integer) v);
roomGetters.put(HIDE_UNTIL.name, t -> t.hideUntil); roomSetters.put(HIDE_UNTIL.name, (t, v) -> t.hideUntil = (Long) v);
roomGetters.put(ID.name, t -> t.id); roomSetters.put(ID.name, (t, v) -> t.id = (Long) v);
roomGetters.put(IMPORTANCE.name, t -> t.importance); roomSetters.put(IMPORTANCE.name, (t, v) -> t.importance = v instanceof String ? Integer.valueOf((String) v) : (Integer) v);
roomGetters.put(MODIFICATION_DATE.name, t -> t.modified); roomSetters.put(MODIFICATION_DATE.name, (t, v) -> t.modified = (Long) v);
roomGetters.put(NOTES.name, t -> t.notes); roomSetters.put(NOTES.name, (t, v) -> t.notes = (String) v);
roomGetters.put(RECURRENCE.name, t -> t.recurrence); roomSetters.put(RECURRENCE.name, (t, v) -> t.recurrence = (String) v);
roomGetters.put(REMINDER_FLAGS.name, t -> t.notificationFlags); roomSetters.put(REMINDER_FLAGS.name, (t, v) -> t.notificationFlags = (Integer) v);
roomGetters.put(REMINDER_LAST.name, t -> t.lastNotified); roomSetters.put(REMINDER_LAST.name, (t, v) -> t.lastNotified = (Long) v);
roomGetters.put(REMINDER_PERIOD.name, t -> t.notifications); roomSetters.put(REMINDER_PERIOD.name, (t, v) -> t.notifications = (Long) v);
roomGetters.put(REMINDER_SNOOZE.name, t -> t.snoozeTime); roomSetters.put(REMINDER_SNOOZE.name, (t, v) -> t.snoozeTime = (Long) v);
roomGetters.put(REPEAT_UNTIL.name, t -> t.repeatUntil); roomSetters.put(REPEAT_UNTIL.name, (t, v) -> t.repeatUntil = (Long) v);
roomGetters.put(TIMER_START.name, t -> t.timerStart); roomSetters.put(TIMER_START.name, (t, v) -> t.timerStart = (Long) v);
roomGetters.put(TITLE.name, t -> t.title); roomSetters.put(TITLE.name, (t, v) -> t.title = (String) v);
roomGetters.put(UUID.name, t -> t.remoteId); roomSetters.put(UUID.name, (t, v) -> t.remoteId = (String) v);
roomSetters.put(GoogleTask.INDENT.name, (t, v) -> t.googleTaskIndent = (Integer) v);
} }
@Override @Ignore
public Map<String, ValueReader<?>> getRoomGetters() { private int googleTaskIndent;
return roomGetters;
}
// --- data access boilerplate // --- data access boilerplate
@ -270,7 +268,17 @@ public class Task extends AbstractModel implements Parcelable {
@Ignore @Ignore
public Task(TodorooCursor cursor) { public Task(TodorooCursor cursor) {
super(cursor); for (Property<?> property : cursor.getProperties()) {
try {
ValueWriter<?> writer = roomSetters.get(property.getColumnName());
if (writer != null) {
writer.setValue(this, cursor.get(property));
}
} catch (IllegalArgumentException e) {
// underlying cursor may have changed, suppress
Timber.e(e, e.getMessage());
}
}
} }
@Ignore @Ignore
@ -343,34 +351,17 @@ public class Task extends AbstractModel implements Parcelable {
timerStart = parcel.readLong(); timerStart = parcel.readLong();
title = parcel.readString(); title = parcel.readString();
remoteId = parcel.readString(); remoteId = parcel.readString();
setValues = parcel.readParcelable(ContentValues.class.getClassLoader()); setValues.addAll(parcel.readArrayList(getClass().getClassLoader()));
values = parcel.readParcelable(ContentValues.class.getClassLoader());
transitoryData = parcel.readHashMap(ContentValues.class.getClassLoader()); transitoryData = parcel.readHashMap(ContentValues.class.getClassLoader());
} }
@Override @Override
public long getId() { public long getId() {
if(setValues != null && setValues.containsKey(ID.name)) { return id == null ? NO_ID : id;
return setValues.getAsLong(ID.name);
} else if(values != null && values.containsKey(ID.name)) {
return values.getAsLong(ID.name);
} else if (id != null) {
return id;
} else {
return NO_ID;
}
} }
public String getUuid() { public String getUuid() {
if(setValues != null && setValues.containsKey(UUID.name)) { return Strings.isNullOrEmpty(remoteId) ? NO_UUID : remoteId;
return setValues.getAsString(UUID.name);
} else if(values != null && values.containsKey(UUID.name)) {
return values.getAsString(UUID.name);
} else if (!Strings.isNullOrEmpty(remoteId)) {
return remoteId;
} else {
return NO_UUID;
}
} }
// --- parcelable helpers // --- parcelable helpers
@ -391,31 +382,26 @@ public class Task extends AbstractModel implements Parcelable {
/** Checks whether task is done. Requires COMPLETION_DATE */ /** Checks whether task is done. Requires COMPLETION_DATE */
public boolean isCompleted() { public boolean isCompleted() {
return getValue(COMPLETION_DATE) > 0; return completed > 0;
} }
/** Checks whether task is deleted. Will return false if DELETION_DATE not read */ /** Checks whether task is deleted. Will return false if DELETION_DATE not read */
public boolean isDeleted() { public boolean isDeleted() {
// assume false if we didn't load deletion date return deleted > 0;
if(!containsValue(DELETION_DATE)) {
return false;
} else {
return getValue(DELETION_DATE) > 0;
}
} }
/** Checks whether task is hidden. Requires HIDDEN_UNTIL */ /** Checks whether task is hidden. Requires HIDDEN_UNTIL */
public boolean isHidden() { public boolean isHidden() {
return getValue(HIDE_UNTIL) > DateUtilities.now(); return hideUntil > DateUtilities.now();
} }
public boolean hasHideUntilDate() { public boolean hasHideUntilDate() {
return getValue(HIDE_UNTIL) > 0; return hideUntil > 0;
} }
/** Checks whether task is done. Requires DUE_DATE */ /** Checks whether task is done. Requires DUE_DATE */
public boolean hasDueDate() { public boolean hasDueDate() {
return getValue(DUE_DATE) > 0; return dueDate > 0;
} }
// --- due and hide until date management // --- due and hide until date management
@ -423,12 +409,12 @@ public class Task extends AbstractModel implements Parcelable {
/** urgency array index -> significance */ /** urgency array index -> significance */
public static final int URGENCY_NONE = 0; public static final int URGENCY_NONE = 0;
public static final int URGENCY_TODAY = 1; static final int URGENCY_TODAY = 1;
public static final int URGENCY_TOMORROW = 2; static final int URGENCY_TOMORROW = 2;
public static final int URGENCY_DAY_AFTER = 3; static final int URGENCY_DAY_AFTER = 3;
public static final int URGENCY_NEXT_WEEK = 4; static final int URGENCY_NEXT_WEEK = 4;
public static final int URGENCY_IN_TWO_WEEKS = 5; static final int URGENCY_IN_TWO_WEEKS = 5;
public static final int URGENCY_NEXT_MONTH = 6; static final int URGENCY_NEXT_MONTH = 6;
public static final int URGENCY_SPECIFIC_DAY = 7; public static final int URGENCY_SPECIFIC_DAY = 7;
public static final int URGENCY_SPECIFIC_DAY_TIME = 8; public static final int URGENCY_SPECIFIC_DAY_TIME = 8;
/** hide until array index -> significance */ /** hide until array index -> significance */
@ -514,13 +500,13 @@ public class Task extends AbstractModel implements Parcelable {
return 0; return 0;
case HIDE_UNTIL_DUE: case HIDE_UNTIL_DUE:
case HIDE_UNTIL_DUE_TIME: case HIDE_UNTIL_DUE_TIME:
date = getValue(DUE_DATE); date = dueDate;
break; break;
case HIDE_UNTIL_DAY_BEFORE: case HIDE_UNTIL_DAY_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_DAY; date = dueDate - DateUtilities.ONE_DAY;
break; break;
case HIDE_UNTIL_WEEK_BEFORE: case HIDE_UNTIL_WEEK_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_WEEK; date = dueDate - DateUtilities.ONE_WEEK;
break; break;
case HIDE_UNTIL_SPECIFIC_DAY: case HIDE_UNTIL_SPECIFIC_DAY:
case HIDE_UNTIL_SPECIFIC_DAY_TIME: case HIDE_UNTIL_SPECIFIC_DAY_TIME:
@ -580,7 +566,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getDueDate() { public Long getDueDate() {
return getValue(DUE_DATE); return dueDate;
} }
public void setDueDate(Long dueDate) { public void setDueDate(Long dueDate) {
@ -588,9 +574,9 @@ public class Task extends AbstractModel implements Parcelable {
} }
public void setDueDateAdjustingHideUntil(Long dueDate) { public void setDueDateAdjustingHideUntil(Long dueDate) {
long oldDueDate = getValue(DUE_DATE); long oldDueDate = dueDate;
if (oldDueDate > 0) { if (oldDueDate > 0) {
long hideUntil = getValue(HIDE_UNTIL); long hideUntil = this.hideUntil;
if (hideUntil > 0) { if (hideUntil > 0) {
setHideUntil(dueDate > 0 ? hideUntil + dueDate - oldDueDate : 0); setHideUntil(dueDate > 0 ? hideUntil + dueDate - oldDueDate : 0);
} }
@ -599,7 +585,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public String getRecurrence() { public String getRecurrence() {
return getValue(RECURRENCE); return recurrence;
} }
public void setRecurrence(String recurrence) { public void setRecurrence(String recurrence) {
@ -611,7 +597,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getCreationDate() { public Long getCreationDate() {
return getValue(CREATION_DATE); return created;
} }
public void setCreationDate(Long creationDate) { public void setCreationDate(Long creationDate) {
@ -619,7 +605,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public String getTitle() { public String getTitle() {
return getValue(TITLE); return title;
} }
public void setTitle(String title) { public void setTitle(String title) {
@ -627,7 +613,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getDeletionDate() { public Long getDeletionDate() {
return getValue(DELETION_DATE); return deleted;
} }
public void setDeletionDate(Long deletionDate) { public void setDeletionDate(Long deletionDate) {
@ -635,7 +621,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getHideUntil() { public Long getHideUntil() {
return getValue(HIDE_UNTIL); return hideUntil;
} }
public void setHideUntil(Long hideUntil) { public void setHideUntil(Long hideUntil) {
@ -643,7 +629,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getReminderLast() { public Long getReminderLast() {
return getValue(REMINDER_LAST); return lastNotified;
} }
public void setReminderLast(Long reminderLast) { public void setReminderLast(Long reminderLast) {
@ -651,7 +637,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getReminderSnooze() { public Long getReminderSnooze() {
return getValue(REMINDER_SNOOZE); return snoozeTime;
} }
public void setReminderSnooze(Long reminderSnooze) { public void setReminderSnooze(Long reminderSnooze) {
@ -659,11 +645,11 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Integer getElapsedSeconds() { public Integer getElapsedSeconds() {
return getValue(ELAPSED_SECONDS); return elapsedSeconds;
} }
public Long getTimerStart() { public Long getTimerStart() {
return getValue(TIMER_START); return timerStart;
} }
public void setTimerStart(Long timerStart) { public void setTimerStart(Long timerStart) {
@ -671,7 +657,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getRepeatUntil() { public Long getRepeatUntil() {
return getValue(REPEAT_UNTIL); return repeatUntil;
} }
public void setRepeatUntil(Long repeatUntil) { public void setRepeatUntil(Long repeatUntil) {
@ -679,11 +665,11 @@ public class Task extends AbstractModel implements Parcelable {
} }
public String getCalendarURI() { public String getCalendarURI() {
return getValue(CALENDAR_URI); return calendarUri;
} }
public Integer getImportance() { public Integer getImportance() {
return getValue(IMPORTANCE); return importance;
} }
public void setImportance(Integer importance) { public void setImportance(Integer importance) {
@ -691,7 +677,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getCompletionDate() { public Long getCompletionDate() {
return getValue(COMPLETION_DATE); return completed;
} }
public void setCompletionDate(Long completionDate) { public void setCompletionDate(Long completionDate) {
@ -699,11 +685,11 @@ public class Task extends AbstractModel implements Parcelable {
} }
public String getNotes() { public String getNotes() {
return getValue(NOTES); return notes;
} }
public boolean hasNotes() { public boolean hasNotes() {
return !TextUtils.isEmpty(getValue(NOTES)); return !TextUtils.isEmpty(notes);
} }
public void setNotes(String notes) { public void setNotes(String notes) {
@ -715,7 +701,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Integer getReminderFlags() { public Integer getReminderFlags() {
return getValue(REMINDER_FLAGS); return notificationFlags;
} }
public void setReminderFlags(Integer reminderFlags) { public void setReminderFlags(Integer reminderFlags) {
@ -723,7 +709,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Long getReminderPeriod() { public Long getReminderPeriod() {
return getValue(REMINDER_PERIOD); return notifications;
} }
public void setReminderPeriod(Long reminderPeriod) { public void setReminderPeriod(Long reminderPeriod) {
@ -731,7 +717,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public Integer getEstimatedSeconds() { public Integer getEstimatedSeconds() {
return getValue(ESTIMATED_SECONDS); return estimatedSeconds;
} }
public void setElapsedSeconds(Integer elapsedSeconds) { public void setElapsedSeconds(Integer elapsedSeconds) {
@ -763,7 +749,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
private boolean isReminderFlagSet(int flag) { private boolean isReminderFlagSet(int flag) {
return (getReminderFlags() & flag) > 0; return (notificationFlags & flag) > 0;
} }
public boolean isNew() { public boolean isNew() {
@ -781,15 +767,7 @@ public class Task extends AbstractModel implements Parcelable {
} }
public void setUuid(String uuid) { public void setUuid(String uuid) {
if (setValues == null) { setValue(UUID, uuid);
setValues = new ContentValues();
}
if(NO_UUID.equals(uuid)) {
clearValue(UUID);
} else {
setValues.put(UUID.name, uuid);
}
} }
public static boolean isUuidEmpty(String uuid) { public static boolean isUuidEmpty(String uuid) {
@ -830,8 +808,40 @@ public class Task extends AbstractModel implements Parcelable {
dest.writeLong(timerStart); dest.writeLong(timerStart);
dest.writeString(title); dest.writeString(title);
dest.writeString(remoteId); dest.writeString(remoteId);
dest.writeParcelable(setValues, 0); dest.writeList(newArrayList(setValues));
dest.writeParcelable(values, 0);
dest.writeMap(transitoryData); dest.writeMap(transitoryData);
} }
@Override
public String toString() {
return "Task{" +
"id=" + id +
", title='" + title + '\'' +
", importance=" + importance +
", setValues=" + setValues +
", dueDate=" + dueDate +
", transitoryData=" + transitoryData +
", hideUntil=" + hideUntil +
", created=" + created +
", modified=" + modified +
", completed=" + completed +
", deleted=" + deleted +
", notes='" + notes + '\'' +
", estimatedSeconds=" + estimatedSeconds +
", elapsedSeconds=" + elapsedSeconds +
", timerStart=" + timerStart +
", notificationFlags=" + notificationFlags +
", notifications=" + notifications +
", lastNotified=" + lastNotified +
", snoozeTime=" + snoozeTime +
", recurrence='" + recurrence + '\'' +
", repeatUntil=" + repeatUntil +
", calendarUri='" + calendarUri + '\'' +
", remoteId='" + remoteId + '\'' +
'}';
}
public int getGoogleTaskIndent() {
return googleTaskIndent;
}
} }

@ -5,7 +5,8 @@
*/ */
package com.todoroo.astrid.data; package com.todoroo.astrid.data;
import android.content.ContentValues; import java.util.Collection;
import java.util.Set;
/** /**
* Data access object for accessing Astrid's {@link Task} table. If you * Data access object for accessing Astrid's {@link Task} table. If you
@ -18,22 +19,22 @@ import android.content.ContentValues;
public class TaskApiDao { public class TaskApiDao {
/** @return true if task change shouldn't be broadcast */ /** @return true if task change shouldn't be broadcast */
public static boolean insignificantChange(ContentValues values) { public static boolean insignificantChange(Collection<String> values) {
if(values == null || values.size() == 0) { if(values == null || values.size() == 0) {
return true; return true;
} }
if(values.containsKey(Task.REMINDER_LAST.name) && if(values.contains(Task.REMINDER_LAST.name) &&
values.size() <= 2) { values.size() <= 2) {
return true; return true;
} }
if(values.containsKey(Task.REMINDER_SNOOZE.name) && if(values.contains(Task.REMINDER_SNOOZE.name) &&
values.size() <= 2) { values.size() <= 2) {
return true; return true;
} }
if(values.containsKey(Task.TIMER_START.name) && if(values.contains(Task.TIMER_START.name) &&
values.size() <= 2) { values.size() <= 2) {
return true; return true;
} }

@ -114,16 +114,16 @@ public class TaskCreator {
task.mergeWithoutReplacement(forTask); task.mergeWithoutReplacement(forTask);
} }
if (!task.containsValue(Task.IMPORTANCE)) { if (!task.isModified(Task.IMPORTANCE)) {
task.setImportance(preferences.getIntegerFromString(R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO)); task.setImportance(preferences.getIntegerFromString(R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO));
} }
if(!task.containsValue(Task.DUE_DATE)) { if(!task.isModified(Task.DUE_DATE)) {
task.setDueDate(Task.createDueDate( task.setDueDate(Task.createDueDate(
preferences.getIntegerFromString(R.string.p_default_urgency_key, Task.URGENCY_NONE), 0)); preferences.getIntegerFromString(R.string.p_default_urgency_key, Task.URGENCY_NONE), 0));
} }
if(!task.containsValue(Task.HIDE_UNTIL)) { if(!task.isModified(Task.HIDE_UNTIL)) {
int setting = preferences.getIntegerFromString(R.string.p_default_hideUntil_key, int setting = preferences.getIntegerFromString(R.string.p_default_hideUntil_key,
Task.HIDE_UNTIL_NONE); Task.HIDE_UNTIL_NONE);
task.setHideUntil(task.createHideUntil(setting, 0)); task.setHideUntil(task.createHideUntil(setting, 0));
@ -137,13 +137,13 @@ public class TaskCreator {
} }
public static void setDefaultReminders(Preferences preferences, Task task) { public static void setDefaultReminders(Preferences preferences, Task task) {
if(!task.containsValue(Task.REMINDER_PERIOD)) { if(!task.isModified(Task.REMINDER_PERIOD)) {
task.setReminderPeriod(DateUtilities.ONE_HOUR * task.setReminderPeriod(DateUtilities.ONE_HOUR *
preferences.getIntegerFromString(R.string.p_rmd_default_random_hours, preferences.getIntegerFromString(R.string.p_rmd_default_random_hours,
0)); 0));
} }
if(!task.containsValue(Task.REMINDER_FLAGS)) { if(!task.isModified(Task.REMINDER_FLAGS)) {
task.setReminderFlags(preferences.getDefaultReminders() | preferences.getDefaultRingMode()); task.setReminderFlags(preferences.getDefaultReminders() | preferences.getDefaultRingMode());
} }
} }

@ -23,9 +23,6 @@ public class GoogleTask {
@Deprecated @Deprecated
public static final Property.IntegerProperty INDENT = new Property.IntegerProperty(GoogleTask.TABLE, "indent"); public static final Property.IntegerProperty INDENT = new Property.IntegerProperty(GoogleTask.TABLE, "indent");
@Deprecated
public static final Property.LongProperty ORDER = new Property.LongProperty(GoogleTask.TABLE, "`order`");
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
private long id; private long id;

@ -26,10 +26,15 @@ import org.tasks.notifications.NotificationManager;
import org.tasks.receivers.PushReceiver; import org.tasks.receivers.PushReceiver;
import org.tasks.scheduling.RefreshScheduler; import org.tasks.scheduling.RefreshScheduler;
import java.util.ArrayList;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static com.todoroo.astrid.dao.TaskDao.TRANS_SUPPRESS_REFRESH; import static com.todoroo.astrid.dao.TaskDao.TRANS_SUPPRESS_REFRESH;
public class AfterSaveIntentService extends InjectingJobIntentService { public class AfterSaveIntentService extends InjectingJobIntentService {
@ -37,10 +42,10 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
private static final String EXTRA_TASK_ID = "extra_task_id"; private static final String EXTRA_TASK_ID = "extra_task_id";
private static final String EXTRA_MODIFIED_VALUES = "extra_modified_values"; private static final String EXTRA_MODIFIED_VALUES = "extra_modified_values";
public static void enqueue(Context context, long taskId, ContentValues modifiedValues) { public static void enqueue(Context context, long taskId, Set<String> modifiedValues) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(EXTRA_TASK_ID, taskId); intent.putExtra(EXTRA_TASK_ID, taskId);
intent.putExtra(EXTRA_MODIFIED_VALUES, modifiedValues); intent.putStringArrayListExtra(EXTRA_MODIFIED_VALUES, newArrayList(modifiedValues));
AfterSaveIntentService.enqueueWork(context, AfterSaveIntentService.class, JobManager.JOB_ID_TASK_STATUS_CHANGE, intent); AfterSaveIntentService.enqueueWork(context, AfterSaveIntentService.class, JobManager.JOB_ID_TASK_STATUS_CHANGE, intent);
} }
@ -59,7 +64,7 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
super.onHandleWork(intent); super.onHandleWork(intent);
long taskId = intent.getLongExtra(EXTRA_TASK_ID, -1); long taskId = intent.getLongExtra(EXTRA_TASK_ID, -1);
ContentValues modifiedValues = intent.getParcelableExtra(EXTRA_MODIFIED_VALUES); ArrayList<String> modifiedValues = intent.getStringArrayListExtra(EXTRA_MODIFIED_VALUES);
if (taskId == -1 || modifiedValues == null) { if (taskId == -1 || modifiedValues == null) {
Timber.e("Invalid extras, taskId=%s modifiedValues=%s", taskId, modifiedValues); Timber.e("Invalid extras, taskId=%s modifiedValues=%s", taskId, modifiedValues);
@ -72,11 +77,11 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
return; return;
} }
if(modifiedValues.containsKey(Task.DUE_DATE.name) || if(modifiedValues.contains(Task.DUE_DATE.name) ||
modifiedValues.containsKey(Task.REMINDER_FLAGS.name) || modifiedValues.contains(Task.REMINDER_FLAGS.name) ||
modifiedValues.containsKey(Task.REMINDER_PERIOD.name) || modifiedValues.contains(Task.REMINDER_PERIOD.name) ||
modifiedValues.containsKey(Task.REMINDER_LAST.name) || modifiedValues.contains(Task.REMINDER_LAST.name) ||
modifiedValues.containsKey(Task.REMINDER_SNOOZE.name)) { modifiedValues.contains(Task.REMINDER_SNOOZE.name)) {
reminderService.scheduleAlarm(task); reminderService.scheduleAlarm(task);
} }
@ -84,8 +89,8 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
return; return;
} }
boolean completionDateModified = modifiedValues.containsKey(Task.COMPLETION_DATE.name); boolean completionDateModified = modifiedValues.contains(Task.COMPLETION_DATE.name);
boolean deletionDateModified = modifiedValues.containsKey(Task.DELETION_DATE.name); boolean deletionDateModified = modifiedValues.contains(Task.DELETION_DATE.name);
boolean justCompleted = completionDateModified && task.isCompleted(); boolean justCompleted = completionDateModified && task.isCompleted();
boolean justDeleted = deletionDateModified && task.isDeleted(); boolean justDeleted = deletionDateModified && task.isDeleted();

@ -195,7 +195,7 @@ class ViewHolder extends MultiSelectorBindingHolder {
void bindView(TodorooCursor cursor) { void bindView(TodorooCursor cursor) {
tagsString = cursor.get(TaskAdapter.TAGS); tagsString = cursor.get(TaskAdapter.TAGS);
hasFiles = cursor.get(TaskAdapter.FILE_ID_PROPERTY) > 0; hasFiles = (Long) cursor.get(TaskAdapter.FILE_ID_PROPERTY) > 0;
// TODO: see if this is a performance issue // TODO: see if this is a performance issue
task = new Task(cursor); task = new Task(cursor);

@ -161,14 +161,13 @@ public class CalendarControlSet extends TaskEditControlFragment {
ContentValues updateValues = new ContentValues(); ContentValues updateValues = new ContentValues();
// check if we need to update the item // check if we need to update the item
ContentValues setValues = task.getSetValues(); if(task.isModified(Task.TITLE)) {
if(setValues.containsKey(Task.TITLE.name)) {
updateValues.put(CalendarContract.Events.TITLE, task.getTitle()); updateValues.put(CalendarContract.Events.TITLE, task.getTitle());
} }
if(setValues.containsKey(Task.NOTES.name)) { if(task.isModified(Task.NOTES)) {
updateValues.put(CalendarContract.Events.DESCRIPTION, task.getNotes()); updateValues.put(CalendarContract.Events.DESCRIPTION, task.getNotes());
} }
if(setValues.containsKey(Task.DUE_DATE.name) || setValues.containsKey(Task.ESTIMATED_SECONDS.name)) { if(task.isModified(Task.DUE_DATE) || task.isModified(Task.ESTIMATED_SECONDS)) {
gcalHelper.createStartAndEndDate(task, updateValues); gcalHelper.createStartAndEndDate(task, updateValues);
} }

@ -16,6 +16,8 @@ import org.tasks.injection.FragmentComponent;
import butterknife.BindView; import butterknife.BindView;
import butterknife.OnTextChanged; import butterknife.OnTextChanged;
import static com.google.common.base.Strings.isNullOrEmpty;
public class DescriptionControlSet extends TaskEditControlFragment { public class DescriptionControlSet extends TaskEditControlFragment {
public static final int TAG = R.string.TEA_ctrl_notes_pref; public static final int TAG = R.string.TEA_ctrl_notes_pref;
@ -34,7 +36,7 @@ public class DescriptionControlSet extends TaskEditControlFragment {
} else { } else {
description = savedInstanceState.getString(EXTRA_DESCRIPTION); description = savedInstanceState.getString(EXTRA_DESCRIPTION);
} }
if (!Strings.isNullOrEmpty(description)) { if (!isNullOrEmpty(description)) {
editText.setTextKeepState(description); editText.setTextKeepState(description);
} }
return view; return view;
@ -74,7 +76,9 @@ public class DescriptionControlSet extends TaskEditControlFragment {
@Override @Override
public boolean hasChanges(Task original) { public boolean hasChanges(Task original) {
return !description.equals(original.getNotes()); return isNullOrEmpty(description)
? isNullOrEmpty(original.getNotes())
: description.equals(original.getNotes());
} }
@Override @Override

Loading…
Cancel
Save