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

Support for advanced recurrence rules, attendees, reminders and fixes #112

Merged
merged 50 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3cd6599
make event title optional
MaikuB Aug 6, 2019
233f5e0
remove optional new
MaikuB Aug 6, 2019
19945be
WIP of setting day of week on Android
MaikuB Aug 6, 2019
c787358
handle parsing days of week from android
MaikuB Aug 7, 2019
4d49ea0
support days of week on iOS, tidy up example a bit
MaikuB Aug 8, 2019
36cf74a
refactor ios code for creating and reading recurrence rules
MaikuB Aug 8, 2019
b2bad4d
update example app to be able to configure days of the week
MaikuB Aug 8, 2019
b954cd0
rename daysOfWeek to daysOfTheWeek
MaikuB Aug 9, 2019
ce317b9
add assert statement for days of the week
MaikuB Aug 9, 2019
b93e295
support for days of the month on ios
MaikuB Aug 9, 2019
5c5d487
add support for days of the month on android
MaikuB Aug 12, 2019
7484f32
add ios support for months of the year
MaikuB Aug 12, 2019
203fd25
handle months of the year on android and refactor code
MaikuB Aug 12, 2019
8acaca6
support weekOfTheYear and setPositions
MaikuB Aug 13, 2019
b4673e8
add notes around proguard
MaikuB Aug 13, 2019
3d67c49
add saturday to android DayOfWeek enum
MaikuB Aug 13, 2019
fe1ac08
address code analysis problem
MaikuB Aug 13, 2019
433656c
rename constants
MaikuB Aug 13, 2019
b24093e
rename constants in DeviceCalendarPlugin.kt file
MaikuB Aug 13, 2019
e646d23
remove unnecessary variable initialisation
MaikuB Aug 13, 2019
c63e1ee
refactor parsing native recurrence rule
MaikuB Aug 13, 2019
d7a5ecf
tidy up kotlin code
MaikuB Aug 13, 2019
339cfd7
update note in readme about the example app
MaikuB Aug 13, 2019
2341f4a
make retrieveCalendars and retrieveEvents return read-only lists
MaikuB Aug 16, 2019
7766cdd
Merge branch 'master' into 1.0
MaikuB Aug 20, 2019
8b2a87c
remove more usages of new operator in example code
MaikuB Aug 20, 2019
8471ad2
validate calendar exists prior to trying to add/update an event
MaikuB Aug 20, 2019
d4e39e0
handle parsing email address of attendees and organiser info
MaikuB Aug 20, 2019
b953ec3
update changelog
MaikuB Aug 20, 2019
5bedc44
include organiser in list of attendees for consistency across platforms
MaikuB Aug 21, 2019
5dd100c
format code and apply some of the suggestions
MaikuB Aug 21, 2019
244a9a8
reapply kotlin naming conventions for constants
MaikuB Aug 21, 2019
95d5dd0
additional code formatting/cleanup
MaikuB Aug 21, 2019
141371c
add ability to read info on if attendee is required for an event
MaikuB Aug 21, 2019
1076241
format code
MaikuB Aug 21, 2019
d3ad3d1
handle parsing of attendance status on ios and android
MaikuB Aug 21, 2019
b73bcaa
update changelog about returning attendee status and if they're required
MaikuB Aug 21, 2019
b61804c
remove dependency on platform package
MaikuB Aug 22, 2019
163f75e
remove old code on create recurrence rules
MaikuB Aug 23, 2019
30cfa5a
support modifying attendees on iOS
MaikuB Aug 25, 2019
5070399
add ability to modify attendees on android
MaikuB Aug 26, 2019
e697ee3
fix reading email address of attendees on ios
MaikuB Aug 26, 2019
36924c3
remove event id from android attendee ctor
MaikuB Aug 26, 2019
7d64c12
support reading/writing of event reminders on android
MaikuB Aug 26, 2019
e01373a
add ability to create reminders on ios relative to start of event
MaikuB Aug 27, 2019
c297f4a
update example app for modifying attendees and reminders
MaikuB Aug 27, 2019
400154d
refactor code around handling permissions
MaikuB Aug 28, 2019
016f919
remove redundant channel creation code
MaikuB Aug 28, 2019
7dcdacd
return UnmodifiableListView types for retrieveEvents and retrieveCale…
MaikuB Aug 28, 2019
b1e1460
update changelog to include date for 1.0.0 release
MaikuB Aug 28, 2019
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
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()

MaikuB marked this conversation as resolved.
Show resolved Hide resolved
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