Skip to content

Commit

Permalink
Merge pull request #112 from builttoroam/1.0
Browse files Browse the repository at this point in the history
Support for advanced recurrence rules, attendees, reminders and fixes
  • Loading branch information
MaikuB authored Aug 28, 2019
2 parents 5dc5273 + b1e1460 commit c0842a0
Show file tree
Hide file tree
Showing 38 changed files with 1,841 additions and 761 deletions.
10 changes: 10 additions & 0 deletions device_calendar/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 1.0.0 28th August 2019
* Support for more advanced recurrence rules
* Update README to include information about using ProGuard for issue [99](https://github.com/builttoroam/flutter_plugins/issues/99)
* Made event title optional to fix issue [72](https://github.com/builttoroam/flutter_plugins/issues/72)
* Return information about the organiser of the event as per issue [73](https://github.com/builttoroam/flutter_plugins/issues/73)
* Return attendance status of attendees and if they're required for an event. These are details are different across iOS and Android and so are returned within platform-specific objects
* Ability to modify attendees for an event
* Ability to create reminders for events expressed in minutes before the event starts
* **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified (`UnmodifiableListView`) to address part of issue [113](https://github.com/builttoroam/flutter_plugins/issues/113)

# 0.2.2 19th August 2019
* Add support for specifying the location of an event. Thanks to [oli06](https://github.com/oli06) and [zemanux](https://github.com/zemanux) for submitting PRs to add the functionality to iOS and Android respectively

Expand Down
10 changes: 10 additions & 0 deletions device_calendar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ A cross platform plugin for modifying calendars on the user's device.
* Retrieve events associated with a calendar
* Ability to add, update or delete events from a calendar
* Ability to set up recurring events (NOTE: deleting a recurring event will currently delete all instances of it)
* Ability to modify attendees for an event (NOTE: certain information is read-only like attendance status. Please refer to API docs)
* Ability to setup reminders for an event

**NOTE**: there is a known issue where it looks as though specifying `weeksOfTheYear` and `setPositions` for recurrence rules doesn't appear to have an effect. Also note that the example app only provides entering simple scenarios e.g. it may be possible to specify multiple months that a yearly event should occur on but the example app will only allow specifying a single month.

## Android Integration

Expand All @@ -21,6 +25,12 @@ The following will need to be added to the manifest file for your application to
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
```

If you have Proguard enabled, you may need to add the following to your configuration (thanks to [Britannio Jarrett](https://github.com/britannio) who posted about it [here](https://github.com/builttoroam/flutter_plugins/issues/99))

```
-keep class com.builttoroam.devicecalendar.** { *; }
```

**IMPORTANT**: Since version 0.1.0, this version has migrated to use AndroidX instead of the deprecated Android support libraries. When using version 0.10.0 and onwards for this plugin, please ensure your application has been migrated following the guide [here](https://developer.android.com/jetpack/androidx/migrate)

## iOS Integration
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.builttoroam.devicecalendar

import com.builttoroam.devicecalendar.common.DayOfWeek
import com.google.gson.*
import java.lang.reflect.Type

class DayOfWeekSerializer: JsonSerializer<DayOfWeek> {
override fun serialize(src: DayOfWeek?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
if(src != null) {
return JsonPrimitive(src.ordinal)
}
return JsonObject()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@ package com.builttoroam.devicecalendar

import android.app.Activity
import android.content.Context
import com.builttoroam.devicecalendar.common.DayOfWeek
import com.builttoroam.devicecalendar.common.RecurrenceFrequency
import com.builttoroam.devicecalendar.models.Attendee
import com.builttoroam.devicecalendar.models.Event
import com.builttoroam.devicecalendar.models.RecurrenceRule

import com.builttoroam.devicecalendar.models.Reminder
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.PluginRegistry.Registrar


const val CHANNEL_NAME = "plugins.builttoroam.com/device_calendar"


class DeviceCalendarPlugin() : MethodCallHandler {

private lateinit var _registrar: Registrar
private lateinit var _calendarDelegate: CalendarDelegate

// Methods
private val REQUEST_PERMISSIONS_METHOD = "requestPermissions"
private val HAS_PERMISSIONS_METHOD = "hasPermissions"
Expand All @@ -31,20 +27,33 @@ class DeviceCalendarPlugin() : MethodCallHandler {

// Method arguments
private val CALENDAR_ID_ARGUMENT = "calendarId"
private val CALENDAR_EVENTS_START_DATE_ARGUMENT = "startDate"
private val CALENDAR_EVENTS_END_DATE_ARGUMENT = "endDate"
private val CALENDAR_EVENTS_IDS_ARGUMENT = "eventIds"
private val START_DATE_ARGUMENT = "startDate"
private val END_DATE_ARGUMENT = "endDate"
private val EVENT_IDS_ARGUMENT = "eventIds"
private val EVENT_ID_ARGUMENT = "eventId"
private val EVENT_TITLE_ARGUMENT = "eventTitle"
private val EVENT_LOCATION_ARGUMENT = "eventLocation"
private val EVENT_DESCRIPTION_ARGUMENT = "eventDescription"
private val EVENT_START_DATE_ARGUMENT = "eventStartDate"
private val EVENT_END_DATE_ARGUMENT = "eventEndDate"
private val EVENT_LOCATION_ARGUMENT = "eventLocation"
private val RECURRENCE_RULE_ARGUMENT = "recurrenceRule"
private val RECURRENCE_FREQUENCY_ARGUMENT = "recurrenceFrequency"
private val TOTAL_OCCURRENCES_ARGUMENT = "totalOccurrences"
private val INTERVAL_ARGUMENT = "interval"
private val END_DATE_ARGUMENT = "endDate"
private val DAYS_OF_THE_WEEK_ARGUMENT = "daysOfTheWeek"
private val DAYS_OF_THE_MONTH_ARGUMENT = "daysOfTheMonth"
private val MONTHS_OF_THE_YEAR_ARGUMENT = "monthsOfTheYear"
private val WEEKS_OF_THE_YEAR_ARGUMENT = "weeksOfTheYear"
private val SET_POSITIONS_ARGUMENT = "setPositions"
private val ATTENDEES_ARGUMENT = "attendees"
private val EMAIL_ADDRESS_ARGUMENT = "emailAddress"
private val NAME_ARGUMENT = "name"
private val IS_REQUIRED_ARGUMENT = "isRequired"
private val REMINDERS_ARGUMENT = "reminders"
private val MINUTES_ARGUMENT = "minutes"

private lateinit var _registrar: Registrar
private lateinit var _calendarDelegate: CalendarDelegate

private constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() {
_registrar = registrar
Expand All @@ -53,24 +62,21 @@ class DeviceCalendarPlugin() : MethodCallHandler {

companion object {
@JvmStatic
fun registerWith(registrar: Registrar): Unit {
fun registerWith(registrar: Registrar) {
val context: Context = registrar.context()
val activity: Activity? = registrar.activity()

val calendarDelegate = CalendarDelegate(activity, context)
val instance = DeviceCalendarPlugin(registrar, calendarDelegate)

val channel = MethodChannel(registrar.messenger(), "device_calendar")
channel.setMethodCallHandler(instance)

val calendarsChannel = MethodChannel(registrar.messenger(), CHANNEL_NAME)
calendarsChannel.setMethodCallHandler(instance)

registrar.addRequestPermissionsResultListener(calendarDelegate)
}
}

override fun onMethodCall(call: MethodCall, result: Result): Unit {
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
REQUEST_PERMISSIONS_METHOD -> {
_calendarDelegate.requestPermissions(result)
Expand All @@ -83,47 +89,15 @@ class DeviceCalendarPlugin() : MethodCallHandler {
}
RETRIEVE_EVENTS_METHOD -> {
val calendarId = call.argument<String>(CALENDAR_ID_ARGUMENT)
val startDate = call.argument<Long>(CALENDAR_EVENTS_START_DATE_ARGUMENT)
val endDate = call.argument<Long>(CALENDAR_EVENTS_END_DATE_ARGUMENT)
val eventIds = call.argument<List<String>>(CALENDAR_EVENTS_IDS_ARGUMENT) ?: listOf()
val startDate = call.argument<Long>(START_DATE_ARGUMENT)
val endDate = call.argument<Long>(END_DATE_ARGUMENT)
val eventIds = call.argument<List<String>>(EVENT_IDS_ARGUMENT) ?: listOf()

_calendarDelegate.retrieveEvents(calendarId!!, startDate, endDate, eventIds, result)
}
CREATE_OR_UPDATE_EVENT_METHOD -> {
val calendarId = call.argument<String>(CALENDAR_ID_ARGUMENT)
val eventId = call.argument<String>(EVENT_ID_ARGUMENT)
val eventTitle = call.argument<String>(EVENT_TITLE_ARGUMENT)
val eventDescription = call.argument<String>(EVENT_DESCRIPTION_ARGUMENT)
val eventLocation = call.argument<String>(EVENT_LOCATION_ARGUMENT)
val eventStart = call.argument<Long>(EVENT_START_DATE_ARGUMENT)
val eventEnd = call.argument<Long>(EVENT_END_DATE_ARGUMENT)

val event = Event(eventTitle!!)
event.calendarId = calendarId
event.eventId = eventId
event.description = eventDescription
event.location = eventLocation
event.start = eventStart!!
event.end = eventEnd!!

if(call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument<Map<String, Any>>(RECURRENCE_RULE_ARGUMENT) != null) {
val recurrenceRuleArgs = call.argument<Map<String, Any>>(RECURRENCE_RULE_ARGUMENT)!!
val recurrenceFrequencyIndex = recurrenceRuleArgs[RECURRENCE_FREQUENCY_ARGUMENT] as Int
val recurrenceRule = RecurrenceRule(RecurrenceFrequency.values()[recurrenceFrequencyIndex])
if(recurrenceRuleArgs.containsKey(TOTAL_OCCURRENCES_ARGUMENT)) {
recurrenceRule.totalOccurrences = recurrenceRuleArgs[TOTAL_OCCURRENCES_ARGUMENT] as Int
}

if(recurrenceRuleArgs.containsKey(INTERVAL_ARGUMENT)) {
recurrenceRule.interval = recurrenceRuleArgs[INTERVAL_ARGUMENT] as Int
}

if (recurrenceRuleArgs.containsKey(END_DATE_ARGUMENT)) {
recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long
}

event.recurrenceRule = recurrenceRule
}
val event = parseEventArgs(call, calendarId)

_calendarDelegate.createOrUpdateEvent(calendarId!!, event, result)
}
Expand All @@ -138,4 +112,85 @@ class DeviceCalendarPlugin() : MethodCallHandler {
}
}
}

private fun parseEventArgs(call: MethodCall, calendarId: String?): Event {
val event = Event()
event.title = call.argument<String>(EVENT_TITLE_ARGUMENT)
event.calendarId = calendarId
event.eventId = call.argument<String>(EVENT_ID_ARGUMENT)
event.description = call.argument<String>(EVENT_DESCRIPTION_ARGUMENT)
event.start = call.argument<Long>(EVENT_START_DATE_ARGUMENT)!!
event.end = call.argument<Long>(EVENT_END_DATE_ARGUMENT)!!
event.location = call.argument<String>(EVENT_LOCATION_ARGUMENT)

if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument<Map<String, Any>>(RECURRENCE_RULE_ARGUMENT) != null) {
val recurrenceRule = parseRecurrenceRuleArgs(call)
event.recurrenceRule = recurrenceRule
}

if (call.hasArgument(ATTENDEES_ARGUMENT) && call.argument<List<Map<String, Any>>>(ATTENDEES_ARGUMENT) != null) {
event.attendees = mutableListOf()
val attendeesArgs = call.argument<List<Map<String, Any>>>(ATTENDEES_ARGUMENT)!!
for (attendeeArgs in attendeesArgs) {
event.attendees.add(Attendee(attendeeArgs[EMAIL_ADDRESS_ARGUMENT] as String, attendeeArgs[NAME_ARGUMENT] as String?, attendeeArgs[IS_REQUIRED_ARGUMENT] as Boolean?, null, null))
}
}

if (call.hasArgument(REMINDERS_ARGUMENT) && call.argument<List<Map<String, Any>>>(REMINDERS_ARGUMENT) != null) {
event.reminders = mutableListOf()
val remindersArgs = call.argument<List<Map<String, Any>>>(REMINDERS_ARGUMENT)!!
for (reminderArgs in remindersArgs) {
event.reminders.add(Reminder(reminderArgs[MINUTES_ARGUMENT] as Int))
}
}

return event
}

private fun parseRecurrenceRuleArgs(call: MethodCall): RecurrenceRule {
val recurrenceRuleArgs = call.argument<Map<String, Any>>(RECURRENCE_RULE_ARGUMENT)!!
val recurrenceFrequencyIndex = recurrenceRuleArgs[RECURRENCE_FREQUENCY_ARGUMENT] as Int
val recurrenceRule = RecurrenceRule(RecurrenceFrequency.values()[recurrenceFrequencyIndex])
if (recurrenceRuleArgs.containsKey(TOTAL_OCCURRENCES_ARGUMENT)) {
recurrenceRule.totalOccurrences = recurrenceRuleArgs[TOTAL_OCCURRENCES_ARGUMENT] as Int
}

if (recurrenceRuleArgs.containsKey(INTERVAL_ARGUMENT)) {
recurrenceRule.interval = recurrenceRuleArgs[INTERVAL_ARGUMENT] as Int
}

if (recurrenceRuleArgs.containsKey(END_DATE_ARGUMENT)) {
recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long
}

if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_WEEK_ARGUMENT)) {
recurrenceRule.daysOfTheWeek = recurrenceRuleArgs[DAYS_OF_THE_WEEK_ARGUMENT].toListOf<Int>()?.map { DayOfWeek.values()[it] }?.toMutableList()
}

if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_MONTH_ARGUMENT)) {
recurrenceRule.daysOfTheMonth = recurrenceRuleArgs[DAYS_OF_THE_MONTH_ARGUMENT].toMutableListOf()
}

if (recurrenceRuleArgs.containsKey(MONTHS_OF_THE_YEAR_ARGUMENT)) {
recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[MONTHS_OF_THE_YEAR_ARGUMENT].toMutableListOf()
}

if (recurrenceRuleArgs.containsKey(WEEKS_OF_THE_YEAR_ARGUMENT)) {
recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[WEEKS_OF_THE_YEAR_ARGUMENT].toMutableListOf()
}

if (recurrenceRuleArgs.containsKey(SET_POSITIONS_ARGUMENT)) {
recurrenceRule.setPositions = recurrenceRuleArgs[SET_POSITIONS_ARGUMENT].toMutableListOf()
}

return recurrenceRule
}

private inline fun <reified T : Any> Any?.toListOf(): List<T>? {
return (this as List<*>?)?.filterIsInstance<T>()?.toList()
}

private inline fun <reified T : Any> Any?.toMutableListOf(): MutableList<T>? {
return this?.toListOf<T>()?.toMutableList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,11 @@ class Constants {
CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
CalendarContract.Attendees.ATTENDEE_STATUS
)

const val REMINDER_MINUTES_INDEX = 1
val REMINDER_PROJECTION: Array<String> = arrayOf(
CalendarContract.Reminders.EVENT_ID,
CalendarContract.Reminders.MINUTES
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.builttoroam.devicecalendar.common

enum class DayOfWeek {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
package com.builttoroam.devicecalendar.models

class Attendee(val name: String) {
var id: Long = -1
var eventId: Long = -1
var email: String? = null
var attendanceRequired: Boolean = false
class Attendee(val emailAddress: String, val name: String?, val isRequired: Boolean?, val attendanceStatus: Int?, val isOrganizer: Boolean?) {
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.builttoroam.devicecalendar.models

class Event(val title: String) {
class Event {
var title: String? = null
var eventId: String? = null
var calendarId: String? = null
var description: String? = null
var start: Long = -1
var end: Long = -1
var start: Long? = null
var end: Long? = null
var allDay: Boolean = false
var location: String? = null
var attendees: MutableList<Attendee> = mutableListOf()
var recurrenceRule: RecurrenceRule? = null
var organizer: Attendee? = null
var reminders: MutableList<Reminder> = mutableListOf()
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.builttoroam.devicecalendar.models

import com.builttoroam.devicecalendar.common.DayOfWeek
import com.builttoroam.devicecalendar.common.RecurrenceFrequency


class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) {
var totalOccurrences: Int? = null
var interval: Int? = null
var endDate: Long? = null
var daysOfTheWeek: MutableList<DayOfWeek>? = null
var daysOfTheMonth: MutableList<Int>? = null
var monthsOfTheYear: MutableList<Int>? = null
var weeksOfTheYear: MutableList<Int>? = null
var setPositions: MutableList<Int>? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.builttoroam.devicecalendar.models

class Reminder(val minutes: Int) {
}
16 changes: 9 additions & 7 deletions device_calendar/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import 'package:flutter/material.dart';
import 'common/app_routes.dart';
import 'presentation/pages/calendars.dart';

void main() => runApp(new MyApp());
void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return new MaterialApp(routes: {
AppRoutes.calendars: (context) {
return new CalendarsPage();
}
});
return MaterialApp(
routes: {
AppRoutes.calendars: (context) {
return CalendarsPage();
}
},
);
}
}
Loading

0 comments on commit c0842a0

Please sign in to comment.