Skip to content

Commit 6a9733c

Browse files
authored
Merge pull request #546 from chris-wolf/develop
Added event color (Android) + Updating of calendar color (Android/iOS)
2 parents a6352f6 + 7791f59 commit 6a9733c

23 files changed

+439
-61
lines changed

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ group 'com.builttoroam.devicecalendar'
22
version '1.0-SNAPSHOT'
33

44
buildscript {
5-
ext.kotlin_version = '1.6.0'
5+
ext.kotlin_version = '1.8.22'
66
repositories {
77
google()
88
mavenCentral()
@@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
2525
apply plugin: 'kotlin-android'
2626

2727
android {
28-
compileSdkVersion 33
28+
compileSdkVersion 34
2929

3030
sourceSets {
3131
main.java.srcDirs += 'src/main/kotlin'

android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import com.builttoroam.devicecalendar.common.ErrorCodes.Companion as EC
3939
import com.builttoroam.devicecalendar.common.ErrorMessages.Companion as EM
4040
import org.dmfs.rfc5545.recur.Freq as RruleFreq
4141
import org.dmfs.rfc5545.recur.RecurrenceRule as Rrule
42+
import android.provider.CalendarContract.Colors
43+
import androidx.collection.SparseArrayCompat
4244

4345
private const val RETRIEVE_CALENDARS_REQUEST_CODE = 0
4446
private const val RETRIEVE_EVENTS_REQUEST_CODE = RETRIEVE_CALENDARS_REQUEST_CODE + 1
@@ -625,6 +627,8 @@ class CalendarDelegate(binding: ActivityPluginBinding?, context: Context) :
625627
values.put(Events.DTEND, end)
626628
values.put(Events.EVENT_END_TIMEZONE, endTimeZone)
627629
values.put(Events.DURATION, duration)
630+
values.put(Events.EVENT_COLOR_KEY, event.eventColorKey)
631+
values.put(Events.EVENT_COLOR, event.eventColor)
628632
return values
629633
}
630634

@@ -938,6 +942,8 @@ class CalendarDelegate(binding: ActivityPluginBinding?, context: Context) :
938942
val endTimeZone = cursor.getString(Cst.EVENT_PROJECTION_END_TIMEZONE_INDEX)
939943
val availability = parseAvailability(cursor.getInt(Cst.EVENT_PROJECTION_AVAILABILITY_INDEX))
940944
val eventStatus = parseEventStatus(cursor.getInt(Cst.EVENT_PROJECTION_STATUS_INDEX))
945+
val eventColor = cursor.getInt(Cst.EVENT_PROJECTION_EVENT_COLOR_INDEX)
946+
val eventColorKey = cursor.getInt(Cst.EVENT_PROJECTION_EVENT_COLOR_KEY_INDEX)
941947
val event = Event()
942948
event.eventTitle = title ?: "New Event"
943949
event.eventId = eventId.toString()
@@ -953,6 +959,8 @@ class CalendarDelegate(binding: ActivityPluginBinding?, context: Context) :
953959
event.eventEndTimeZone = endTimeZone
954960
event.availability = availability
955961
event.eventStatus = eventStatus
962+
event.eventColor = if (eventColor == 0) null else eventColor
963+
event.eventColorKey = if (eventColorKey == 0) null else eventColorKey
956964

957965
return event
958966
}
@@ -1125,6 +1133,73 @@ class CalendarDelegate(binding: ActivityPluginBinding?, context: Context) :
11251133
return reminders
11261134
}
11271135

1136+
/**
1137+
* load available event colors for the given account name
1138+
* unable to find official documentation, so logic is based on https://android.googlesource.com/platform/packages/apps/Calendar.git/+/refs/heads/pie-release/src/com/android/calendar/EventInfoFragment.java
1139+
**/
1140+
private fun retrieveColors(accountName: String, colorType: Int): List<Pair<Int, Int>> {
1141+
val contentResolver: ContentResolver? = _context?.contentResolver
1142+
val uri: Uri = Colors.CONTENT_URI
1143+
val colors = mutableListOf<Int>()
1144+
val displayColorKeyMap = SparseArrayCompat<Int>()
1145+
1146+
val projection = arrayOf(
1147+
Colors.COLOR,
1148+
Colors.COLOR_KEY,
1149+
)
1150+
1151+
// load only event colors for the given account name
1152+
val selection = "${Colors.COLOR_TYPE} = ? AND ${Colors.ACCOUNT_NAME} = ?"
1153+
val selectionArgs = arrayOf(colorType.toString(), accountName)
1154+
1155+
1156+
val cursor: Cursor? = contentResolver?.query(uri, projection, selection, selectionArgs, null)
1157+
cursor?.use {
1158+
while (it.moveToNext()) {
1159+
val color = it.getInt(it.getColumnIndexOrThrow(Colors.COLOR))
1160+
val colorKey = it.getInt(it.getColumnIndexOrThrow(Colors.COLOR_KEY))
1161+
displayColorKeyMap.put(color, colorKey);
1162+
colors.add(color)
1163+
}
1164+
cursor.close();
1165+
// sort colors by colorValue, since they are loaded unordered
1166+
colors.sortWith(HsvColorComparator())
1167+
}
1168+
return colors.map { Pair(it, displayColorKeyMap[it]!! ) }.toList()
1169+
}
1170+
1171+
fun retrieveEventColors(accountName: String): List<Pair<Int, Int>> {
1172+
return retrieveColors(accountName, Colors.TYPE_EVENT)
1173+
}
1174+
fun retrieveCalendarColors(accountName: String): List<Pair<Int, Int>> {
1175+
return retrieveColors(accountName, Colors.TYPE_CALENDAR)
1176+
}
1177+
1178+
fun updateCalendarColor(calendarId: Long, newColorKey: Int?, newColor: Int?): Boolean {
1179+
val contentResolver: ContentResolver? = _context?.contentResolver
1180+
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarId)
1181+
val values = ContentValues().apply {
1182+
put(CalendarContract.Calendars.CALENDAR_COLOR_KEY, newColorKey)
1183+
put(CalendarContract.Calendars.CALENDAR_COLOR, newColor)
1184+
}
1185+
val rows = contentResolver?.update(uri, values, null, null)
1186+
return (rows ?: 0) > 0
1187+
}
1188+
1189+
/**
1190+
* Compares colors based on their hue values in the HSV color space.
1191+
* https://android.googlesource.com/platform/prebuilts/fullsdk/sources/+/refs/heads/androidx-compose-integration-release/android-34/com/android/colorpicker/HsvColorComparator.java
1192+
*/
1193+
private class HsvColorComparator : Comparator<Int> {
1194+
override fun compare(color1: Int, color2: Int): Int {
1195+
val hsv1 = FloatArray(3)
1196+
val hsv2 = FloatArray(3)
1197+
Color.colorToHSV(color1, hsv1)
1198+
Color.colorToHSV(color2, hsv2)
1199+
return hsv1[0].compareTo(hsv2[0])
1200+
}
1201+
}
1202+
11281203
@Synchronized
11291204
private fun generateUniqueRequestCodeAndCacheParameters(parameters: CalendarMethodsParametersCacheModel): Int {
11301205
// TODO we can ran out of Int's at some point so this probably should re-use some of the freed ones

android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ private const val DELETE_EVENT_INSTANCE_METHOD = "deleteEventInstance"
2525
private const val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent"
2626
private const val CREATE_CALENDAR_METHOD = "createCalendar"
2727
private const val DELETE_CALENDAR_METHOD = "deleteCalendar"
28+
private const val RETRIEVE_EVENT_COLORS_METHOD = "retrieveEventColors"
29+
private const val RETRIEVE_CALENDAR_COLORS_METHOD = "retrieveCalendarColors"
30+
private const val UPDATE_CALENDAR_COLOR = "updateCalendarColor"
2831

2932
// Method arguments
3033
private const val CALENDAR_ID_ARGUMENT = "calendarId"
3134
private const val CALENDAR_NAME_ARGUMENT = "calendarName"
35+
private const val CALENDAR_ACCOUNT_NAME_ARGUMENT = "accountName"
3236
private const val START_DATE_ARGUMENT = "startDate"
3337
private const val END_DATE_ARGUMENT = "endDate"
3438
private const val EVENT_IDS_ARGUMENT = "eventIds"
@@ -66,6 +70,8 @@ private const val LOCAL_ACCOUNT_NAME_ARGUMENT = "localAccountName"
6670
private const val EVENT_AVAILABILITY_ARGUMENT = "availability"
6771
private const val ATTENDANCE_STATUS_ARGUMENT = "attendanceStatus"
6872
private const val EVENT_STATUS_ARGUMENT = "eventStatus"
73+
private const val EVENT_COLOR_KEY_ARGUMENT = "eventColorKey"
74+
private const val CALENDAR_COLOR_KEY_ARGUMENT = "calendarColorKey"
6975

7076
class DeviceCalendarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
7177

@@ -171,6 +177,35 @@ class DeviceCalendarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
171177
val calendarId = call.argument<String>(CALENDAR_ID_ARGUMENT)
172178
_calendarDelegate.deleteCalendar(calendarId!!, result)
173179
}
180+
RETRIEVE_EVENT_COLORS_METHOD -> {
181+
val accountName = call.argument<String>(CALENDAR_ACCOUNT_NAME_ARGUMENT)
182+
if (accountName == null) {
183+
result.success(intArrayOf())
184+
return;
185+
}
186+
val colors = _calendarDelegate.retrieveEventColors(accountName!!, )
187+
result.success(colors.map { listOf(it.first, it.second) })
188+
}
189+
RETRIEVE_CALENDAR_COLORS_METHOD -> {
190+
val accountName = call.argument<String>(CALENDAR_ACCOUNT_NAME_ARGUMENT)
191+
if (accountName == null) {
192+
result.success(intArrayOf())
193+
return;
194+
}
195+
val colors = _calendarDelegate.retrieveCalendarColors(accountName)
196+
result.success(colors.map { listOf(it.first, it.second) })
197+
}
198+
UPDATE_CALENDAR_COLOR -> {
199+
val calendarId = call.argument<Number>(CALENDAR_ID_ARGUMENT)?.toLong()
200+
if (calendarId == null) {
201+
result.success(false)
202+
return
203+
}
204+
val newColorKey = (call.argument<Number>(CALENDAR_COLOR_KEY_ARGUMENT))?.toInt()
205+
val newColor = (call.argument<Number>(CALENDAR_COLOR_ARGUMENT))?.toInt()
206+
val success = _calendarDelegate.updateCalendarColor(calendarId, newColorKey, newColor)
207+
result.success(success)
208+
}
174209
else -> {
175210
result.notImplemented()
176211
}
@@ -192,6 +227,7 @@ class DeviceCalendarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
192227
event.eventURL = call.argument<String>(EVENT_URL_ARGUMENT)
193228
event.availability = parseAvailability(call.argument<String>(EVENT_AVAILABILITY_ARGUMENT))
194229
event.eventStatus = parseEventStatus(call.argument<String>(EVENT_STATUS_ARGUMENT))
230+
event.eventColorKey = call.argument<Int>(EVENT_COLOR_KEY_ARGUMENT)
195231

196232
if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument<Map<String, Any>>(
197233
RECURRENCE_RULE_ARGUMENT

android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class Constants {
5050
const val EVENT_PROJECTION_END_TIMEZONE_INDEX: Int = 12
5151
const val EVENT_PROJECTION_AVAILABILITY_INDEX: Int = 13
5252
const val EVENT_PROJECTION_STATUS_INDEX: Int = 14
53+
const val EVENT_PROJECTION_EVENT_COLOR_INDEX: Int = 15
54+
const val EVENT_PROJECTION_EVENT_COLOR_KEY_INDEX: Int = 16
5355

5456
val EVENT_PROJECTION: Array<String> = arrayOf(
5557
CalendarContract.Instances.EVENT_ID,
@@ -66,7 +68,9 @@ class Constants {
6668
CalendarContract.Events.EVENT_TIMEZONE,
6769
CalendarContract.Events.EVENT_END_TIMEZONE,
6870
CalendarContract.Events.AVAILABILITY,
69-
CalendarContract.Events.STATUS
71+
CalendarContract.Events.STATUS,
72+
CalendarContract.Events.EVENT_COLOR,
73+
CalendarContract.Events.EVENT_COLOR_KEY
7074
)
7175

7276
const val EVENT_INSTANCE_DELETION_ID_INDEX: Int = 0

android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ class Event {
1818
var reminders: MutableList<Reminder> = mutableListOf()
1919
var availability: Availability? = null
2020
var eventStatus: EventStatus? = null
21+
var eventColor: Int? = null
22+
var eventColorKey: Int? = null
2123
}

example/android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ apply plugin: 'kotlin-android'
1616
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
1717

1818
android {
19-
compileSdkVersion 32
19+
compileSdkVersion 34
2020
ndkVersion '22.1.7171670'
2121

2222
sourceSets {
@@ -30,7 +30,7 @@ android {
3030
defaultConfig {
3131
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
3232
applicationId "com.builttoroam.devicecalendarexample"
33-
minSdkVersion 19
33+
minSdkVersion flutter.minSdkVersion
3434
targetSdkVersion 31
3535
versionCode 1
3636
versionName "1.0"

example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.0'
2+
ext.kotlin_version = '1.8.22'
33
repositories {
44
google()
55
mavenCentral()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter/material.dart';
3+
4+
class ColorPickerDialog {
5+
static Future<Color?> selectColorDialog(List<Color> colors, BuildContext context) async {
6+
return await showDialog<Color>(
7+
context: context,
8+
builder: (BuildContext context) {
9+
return SimpleDialog(
10+
title: const Text('Select color'),
11+
children: [
12+
...colors.map((color) =>
13+
SimpleDialogOption(
14+
onPressed: () { Navigator.pop(context, color); },
15+
child: Container(
16+
width: 48,
17+
height: 48,
18+
decoration: BoxDecoration(
19+
shape: BoxShape.circle,
20+
color: color),
21+
),
22+
)
23+
)]
24+
);
25+
}
26+
);
27+
}
28+
}

example/lib/presentation/date_time_picker.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class DateTimePicker extends StatelessWidget {
4545

4646
@override
4747
Widget build(BuildContext context) {
48-
final valueStyle = Theme.of(context).textTheme.headline6;
48+
final valueStyle = Theme.of(context).textTheme.titleLarge;
4949
return Row(
5050
crossAxisAlignment: CrossAxisAlignment.end,
5151
children: [

example/lib/presentation/event_item.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'dart:io';
22

33
import 'package:device_calendar/device_calendar.dart';
44
import 'package:flutter/material.dart';
5-
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
5+
import 'package:flutter_timezone/flutter_timezone.dart';
66
import 'package:intl/intl.dart';
77

88
import 'recurring_event_dialog.dart';
@@ -313,7 +313,7 @@ class _EventItemState extends State<EventItem> {
313313
void setCurentLocation() async {
314314
String? timezone;
315315
try {
316-
timezone = await FlutterNativeTimezone.getLocalTimezone();
316+
timezone = await FlutterTimezone.getLocalTimezone();
317317
} catch (e) {
318318
debugPrint('Could not get the local timezone');
319319
}

0 commit comments

Comments
 (0)