Commit 4030355e authored by bernhard's avatar bernhard

Merge remote-tracking branch 'upstream/master' into tasks

# Conflicts:
#	build.gradle
parents db22ee8b e04df070
Pipeline #30 canceled with stages
...@@ -9,9 +9,10 @@ cache: ...@@ -9,9 +9,10 @@ cache:
test: test:
script: script:
- (cd /sdk/emulator; ./emulator @test -no-audio -no-window & wait-for-emulator.sh) # - (cd /sdk/emulator; ./emulator @test -no-audio -no-window & wait-for-emulator.sh)
- adb install src/androidTest/resources/org.dmfs.tasks_6880.apk # - adb install src/androidTest/resources/org.dmfs.tasks_6880.apk
- ./gradlew check connectedCheck # - ./gradlew check connectedCheck
- ./gradlew check
artifacts: artifacts:
paths: paths:
- build/outputs/lint-results-debug.html - build/outputs/lint-results-debug.html
......
buildscript { buildscript {
ext.kotlin_version = '1.2.70' ext.kotlin_version = '1.3.11'
ext.dokka_version = '0.9.16' ext.dokka_version = '0.9.17'
repositories { repositories {
jcenter() jcenter()
...@@ -30,22 +30,16 @@ ext { ...@@ -30,22 +30,16 @@ ext {
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion '28.0.2' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "version_ical4j", "\"$ical4j_version\"" buildConfigField "String", "version_ical4j", "\"$ical4j_version\""
} }
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions { lintOptions {
disable 'AllowBackup' disable 'AllowBackup'
disable 'InvalidPackage' disable 'InvalidPackage'
...@@ -66,8 +60,8 @@ dependencies { ...@@ -66,8 +60,8 @@ dependencies {
api "org.mnode.ical4j:ical4j:$ical4j_version" api "org.mnode.ical4j:ical4j:$ical4j_version"
implementation 'org.slf4j:slf4j-jdk14:1.7.25' implementation 'org.slf4j:slf4j-jdk14:1.7.25'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'androidx.test:rules:1.1.1'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
} }
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
...@@ -512,7 +512,7 @@ public final class TaskContract ...@@ -512,7 +512,7 @@ public final class TaskContract
String DESCRIPTION = "description"; String DESCRIPTION = "description";
/** /**
* An URL for this task. Must be a valid URL if not <code>null</code>- * The URL iCalendar field for this task. Must be a valid URI if not <code>null</code>-
* <p> * <p>
* Value: String * Value: String
* </p> * </p>
...@@ -1036,27 +1036,80 @@ public final class TaskContract ...@@ -1036,27 +1036,80 @@ public final class TaskContract
String INSTANCE_DURATION = "instance_duration"; String INSTANCE_DURATION = "instance_duration";
/** /**
* The start of the original instance as specified in the master task. For non-recurring task instances this equals the value of {@link * The start of the original instance as specified in the master task. For non-recurring task instances this is {@code null}.
* #INSTANCE_START}, except that `null` values are represented as `0`.
* <p> * <p>
* For recurring tasks, these are the timestamps which have been derived from the recurrence rule or dates, except those specified as exdates. * For recurring tasks, these are the timestamps which have been derived from the recurrence rule or dates, except those specified as exdates.
*/ */
String INSTANCE_ORIGINAL_TIME = "instance_original_time"; String INSTANCE_ORIGINAL_TIME = "instance_original_time";
/**
* The distance of the instance from the current one. For closed instances this is always {@code -1}, for the current instance this is {@code 0}. For
* the instance after the current one this is {@code 1}, for the instance after that one it's {@code 2}, etc..
* <p>
* Value: Integer
* <p>
* read-only
*/
String DISTANCE_FROM_CURRENT = "distance_from_current";
} }
/** /**
* Instances of a task. At present this table is read only. Currently it contains exactly one entry per task (and task exception), so it's merely a copy of * A table containing one entry per task instance. This table is writable in order to allow modification of single instances of a task. Write operations to
* {@link Tasks}. * this table will be converted into operations on overrides and forwarded to the task table.
* <p>
* Note: The {@link #DTSTART}, {@link #DUE} values of instances of recurring tasks represent the actual instance values, i.e. they are different for each
* instance ({@link #DURATION} is always {@code null}).
* <p>
* Also, none of the instances are recurring themselves, so {@link #RRULE}, {@link #RDATE} and {@link #EXDATE} are always {@code null}.
* <p>
* TODO: Insert all instances of recurring tasks.
* <p>
* The following operations are supported:
* <p> * <p>
* TODO: Insert all instances of recurring the tasks. * <h2>Insert</h2>
* </p>
* <p> * <p>
* TODO: In later releases it's planned to provide a convenient interface to add, change or delete task instances via this URI. * Note, the data of an insert must not contain the fields {@link #RRULE}, {@link #RDATE} or {@link #EXDATE}. If the new instance belongs to an existing
* </p> * task the data must contain the fields {@link #ORIGINAL_INSTANCE_ID} and {@link #ORIGINAL_INSTANCE_TIME}. Also note, this table supports writing {@link
* #DURATION} (if the instance has a {@link #DTSTART}), but reading it back will always return a {@code null} {@link #DURATION} and a non-{@code null}
* {@link #DUE} date. Reading the task in the tasks table will, however, return the original {@link #DURATION}.
* <p>
* If there already is an instance (with or without override) for the given {@link #ORIGINAL_INSTANCE_ID} and {@link #ORIGINAL_INSTANCE_TIME} an exception
* is thrown.
* <p>
* <table> <tr><th>ORIGINAL_INSTANCE_ID value</th><th>Result</th></tr> <tr><td>absent or empty</td><td>A new non-recurring task is created with the given
* values.</td></tr> <tr><td>a valid {@link Tasks} row {@code _ID}</td><td>An {@link #RDATE} for the given {@link #ORIGINAL_INSTANCE_TIME} time is added to
* the given master task, any {@link #EXDATE} for this time is removed. The task is inserted as an override to the given master. No fields are inherited
* though. {@link #ORIGINAL_INSTANCE_ALLDAY} will be set to {@link #IS_ALLDAY} of the master.
* <p>
* Note, if the given master is non-recurring, this operation will turn it into a recurring task. </td></tr> <tr><td>invalid {@link Tasks} row {@code
* _ID}</td><td>An exception is thrown.</td></tr></table>
* <p>
* <h2>Update</h2>
* <p>
* Note, the data of an update must not contain any fields related to recurrence ({@link #RRULE}, {@link #RDATE}, {@link #EXDATE}, {@link
* #ORIGINAL_INSTANCE_ID}, {@link #ORIGINAL_INSTANCE_TIME} and {@link #ORIGINAL_INSTANCE_ALLDAY}). Also note, this table supports writing {@link #DURATION}
* (if the instance has a {@link #DTSTART}), but reading it back will always return a {@code null} {@link #DURATION} and a non-{@code null} {@link #DUE}
* date. Reading the task in the tasks table will, however, return the original {@link #DURATION}.
* <p>
* <table> <tr><th>Target task type</th><th>Result</th></tr> <tr><td>Recurring master task</td><td>A new override is created with the given data.<p> Note,
* any fields which are not provided are inherited from the master, except for {@link #DTSTART} and {@link #DUE} which will be inherited from the instance
* and {@link #DURATION}, {@link #RRULE}, {@link #RDATE} and {@link #EXDATE} which are set to {@code null}. {@link #ORIGINAL_INSTANCE_ID}, {@link
* #ORIGINAL_INSTANCE_TIME} and {@link #ORIGINAL_INSTANCE_ALLDAY} will be set accordingly.</td></tr> <tr><td>Single instance task</td><td>The task is
* updated with the given values.</td></tr> <tr><td>Recurrence override with existing master</td><td>The task is updated with the given values.</td></tr>
* <tr><td>Recurrence override without existing master</td><td>The task is updated with the given values.</td></tr> </table>
* <p>
* <h2>Delete</h2>
* <p>
* <table> <tr><th>Target task type</th><th>Result</th></tr> <tr><td>Recurring master task</td><td>An {@link #EXDATE} for this instance is added, any {@link
* #RDATE} for this instance is removed. The instance row is removed.<p> TODO: mark the task deleted if the remaining recurrence set is empty </td></tr>
* <tr><td>Single instance task</td><td>The {@link Tasks#_DELETED} flag of the task is set.</td></tr> <tr><td>Recurrence override with existing
* master</td><td>The {@link Tasks#_DELETED} flag of the override is set, an {@link #EXDATE} for this instance is added to the master, any {@link #RDATE}
* for this instance is removed from the master. TODO: mark the master deleted if the remaining recurrence set of the master is empty </td></tr>
* <tr><td>Recurrence override without existing master</td><td>The {@link Tasks#_DELETED} flag of the task is set.</td></tr> </table>
* *
* @author Yannic Ahrens <yannic@dmfs.org> * @author Yannic Ahrens
* @author Marten Gajda
*/ */
public static final class Instances implements TaskColumns, InstanceColumns public static final class Instances implements TaskColumns, InstanceColumns
{ {
......
...@@ -34,7 +34,7 @@ public final class UriFactory ...@@ -34,7 +34,7 @@ public final class UriFactory
UriFactory(String authority) UriFactory(String authority)
{ {
mAuthority = authority; mAuthority = authority;
mUriMap.put((String) null, Uri.parse("content://" + authority)); mUriMap.put(null, Uri.parse("content://" + authority));
} }
......
...@@ -15,9 +15,9 @@ import android.content.ContentUris ...@@ -15,9 +15,9 @@ import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.os.Build import android.os.Build
import android.provider.CalendarContract import android.provider.CalendarContract
import android.support.test.InstrumentationRegistry import androidx.test.filters.MediumTest
import android.support.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry
import android.support.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import at.bitfire.ical4android.impl.TestCalendar import at.bitfire.ical4android.impl.TestCalendar
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
...@@ -41,11 +41,12 @@ class AndroidCalendarTest { ...@@ -41,11 +41,12 @@ class AndroidCalendarTest {
@Before @Before
fun prepare() { fun prepare() {
provider = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY) provider = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
} }
@After @After
fun shutdown() { fun shutdown() {
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= 24) if (Build.VERSION.SDK_INT >= 24)
provider.close() provider.close()
else else
......
...@@ -15,9 +15,9 @@ import android.content.ContentValues ...@@ -15,9 +15,9 @@ import android.content.ContentValues
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.CalendarContract import android.provider.CalendarContract
import android.support.test.InstrumentationRegistry.getInstrumentation import androidx.test.filters.MediumTest
import android.support.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import android.support.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import at.bitfire.ical4android.AndroidCalendar.Companion.syncAdapterURI import at.bitfire.ical4android.AndroidCalendar.Companion.syncAdapterURI
import at.bitfire.ical4android.impl.TestCalendar import at.bitfire.ical4android.impl.TestCalendar
import at.bitfire.ical4android.impl.TestEvent import at.bitfire.ical4android.impl.TestEvent
...@@ -53,7 +53,7 @@ class AndroidEventTest { ...@@ -53,7 +53,7 @@ class AndroidEventTest {
@Before @Before
fun prepare() { fun prepare() {
provider = getInstrumentation().targetContext.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY) provider = getInstrumentation().targetContext.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
AndroidCalendar.insertColors(provider, testAccount) AndroidCalendar.insertColors(provider, testAccount)
...@@ -67,6 +67,7 @@ class AndroidEventTest { ...@@ -67,6 +67,7 @@ class AndroidEventTest {
fun shutdown() { fun shutdown() {
calendar.delete() calendar.delete()
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= 24) if (Build.VERSION.SDK_INT >= 24)
provider.close() provider.close()
else else
......
...@@ -11,8 +11,8 @@ package at.bitfire.ical4android ...@@ -11,8 +11,8 @@ package at.bitfire.ical4android
import android.accounts.Account import android.accounts.Account
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.support.test.InstrumentationRegistry.getInstrumentation import androidx.test.filters.MediumTest
import android.support.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import at.bitfire.ical4android.impl.TestTaskList import at.bitfire.ical4android.impl.TestTaskList
import org.dmfs.tasks.contract.TaskContract import org.dmfs.tasks.contract.TaskContract
import org.junit.After import org.junit.After
......
...@@ -11,8 +11,8 @@ package at.bitfire.ical4android ...@@ -11,8 +11,8 @@ package at.bitfire.ical4android
import android.accounts.Account import android.accounts.Account
import android.content.ContentUris import android.content.ContentUris
import android.net.Uri import android.net.Uri
import android.support.test.InstrumentationRegistry.getInstrumentation import androidx.test.filters.MediumTest
import android.support.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import at.bitfire.ical4android.impl.TestTask import at.bitfire.ical4android.impl.TestTask
import at.bitfire.ical4android.impl.TestTaskList import at.bitfire.ical4android.impl.TestTaskList
import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.Date
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package at.bitfire.ical4android package at.bitfire.ical4android
import android.content.ContentValues import android.content.ContentValues
import android.support.test.filters.SmallTest import androidx.test.filters.SmallTest
import net.fortuna.ical4j.data.CalendarBuilder import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime import net.fortuna.ical4j.model.DateTime
...@@ -98,6 +98,7 @@ class MiscUtilsTest { ...@@ -98,6 +98,7 @@ class MiscUtilsTest {
} }
@Suppress("unused")
private class TestClass { private class TestClass {
private val s = "test" private val s = "test"
val i = 2 val i = 2
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
package at.bitfire.ical4android package at.bitfire.ical4android
import android.support.test.runner.permission.PermissionRequester import androidx.test.runner.permission.PermissionRequester
import junit.framework.AssertionFailedError import junit.framework.AssertionFailedError
object TestUtils { object TestUtils {
......
...@@ -44,7 +44,8 @@ abstract class AndroidCalendar<out T: AndroidEvent>( ...@@ -44,7 +44,8 @@ abstract class AndroidCalendar<out T: AndroidEvent>(
info.put(Calendars.ALLOWED_ATTENDEE_TYPES, "${Attendees.TYPE_NONE},${Attendees.TYPE_OPTIONAL},${Attendees.TYPE_REQUIRED},${Attendees.TYPE_RESOURCE}") info.put(Calendars.ALLOWED_ATTENDEE_TYPES, "${Attendees.TYPE_NONE},${Attendees.TYPE_OPTIONAL},${Attendees.TYPE_REQUIRED},${Attendees.TYPE_RESOURCE}")
Constants.log.info("Creating local calendar: " + info.toString()) Constants.log.info("Creating local calendar: " + info.toString())
return provider.insert(syncAdapterURI(Calendars.CONTENT_URI, account), info) return provider.insert(syncAdapterURI(Calendars.CONTENT_URI, account), info) ?:
throw Exception("Couldn't create calendar: provider returned null")
} }
fun insertColors(provider: ContentProviderClient, account: Account) { fun insertColors(provider: ContentProviderClient, account: Account) {
...@@ -150,13 +151,13 @@ abstract class AndroidCalendar<out T: AndroidEvent>( ...@@ -150,13 +151,13 @@ abstract class AndroidCalendar<out T: AndroidEvent>(
/** /**
* Queries events from this calendar. Adds a WHERE clause that restricts the * Queries events from this calendar. Adds a WHERE clause that restricts the
* query to [Events.CALENDAR_ID] = [id]. * query to [Events.CALENDAR_ID] = [id].
* @param where selection * @param _where selection
* @param whereArgs arguments for selection * @param _whereArgs arguments for selection
* @return events from this calendar which match the selection * @return events from this calendar which match the selection
*/ */
fun queryEvents(where: String? = null, whereArgs: Array<String>? = null): List<T> { fun queryEvents(_where: String? = null, _whereArgs: Array<String>? = null): List<T> {
val where = "(${where ?: "1"}) AND " + Events.CALENDAR_ID + "=?" val where = "(${_where ?: "1"}) AND " + Events.CALENDAR_ID + "=?"
val whereArgs = (whereArgs ?: arrayOf()) + id.toString() val whereArgs = (_whereArgs ?: arrayOf()) + id.toString()
val events = LinkedList<T>() val events = LinkedList<T>()
provider.query(eventsSyncURI(), null, where, whereArgs, null)?.use { cursor -> provider.query(eventsSyncURI(), null, where, whereArgs, null)?.use { cursor ->
......
...@@ -59,7 +59,8 @@ abstract class AndroidTaskList<out T: AndroidTask>( ...@@ -59,7 +59,8 @@ abstract class AndroidTaskList<out T: AndroidTask>(
info.put(TaskLists.ACCESS_LEVEL, 0) info.put(TaskLists.ACCESS_LEVEL, 0)
Constants.log.info("Creating local task list: " + info.toString()) Constants.log.info("Creating local task list: " + info.toString())
return provider.client.insert(TaskProvider.syncAdapterUri(provider.taskListsUri(), account), info) return provider.client.insert(TaskProvider.syncAdapterUri(provider.taskListsUri(), account), info) ?:
throw CalendarStorageException("Couldn't create task list (empty result from provider)")
} }
fun<T: AndroidTaskList<AndroidTask>> findByID(account: Account, provider: TaskProvider, factory: AndroidTaskListFactory<T>, id: Long): T { fun<T: AndroidTaskList<AndroidTask>> findByID(account: Account, provider: TaskProvider, factory: AndroidTaskListFactory<T>, id: Long): T {
...@@ -113,13 +114,13 @@ abstract class AndroidTaskList<out T: AndroidTask>( ...@@ -113,13 +114,13 @@ abstract class AndroidTaskList<out T: AndroidTask>(
/** /**
* Queries tasks from this task list. Adds a WHERE clause that restricts the * Queries tasks from this task list. Adds a WHERE clause that restricts the
* query to [Tasks.LIST_ID] = [id]. * query to [Tasks.LIST_ID] = [id].
* @param where selection * @param _where selection
* @param whereArgs arguments for selection * @param _whereArgs arguments for selection
* @return events from this task list which match the selection * @return events from this task list which match the selection
*/ */
fun queryTasks(where: String? = null, whereArgs: Array<String>? = null): List<T> { fun queryTasks(_where: String? = null, _whereArgs: Array<String>? = null): List<T> {
val where = "(${where ?: "1"}) AND ${Tasks.LIST_ID}=?" val where = "(${_where ?: "1"}) AND ${Tasks.LIST_ID}=?"
val whereArgs = (whereArgs ?: arrayOf()) + id.toString() val whereArgs = (_whereArgs ?: arrayOf()) + id.toString()
val tasks = LinkedList<T>() val tasks = LinkedList<T>()
provider.client.query( provider.client.query(
......
...@@ -176,7 +176,7 @@ class Event: ICalendar() { ...@@ -176,7 +176,7 @@ class Event: ICalendar() {
is Organizer -> e.organizer = prop is Organizer -> e.organizer = prop
is Attendee -> e.attendees += prop is Attendee -> e.attendees += prop
is LastModified -> e.lastModified = prop is LastModified -> e.lastModified = prop
is ProdId, is DtStamp -> { /* don't save those as unknown properties */ } is ProdId, is DtStamp -> { /* don't save these as unknown properties */ }
else -> e.unknownProperties += prop else -> e.unknownProperties += prop
} }
......
...@@ -10,9 +10,8 @@ package at.bitfire.ical4android ...@@ -10,9 +10,8 @@ package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarOutputter import net.fortuna.ical4j.data.CalendarOutputter
import net.fortuna.ical4j.data.ParserException import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.*
import net.fortuna.ical4j.model.Calendar import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Component
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.TimeZone import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.component.VToDo import net.fortuna.ical4j.model.component.VToDo
import net.fortuna.ical4j.model.property.* import net.fortuna.ical4j.model.property.*
...@@ -26,7 +25,7 @@ import java.util.logging.Level ...@@ -26,7 +25,7 @@ import java.util.logging.Level
class Task: ICalendar() { class Task: ICalendar() {
var createdAt: Long? = null var createdAt: Long? = null
var lastModified: Long? = null var lastModified: Long? = null
var summary: String? = null var summary: String? = null
...@@ -50,6 +49,8 @@ class Task: ICalendar() { ...@@ -50,6 +49,8 @@ class Task: ICalendar() {
val rDates = LinkedList<RDate>() val rDates = LinkedList<RDate>()
val exDates = LinkedList<ExDate>() val exDates = LinkedList<ExDate>()
val unknownProperties = LinkedList<Property>()
companion object { companion object {
/** /**
...@@ -108,6 +109,8 @@ class Task: ICalendar() { ...@@ -108,6 +109,8 @@ class Task: ICalendar() {
is RRule -> t.rRule = prop is RRule -> t.rRule = prop
is RDate -> t.rDates += prop is RDate -> t.rDates += prop
is ExDate -> t.exDates += prop is ExDate -> t.exDates += prop
is ProdId, is DtStamp -> { /* don't save these as unknown properties */ }
else -> t.unknownProperties += prop
} }
// there seem to be many invalid tasks out there because of some defect clients, // there seem to be many invalid tasks out there because of some defect clients,
...@@ -125,19 +128,19 @@ class Task: ICalendar() { ...@@ -125,19 +128,19 @@ class Task: ICalendar() {
} }
fun write(os: OutputStream) { fun write(os: OutputStream) {
val ical = Calendar() val ical = Calendar()
ical.properties += Version.VERSION_2_0 ical.properties += Version.VERSION_2_0
ical.properties += prodId ical.properties += prodId
val todo = VToDo() val todo = VToDo()
ical.components += todo ical.components += todo
val props = todo.properties val props = todo.properties
uid?.let { props += Uid(it) } uid?.let { props += Uid(it) }
sequence?.let { if (it != 0) props += Sequence(sequence as Int) } sequence?.let { if (it != 0) props += Sequence(sequence as Int) }
createdAt?.let { props += Created(DateTime(it)) } createdAt?.let { props += Created(DateTime(it)) }
lastModified?.let { props += LastModified(DateTime(it)) } lastModified?.let { props += LastModified(DateTime(it)) }
summary?.let { props += Summary(it) } summary?.let { props += Summary(it) }
...@@ -145,7 +148,7 @@ class Task: ICalendar() { ...@@ -145,7 +148,7 @@ class Task: ICalendar() {
geoPosition?.let { props += it } geoPosition?.let { props += it }
description?.let { props += Description(it) } description?.let { props += Description(it) }
color?.let { props += Color(EventColor.nearestMatch(it)) } color?.let { props += Color(EventColor.nearestMatch(it)) }
url?.let { url?.let {
try { try {
props += Url(URI(it)) props += Url(URI(it))
} catch (e: URISyntaxException) { } catch (e: URISyntaxException) {
...@@ -155,7 +158,7 @@ class Task: ICalendar() { ...@@ -155,7 +158,7 @@ class Task: ICalendar() {
organizer?.let { props += it } organizer?.let { props += it }
if (priority != Priority.UNDEFINED.level) if (priority != Priority.UNDEFINED.level)
props += Priority(priority) props += Priority(priority)
classification?.let { props += it } classification?.let { props += it }
status?.let { props += it } status?.let { props += it }
...@@ -163,12 +166,12 @@ class Task: ICalendar() { ...@@ -163,12 +166,12 @@ class Task: ICalendar() {
rDates.forEach { props += it } rDates.forEach { props += it }
exDates.forEach { props += it } exDates.forEach { props += it }
// remember used time zones // remember used time zones
val usedTimeZones = HashSet<TimeZone>() val usedTimeZones = HashSet<TimeZone>()
due?.let { due?.let {
props += it props += it
it.timeZone?.let(usedTimeZones::add) it.timeZone?.let(usedTimeZones::add)
} }
duration?.let(props::add) duration?.let(props::add)
dtStart?.let { dtStart?.let {
props += it props += it
...@@ -178,13 +181,15 @@ class Task: ICalendar() { ...@@ -178,13 +181,15 @@ class Task: ICalendar() {
props += it props += it
it.timeZone?.let(usedTimeZones::add) it.timeZone?.let(usedTimeZones::add)
} }
percentComplete?.let { props += PercentComplete(it) } percentComplete?.let { props += PercentComplete(it) }
// add VTIMEZONE components props.addAll(unknownProperties)
// add VTIMEZONE components
usedTimeZones.forEach { ical.components += it.vTimeZone } usedTimeZones.forEach { ical.components += it.vTimeZone }
CalendarOutputter(false).output(ical, os) CalendarOutputter(false).output(ical, os)
} }
fun isAllDay(): Boolean { fun isAllDay(): Boolean {
......
...@@ -113,7 +113,7 @@ class TaskProvider private constructor( ...@@ -113,7 +113,7 @@ class TaskProvider private constructor(
class ProviderTooOldException( class ProviderTooOldException(
val provider: ProviderName, val provider: ProviderName,
val installedVersionCode: Int, installedVersionCode: Int,
val installedVersionName: String val installedVersionName: String
): Exception("Package ${provider.packageName} has version $installedVersionName ($installedVersionCode), " + ): Exception("Package ${provider.packageName} has version $installedVersionName ($installedVersionCode), " +
"required: ${provider.minVersionName} (${provider.minVersionCode})") "required: ${provider.minVersionName} (${provider.minVersionCode})")
......
...@@ -11,7 +11,6 @@ import org.junit.Assert.* ...@@ -11,7 +11,6 @@ import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.InputStreamReader import java.io.InputStreamReader
import java.lang.AssertionError
import java.nio.charset.Charset import java.nio.charset.Charset
class EventTest { class EventTest {
...@@ -20,7 +19,7 @@ class EventTest { ...@@ -20,7 +19,7 @@ class EventTest {
@Test @Test
fun testCalendarProperties() { fun testCalendarProperties() {
javaClass.classLoader.getResourceAsStream("events/multiple.ics").use { stream -> javaClass.classLoader!!.getResourceAsStream("events/multiple.ics").use { stream ->
val properties = mutableMapOf<String, String>() val properties = mutableMapOf<String, String>()
Event.fromReader(InputStreamReader(stream, Charsets.UTF_8), properties) Event.fromReader(InputStreamReader(stream, Charsets.UTF_8), properties)
assertEquals(1, properties.size) assertEquals(1, properties.size)
...@@ -201,7 +200,7 @@ class EventTest { ...@@ -201,7 +200,7 @@ class EventTest {
} }
private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): List<Event> = private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): List<Event> =
javaClass.classLoader.getResourceAsStream("events/$fname").use { stream -> javaClass.classLoader!!.getResourceAsStream("events/$fname").use { stream ->
return Event.fromReader(InputStreamReader(stream, charset)) return Event.fromReader(InputStreamReader(stream, charset))
} }
......
...@@ -98,7 +98,7 @@ class TaskTest { ...@@ -98,7 +98,7 @@ class TaskTest {
/* helpers */ /* helpers */
private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): Task { private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): Task {
javaClass.classLoader.getResourceAsStream("tasks/$fname").use { stream -> javaClass.classLoader!!.getResourceAsStream("tasks/$fname").use { stream ->
return Task.fromReader(InputStreamReader(stream, charset)).first() return Task.fromReader(InputStreamReader(stream, charset)).first()
} }