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

Initial support for event STATUS attribute #200

Merged
merged 14 commits into from
Nov 14, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.net.Uri
import android.os.Bundle
import android.provider.CalendarContract.Attendees
import android.provider.CalendarContract.Colors
import android.provider.CalendarContract.Events
import android.provider.ContactsContract.CommonDataKinds
import android.provider.ContactsContract.CommonDataKinds.StructuredName
import android.provider.ContactsContract.Data
Expand Down Expand Up @@ -72,6 +73,7 @@ class EventActivity : SimpleActivity() {
private var mAvailableContacts = ArrayList<Attendee>()
private var mSelectedContacts = ArrayList<Attendee>()
private var mAvailability = Attendees.AVAILABILITY_BUSY
private var mStatus = Events.STATUS_CONFIRMED
private var mStoredEventTypes = ArrayList<EventType>()
private var mOriginalTimeZone = DateTimeZone.getDefault().id
private var mOriginalStartTS = 0L
Expand Down Expand Up @@ -174,6 +176,7 @@ class EventActivity : SimpleActivity() {
putString(ATTENDEES, Gson().toJson(getAllAttendees(false)))

putInt(AVAILABILITY, mAvailability)
putInt(STATUS, mStatus)
putInt(EVENT_COLOR, mEventColor)

putLong(EVENT_TYPE_ID, mEventTypeId)
Expand Down Expand Up @@ -207,6 +210,7 @@ class EventActivity : SimpleActivity() {
mReminder3Type = getInt(REMINDER_3_TYPE)

mAvailability = getInt(AVAILABILITY)
mStatus = getInt(STATUS)
mEventColor = getInt(EVENT_COLOR)

mRepeatInterval = getInt(REPEAT_INTERVAL)
Expand Down Expand Up @@ -337,6 +341,14 @@ class EventActivity : SimpleActivity() {
}
}

eventStatus.setOnClickListener {
showStatusPicker(mStatus) {
mStatus = it
updateStatusText()
updateStatusImage()
}
}

eventTypeHolder.setOnClickListener { showEventTypeDialog() }
eventAllDay.apply {
isChecked = mEvent.getIsAllDay()
Expand Down Expand Up @@ -458,6 +470,8 @@ class EventActivity : SimpleActivity() {
updateCalDAVVisibility()
updateAvailabilityText()
updateAvailabilityImage()
updateStatusText()
updateStatusImage()
}

private fun setupEditEvent() {
Expand Down Expand Up @@ -500,6 +514,7 @@ class EventActivity : SimpleActivity() {
mEventTypeId = mEvent.eventType
mEventCalendarId = mEvent.getCalDAVCalendarId()
mAvailability = mEvent.availability
mStatus = mEvent.status
mEventColor = mEvent.color

mAttendees = mEvent.attendees.toMutableList() as ArrayList<Attendee>
Expand Down Expand Up @@ -576,6 +591,7 @@ class EventActivity : SimpleActivity() {
reminder2Type = mReminder2Type
reminder3Minutes = mReminder3Minutes
reminder3Type = mReminder3Type
status = mStatus
eventType = mEventTypeId
}
}
Expand Down Expand Up @@ -956,6 +972,17 @@ class EventActivity : SimpleActivity() {
}
}

private fun showStatusPicker(currentValue: Int, callback: (Int) -> Unit) {
val items = arrayListOf(
RadioItem(Events.STATUS_TENTATIVE, getString(R.string.status_tentative)),
RadioItem(Events.STATUS_CONFIRMED, getString(R.string.status_confirmed)),
RadioItem(Events.STATUS_CANCELED, getString(R.string.status_canceled)),
)
RadioGroupDialog(this, items, currentValue) {
callback(it as Int)
}
}

private fun updateReminderTypeImages() {
updateReminderTypeImage(binding.eventReminder1Type, Reminder(mReminder1Minutes, mReminder1Type))
updateReminderTypeImage(binding.eventReminder2Type, Reminder(mReminder2Minutes, mReminder2Type))
Expand Down Expand Up @@ -994,6 +1021,24 @@ class EventActivity : SimpleActivity() {
binding.eventAvailability.text = if (mAvailability == Attendees.AVAILABILITY_FREE) getString(R.string.status_free) else getString(R.string.status_busy)
}

private fun updateStatusText() {
when (mStatus) {
Events.STATUS_CONFIRMED -> binding.eventStatus.text = getString(R.string.status_confirmed)
Events.STATUS_TENTATIVE -> binding.eventStatus.text = getString(R.string.status_tentative)
Events.STATUS_CANCELED -> binding.eventStatus.text = getString(R.string.status_canceled)
}
}

private fun updateStatusImage() {
val drawable = when (mStatus) {
Events.STATUS_CONFIRMED -> R.drawable.ic_check_circle_outline_vector
Events.STATUS_CANCELED -> R.drawable.ic_cancel_circle_outline_vector
else -> R.drawable.ic_question_circle_outline_vector
}
val icon = resources.getColoredDrawableWithColor(drawable, getProperTextColor())
binding.eventStatusImage.setImageDrawable(icon)
}

private fun updateRepetitionText() {
binding.eventRepetition.text = getRepetitionText(mRepeatInterval)
}
Expand Down Expand Up @@ -1036,6 +1081,8 @@ class EventActivity : SimpleActivity() {
updateCalDAVVisibility()
updateAvailabilityText()
updateAvailabilityImage()
updateStatusText()
updateStatusImage()
}
}
} else {
Expand Down Expand Up @@ -1310,6 +1357,7 @@ class EventActivity : SimpleActivity() {
source = newSource
location = binding.eventLocation.value
availability = mAvailability
status = mStatus
color = mEventColor
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.fossify.calendar.adapters
import android.content.Context
import android.content.Intent
import android.graphics.Paint
import android.provider.CalendarContract
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import org.fossify.calendar.R
Expand Down Expand Up @@ -236,7 +237,8 @@ class EventListWidgetAdapter(val context: Context, val intent: Intent) : RemoteV
isRepeatable = event.repeatInterval > 0,
isTask = event.isTask(),
isTaskCompleted = event.isTaskCompleted(),
isAttendeeInviteDeclined = event.isAttendeeInviteDeclined()
isAttendeeInviteDeclined = event.isAttendeeInviteDeclined(),
isEventCanceled = event.isEventCanceled()
)
listItems.add(listEvent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.fossify.calendar.models.Widget
import org.fossify.commons.extensions.getProperPrimaryColor
import java.util.concurrent.Executors

@Database(entities = [Event::class, EventType::class, Widget::class, Task::class], version = 8)
@Database(entities = [Event::class, EventType::class, Widget::class, Task::class], version = 9)
@TypeConverters(Converters::class)
abstract class EventsDatabase : RoomDatabase() {

Expand Down Expand Up @@ -55,6 +55,7 @@ abstract class EventsDatabase : RoomDatabase() {
.addMigrations(MIGRATION_5_6)
.addMigrations(MIGRATION_6_7)
.addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_8_9)
.build()
db!!.openHelper.setWriteAheadLoggingEnabled(true)
}
Expand Down Expand Up @@ -136,5 +137,13 @@ abstract class EventsDatabase : RoomDatabase() {
}
}
}

private val MIGRATION_8_9 = object : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("ALTER TABLE events ADD COLUMN status INTEGER NOT NULL DEFAULT 1")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,8 @@ fun Context.getEventListItems(events: List<Event>, addSectionDays: Boolean = tru
it.repeatInterval > 0,
it.isTask(),
it.isTaskCompleted(),
it.isAttendeeInviteDeclined()
it.isAttendeeInviteDeclined(),
it.isEventCanceled()
)
listItems.add(listEvent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ fun Event.maybeAdjustRepeatLimitCount(original: Event, occurrenceTS: Long) {
}
}

fun Event.shouldStrikeThrough() = isTaskCompleted() || isAttendeeInviteDeclined()
fun Event.shouldStrikeThrough() = isTaskCompleted() || isAttendeeInviteDeclined() || isEventCanceled()
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package org.fossify.calendar.extensions

import org.fossify.calendar.models.ListEvent

fun ListEvent.shouldStrikeThrough() = isTaskCompleted || isAttendeeInviteDeclined
fun ListEvent.shouldStrikeThrough() = isTaskCompleted || isAttendeeInviteDeclined || isEventCanceled
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package org.fossify.calendar.extensions

import org.fossify.calendar.models.MonthViewEvent

fun MonthViewEvent.shouldStrikeThrough() = isTaskCompleted || isAttendeeInviteDeclined
fun MonthViewEvent.shouldStrikeThrough() = isTaskCompleted || isAttendeeInviteDeclined || isEventCanceled
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.graphics.Color
import android.provider.CalendarContract
import android.provider.CalendarContract.*
import android.widget.Toast
import org.fossify.calendar.R
Expand Down Expand Up @@ -242,7 +243,7 @@ class CalDAVHelper(val context: Context) {
reminder1?.type ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION,
reminder3?.type ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule,
repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId,
source = source, availability = availability, color = displayColor
source = source, availability = availability, color = displayColor, status = status
)

if (event.getIsAllDay()) {
Expand Down Expand Up @@ -421,7 +422,7 @@ class CalDAVHelper(val context: Context) {
put(Events.TITLE, event.title)
put(Events.DESCRIPTION, event.description)
put(Events.EVENT_LOCATION, event.location)
put(Events.STATUS, Events.STATUS_CONFIRMED)
put(Events.STATUS, event.status)
put(Events.AVAILABILITY, event.availability)

if (event.color == 0) {
Expand Down
32 changes: 26 additions & 6 deletions app/src/main/kotlin/org/fossify/calendar/helpers/Constants.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.fossify.calendar.helpers

import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import org.fossify.calendar.activities.EventActivity
import org.fossify.calendar.activities.TaskActivity
import org.fossify.commons.helpers.MONTH_SECONDS
Expand All @@ -14,7 +16,8 @@ const val COLUMN_COUNT = 7
const val SCHEDULE_CALDAV_REQUEST_CODE = 10000
const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001
const val FETCH_INTERVAL = 3 * MONTH_SECONDS
const val MAX_SEARCH_YEAR = 2051218800L // 2035, limit search results for events repeating indefinitely
const val MAX_SEARCH_YEAR =
2051218800L // 2035, limit search results for events repeating indefinitely

// endless scrolling updating
const val MIN_EVENTS_TRESHOLD = 30
Expand Down Expand Up @@ -71,7 +74,8 @@ const val TYPE_TASK = 1
const val TWELVE_HOURS = 43200
const val DAY = 86400
const val WEEK = 604800
const val MONTH = 2592001 // exact value not taken into account, Joda is used for adding months and years
const val MONTH =
2592001 // exact value not taken into account, Joda is used for adding months and years
const val YEAR = 31536000

const val EVENT_PERIOD_TODAY = -1
Expand Down Expand Up @@ -148,10 +152,13 @@ const val AUTO_BACKUP_PAST_ENTRIES = "auto_backup_past_entries"
const val LAST_AUTO_BACKUP_TIME = "last_auto_backup_time"

// repeat_rule for monthly and yearly repetition
const val REPEAT_SAME_DAY = 1 // i.e. 25th every month, or 3rd june (if yearly repetition)
const val REPEAT_ORDER_WEEKDAY_USE_LAST = 2 // i.e. every last sunday. 4th if a month has 4 sundays, 5th if 5 (or last sunday in june, if yearly)
const val REPEAT_SAME_DAY =
1 // i.e. 25th every month, or 3rd june (if yearly repetition)
const val REPEAT_ORDER_WEEKDAY_USE_LAST =
2 // i.e. every last sunday. 4th if a month has 4 sundays, 5th if 5 (or last sunday in june, if yearly)
const val REPEAT_LAST_DAY = 3 // i.e. every last day of the month
const val REPEAT_ORDER_WEEKDAY = 4 // i.e. every 4th sunday, even if a month has 4 sundays only (will stay 4th even at months with 5)
const val REPEAT_ORDER_WEEKDAY =
4 // i.e. every 4th sunday, even if a month has 4 sundays only (will stay 4th even at months with 5)

// special event and task flags
const val FLAG_ALL_DAY = 1
Expand All @@ -178,7 +185,8 @@ const val DURATION = "DURATION:"
const val SUMMARY = "SUMMARY"
const val DESCRIPTION = "DESCRIPTION"
const val DESCRIPTION_EXPORT = "DESCRIPTION:"
val DESCRIPTION_REGEX = Regex("""DESCRIPTION(?:(?:;[^:;]*="[^"]*")*;?(?:;LANGUAGE=[^:;]*)?(?:;[^:;]*="[^"]*")*)*:(.*(?:\r?\n\s+.*)*)""")
val DESCRIPTION_REGEX =
Regex("""DESCRIPTION(?:(?:;[^:;]*="[^"]*")*;?(?:;LANGUAGE=[^:;]*)?(?:;[^:;]*="[^"]*")*)*:(.*(?:\r?\n\s+.*)*)""")
const val UID = "UID:"
const val ACTION = "ACTION:"
const val TRANSP = "TRANSP:"
Expand Down Expand Up @@ -269,6 +277,10 @@ const val EVENT_CALENDAR_ID = "EVENT_CALENDAR_ID"
const val IS_NEW_EVENT = "IS_NEW_EVENT"
const val EVENT_COLOR = "EVENT_COLOR"

// From Status attribute (RFC 5545 3.8.1.11)
const val CANCELLED = "CANCELLED"
const val TENTATIVE = "TENTATIVE"

// actions
const val ACTION_MARK_COMPLETED = "ACTION_MARK_COMPLETED"

Expand Down Expand Up @@ -330,3 +342,11 @@ fun getJavaDayOfWeekFromJoda(dayOfWeek: Int): Int {
else -> throw IllegalArgumentException("Invalid day: $dayOfWeek")
}
}

fun getStatusStringFromEventStatus(statusCode: Int): String {
return when (statusCode) {
Events.STATUS_CONFIRMED -> CONFIRMED
Events.STATUS_CANCELED -> CANCELLED
else -> TENTATIVE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class IcsExporter(private val context: Context) {
private var calendars = ArrayList<CalDAVCalendar>()
private val reminderLabel = context.getString(R.string.reminder)
private val exportTime = Formatter.getExportedTime(System.currentTimeMillis())
private val status = 1

fun exportEvents(
outputStream: OutputStream?,
Expand Down Expand Up @@ -149,7 +150,7 @@ class IcsExporter(private val context: Context) {
writeLn("$MISSING_YEAR${if (event.hasMissingYear()) 1 else 0}")

writeLn("$DTSTAMP$exportTime")
writeLn("$STATUS$CONFIRMED")
writeLn("$STATUS${getStatusStringFromEventStatus(event.status)}")
Parser().getRepeatCode(event).let { if (it.isNotEmpty()) writeLn("$RRULE$it") }

fillDescription(event.description.replace("\n", "\\n"), writer)
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/kotlin/org/fossify/calendar/helpers/IcsImporter.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.fossify.calendar.helpers

import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import org.fossify.calendar.R
import org.fossify.calendar.activities.SimpleActivity
import org.fossify.calendar.extensions.eventsDB
import org.fossify.calendar.extensions.eventsHelper
Expand Down Expand Up @@ -44,6 +46,7 @@ class IcsImporter(val activity: SimpleActivity) {
private var curLastModified = 0L
private var curCategoryColor = -2
private var curAvailability = Events.AVAILABILITY_BUSY
private var curStatus = Events.STATUS_CONFIRMED
private var isNotificationDescription = false
private var isProperReminderAction = false
private var isSequence = false
Expand Down Expand Up @@ -174,6 +177,12 @@ class IcsImporter(val activity: SimpleActivity) {
} else if (line.startsWith(STATUS)) {
if (isParsingTask && line.substring(STATUS.length) == COMPLETED) {
curFlags = curFlags or FLAG_TASK_COMPLETED
} else {
curStatus = when (line.substring(STATUS.length)) {
CONFIRMED -> Events.STATUS_CONFIRMED
CANCELLED -> Events.STATUS_CANCELED
else -> Events.STATUS_TENTATIVE
}
}
} else if (line.startsWith(COMPLETED)) {
if (isParsingTask && line.substring(COMPLETED.length).trim().isNotEmpty()) {
Expand Down Expand Up @@ -274,7 +283,8 @@ class IcsImporter(val activity: SimpleActivity) {
curLastModified,
source,
curAvailability,
type = curType
type = curType,
status = curStatus
)

if (isAllDay && curEnd > curStart && !event.isTask()) {
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/kotlin/org/fossify/calendar/models/Event.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fossify.calendar.models

import android.provider.CalendarContract
import android.provider.CalendarContract.Attendees
import androidx.collection.LongSparseArray
import androidx.room.ColumnInfo
Expand Down Expand Up @@ -41,7 +42,8 @@ data class Event(
@ColumnInfo(name = "source") var source: String = SOURCE_SIMPLE_CALENDAR,
@ColumnInfo(name = "availability") var availability: Int = 0,
@ColumnInfo(name = "color") var color: Int = 0,
@ColumnInfo(name = "type") var type: Int = TYPE_EVENT
@ColumnInfo(name = "type") var type: Int = TYPE_EVENT,
@ColumnInfo(name = "status") var status: Int = CalendarContract.Events.STATUS_CONFIRMED,
) : Serializable {

companion object {
Expand Down Expand Up @@ -214,4 +216,8 @@ data class Event(
fun isAttendeeInviteDeclined() = attendees.any {
it.isMe && it.status == Attendees.ATTENDEE_STATUS_DECLINED
}

fun isEventCanceled(): Boolean {
return status == CalendarContract.Events.STATUS_CANCELED
}
}
Loading
Loading