Skip to content

Commit

Permalink
New constraints and defaults (forbid DUE=START) (#309)
Browse files Browse the repository at this point in the history
* New constraints and defaults:
- forbid DUE=START;
- use defaults satisfying DUE>START;
- shift DUE if START violates constraint

* replace TimeFieldAdapter with FieldAdapter<Time>
  • Loading branch information
korelstar authored and dmfs committed Jul 3, 2017
1 parent 58a46b4 commit 722d9c9
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 41 deletions.
Expand Up @@ -22,7 +22,6 @@
import org.dmfs.tasks.R;
import org.dmfs.tasks.groupings.cursorloaders.SearchHistoryCursorLoaderFactory;
import org.dmfs.tasks.model.TaskFieldAdapters;
import org.dmfs.tasks.model.adapters.TimeFieldAdapter;
import org.dmfs.tasks.utils.ExpandableChildDescriptor;
import org.dmfs.tasks.utils.ExpandableGroupDescriptor;
import org.dmfs.tasks.utils.ExpandableGroupDescriptorAdapter;
Expand Down Expand Up @@ -56,11 +55,6 @@
*/
public class BySearch extends AbstractGroupingFactory
{
/**
* An adapter to load the due date from the tasks projection.
*/
public final static TimeFieldAdapter TASK_DUE_ADAPTER = new TimeFieldAdapter(Tasks.DUE, Tasks.TZ, Tasks.IS_ALLDAY);

/**
* A {@link ViewDescriptor} that knows how to present the tasks in the task list grouped by priority.
*/
Expand Down
Expand Up @@ -17,12 +17,16 @@

package org.dmfs.tasks.model;

import android.text.format.Time;

import org.dmfs.provider.tasks.TaskContract;
import org.dmfs.provider.tasks.TaskContract.Tasks;
import org.dmfs.tasks.model.adapters.BooleanFieldAdapter;
import org.dmfs.tasks.model.adapters.ChecklistFieldAdapter;
import org.dmfs.tasks.model.adapters.ColorFieldAdapter;
import org.dmfs.tasks.model.adapters.CustomizedDefaultFieldAdapter;
import org.dmfs.tasks.model.adapters.DescriptionStringFieldAdapter;
import org.dmfs.tasks.model.adapters.FieldAdapter;
import org.dmfs.tasks.model.adapters.FloatFieldAdapter;
import org.dmfs.tasks.model.adapters.FormattedStringFieldAdapter;
import org.dmfs.tasks.model.adapters.IntegerFieldAdapter;
Expand All @@ -31,9 +35,11 @@
import org.dmfs.tasks.model.adapters.TimezoneFieldAdapter;
import org.dmfs.tasks.model.adapters.UrlFieldAdapter;
import org.dmfs.tasks.model.constraints.AdjustPercentComplete;
import org.dmfs.tasks.model.constraints.After;
import org.dmfs.tasks.model.constraints.BeforeOrShiftTime;
import org.dmfs.tasks.model.constraints.ChecklistConstraint;
import org.dmfs.tasks.model.constraints.NotBefore;
import org.dmfs.tasks.model.constraints.ShiftIfAfter;
import org.dmfs.tasks.model.defaults.DefaultAfter;
import org.dmfs.tasks.model.defaults.DefaultBefore;


/**
Expand Down Expand Up @@ -109,17 +115,17 @@ public final class TaskFieldAdapters
* Private adapter for the start date of a task. We need this to reference DTSTART from DUE.
*/
private final static TimeFieldAdapter _DTSTART = new TimeFieldAdapter(Tasks.DTSTART, Tasks.TZ, Tasks.IS_ALLDAY);
private final static TimeFieldAdapter _DUE = new TimeFieldAdapter(Tasks.DUE, Tasks.TZ, Tasks.IS_ALLDAY);

/**
* Adapter for the due date of a task.
*/
public final static TimeFieldAdapter DUE = (TimeFieldAdapter) new TimeFieldAdapter(Tasks.DUE, Tasks.TZ, Tasks.IS_ALLDAY).addContraint(new NotBefore(
_DTSTART));
public final static FieldAdapter<Time> DUE = new CustomizedDefaultFieldAdapter<Time>(_DUE, new DefaultAfter(_DTSTART)).addContraint(new After(_DTSTART));

/**
* Adapter for the start date of a task.
*/
public final static TimeFieldAdapter DTSTART = (TimeFieldAdapter) _DTSTART.addContraint(new ShiftIfAfter(DUE));
public final static FieldAdapter<Time> DTSTART = new CustomizedDefaultFieldAdapter<Time>(_DTSTART, new DefaultBefore(DUE)).addContraint(new BeforeOrShiftTime(DUE));

/**
* Adapter for the completed date of a task.
Expand Down
@@ -0,0 +1,93 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.dmfs.tasks.model.adapters;

import android.content.ContentValues;
import android.database.Cursor;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.OnContentChangeListener;
import org.dmfs.tasks.model.defaults.Default;


/**
* Enhances an existing {@link FieldAdapter} with a custom default value generator.
*
* @param <Type> Type of the {@link FieldAdapter}
*/
public class CustomizedDefaultFieldAdapter<Type> extends FieldAdapter<Type> {

private final FieldAdapter<Type> mFieldAdapter;
private final Default<Type> mDefault;

/**
* Constructor for a new CustomizedDefaultFieldAdapter
* @param fieldAdapter FieldAdapter which forms the base for this Adapter.
* @param defaultGenerator Custom default value generator.
*/
public CustomizedDefaultFieldAdapter(FieldAdapter<Type> fieldAdapter, Default<Type> defaultGenerator) {
if (fieldAdapter == null) {
throw new IllegalArgumentException("fieldAdapter must not be null");
}
if (defaultGenerator == null) {
throw new IllegalArgumentException("defaultGenerator must not be null");
}
this.mFieldAdapter = fieldAdapter;
this.mDefault = defaultGenerator;
}

@Override
public Type get(ContentSet values) {
return mFieldAdapter.get(values);
}

@Override
public Type get(Cursor cursor) {
return mFieldAdapter.get(cursor);
}

/**
* Get a default value for the {@link FieldAdapter} based on the {@link Default} instance.
*
* @param values The {@link ContentSet}.
* @return A default Value
*/
@Override
public Type getDefault(ContentSet values) {
Type defaultValue = mFieldAdapter.getDefault(values);
return mDefault.getCustomDefault(values, defaultValue);
}

@Override
public void set(ContentSet values, Type value) {
mFieldAdapter.set(values, value);
}

@Override
public void set(ContentValues values, Type value) {
mFieldAdapter.set(values, value);
}

@Override
public void registerListener(ContentSet values, OnContentChangeListener listener, boolean initialNotification) {
mFieldAdapter.registerListener(values, listener, initialNotification);
}

@Override
public void unregisterListener(ContentSet values, OnContentChangeListener listener) {
mFieldAdapter.unregisterListener(values, listener);
}
}
Expand Up @@ -156,13 +156,7 @@ public Time getDefault(ContentSet values)
{
// make it an allday value
value.set(value.monthDay, value.month, value.year);
}
else
{
value.second = 0;
// round up to next quarter-hour
value.minute = ((value.minute + 14) / 15) * 15;
value.normalize(false);
value.timezone = Time.TIMEZONE_UTC; // all-day values are saved in UTC
}

return value;
Expand Down
@@ -0,0 +1,53 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.dmfs.tasks.model.constraints;

import android.text.format.Time;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.adapters.FieldAdapter;
import org.dmfs.tasks.model.defaults.Default;
import org.dmfs.tasks.model.defaults.DefaultAfter;


/**
* Ensure a time is after a specific reference time. The new value will be set using {@link DefaultAfter} otherwise.
*/
public class After extends AbstractConstraint<Time>
{
private final FieldAdapter<Time> mReferenceAdapter;
private final Default<Time> mDefault;


public After(FieldAdapter<Time> referenceAdapter)
{
mReferenceAdapter = referenceAdapter;
mDefault = new DefaultAfter(referenceAdapter);
}


@Override
public Time apply(ContentSet currentValues, Time oldValue, Time newValue)
{
Time reference = mReferenceAdapter.get(currentValues);
if (reference != null && newValue != null && !newValue.after(reference))
{
newValue.set(mDefault.getCustomDefault(currentValues, reference));
}
return newValue;
}

}
@@ -0,0 +1,77 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.dmfs.tasks.model.constraints;

import android.text.format.Time;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.adapters.FieldAdapter;
import org.dmfs.tasks.model.defaults.Default;
import org.dmfs.tasks.model.defaults.DefaultAfter;


/**
* Ensure a time is before a specific reference time.
* Otherwise, shift the reference time by the same amount that value has been shifted.
* If this still violated the constraint, then set the reference value to its default value.
*/
public class BeforeOrShiftTime extends AbstractConstraint<Time>
{
private final FieldAdapter<Time> mReferenceAdapter;
private final Default<Time> mDefault;


public BeforeOrShiftTime(FieldAdapter<Time> referenceAdapter)
{
mReferenceAdapter = referenceAdapter;
mDefault = new DefaultAfter(null);
}


@Override
public Time apply(ContentSet currentValues, Time oldValue, Time newValue)
{
Time reference = mReferenceAdapter.get(currentValues);
if (reference != null && newValue != null)
{
if (oldValue != null && !newValue.before(reference))
{
// try to shift the reference value
long diff = newValue.toMillis(false) - oldValue.toMillis(false);
if (diff > 0)
{
boolean isAllDay = reference.allDay;
reference.set(reference.toMillis(false) + diff);

// ensure the event is still allday if is was allday before.
if (isAllDay)
{
reference.set(reference.monthDay, reference.month, reference.year);
}
mReferenceAdapter.set(currentValues, reference);
}
}
if (!newValue.before(reference))
{
// constraint is still violated, so set reference to its default value
reference.set(mDefault.getCustomDefault(currentValues, newValue));
mReferenceAdapter.set(currentValues, reference);
}
}
return newValue;
}

}
Expand Up @@ -18,7 +18,7 @@
package org.dmfs.tasks.model.constraints;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.adapters.TimeFieldAdapter;
import org.dmfs.tasks.model.adapters.FieldAdapter;

import android.text.format.Time;

Expand All @@ -30,10 +30,10 @@
*/
public class NotAfter extends AbstractConstraint<Time>
{
private final TimeFieldAdapter mTimeAdapter;
private final FieldAdapter<Time> mTimeAdapter;


public NotAfter(TimeFieldAdapter adapter)
public NotAfter(FieldAdapter<Time> adapter)
{
mTimeAdapter = adapter;
}
Expand Down
Expand Up @@ -18,7 +18,7 @@
package org.dmfs.tasks.model.constraints;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.adapters.TimeFieldAdapter;
import org.dmfs.tasks.model.adapters.FieldAdapter;

import android.text.format.Time;

Expand All @@ -30,10 +30,10 @@
*/
public class NotBefore extends AbstractConstraint<Time>
{
private final TimeFieldAdapter mTimeAdapter;
private final FieldAdapter<Time> mTimeAdapter;


public NotBefore(TimeFieldAdapter adapter)
public NotBefore(FieldAdapter<Time> adapter)
{
mTimeAdapter = adapter;
}
Expand Down
Expand Up @@ -18,7 +18,7 @@
package org.dmfs.tasks.model.constraints;

import org.dmfs.tasks.model.ContentSet;
import org.dmfs.tasks.model.adapters.TimeFieldAdapter;
import org.dmfs.tasks.model.adapters.FieldAdapter;

import android.text.format.Time;

Expand All @@ -30,10 +30,10 @@
*/
public class ShiftIfAfter extends AbstractConstraint<Time>
{
private final TimeFieldAdapter mTimeAdapter;
private final FieldAdapter<Time> mTimeAdapter;


public ShiftIfAfter(TimeFieldAdapter adapter)
public ShiftIfAfter(FieldAdapter<Time> adapter)
{
mTimeAdapter = adapter;
}
Expand Down

0 comments on commit 722d9c9

Please sign in to comment.