Merge pull request #2041 from Naveen3Singh/feature_event_color

Add support for CalDAV event colors
This commit is contained in:
Tibor Kaputa 2023-04-11 15:22:51 +02:00 committed by GitHub
commit cf94cf5ac2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 412 additions and 136 deletions

View File

@ -10,6 +10,7 @@ import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.CalendarContract.Attendees import android.provider.CalendarContract.Attendees
import android.provider.CalendarContract.Colors
import android.provider.ContactsContract.CommonDataKinds import android.provider.ContactsContract.CommonDataKinds
import android.provider.ContactsContract.CommonDataKinds.StructuredName import android.provider.ContactsContract.CommonDataKinds.StructuredName
import android.provider.ContactsContract.Data import android.provider.ContactsContract.Data
@ -76,6 +77,7 @@ class EventActivity : SimpleActivity() {
private var mOriginalStartTS = 0L private var mOriginalStartTS = 0L
private var mOriginalEndTS = 0L private var mOriginalEndTS = 0L
private var mIsNewEvent = true private var mIsNewEvent = true
private var mEventColor = 0
private lateinit var mEventStartDateTime: DateTime private lateinit var mEventStartDateTime: DateTime
private lateinit var mEventEndDateTime: DateTime private lateinit var mEventEndDateTime: DateTime
@ -164,6 +166,7 @@ class EventActivity : SimpleActivity() {
putString(ATTENDEES, getAllAttendees(false)) putString(ATTENDEES, getAllAttendees(false))
putInt(AVAILABILITY, mAvailability) putInt(AVAILABILITY, mAvailability)
putInt(EVENT_COLOR, mEventColor)
putLong(EVENT_TYPE_ID, mEventTypeId) putLong(EVENT_TYPE_ID, mEventTypeId)
putInt(EVENT_CALENDAR_ID, mEventCalendarId) putInt(EVENT_CALENDAR_ID, mEventCalendarId)
@ -196,6 +199,7 @@ class EventActivity : SimpleActivity() {
mReminder3Type = getInt(REMINDER_3_TYPE) mReminder3Type = getInt(REMINDER_3_TYPE)
mAvailability = getInt(AVAILABILITY) mAvailability = getInt(AVAILABILITY)
mEventColor = getInt(EVENT_COLOR)
mRepeatInterval = getInt(REPEAT_INTERVAL) mRepeatInterval = getInt(REPEAT_INTERVAL)
mRepeatRule = getInt(REPEAT_RULE) mRepeatRule = getInt(REPEAT_RULE)
@ -429,6 +433,7 @@ class EventActivity : SimpleActivity() {
mEventTypeId != mEvent.eventType || mEventTypeId != mEvent.eventType ||
mWasCalendarChanged || mWasCalendarChanged ||
mIsAllDayEvent != mEvent.getIsAllDay() || mIsAllDayEvent != mEvent.getIsAllDay() ||
mEventColor != mEvent.color ||
hasTimeChanged hasTimeChanged
) { ) {
return true return true
@ -488,6 +493,7 @@ class EventActivity : SimpleActivity() {
mEventTypeId = mEvent.eventType mEventTypeId = mEvent.eventType
mEventCalendarId = mEvent.getCalDAVCalendarId() mEventCalendarId = mEvent.getCalDAVCalendarId()
mAvailability = mEvent.availability mAvailability = mEvent.availability
mEventColor = mEvent.color
val token = object : TypeToken<List<Attendee>>() {}.type val token = object : TypeToken<List<Attendee>>() {}.type
mAttendees = Gson().fromJson<ArrayList<Attendee>>(mEvent.attendees, token) ?: ArrayList() mAttendees = Gson().fromJson<ArrayList<Attendee>>(mEvent.attendees, token) ?: ArrayList()
@ -825,6 +831,28 @@ class EventActivity : SimpleActivity() {
} }
} }
private fun showEventColorDialog() {
hideKeyboard()
ensureBackgroundThread {
val eventType = eventsHelper.getEventTypeWithCalDAVCalendarId(calendarId = mEventCalendarId)!!
val eventColors = getEventColors(eventType)
runOnUiThread {
val currentColor = if (mEventColor == 0) {
eventType.color
} else {
mEventColor
}
SelectEventColorDialog(activity = this, colors = eventColors, currentColor = currentColor) { newColor ->
if (newColor != currentColor) {
mEventColor = newColor
updateEventColorInfo(defaultColor = eventType.color)
}
}
}
}
}
private fun checkReminderTexts() { private fun checkReminderTexts() {
updateReminder1Text() updateReminder1Text()
updateReminder2Text() updateReminder2Text()
@ -959,6 +987,10 @@ class EventActivity : SimpleActivity() {
updateAvailabilityImage() updateAvailabilityImage()
} }
} }
event_caldav_color_holder.setOnClickListener {
showEventColorDialog()
}
} else { } else {
updateCurrentCalendarInfo(null) updateCurrentCalendarInfo(null)
} }
@ -973,7 +1005,6 @@ class EventActivity : SimpleActivity() {
event_type_holder.beVisibleIf(currentCalendar == null) event_type_holder.beVisibleIf(currentCalendar == null)
event_caldav_calendar_divider.beVisibleIf(currentCalendar == null) event_caldav_calendar_divider.beVisibleIf(currentCalendar == null)
event_caldav_calendar_email.beGoneIf(currentCalendar == null) event_caldav_calendar_email.beGoneIf(currentCalendar == null)
event_caldav_calendar_color.beGoneIf(currentCalendar == null)
if (currentCalendar == null) { if (currentCalendar == null) {
mEventCalendarId = STORED_LOCALLY_ONLY mEventCalendarId = STORED_LOCALLY_ONLY
@ -986,14 +1017,23 @@ class EventActivity : SimpleActivity() {
event_caldav_calendar_holder.apply { event_caldav_calendar_holder.apply {
setPadding(paddingLeft, mediumMargin, paddingRight, mediumMargin) setPadding(paddingLeft, mediumMargin, paddingRight, mediumMargin)
} }
event_caldav_color_image.beGone()
event_caldav_color_holder.beGone()
event_caldav_color_divider.beGone()
} else { } else {
event_caldav_calendar_email.text = currentCalendar.accountName event_caldav_calendar_email.text = currentCalendar.accountName
ensureBackgroundThread { ensureBackgroundThread {
val calendarColor = eventsHelper.getEventTypeWithCalDAVCalendarId(currentCalendar.id)?.color ?: currentCalendar.color val eventType = eventsHelper.getEventTypeWithCalDAVCalendarId(currentCalendar.id)
val calendarColor = eventType?.color ?: currentCalendar.color
val canCustomizeColors = if (eventType != null) {
getEventColors(eventType).isNotEmpty()
} else {
false
}
runOnUiThread { runOnUiThread {
event_caldav_calendar_color.setFillWithStroke(calendarColor, getProperBackgroundColor())
event_caldav_calendar_name.apply { event_caldav_calendar_name.apply {
text = currentCalendar.displayName text = currentCalendar.displayName
setPadding(paddingLeft, paddingTop, paddingRight, resources.getDimension(R.dimen.tiny_margin).toInt()) setPadding(paddingLeft, paddingTop, paddingRight, resources.getDimension(R.dimen.tiny_margin).toInt())
@ -1002,11 +1042,31 @@ class EventActivity : SimpleActivity() {
event_caldav_calendar_holder.apply { event_caldav_calendar_holder.apply {
setPadding(paddingLeft, 0, paddingRight, 0) setPadding(paddingLeft, 0, paddingRight, 0)
} }
event_caldav_color_image.beVisibleIf(canCustomizeColors)
event_caldav_color_holder.beVisibleIf(canCustomizeColors)
event_caldav_color_divider.beVisibleIf(canCustomizeColors)
if (canCustomizeColors) {
updateEventColorInfo(calendarColor)
}
} }
} }
} }
} }
private fun updateEventColorInfo(defaultColor: Int) {
val eventColor = if (mEventColor == 0) {
defaultColor
} else {
mEventColor
}
event_caldav_color.setFillWithStroke(eventColor, getProperBackgroundColor())
}
private fun getEventColors(eventType: EventType): IntArray {
return calDAVHelper.getAvailableCalDAVCalendarColors(eventType, Colors.TYPE_EVENT).keys.toIntArray()
}
private fun resetTime() { private fun resetTime() {
if (mEventEndDateTime.isBefore(mEventStartDateTime) && if (mEventEndDateTime.isBefore(mEventStartDateTime) &&
mEventStartDateTime.dayOfMonth() == mEventEndDateTime.dayOfMonth() && mEventStartDateTime.dayOfMonth() == mEventEndDateTime.dayOfMonth() &&
@ -1195,6 +1255,7 @@ class EventActivity : SimpleActivity() {
source = newSource source = newSource
location = event_location.value location = event_location.value
availability = mAvailability availability = mAvailability
color = mEventColor
} }
// recreate the event if it was moved in a different CalDAV calendar // recreate the event if it was moved in a different CalDAV calendar
@ -1791,7 +1852,7 @@ class EventActivity : SimpleActivity() {
val textColor = getProperTextColor() val textColor = getProperTextColor()
arrayOf( arrayOf(
event_time_image, event_time_zone_image, event_repetition_image, event_reminder_image, event_type_image, event_caldav_calendar_image, event_time_image, event_time_zone_image, event_repetition_image, event_reminder_image, event_type_image, event_caldav_calendar_image,
event_reminder_1_type, event_reminder_2_type, event_reminder_3_type, event_attendees_image, event_availability_image event_reminder_1_type, event_reminder_2_type, event_reminder_3_type, event_attendees_image, event_availability_image, event_caldav_color_image
).forEach { ).forEach {
it.applyColorFilter(textColor) it.applyColorFilter(textColor)
} }

View File

@ -0,0 +1,52 @@
package com.simplemobiletools.calendar.pro.adapters
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.commons.extensions.applyColorFilter
import kotlinx.android.synthetic.main.checkable_color_button.view.*
class CheckableColorAdapter(private val activity: Activity, private val colors: IntArray, var currentColor: Int, val callback: (color: Int) -> Unit) :
RecyclerView.Adapter<CheckableColorAdapter.CheckableColorViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheckableColorViewHolder {
val itemView = LayoutInflater.from(activity).inflate(R.layout.checkable_color_button, parent, false)
return CheckableColorViewHolder(itemView)
}
override fun onBindViewHolder(holder: CheckableColorViewHolder, position: Int) {
val color = colors[position]
holder.bindView(color = color, checked = color == currentColor)
}
override fun getItemCount() = colors.size
private fun updateSelection(color: Int) {
currentColor = color
callback(color)
}
inner class CheckableColorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindView(color: Int, checked: Boolean) {
itemView.checkable_color_button.apply {
backgroundTintList = ColorStateList.valueOf(color)
setOnClickListener {
updateSelection(color)
}
if (checked) {
setImageResource(R.drawable.ic_check_vector)
applyColorFilter(Color.WHITE)
} else {
setImageDrawable(null)
}
}
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper
import com.simplemobiletools.calendar.pro.extensions.eventsHelper import com.simplemobiletools.calendar.pro.extensions.eventsHelper
import com.simplemobiletools.calendar.pro.helpers.OTHER_EVENT import com.simplemobiletools.calendar.pro.helpers.OTHER_EVENT
import com.simplemobiletools.calendar.pro.models.EventType import com.simplemobiletools.calendar.pro.models.EventType
@ -32,7 +33,9 @@ class EditEventTypeDialog(val activity: Activity, var eventType: EventType? = nu
} }
} }
} else { } else {
SelectEventTypeColorDialog(activity, eventType!!) { val currentColor = eventType!!.color
val colors = activity.calDAVHelper.getAvailableCalDAVCalendarColors(eventType!!).keys.toIntArray()
SelectEventTypeColorDialog(activity, colors = colors, currentColor = currentColor) {
eventType!!.color = it eventType!!.color = it
setupColor(type_color) setupColor(type_color)
} }

View File

@ -0,0 +1,42 @@
package com.simplemobiletools.calendar.pro.dialogs
import android.app.Activity
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.adapters.CheckableColorAdapter
import com.simplemobiletools.calendar.pro.views.AutoGridLayoutManager
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import kotlinx.android.synthetic.main.dialog_select_color.view.*
class SelectEventColorDialog(val activity: Activity, val colors: IntArray, var currentColor: Int, val callback: (color: Int) -> Unit) {
private var dialog: AlertDialog? = null
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_select_color, null) as ViewGroup
val colorAdapter = CheckableColorAdapter(activity, colors, currentColor) { color ->
callback(color)
dialog?.dismiss()
}
view.color_grid.apply {
val width = activity.resources.getDimensionPixelSize(R.dimen.smaller_icon_size)
val spacing = activity.resources.getDimensionPixelSize(R.dimen.small_margin) * 2
layoutManager = AutoGridLayoutManager(context = activity, itemWidth = width + spacing)
adapter = colorAdapter
}
activity.getAlertDialogBuilder()
.apply {
setNeutralButton(R.string.default_calendar_color) { dialog, _ ->
callback(0)
dialog?.dismiss()
}
activity.setupDialogStuff(view, this, R.string.event_color) {
dialog = it
}
}
}
}

View File

@ -2,42 +2,37 @@ package com.simplemobiletools.calendar.pro.dialogs
import android.app.Activity import android.app.Activity
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper import com.simplemobiletools.calendar.pro.adapters.CheckableColorAdapter
import com.simplemobiletools.calendar.pro.models.EventType import com.simplemobiletools.calendar.pro.views.AutoGridLayoutManager
import com.simplemobiletools.commons.dialogs.ColorPickerDialog import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.getProperBackgroundColor
import com.simplemobiletools.commons.extensions.setFillWithStroke
import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.setupDialogStuff
import kotlinx.android.synthetic.main.dialog_select_event_type_color.view.* import kotlinx.android.synthetic.main.dialog_select_color.view.*
import kotlinx.android.synthetic.main.radio_button_with_color.view.*
class SelectEventTypeColorDialog(val activity: Activity, val eventType: EventType, val callback: (color: Int) -> Unit) { class SelectEventTypeColorDialog(val activity: Activity, val colors: IntArray, var currentColor: Int, val callback: (color: Int) -> Unit) {
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private val radioGroup: RadioGroup
private var wasInit = false
private val colors = activity.calDAVHelper.getAvailableCalDAVCalendarColors(eventType)
init { init {
val view = activity.layoutInflater.inflate(R.layout.dialog_select_event_type_color, null) as ViewGroup val view = activity.layoutInflater.inflate(R.layout.dialog_select_color, null) as ViewGroup
radioGroup = view.dialog_select_event_type_color_radio val colorAdapter = CheckableColorAdapter(activity, colors, currentColor) { color ->
view.dialog_select_event_type_other_value.setOnClickListener { callback(color)
showCustomColorPicker() dialog?.dismiss()
} }
colors.forEachIndexed { index, value -> view.color_grid.apply {
addRadioButton(index, value) val width = activity.resources.getDimensionPixelSize(R.dimen.smaller_icon_size)
val spacing = activity.resources.getDimensionPixelSize(R.dimen.small_margin) * 2
layoutManager = AutoGridLayoutManager(context = activity, itemWidth = width + spacing)
adapter = colorAdapter
} }
wasInit = true
activity.getAlertDialogBuilder() activity.getAlertDialogBuilder()
.setNegativeButton(R.string.cancel, null)
.apply { .apply {
activity.setupDialogStuff(view, this) { alertDialog -> activity.setupDialogStuff(view, this, R.string.color) {
dialog = alertDialog dialog = it
} }
if (colors.isEmpty()) { if (colors.isEmpty()) {
@ -46,34 +41,12 @@ class SelectEventTypeColorDialog(val activity: Activity, val eventType: EventTyp
} }
} }
private fun addRadioButton(colorKey: Int, color: Int) {
val view = activity.layoutInflater.inflate(R.layout.radio_button_with_color, null)
(view.dialog_radio_button as RadioButton).apply {
text = if (color == 0) activity.getString(R.string.transparent) else String.format("#%06X", 0xFFFFFF and color)
isChecked = color == eventType.color
id = colorKey
}
view.dialog_radio_color.setFillWithStroke(color, activity.getProperBackgroundColor())
view.setOnClickListener {
viewClicked(colorKey)
}
radioGroup.addView(view, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
}
private fun viewClicked(colorKey: Int) {
if (!wasInit)
return
callback(colors[colorKey])
dialog?.dismiss()
}
private fun showCustomColorPicker() { private fun showCustomColorPicker() {
ColorPickerDialog(activity, eventType.color) { wasPositivePressed, color -> ColorPickerDialog(activity, currentColor) { wasPositivePressed, color ->
if (wasPositivePressed) { if (wasPositivePressed) {
callback(color) callback(color)
} }
dialog?.dismiss() dialog?.dismiss()
} }
} }

View File

@ -557,9 +557,13 @@ class WeekFragment : Fragment(), WeeklyCalendar {
val dayColumn = dayColumns[dayOfWeek] val dayColumn = dayColumns[dayOfWeek]
(inflater.inflate(R.layout.week_event_marker, null, false) as ConstraintLayout).apply { (inflater.inflate(R.layout.week_event_marker, null, false) as ConstraintLayout).apply {
var backgroundColor = eventTypeColors.get(event.eventType, primaryColor) var backgroundColor = if (event.color == 0) {
eventTypeColors.get(event.eventType, primaryColor)
} else {
event.color
}
var textColor = backgroundColor.getContrastColor() var textColor = backgroundColor.getContrastColor()
val currentEventWeeklyView = eventTimeRanges[currentDayCode]!!.get(event.id) val currentEventWeeklyView = eventTimeRanges[currentDayCode]!![event.id]
val adjustAlpha = if (event.isTask()) { val adjustAlpha = if (event.isTask()) {
dimCompletedTasks && event.isTaskCompleted() dimCompletedTasks && event.isTaskCompleted()

View File

@ -4,8 +4,8 @@ import android.annotation.SuppressLint
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.graphics.Color
import android.provider.CalendarContract.* import android.provider.CalendarContract.*
import android.util.SparseIntArray
import android.widget.Toast import android.widget.Toast
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
@ -20,6 +20,7 @@ import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormat import org.joda.time.format.DateTimeFormat
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
class CalDAVHelper(val context: Context) { class CalDAVHelper(val context: Context) {
@ -84,8 +85,9 @@ class CalDAVHelper(val context: Context) {
val accountType = cursor.getStringValue(Calendars.ACCOUNT_TYPE) val accountType = cursor.getStringValue(Calendars.ACCOUNT_TYPE)
val ownerName = cursor.getStringValue(Calendars.OWNER_ACCOUNT) ?: "" val ownerName = cursor.getStringValue(Calendars.OWNER_ACCOUNT) ?: ""
val color = cursor.getIntValue(Calendars.CALENDAR_COLOR) val color = cursor.getIntValue(Calendars.CALENDAR_COLOR)
val displayColor = getDisplayColorFromColor(color)
val accessLevel = cursor.getIntValue(Calendars.CALENDAR_ACCESS_LEVEL) val accessLevel = cursor.getIntValue(Calendars.CALENDAR_ACCESS_LEVEL)
val calendar = CalDAVCalendar(id, displayName, accountName, accountType, ownerName, color, accessLevel) val calendar = CalDAVCalendar(id, displayName, accountName, accountType, ownerName, displayColor, accessLevel)
calendars.add(calendar) calendars.add(calendar)
} }
@ -116,35 +118,33 @@ class CalDAVHelper(val context: Context) {
private fun getCalDAVColorKey(eventType: EventType): String? { private fun getCalDAVColorKey(eventType: EventType): String? {
val colors = getAvailableCalDAVCalendarColors(eventType) val colors = getAvailableCalDAVCalendarColors(eventType)
val colorKey = colors.indexOf(eventType.color) return colors[eventType.color]
return if (colorKey > 0) { }
colorKey.toString()
} else { // darkens the given color to ensure that white text is clearly visible on top of it
null private fun getDisplayColorFromColor(color: Int): Int {
} val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[1] = min(hsv[1] * SATURATION_ADJUST, 1.0f)
hsv[2] = hsv[2] * INTENSITY_ADJUST
return Color.HSVToColor(hsv)
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
fun getAvailableCalDAVCalendarColors(eventType: EventType): ArrayList<Int> { fun getAvailableCalDAVCalendarColors(eventType: EventType, colorType: Int = Colors.TYPE_CALENDAR): Map<Int, String> {
val colors = SparseIntArray() val colors = mutableMapOf<Int, String>()
val uri = Colors.CONTENT_URI val uri = Colors.CONTENT_URI
val projection = arrayOf(Colors.COLOR, Colors.COLOR_KEY) val projection = arrayOf(Colors.COLOR, Colors.COLOR_KEY)
val selection = "${Colors.COLOR_TYPE} = ? AND ${Colors.ACCOUNT_NAME} = ?" val selection = "${Colors.COLOR_TYPE} = ? AND ${Colors.ACCOUNT_NAME} = ?"
val selectionArgs = arrayOf(Colors.TYPE_CALENDAR.toString(), eventType.caldavEmail) val selectionArgs = arrayOf(colorType.toString(), eventType.caldavEmail)
context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> context.queryCursor(uri, projection, selection, selectionArgs) { cursor ->
val colorKey = cursor.getIntValue(Colors.COLOR_KEY) val colorKey = cursor.getStringValue(Colors.COLOR_KEY)
val color = cursor.getIntValue(Colors.COLOR) val color = cursor.getIntValue(Colors.COLOR)
colors.put(colorKey, color) val displayColor = getDisplayColorFromColor(color)
colors[displayColor] = colorKey
} }
return colors.toSortedMap(HsvColorComparator())
var sortedColors = ArrayList<Int>(colors.size())
(0 until colors.size()).mapTo(sortedColors) { colors[it] }
if (sortedColors.isNotEmpty()) {
sortedColors = sortedColors.distinct() as ArrayList<Int>
}
return sortedColors
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@ -181,7 +181,8 @@ class CalDAVHelper(val context: Context) {
Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE,
Events.CALENDAR_TIME_ZONE, Events.CALENDAR_TIME_ZONE,
Events.DELETED, Events.DELETED,
Events.AVAILABILITY Events.AVAILABILITY,
Events.EVENT_COLOR
) )
val selection = "${Events.CALENDAR_ID} = $calendarId" val selection = "${Events.CALENDAR_ID} = $calendarId"
@ -210,6 +211,12 @@ class CalDAVHelper(val context: Context) {
val reminders = getCalDAVEventReminders(id) val reminders = getCalDAVEventReminders(id)
val attendees = Gson().toJson(getCalDAVEventAttendees(id)) val attendees = Gson().toJson(getCalDAVEventAttendees(id))
val availability = cursor.getIntValue(Events.AVAILABILITY) val availability = cursor.getIntValue(Events.AVAILABILITY)
val color = cursor.getIntValueOrNull(Events.EVENT_COLOR)
val displayColor = if (color != null) {
getDisplayColorFromColor(color)
} else {
0
}
if (endTS == 0L) { if (endTS == 0L) {
val duration = cursor.getStringValue(Events.DURATION) ?: "" val duration = cursor.getStringValue(Events.DURATION) ?: ""
@ -230,7 +237,8 @@ class CalDAVHelper(val context: Context) {
reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF,
reminder1?.type ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, reminder1?.type ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION,
reminder3?.type ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule, reminder3?.type ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule,
repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId, source = source, availability = availability repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId,
source = source, availability = availability, color = displayColor
) )
if (event.getIsAllDay()) { if (event.getIsAllDay()) {
@ -291,7 +299,6 @@ class CalDAVHelper(val context: Context) {
existingEvent.apply { existingEvent.apply {
this.id = null this.id = null
color = 0
lastUpdated = 0L lastUpdated = 0L
repetitionExceptions = ArrayList() repetitionExceptions = ArrayList()
} }
@ -394,14 +401,23 @@ class CalDAVHelper(val context: Context) {
} }
private fun fillEventContentValues(event: Event): ContentValues { private fun fillEventContentValues(event: Event): ContentValues {
val calendarId = event.getCalDAVCalendarId()
return ContentValues().apply { return ContentValues().apply {
put(Events.CALENDAR_ID, event.getCalDAVCalendarId()) put(Events.CALENDAR_ID, calendarId)
put(Events.TITLE, event.title) put(Events.TITLE, event.title)
put(Events.DESCRIPTION, event.description) put(Events.DESCRIPTION, event.description)
put(Events.EVENT_LOCATION, event.location) put(Events.EVENT_LOCATION, event.location)
put(Events.STATUS, Events.STATUS_CONFIRMED) put(Events.STATUS, Events.STATUS_CONFIRMED)
put(Events.AVAILABILITY, event.availability) put(Events.AVAILABILITY, event.availability)
if (event.color == 0) {
put(Events.EVENT_COLOR_KEY, "")
} else {
val eventType = eventsHelper.getEventTypeWithCalDAVCalendarId(calendarId)!!
val colors = getAvailableCalDAVCalendarColors(eventType, Colors.TYPE_EVENT)
put(Events.EVENT_COLOR_KEY, colors[event.color])
}
val repeatRule = Parser().getRepeatCode(event) val repeatRule = Parser().getRepeatCode(event)
if (repeatRule.isEmpty()) { if (repeatRule.isEmpty()) {
putNull(Events.RRULE) putNull(Events.RRULE)
@ -534,4 +550,9 @@ class CalDAVHelper(val context: Context) {
private fun getCalDAVEventImportId(calendarId: Int, eventId: Long) = "$CALDAV-$calendarId-$eventId" private fun getCalDAVEventImportId(calendarId: Int, eventId: Long) = "$CALDAV-$calendarId-$eventId"
private fun refreshCalDAVCalendar(event: Event) = context.refreshCalDAVCalendars(event.getCalDAVCalendarId().toString(), false) private fun refreshCalDAVCalendar(event: Event) = context.refreshCalDAVCalendars(event.getCalDAVCalendarId().toString(), false)
companion object {
private const val INTENSITY_ADJUST = 0.8f
private const val SATURATION_ADJUST = 1.3f
}
} }

View File

@ -255,6 +255,7 @@ const val AVAILABILITY = "AVAILABILITY"
const val EVENT_TYPE_ID = "EVENT_TYPE_ID" const val EVENT_TYPE_ID = "EVENT_TYPE_ID"
const val EVENT_CALENDAR_ID = "EVENT_CALENDAR_ID" const val EVENT_CALENDAR_ID = "EVENT_CALENDAR_ID"
const val IS_NEW_EVENT = "IS_NEW_EVENT" const val IS_NEW_EVENT = "IS_NEW_EVENT"
const val EVENT_COLOR = "EVENT_COLOR"
// actions // actions
const val ACTION_MARK_COMPLETED = "ACTION_MARK_COMPLETED" const val ACTION_MARK_COMPLETED = "ACTION_MARK_COMPLETED"

View File

@ -337,7 +337,10 @@ class EventsHelper(val context: Context) {
} }
} }
} }
it.color = eventTypeColors.get(it.eventType) ?: context.getProperPrimaryColor()
if (it.color == 0) {
it.color = eventTypeColors.get(it.eventType) ?: context.getProperPrimaryColor()
}
} }
callback(events) callback(events)

View File

@ -0,0 +1,40 @@
package com.simplemobiletools.calendar.pro.helpers
import android.graphics.Color
/**
* A color comparator which compares based on hue, saturation, and value.
* Source: AOSP Color picker, https://cs.android.com/android/platform/superproject/+/master:frameworks/opt/colorpicker/src/com/android/colorpicker/HsvColorComparator.java
*/
class HsvColorComparator : Comparator<Int?> {
override fun compare(lhs: Int?, rhs: Int?): Int {
val hsv = FloatArray(3)
Color.colorToHSV(lhs!!, hsv)
val hue1 = hsv[0]
val sat1 = hsv[1]
val val1 = hsv[2]
val hsv2 = FloatArray(3)
Color.colorToHSV(rhs!!, hsv2)
val hue2 = hsv2[0]
val sat2 = hsv2[1]
val val2 = hsv2[2]
if (hue1 < hue2) {
return 1
} else if (hue1 > hue2) {
return -1
} else {
if (sat1 < sat2) {
return 1
} else if (sat1 > sat2) {
return -1
} else {
if (val1 < val2) {
return 1
} else if (val1 > val2) {
return -1
}
}
}
return 0
}
}

View File

@ -0,0 +1,34 @@
package com.simplemobiletools.calendar.pro.views
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
/**
* RecyclerView GridLayoutManager but with automatic spanCount calculation
* @param itemWidth: Grid item width in pixels. Will be used to calculate span count.
*/
class AutoGridLayoutManager(
context: Context,
private var itemWidth: Int
) : GridLayoutManager(context, 1) {
init {
require(itemWidth >= 0)
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
val width = width
val height = height
if (itemWidth > 0 && width > 0 && height > 0) {
val totalSpace = if (orientation == VERTICAL) {
width - paddingRight - paddingLeft
} else {
height - paddingTop - paddingBottom
}
spanCount = max(1, totalSpace / itemWidth)
}
super.onLayoutChildren(recycler, state)
}
}

View File

@ -507,10 +507,63 @@
android:importantForAccessibility="no" /> android:importantForAccessibility="no" />
<ImageView <ImageView
android:id="@+id/event_caldav_calendar_image" android:id="@+id/event_caldav_color_image"
android:layout_width="@dimen/smaller_icon_size" android:layout_width="@dimen/smaller_icon_size"
android:layout_height="@dimen/smaller_icon_size" android:layout_height="@dimen/smaller_icon_size"
android:layout_below="@+id/event_availability_divider" android:layout_below="@+id/event_availability_divider"
android:layout_alignTop="@+id/event_caldav_color_holder"
android:layout_alignBottom="@+id/event_caldav_color_holder"
android:layout_marginStart="@dimen/normal_margin"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_color_vector" />
<RelativeLayout
android:id="@+id/event_caldav_color_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/event_availability_divider"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginBottom="@dimen/medium_margin"
android:layout_toEndOf="@+id/event_caldav_color_image"
android:background="?attr/selectableItemBackground">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/event_color_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/small_margin"
android:layout_marginEnd="@dimen/medium_margin"
android:layout_toStartOf="@+id/event_caldav_color"
android:paddingTop="@dimen/normal_margin"
android:paddingBottom="@dimen/normal_margin"
android:text="@string/event_color"
android:textSize="@dimen/day_text_size" />
<ImageView
android:id="@+id/event_caldav_color"
android:layout_width="@dimen/color_sample_size"
android:layout_height="@dimen/color_sample_size"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/activity_margin"
android:clickable="false" />
</RelativeLayout>
<ImageView
android:id="@+id/event_caldav_color_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_below="@+id/event_caldav_color_image"
android:layout_marginTop="@dimen/medium_margin"
android:background="@color/divider_grey"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/event_caldav_calendar_image"
android:layout_width="@dimen/smaller_icon_size"
android:layout_height="@dimen/smaller_icon_size"
android:layout_below="@+id/event_caldav_color_divider"
android:layout_alignTop="@+id/event_caldav_calendar_holder" android:layout_alignTop="@+id/event_caldav_calendar_holder"
android:layout_alignEnd="@+id/event_time_image" android:layout_alignEnd="@+id/event_time_image"
android:layout_alignBottom="@+id/event_caldav_calendar_holder" android:layout_alignBottom="@+id/event_caldav_calendar_holder"
@ -523,7 +576,7 @@
android:id="@+id/event_caldav_calendar_holder" android:id="@+id/event_caldav_calendar_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/event_availability_divider" android:layout_below="@+id/event_caldav_color_divider"
android:layout_toEndOf="@+id/event_caldav_calendar_image" android:layout_toEndOf="@+id/event_caldav_calendar_image"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:visibility="gone"> android:visibility="gone">
@ -533,7 +586,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin"
android:layout_toStartOf="@+id/event_caldav_calendar_color"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:paddingTop="@dimen/medium_margin" android:paddingTop="@dimen/medium_margin"
@ -548,7 +600,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/event_caldav_calendar_name" android:layout_below="@+id/event_caldav_calendar_name"
android:layout_marginStart="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin"
android:layout_toStartOf="@+id/event_caldav_calendar_color"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:paddingEnd="@dimen/medium_margin" android:paddingEnd="@dimen/medium_margin"
@ -556,15 +607,6 @@
android:textSize="@dimen/meta_text_size" android:textSize="@dimen/meta_text_size"
tools:text="hello@simplemobiletools.com" /> tools:text="hello@simplemobiletools.com" />
<ImageView
android:id="@+id/event_caldav_calendar_color"
android:layout_width="@dimen/color_sample_size"
android:layout_height="@dimen/color_sample_size"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/activity_margin"
android:clickable="false" />
</RelativeLayout> </RelativeLayout>
<ImageView <ImageView

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/small_margin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/checkable_color_button"
android:layout_width="@dimen/smaller_icon_size"
android:layout_height="@dimen/smaller_icon_size"
android:background="@drawable/button_background_rounded"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/medium_margin"
tools:src="@drawable/ic_check_vector" />
</FrameLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/color_grid_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/normal_margin"
android:paddingTop="@dimen/big_margin"
android:paddingBottom="@dimen/medium_margin">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/color_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:overScrollMode="ifContentScrolls"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:itemCount="16"
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/checkable_color_button"
tools:spanCount="6" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_select_event_type_color_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/dialog_select_event_type_other_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/dialog_select_event_type_other_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingStart="@dimen/big_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/activity_margin"
android:text="@string/select_a_different_caldav_color"
android:textSize="@dimen/normal_text_size"
android:visibility="gone" />
<ImageView
android:id="@+id/dialog_select_event_type_other_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_below="@+id/dialog_select_event_type_other_value"
android:background="@color/divider_grey"
android:importantForAccessibility="no"
android:visibility="gone" />
<RadioGroup
android:id="@+id/dialog_select_event_type_color_radio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/dialog_select_event_type_other_divider"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/normal_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/normal_margin" />
</RelativeLayout>
</ScrollView>