Load task values into fields

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

@ -2,7 +2,6 @@ package com.todoroo.astrid.model;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
@ -11,15 +10,11 @@ import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences;
import java.util.Map;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.tasks.Freeze.freezeClock;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@ -27,11 +22,10 @@ import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class TaskTest extends InjectingTestCase {
@Inject TaskDao taskDao;
@Inject Preferences preferences;
@Test
public void testNewTaskHasNoCreationDate() {
assertFalse(new Task().containsValue(Task.CREATION_DATE));
assertFalse(new Task().isModified(Task.CREATION_DATE));
}
@Test
@ -51,17 +45,6 @@ public class TaskTest extends InjectingTestCase {
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
protected void inject(TestComponent component) {
component.inject(this);

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

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

@ -249,33 +249,26 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
//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
//deleted tasks
if (newlyCreated &&
(!task.containsValue(Task.TITLE) || TextUtils.isEmpty(task.getTitle()) || task.getDeletionDate() > 0)) {
if (newlyCreated && (TextUtils.isEmpty(task.getTitle()) || task.getDeletionDate() > 0)) {
return;
}
//Update the remote model's changed properties
if (task.containsValue(Task.DELETION_DATE) && task.isDeleted()) {
if (task.isDeleted()) {
remoteModel.setDeleted(true);
}
if (task.containsValue(Task.TITLE)) {
remoteModel.setTitle(task.getTitle());
}
if (task.containsValue(Task.NOTES)) {
remoteModel.setNotes(task.getNotes());
}
if (task.containsValue(Task.DUE_DATE) && task.hasDueDate()) {
remoteModel.setTitle(task.getTitle());
remoteModel.setNotes(task.getNotes());
if (task.hasDueDate()) {
remoteModel.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(task.getDueDate()));
}
if (task.containsValue(Task.COMPLETION_DATE)) {
if (task.isCompleted()) {
remoteModel.setCompleted(GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.getCompletionDate()));
remoteModel.setStatus("completed"); //$NON-NLS-1$
} else {
remoteModel.setCompleted(null);
remoteModel.setStatus("needsAction"); //$NON-NLS-1$
}
if (task.isCompleted()) {
remoteModel.setCompleted(GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.getCompletionDate()));
remoteModel.setStatus("completed"); //$NON-NLS-1$
} else {
remoteModel.setCompleted(null);
remoteModel.setStatus("needsAction"); //$NON-NLS-1$
}
if (!newlyCreated) {
@ -382,8 +375,8 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
if(task.task.isSaved()) {
Task local = taskDao.fetch(task.task.getId());
if (local == null) {
task.task.clearValue(Task.ID);
task.task.clearValue(Task.UUID);
task.task.setId(NO_ID);
task.task.setUuid(NO_UUID);
} else {
mergeDates(task.task, local);
}

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

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

@ -8,22 +8,18 @@ package com.todoroo.andlib.data;
import android.arch.persistence.room.Ignore;
import android.content.ContentValues;
import com.todoroo.andlib.data.Property.IntegerProperty;
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 org.tasks.data.Tag;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import timber.log.Timber;
import static com.todoroo.andlib.data.Property.DoubleProperty;
import static com.google.common.collect.Sets.newHashSet;
/**
* <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 {
private static final ContentValuesSavingVisitor saver = new ContentValuesSavingVisitor();
/** id property common to all models */
protected static final String ID_PROPERTY_NAME = "_id"; //$NON-NLS-1$
@ -50,13 +44,10 @@ public abstract class AbstractModel {
// --- abstract methods
public interface ValueReader<TYPE> {
TYPE getValue(Task instance);
public interface ValueWriter<TYPE> {
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 Source Ordering:
@ -68,46 +59,15 @@ public abstract class AbstractModel {
/** User set values */
@Ignore
protected ContentValues setValues = null;
/** Values from database */
@Ignore
protected ContentValues values = null;
protected final Set<String> setValues = new HashSet<>();
/** Transitory Metadata (not saved in database) */
@Ignore
protected HashMap<String, Object> transitoryData = null;
protected AbstractModel() {
}
protected AbstractModel(TodorooCursor cursor) {
readPropertiesFromCursor(cursor);
}
/** Get the user-set values for this object */
public ContentValues getSetValues() {
return 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;
public Set<String> getSetValues() {
return newHashSet(setValues);
}
/**
@ -115,93 +75,18 @@ public abstract class AbstractModel {
* saved - future saves will not need to write all the data as before.
*/
public void markSaved() {
if(values == null) {
values = setValues;
} else if(setValues != null) {
values.putAll(setValues);
}
setValues = null;
setValues.clear();
}
/**
* Use merged values to compare two models to each other. Must be of
* exactly the same class.
*/
@Override
public boolean equals(Object other) {
if(other == null || other.getClass() != getClass()) {
return false;
private void setValue(String columnName, Object value) {
ValueWriter<Object> writer = Task.roomSetters.get(columnName);
if (writer == null) {
throw new RuntimeException();
}
return getMergedValues().equals(((AbstractModel) other).getMergedValues());
writer.setValue((Task) this, value);
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.
@ -211,15 +96,7 @@ public abstract class AbstractModel {
abstract public long getId();
public void setId(long id) {
if (setValues == null) {
setValues = new ContentValues();
}
if(id == NO_ID) {
clearValue(ID_PROPERTY);
} else {
setValues.put(ID_PROPERTY_NAME, id);
}
setValue(ID_PROPERTY, id);
}
/**
@ -232,54 +109,17 @@ public abstract class AbstractModel {
/**
* @return true if setValues or values contains this property
*/
public boolean containsValue(Property<?> property) {
if(setValues != null && setValues.containsKey(property.getColumnName())) {
return true;
}
if(values != null && values.containsKey(property.getColumnName())) {
return true;
}
return false;
public boolean isModified(Property<?> property) {
return setValues.contains(property.getColumnName());
}
// --- 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!
*/
public synchronized <TYPE> void setValue(Property<TYPE> property,
TYPE value) {
if (setValues == null) {
setValues = new ContentValues();
}
if (!shouldSaveValue(property, value)) {
return;
}
saver.save(property, setValues, value);
public synchronized <TYPE> void setValue(Property<TYPE> property, TYPE value) {
setValue(property.getColumnName(), value);
}
/**
@ -287,26 +127,11 @@ public abstract class AbstractModel {
* keeping the existing value if one already exists
*/
public synchronized void mergeWithoutReplacement(ContentValues other) {
if (setValues == null) {
setValues = new ContentValues();
}
for (Entry<String, Object> item : other.valueSet()) {
if (setValues.containsKey(item.getKey())) {
continue;
String columnName = item.getKey();
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);
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}
*

@ -70,7 +70,7 @@ public class TodorooCursor extends CursorWrapper {
* @param <PROPERTY_TYPE> type to return
* @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);
}

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

@ -7,11 +7,10 @@ package com.todoroo.astrid.dao;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.content.ContentValues;
import android.arch.persistence.room.Update;
import android.content.Context;
import android.database.Cursor;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Criterion;
@ -28,7 +27,7 @@ import org.tasks.jobs.AfterSaveIntentService;
import org.tasks.receivers.PushReceiver;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Set;
import timber.log.Timber;
@ -154,7 +153,7 @@ public abstract class TaskDao {
*
*/
public void save(Task task) {
ContentValues modifiedValues = saveExisting(task);
Set<String> modifiedValues = saveExisting(task);
if (modifiedValues != null) {
AfterSaveIntentService.enqueue(context, task.getId(), modifiedValues);
} else if (task.checkTransitory(SyncFlags.FORCE_SYNC)) {
@ -165,35 +164,30 @@ public abstract class TaskDao {
@Insert
abstract long insert(Task task);
@Update
abstract int update(Task task);
public void createNew(Task task) {
task.id = null;
task.remoteId = task.getUuid();
task.setId(insert(task));
long insert = insert(task);
task.setId(insert);
}
private ContentValues saveExisting(Task item) {
ContentValues values = item.getSetValues();
private Set<String> saveExisting(Task item) {
Set<String> values = item.getSetValues();
if (values == null || values.size() == 0) {
return null;
}
if (!TaskApiDao.insignificantChange(values)) {
if (!values.containsKey(Task.MODIFICATION_DATE.name)) {
if (!values.contains(Task.MODIFICATION_DATE.name)) {
item.setModificationDate(now());
}
}
DatabaseChangeOp update = new DatabaseChangeOp() {
@Override
public boolean makeChange() {
return database.update(values,
AbstractModel.ID_PROPERTY.eq(item.getId()).toString()) > 0;
}
@Override
public String toString() {
return "UPDATE";
}
};
if (updateAndRecordChanges(item, update)) {
int updated = update(item);
if (updated == 1) {
item.markSaved();
database.onDatabaseUpdated();
return values;
}
return null;
@ -229,23 +223,5 @@ public abstract class TaskDao {
Cursor cursor = database.rawQuery(queryString);
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.XmlWriter;
import org.tasks.data.GoogleTask;
import org.tasks.time.DateTime;
import java.util.HashMap;
@ -36,6 +37,7 @@ import java.util.Map;
import timber.log.Timber;
import static com.google.common.collect.Lists.newArrayList;
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_NONE = 3;
// --- defaults
/** Default values container */
private static final Map<String, ValueReader<?>> roomGetters = new HashMap<>();
public static final Map<String, ValueWriter<Object>> roomSetters = new HashMap<>();
static {
roomGetters.put(CALENDAR_URI.name, t -> t.calendarUri);
roomGetters.put(COMPLETION_DATE.name, t -> t.completed);
roomGetters.put(CREATION_DATE.name, t -> t.created);
roomGetters.put(DELETION_DATE.name, t -> t.deleted);
roomGetters.put(DUE_DATE.name, t -> t.dueDate);
roomGetters.put(ELAPSED_SECONDS.name, t -> t.elapsedSeconds);
roomGetters.put(ESTIMATED_SECONDS.name, t -> t.estimatedSeconds);
roomGetters.put(HIDE_UNTIL.name, t -> t.hideUntil);
roomGetters.put(ID.name, t -> t.id);
roomGetters.put(IMPORTANCE.name, t -> t.importance);
roomGetters.put(MODIFICATION_DATE.name, t -> t.modified);
roomGetters.put(NOTES.name, t -> t.notes);
roomGetters.put(RECURRENCE.name, t -> t.recurrence);
roomGetters.put(REMINDER_FLAGS.name, t -> t.notificationFlags);
roomGetters.put(REMINDER_LAST.name, t -> t.lastNotified);
roomGetters.put(REMINDER_PERIOD.name, t -> t.notifications);
roomGetters.put(REMINDER_SNOOZE.name, t -> t.snoozeTime);
roomGetters.put(REPEAT_UNTIL.name, t -> t.repeatUntil);
roomGetters.put(TIMER_START.name, t -> t.timerStart);
roomGetters.put(TITLE.name, t -> t.title);
roomGetters.put(UUID.name, t -> t.remoteId);
roomSetters.put(CALENDAR_URI.name, (t, v) -> t.calendarUri = (String) v);
roomSetters.put(COMPLETION_DATE.name, (t, v) -> t.completed = (Long) v);
roomSetters.put(CREATION_DATE.name, (t, v) -> t.created = (Long) v);
roomSetters.put(DELETION_DATE.name, (t, v) -> t.deleted = (Long) v);
roomSetters.put(DUE_DATE.name, (t, v) -> t.dueDate = v instanceof String ? Long.valueOf((String) v) : (Long) v);
roomSetters.put(ELAPSED_SECONDS.name, (t, v) -> t.elapsedSeconds = (Integer) v);
roomSetters.put(ESTIMATED_SECONDS.name, (t, v) -> t.estimatedSeconds = (Integer) v);
roomSetters.put(HIDE_UNTIL.name, (t, v) -> t.hideUntil = (Long) v);
roomSetters.put(ID.name, (t, v) -> t.id = (Long) v);
roomSetters.put(IMPORTANCE.name, (t, v) -> t.importance = v instanceof String ? Integer.valueOf((String) v) : (Integer) v);
roomSetters.put(MODIFICATION_DATE.name, (t, v) -> t.modified = (Long) v);
roomSetters.put(NOTES.name, (t, v) -> t.notes = (String) v);
roomSetters.put(RECURRENCE.name, (t, v) -> t.recurrence = (String) v);
roomSetters.put(REMINDER_FLAGS.name, (t, v) -> t.notificationFlags = (Integer) v);
roomSetters.put(REMINDER_LAST.name, (t, v) -> t.lastNotified = (Long) v);
roomSetters.put(REMINDER_PERIOD.name, (t, v) -> t.notifications = (Long) v);
roomSetters.put(REMINDER_SNOOZE.name, (t, v) -> t.snoozeTime = (Long) v);
roomSetters.put(REPEAT_UNTIL.name, (t, v) -> t.repeatUntil = (Long) v);
roomSetters.put(TIMER_START.name, (t, v) -> t.timerStart = (Long) v);
roomSetters.put(TITLE.name, (t, v) -> t.title = (String) v);
roomSetters.put(UUID.name, (t, v) -> t.remoteId = (String) v);
roomSetters.put(GoogleTask.INDENT.name, (t, v) -> t.googleTaskIndent = (Integer) v);
}
@Override
public Map<String, ValueReader<?>> getRoomGetters() {
return roomGetters;
}
@Ignore
private int googleTaskIndent;
// --- data access boilerplate
@ -270,7 +268,17 @@ public class Task extends AbstractModel implements Parcelable {
@Ignore
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
@ -343,34 +351,17 @@ public class Task extends AbstractModel implements Parcelable {
timerStart = parcel.readLong();
title = parcel.readString();
remoteId = parcel.readString();
setValues = parcel.readParcelable(ContentValues.class.getClassLoader());
values = parcel.readParcelable(ContentValues.class.getClassLoader());
setValues.addAll(parcel.readArrayList(getClass().getClassLoader()));
transitoryData = parcel.readHashMap(ContentValues.class.getClassLoader());
}
@Override
public long getId() {
if(setValues != null && setValues.containsKey(ID.name)) {
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;
}
return id == null ? NO_ID : id;
}
public String getUuid() {
if(setValues != null && setValues.containsKey(UUID.name)) {
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;
}
return Strings.isNullOrEmpty(remoteId) ? NO_UUID : remoteId;
}
// --- parcelable helpers
@ -391,31 +382,26 @@ public class Task extends AbstractModel implements Parcelable {
/** Checks whether task is done. Requires COMPLETION_DATE */
public boolean isCompleted() {
return getValue(COMPLETION_DATE) > 0;
return completed > 0;
}
/** Checks whether task is deleted. Will return false if DELETION_DATE not read */
public boolean isDeleted() {
// assume false if we didn't load deletion date
if(!containsValue(DELETION_DATE)) {
return false;
} else {
return getValue(DELETION_DATE) > 0;
}
return deleted > 0;
}
/** Checks whether task is hidden. Requires HIDDEN_UNTIL */
public boolean isHidden() {
return getValue(HIDE_UNTIL) > DateUtilities.now();
return hideUntil > DateUtilities.now();
}
public boolean hasHideUntilDate() {
return getValue(HIDE_UNTIL) > 0;
return hideUntil > 0;
}
/** Checks whether task is done. Requires DUE_DATE */
public boolean hasDueDate() {
return getValue(DUE_DATE) > 0;
return dueDate > 0;
}
// --- due and hide until date management
@ -423,12 +409,12 @@ public class Task extends AbstractModel implements Parcelable {
/** urgency array index -> significance */
public static final int URGENCY_NONE = 0;
public static final int URGENCY_TODAY = 1;
public static final int URGENCY_TOMORROW = 2;
public static final int URGENCY_DAY_AFTER = 3;
public static final int URGENCY_NEXT_WEEK = 4;
public static final int URGENCY_IN_TWO_WEEKS = 5;
public static final int URGENCY_NEXT_MONTH = 6;
static final int URGENCY_TODAY = 1;
static final int URGENCY_TOMORROW = 2;
static final int URGENCY_DAY_AFTER = 3;
static final int URGENCY_NEXT_WEEK = 4;
static final int URGENCY_IN_TWO_WEEKS = 5;
static final int URGENCY_NEXT_MONTH = 6;
public static final int URGENCY_SPECIFIC_DAY = 7;
public static final int URGENCY_SPECIFIC_DAY_TIME = 8;
/** hide until array index -> significance */
@ -514,13 +500,13 @@ public class Task extends AbstractModel implements Parcelable {
return 0;
case HIDE_UNTIL_DUE:
case HIDE_UNTIL_DUE_TIME:
date = getValue(DUE_DATE);
date = dueDate;
break;
case HIDE_UNTIL_DAY_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_DAY;
date = dueDate - DateUtilities.ONE_DAY;
break;
case HIDE_UNTIL_WEEK_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_WEEK;
date = dueDate - DateUtilities.ONE_WEEK;
break;
case HIDE_UNTIL_SPECIFIC_DAY:
case HIDE_UNTIL_SPECIFIC_DAY_TIME:
@ -580,7 +566,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getDueDate() {
return getValue(DUE_DATE);
return dueDate;
}
public void setDueDate(Long dueDate) {
@ -588,9 +574,9 @@ public class Task extends AbstractModel implements Parcelable {
}
public void setDueDateAdjustingHideUntil(Long dueDate) {
long oldDueDate = getValue(DUE_DATE);
long oldDueDate = dueDate;
if (oldDueDate > 0) {
long hideUntil = getValue(HIDE_UNTIL);
long hideUntil = this.hideUntil;
if (hideUntil > 0) {
setHideUntil(dueDate > 0 ? hideUntil + dueDate - oldDueDate : 0);
}
@ -599,7 +585,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public String getRecurrence() {
return getValue(RECURRENCE);
return recurrence;
}
public void setRecurrence(String recurrence) {
@ -611,7 +597,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getCreationDate() {
return getValue(CREATION_DATE);
return created;
}
public void setCreationDate(Long creationDate) {
@ -619,7 +605,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public String getTitle() {
return getValue(TITLE);
return title;
}
public void setTitle(String title) {
@ -627,7 +613,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getDeletionDate() {
return getValue(DELETION_DATE);
return deleted;
}
public void setDeletionDate(Long deletionDate) {
@ -635,7 +621,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getHideUntil() {
return getValue(HIDE_UNTIL);
return hideUntil;
}
public void setHideUntil(Long hideUntil) {
@ -643,7 +629,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getReminderLast() {
return getValue(REMINDER_LAST);
return lastNotified;
}
public void setReminderLast(Long reminderLast) {
@ -651,7 +637,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getReminderSnooze() {
return getValue(REMINDER_SNOOZE);
return snoozeTime;
}
public void setReminderSnooze(Long reminderSnooze) {
@ -659,11 +645,11 @@ public class Task extends AbstractModel implements Parcelable {
}
public Integer getElapsedSeconds() {
return getValue(ELAPSED_SECONDS);
return elapsedSeconds;
}
public Long getTimerStart() {
return getValue(TIMER_START);
return timerStart;
}
public void setTimerStart(Long timerStart) {
@ -671,7 +657,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getRepeatUntil() {
return getValue(REPEAT_UNTIL);
return repeatUntil;
}
public void setRepeatUntil(Long repeatUntil) {
@ -679,11 +665,11 @@ public class Task extends AbstractModel implements Parcelable {
}
public String getCalendarURI() {
return getValue(CALENDAR_URI);
return calendarUri;
}
public Integer getImportance() {
return getValue(IMPORTANCE);
return importance;
}
public void setImportance(Integer importance) {
@ -691,7 +677,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getCompletionDate() {
return getValue(COMPLETION_DATE);
return completed;
}
public void setCompletionDate(Long completionDate) {
@ -699,11 +685,11 @@ public class Task extends AbstractModel implements Parcelable {
}
public String getNotes() {
return getValue(NOTES);
return notes;
}
public boolean hasNotes() {
return !TextUtils.isEmpty(getValue(NOTES));
return !TextUtils.isEmpty(notes);
}
public void setNotes(String notes) {
@ -715,7 +701,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Integer getReminderFlags() {
return getValue(REMINDER_FLAGS);
return notificationFlags;
}
public void setReminderFlags(Integer reminderFlags) {
@ -723,7 +709,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Long getReminderPeriod() {
return getValue(REMINDER_PERIOD);
return notifications;
}
public void setReminderPeriod(Long reminderPeriod) {
@ -731,7 +717,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public Integer getEstimatedSeconds() {
return getValue(ESTIMATED_SECONDS);
return estimatedSeconds;
}
public void setElapsedSeconds(Integer elapsedSeconds) {
@ -763,7 +749,7 @@ public class Task extends AbstractModel implements Parcelable {
}
private boolean isReminderFlagSet(int flag) {
return (getReminderFlags() & flag) > 0;
return (notificationFlags & flag) > 0;
}
public boolean isNew() {
@ -781,15 +767,7 @@ public class Task extends AbstractModel implements Parcelable {
}
public void setUuid(String uuid) {
if (setValues == null) {
setValues = new ContentValues();
}
if(NO_UUID.equals(uuid)) {
clearValue(UUID);
} else {
setValues.put(UUID.name, uuid);
}
setValue(UUID, uuid);
}
public static boolean isUuidEmpty(String uuid) {
@ -830,8 +808,40 @@ public class Task extends AbstractModel implements Parcelable {
dest.writeLong(timerStart);
dest.writeString(title);
dest.writeString(remoteId);
dest.writeParcelable(setValues, 0);
dest.writeParcelable(values, 0);
dest.writeList(newArrayList(setValues));
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;
import android.content.ContentValues;
import java.util.Collection;
import java.util.Set;
/**
* Data access object for accessing Astrid's {@link Task} table. If you
@ -18,22 +19,22 @@ import android.content.ContentValues;
public class TaskApiDao {
/** @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) {
return true;
}
if(values.containsKey(Task.REMINDER_LAST.name) &&
if(values.contains(Task.REMINDER_LAST.name) &&
values.size() <= 2) {
return true;
}
if(values.containsKey(Task.REMINDER_SNOOZE.name) &&
if(values.contains(Task.REMINDER_SNOOZE.name) &&
values.size() <= 2) {
return true;
}
if(values.containsKey(Task.TIMER_START.name) &&
if(values.contains(Task.TIMER_START.name) &&
values.size() <= 2) {
return true;
}

@ -114,16 +114,16 @@ public class TaskCreator {
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));
}
if(!task.containsValue(Task.DUE_DATE)) {
if(!task.isModified(Task.DUE_DATE)) {
task.setDueDate(Task.createDueDate(
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,
Task.HIDE_UNTIL_NONE);
task.setHideUntil(task.createHideUntil(setting, 0));
@ -137,13 +137,13 @@ public class TaskCreator {
}
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 *
preferences.getIntegerFromString(R.string.p_rmd_default_random_hours,
0));
}
if(!task.containsValue(Task.REMINDER_FLAGS)) {
if(!task.isModified(Task.REMINDER_FLAGS)) {
task.setReminderFlags(preferences.getDefaultReminders() | preferences.getDefaultRingMode());
}
}

@ -23,9 +23,6 @@ public class GoogleTask {
@Deprecated
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)
@ColumnInfo(name = "_id")
private long id;

@ -26,10 +26,15 @@ import org.tasks.notifications.NotificationManager;
import org.tasks.receivers.PushReceiver;
import org.tasks.scheduling.RefreshScheduler;
import java.util.ArrayList;
import java.util.Set;
import javax.inject.Inject;
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;
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_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.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);
}
@ -59,7 +64,7 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
super.onHandleWork(intent);
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) {
Timber.e("Invalid extras, taskId=%s modifiedValues=%s", taskId, modifiedValues);
@ -72,11 +77,11 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
return;
}
if(modifiedValues.containsKey(Task.DUE_DATE.name) ||
modifiedValues.containsKey(Task.REMINDER_FLAGS.name) ||
modifiedValues.containsKey(Task.REMINDER_PERIOD.name) ||
modifiedValues.containsKey(Task.REMINDER_LAST.name) ||
modifiedValues.containsKey(Task.REMINDER_SNOOZE.name)) {
if(modifiedValues.contains(Task.DUE_DATE.name) ||
modifiedValues.contains(Task.REMINDER_FLAGS.name) ||
modifiedValues.contains(Task.REMINDER_PERIOD.name) ||
modifiedValues.contains(Task.REMINDER_LAST.name) ||
modifiedValues.contains(Task.REMINDER_SNOOZE.name)) {
reminderService.scheduleAlarm(task);
}
@ -84,8 +89,8 @@ public class AfterSaveIntentService extends InjectingJobIntentService {
return;
}
boolean completionDateModified = modifiedValues.containsKey(Task.COMPLETION_DATE.name);
boolean deletionDateModified = modifiedValues.containsKey(Task.DELETION_DATE.name);
boolean completionDateModified = modifiedValues.contains(Task.COMPLETION_DATE.name);
boolean deletionDateModified = modifiedValues.contains(Task.DELETION_DATE.name);
boolean justCompleted = completionDateModified && task.isCompleted();
boolean justDeleted = deletionDateModified && task.isDeleted();

@ -195,7 +195,7 @@ class ViewHolder extends MultiSelectorBindingHolder {
void bindView(TodorooCursor cursor) {
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
task = new Task(cursor);

@ -161,14 +161,13 @@ public class CalendarControlSet extends TaskEditControlFragment {
ContentValues updateValues = new ContentValues();
// check if we need to update the item
ContentValues setValues = task.getSetValues();
if(setValues.containsKey(Task.TITLE.name)) {
if(task.isModified(Task.TITLE)) {
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());
}
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);
}

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

Loading…
Cancel
Save