@@ -11,6 +11,7 @@ import android.content.ContentValues
1111import android.net.Uri
1212import android.os.ParcelFileDescriptor
1313import android.util.Base64
14+ import androidx.annotation.VisibleForTesting
1415import androidx.core.content.contentValuesOf
1516import at.bitfire.ical4android.ICalendar.Companion.withUserAgents
1617import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate
@@ -183,7 +184,8 @@ open class JtxICalObject(
183184 var alarms: MutableList <Alarm > = mutableListOf ()
184185 var unknown: MutableList <Unknown > = mutableListOf ()
185186
186- private var recurInstances: MutableList <JtxICalObject > = mutableListOf ()
187+ @VisibleForTesting
188+ internal var recurInstances: MutableList <JtxICalObject > = mutableListOf ()
187189
188190
189191
@@ -668,78 +670,88 @@ open class JtxICalObject(
668670 calComponent.propertyList = addProperties(calComponent.propertyList) // Need to re-set the immutable list
669671 ical + = calComponent
670672
673+ // add alarm components
671674 alarms.forEach { alarm ->
675+ val alarmProps = mutableListOf<Property >()
676+ alarm.action?.let {
677+ alarmProps + = when (it) {
678+ JtxContract .JtxAlarm .AlarmAction .DISPLAY .name -> ImmutableAction .DISPLAY
679+ JtxContract .JtxAlarm .AlarmAction .AUDIO .name -> ImmutableAction .AUDIO
680+ JtxContract .JtxAlarm .AlarmAction .EMAIL .name -> ImmutableAction .EMAIL
681+ else -> return @let
682+ }
683+ }
684+
685+ if (alarm.triggerRelativeDuration != null ) {
686+ alarmProps + = Trigger ().apply {
687+ try {
688+ val dur = java.time.Duration .parse(alarm.triggerRelativeDuration)
689+ this .duration = dur
672690
673- val vAlarm = VAlarm ()
674- vAlarm.propertyList.apply {
675- alarm.action?.let {
676- when (it) {
677- JtxContract .JtxAlarm .AlarmAction .DISPLAY .name -> add(ImmutableAction .DISPLAY )
678- JtxContract .JtxAlarm .AlarmAction .AUDIO .name -> add(ImmutableAction .AUDIO )
679- JtxContract .JtxAlarm .AlarmAction .EMAIL .name -> add(ImmutableAction .EMAIL )
680- else -> return @let
691+ // Add the RELATED parameter if present
692+ alarm.triggerRelativeTo?.let {
693+ if (it == JtxContract .JtxAlarm .AlarmRelativeTo .START .name)
694+ this + = Related .START
695+ if (it == JtxContract .JtxAlarm .AlarmRelativeTo .END .name)
696+ this + = Related .END
697+ }
698+ } catch (e: DateTimeParseException ) {
699+ logger.log(Level .WARNING , " Could not parse Trigger duration as Duration." , e)
681700 }
682701 }
683- if (alarm.triggerRelativeDuration != null ) {
684- add(Trigger ().apply {
685- try {
686- val dur = java.time.Duration .parse(alarm.triggerRelativeDuration)
687- this .duration = dur
688-
689- // Add the RELATED parameter if present
690- alarm.triggerRelativeTo?.let {
691- if (it == JtxContract .JtxAlarm .AlarmRelativeTo .START .name)
692- this + = Related .START
693- if (it == JtxContract .JtxAlarm .AlarmRelativeTo .END .name)
694- this + = Related .END
702+ } else if (alarm.triggerTime != null ) {
703+ alarmProps + = Trigger ().apply {
704+ try {
705+ when {
706+ alarm.triggerTimezone == ZoneOffset .UTC .id ||
707+ alarm.triggerTimezone.isNullOrEmpty() ->
708+ this .date = Instant .ofEpochMilli(alarm.triggerTime!! )
709+
710+ else -> {
711+ this .date = ZonedDateTime .ofInstant(
712+ Instant .ofEpochMilli(alarm.triggerTime!! ),
713+ ZoneId .of(alarm.triggerTimezone)
714+ ).toInstant()
695715 }
696- } catch (e: DateTimeParseException ) {
697- logger.log(Level .WARNING , " Could not parse Trigger duration as Duration." , e)
698716 }
699- })
700-
701- } else if (alarm.triggerTime != null ) {
702- add(Trigger ().apply {
703- try {
704- when {
705- alarm.triggerTimezone == ZoneOffset .UTC .id ||
706- alarm.triggerTimezone.isNullOrEmpty() ->
707- this .date = Instant .ofEpochMilli(alarm.triggerTime!! )
708- else -> {
709- this .date = ZonedDateTime .ofInstant(Instant .ofEpochMilli(alarm.triggerTime!! ), ZoneId .of(alarm.triggerTimezone)).toInstant()
710- }
711- }
712- } catch (e: ParseException ) {
713- logger.log(Level .WARNING , " TriggerTime could not be parsed." , e)
714- }})
717+ } catch (e: ParseException ) {
718+ logger.log(Level .WARNING , " TriggerTime could not be parsed." , e)
719+ }
715720 }
716- alarm.summary?.let { add(Summary (it)) }
717- alarm.repeat?.let { add(Repeat ().apply { value = it }) }
718- alarm.duration?.let { add(Duration ().apply {
721+ }
722+
723+ alarm.summary?.let { alarmProps + = Summary (it) }
724+ alarm.repeat?.let { alarmProps + = Repeat ().apply { value = it } }
725+ alarm.duration?.let {
726+ alarmProps + = Duration ().apply {
719727 try {
720728 val dur = java.time.Duration .parse(it)
721729 this .duration = dur
722730 } catch (e: DateTimeParseException ) {
723731 logger.log(Level .WARNING , " Could not parse duration as Duration." , e)
724732 }
725- }) }
726- alarm.description?.let { add(Description (it)) }
727- alarm.attach?.let { add(Attach ().apply { value = it }) }
728- alarm.other?.let { addAll(JtxContract .getXPropertyListFromJson(it).all) }
729-
733+ }
730734 }
731- calComponent.componentList.add(vAlarm)
732- }
735+ alarm.description?.let { alarmProps + = Description (it) }
736+ alarm.attach?.let { alarmProps + = Attach ().apply { value = it } }
737+ alarm.other?.let { alarmProps + = JtxContract .getXPropertyListFromJson(it).all }
733738
739+ // add VALARM to VTODO/VJOURNAL component
740+ val vAlarm = VAlarm (PropertyList (alarmProps))
741+ calComponent + = vAlarm
742+ }
734743
744+ // add a iCalendar component for each exception
735745 recurInstances.forEach { recurInstance ->
746+ // create a fresh component of the correct type and add to iCalendar
736747 val recurCalComponent = when (recurInstance.component) {
737748 JtxContract .JtxICalObject .Component .VTODO .name -> VToDo (true /* generates DTSTAMP */ )
738749 JtxContract .JtxICalObject .Component .VJOURNAL .name -> VJournal (true /* generates DTSTAMP */ )
739750 else -> return null
740751 }
741752 ical + = recurCalComponent
742- recurInstance.addProperties(recurCalComponent.propertyList)
753+ // assign properties (UID, RECURRENCE-ID, modified SUMMARY etc.) from the exception
754+ recurCalComponent.propertyList = recurInstance.addProperties(recurCalComponent.propertyList)
743755 }
744756
745757 ICalendar .softValidate(ical)
0 commit comments