Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RowData to create recurring tasks. Implements #460 #604

Merged
merged 3 commits into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions opentaskspal/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
exclude module: 'jems'
}
implementation 'org.dmfs:rfc5545-datetime:' + RFC5545_DATETIME_VERSION
implementation 'org.dmfs:lib-recur:' + LIB_RECUR_VERSION
implementation 'org.dmfs:jems:' + JEMS_VERSION
implementation 'com.github.dmfs.bolts:color-bolts:' + BOLTS_VERSION

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2018 dmfs GmbH
*
* 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.opentaskspal.rowdata;

import android.content.ContentProviderOperation;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import org.dmfs.android.contentpal.RowData;
import org.dmfs.android.contentpal.TransactionContext;
import org.dmfs.jems.iterable.decorators.Mapped;
import org.dmfs.rfc5545.DateTime;


/**
* {@link RowData} for a field of {@link DateTime}s.
*
* @param <Contract>
* The contract of the table this row data goes to.
*
* @author Marten Gajda
*/
public final class DateTimeListData<Contract> implements RowData<Contract>
{
private final String mField;
private final Iterable<DateTime> mDateTimes;


public DateTimeListData(String field, @NonNull Iterable<DateTime> dateTimes)
{
mField = field;
mDateTimes = dateTimes;
}


@NonNull
@Override
public ContentProviderOperation.Builder updatedBuilder(@NonNull TransactionContext transactionContext, @NonNull ContentProviderOperation.Builder builder)
{
String value = TextUtils.join(",",
new Mapped<>(DateTime::toString, new Mapped<>(dt -> dt.isFloating() ? dt : dt.shiftTimeZone(DateTime.UTC), mDateTimes)));
return builder.withValue(mField, value.isEmpty() ? null : value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017 dmfs GmbH
*
* 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.opentaskspal.tasks;

import android.support.annotation.NonNull;

import org.dmfs.android.contentpal.RowData;
import org.dmfs.android.contentpal.rowdata.DelegatingRowData;
import org.dmfs.opentaskspal.rowdata.DateTimeListData;
import org.dmfs.rfc5545.DateTime;
import org.dmfs.tasks.contract.TaskContract;


/**
* {@link RowData} for tasks with EXDATEs
* <p>
* TODO: how to make sure this is only ever used with tasks having a start and/or due date?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that be the responsibility of the provider? To ignore these fields in the input if there is no start/due date.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a content provider client must send valid data. I want to reduce this kind of fault tolerance to the bare minimum. The provider is supposed to throw if it receives recurrence data for tasks without start or due (at present it doesn't).

*
* @author Marten Gajda
*/
public final class ExDatesTaskData extends DelegatingRowData<TaskContract.Tasks>
{
public ExDatesTaskData(@NonNull Iterable<DateTime> exdates)
{
super(new DateTimeListData<>(TaskContract.Tasks.EXDATE, exdates));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017 dmfs GmbH
*
* 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.opentaskspal.tasks;

import android.support.annotation.NonNull;

import org.dmfs.android.contentpal.RowData;
import org.dmfs.android.contentpal.rowdata.DelegatingRowData;
import org.dmfs.opentaskspal.rowdata.DateTimeListData;
import org.dmfs.rfc5545.DateTime;
import org.dmfs.tasks.contract.TaskContract;


/**
* {@link RowData} for tasks with RDATEs.
* <p>
* TODO: how to make sure this is only ever used with tasks having a start and/or due date?
*
* @author Marten Gajda
*/
public final class RDatesTaskData extends DelegatingRowData<TaskContract.Tasks>
{
public RDatesTaskData(@NonNull Iterable<DateTime> rdates)
{
super(new DateTimeListData<>(TaskContract.Tasks.RDATE, rdates));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2017 dmfs GmbH
*
* 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.opentaskspal.tasks;

import android.content.ContentProviderOperation;
import android.support.annotation.NonNull;

import org.dmfs.android.contentpal.RowData;
import org.dmfs.android.contentpal.TransactionContext;
import org.dmfs.rfc5545.recur.RecurrenceRule;
import org.dmfs.tasks.contract.TaskContract;


/**
* {@link RowData} for tasks with a recurrence rule.
* <p>
* TODO: how to make sure this is only ever used with tasks having a start and/or due date?
*
* @author Marten Gajda
*/
public final class RRuleTaskData implements RowData<TaskContract.Tasks>
{
private final RecurrenceRule mRule;


public RRuleTaskData(@NonNull RecurrenceRule rule)
{
mRule = rule;
}


@NonNull
@Override
public ContentProviderOperation.Builder updatedBuilder(@NonNull TransactionContext transactionContext, @NonNull ContentProviderOperation.Builder builder)
{
return builder.withValue(TaskContract.Tasks.RRULE, mRule.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2018 dmfs GmbH
*
* 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.opentaskspal.rowdata;

import org.dmfs.iterables.SingletonIterable;
import org.dmfs.iterables.elementary.Seq;
import org.dmfs.rfc5545.DateTime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.dmfs.android.contentpal.testing.contentoperationbuilder.WithValues.withValuesOnly;
import static org.dmfs.android.contentpal.testing.contentvalues.Containing.containing;
import static org.dmfs.android.contentpal.testing.contentvalues.NullValue.withNullValue;
import static org.dmfs.android.contentpal.testing.rowdata.RowDataMatcher.builds;
import static org.dmfs.iterables.EmptyIterable.instance;
import static org.junit.Assert.assertThat;


/**
* @author Marten Gajda
*/
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class DateTimeListDataTest
{
@Test
public void testEmpty() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname", instance()),
builds(
withValuesOnly(
withNullValue("somefieldname"))));
}


@Test
public void testSingle() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname", new SingletonIterable<>(DateTime.parse("Europe/Berlin", "20171212T123456"))),
builds(
withValuesOnly(
containing("somefieldname", "20171212T113456Z"))));
}


@Test
public void testSingleFloating() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname", new SingletonIterable<>(DateTime.parse("20171212T123456"))),
builds(
withValuesOnly(
containing("somefieldname", "20171212T123456"))));
}


@Test
public void testSingleAllDay() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname", new SingletonIterable<>(DateTime.parse("20171212"))),
builds(
withValuesOnly(
containing("somefieldname", "20171212"))));
}


@Test
public void testMulti1() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname",
new Seq<>(
DateTime.parse("Europe/Berlin", "20171212T123456"),
DateTime.parse("UTC", "20171213T123456"))),
builds(
withValuesOnly(
containing("somefieldname", "20171212T113456Z,20171213T123456Z"))));
}


@Test
public void testMulti2() throws Exception
{
assertThat(new DateTimeListData<>("somefieldname",
new Seq<>(
DateTime.parse("Europe/Berlin", "20171212T123456"),
DateTime.parse("UTC", "20171213T123456"),
DateTime.parse("America/New_York", "20171214T123456"))),
builds(
withValuesOnly(
containing("somefieldname", "20171212T113456Z,20171213T123456Z,20171214T173456Z"))));
}

}
Loading