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

Delete instance + DayOfWeekGroup enum update #172

Merged
merged 10 commits into from
Mar 19, 2020
Merged
3 changes: 2 additions & 1 deletion device_calendar/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* Updated property summaries for issues [121](https://github.com/builttoroam/flutter_plugins/issues/121) and [122](https://github.com/builttoroam/flutter_plugins/issues/122)
* Updated example documentation for issue [119](https://github.com/builttoroam/flutter_plugins/issues/119)
* Read-only calendars cannot be edited or deleted for the example app
* Added `DayOfWeekGroup` enum and an extension `getDays` to get corresponding dates of the enum values (**NOTE**: `DayOfWeekGroup.Custom.getDays` will return an empty list)
* Added `DayOfWeekGroup` enum and an extension `getDays` to get corresponding dates of the enum values
* Added to retrieve colour for calendars. Thanks to [nadavfima](https://github.com/nadavfima) for the contribution and PR to add colour support for both Android and iOS
* Added compatibility with a new Flutter plugin for Android. Thanks to the PR submitted by [RohitKumarMishra](https://github.com/RohitKumarMishra)
* [Android] Fixed all day timezone issue [164](https://github.com/builttoroam/flutter_plugins/issues/164)
* Added support for deleting individual or multiple instances of a recurring event for issue [108](https://github.com/builttoroam/flutter_plugins/issues/108)

## 3.0.0+3 3rd February 2020

Expand Down
4 changes: 3 additions & 1 deletion device_calendar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ A cross platform plugin for modifying calendars on the user's device.
* Retrieve 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 set up, edit or delete recurring events
* **NOTE**: Editing a recurring event will currently edit all instances of it
* **NOTE**: Deleting multiple instances in **Android** takes time to update, you'll see the changes after a few seconds
* Ability to add, modify or remove attendees and receive if an attendee is an organiser for an event
* Ability to setup reminders for an event

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTIO
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_RECURRING_RULE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_TITLE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_RRULE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_LAST_DATE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_BEGIN_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_END_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_MINUTES_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_PROJECTION
import com.builttoroam.devicecalendar.common.DayOfWeek
Expand Down Expand Up @@ -445,7 +451,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {

}

fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result) {
fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result, startDate: Long? = null, endDate: Long? = null, followingInstances: Boolean? = null) {
if (arePermissionsGranted()) {
val existingCal = retrieveCalendar(calendarId, pendingChannelResult, true)
if (existingCal == null) {
Expand All @@ -465,9 +471,78 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
}

val contentResolver: ContentResolver? = _context?.contentResolver
val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber)
val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0
finishWithSuccess(deleteSucceeded > 0, pendingChannelResult)
if (startDate == null && endDate == null && followingInstances == null) { // Delete all instances
val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber)
val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0
finishWithSuccess(deleteSucceeded > 0, pendingChannelResult)
}
else {
if (!followingInstances!!) { // Only this instance
val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber)
val values = ContentValues()
val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!)

while (instanceCursor.moveToNext()) {
val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)

if (eventIdNumber == foundEventID) {
values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(EVENT_INSTANCE_DELETION_BEGIN_INDEX))
values.put(Events.STATUS, Events.STATUS_CANCELED)
}
}

val deleteSucceeded = contentResolver?.insert(exceptionUriWithId, values)
instanceCursor.close()
finishWithSuccess(deleteSucceeded != null, pendingChannelResult)
}
else { // This and following instances
val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber)
val values = ContentValues()
val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!)

while (instanceCursor.moveToNext()) {
val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)

if (eventIdNumber == foundEventID) {
val newRule = org.dmfs.rfc5545.recur.RecurrenceRule(instanceCursor.getString(EVENT_INSTANCE_DELETION_RRULE_INDEX))
val lastDate = instanceCursor.getLong(EVENT_INSTANCE_DELETION_LAST_DATE_INDEX)

if (lastDate > 0 && newRule.count != null && newRule.count > 0) { // Update occurrence rule
val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate, lastDate)
while (cursor.moveToNext()) {
if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) {
newRule.count--
}
}
cursor.close()
}
else { // Indefinite and specified date rule
val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate - DateUtils.YEAR_IN_MILLIS, startDate - 1)
var lastRecurrenceDate: Long? = null

while (cursor.moveToNext()) {
if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) {
lastRecurrenceDate = cursor.getLong(EVENT_INSTANCE_DELETION_END_INDEX)
}
}

if (lastRecurrenceDate != null) {
newRule.until = DateTime(lastRecurrenceDate)
}
else {
newRule.until = DateTime(startDate - 1)
}
cursor.close()
}

values.put(Events.RRULE, newRule.toString())
contentResolver?.update(eventsUriWithId, values, null, null)
finishWithSuccess(true, pendingChannelResult)
}
}
instanceCursor.close()
}
}
} else {
val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, DELETE_EVENT_REQUEST_CODE, calendarId)
parameters.eventId = eventId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class DeviceCalendarPlugin() : MethodCallHandler {
private val RETRIEVE_CALENDARS_METHOD = "retrieveCalendars"
private val RETRIEVE_EVENTS_METHOD = "retrieveEvents"
private val DELETE_EVENT_METHOD = "deleteEvent"
private val DELETE_EVENT_INSTANCE_METHOD = "deleteEventInstance"
private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent"

// Method arguments
Expand Down Expand Up @@ -52,6 +53,7 @@ class DeviceCalendarPlugin() : MethodCallHandler {
private val ROLE_ARGUMENT = "role"
private val REMINDERS_ARGUMENT = "reminders"
private val MINUTES_ARGUMENT = "minutes"
private val FOLLOWING_INSTANCES = "followingInstances"

private lateinit var _registrar: Registrar
private lateinit var _calendarDelegate: CalendarDelegate
Expand Down Expand Up @@ -107,6 +109,15 @@ class DeviceCalendarPlugin() : MethodCallHandler {

_calendarDelegate.deleteEvent(calendarId!!, eventId!!, result)
}
DELETE_EVENT_INSTANCE_METHOD -> {
val calendarId = call.argument<String>(CALENDAR_ID_ARGUMENT)
val eventId = call.argument<String>(EVENT_ID_ARGUMENT)
val startDate = call.argument<Long>(EVENT_START_DATE_ARGUMENT)
val endDate = call.argument<Long>(EVENT_END_DATE_ARGUMENT)
val followingInstances = call.argument<Boolean>(FOLLOWING_INSTANCES)

_calendarDelegate.deleteEvent(calendarId!!, eventId!!, result, startDate, endDate, followingInstances)
}
else -> {
result.notImplemented()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ class Constants {
CalendarContract.Events.CUSTOM_APP_URI
)

const val EVENT_INSTANCE_DELETION_ID_INDEX: Int = 0
const val EVENT_INSTANCE_DELETION_RRULE_INDEX: Int = 1
const val EVENT_INSTANCE_DELETION_LAST_DATE_INDEX: Int = 2
const val EVENT_INSTANCE_DELETION_BEGIN_INDEX: Int = 3
const val EVENT_INSTANCE_DELETION_END_INDEX: Int = 4

val EVENT_INSTANCE_DELETION: Array<String> = arrayOf(
CalendarContract.Instances.EVENT_ID,
CalendarContract.Events.RRULE,
CalendarContract.Events.LAST_DATE,
CalendarContract.Instances.BEGIN,
CalendarContract.Instances.END
)

const val ATTENDEE_ID_INDEX: Int = 0
const val ATTENDEE_EVENT_ID_INDEX: Int = 1
const val ATTENDEE_NAME_INDEX: Int = 2
Expand Down
36 changes: 22 additions & 14 deletions device_calendar/example/lib/presentation/event_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'recurring_event_dialog.dart';

class EventItem extends StatelessWidget {
final Event _calendarEvent;
final DeviceCalendarPlugin _deviceCalendarPlugin;
Expand Down Expand Up @@ -168,13 +170,13 @@ class EventItem extends StatelessWidget {
),
IconButton(
onPressed: () async {
await showDialog<Null>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
if (_calendarEvent.recurrenceRule == null) {
return AlertDialog(
title: Text(
'Are you sure you want to delete this event?'),
title: Text('Are you sure you want to delete this event?'),
actions: [
FlatButton(
onPressed: () {
Expand All @@ -186,18 +188,24 @@ class EventItem extends StatelessWidget {
onPressed: () async {
Navigator.of(context).pop();
_onLoadingStarted();
final deleteResult =
await _deviceCalendarPlugin.deleteEvent(
_calendarEvent.calendarId,
_calendarEvent.eventId);
_onDeleteFinished(deleteResult.isSuccess &&
deleteResult.data);
final deleteResult = await _deviceCalendarPlugin.deleteEvent(_calendarEvent.calendarId, _calendarEvent.eventId);
_onDeleteFinished(deleteResult.isSuccess && deleteResult.data);
},
child: Text('Ok'),
child: Text('Delete'),
),
],
);
});
}
else {
return RecurringEventDialog(
_deviceCalendarPlugin,
_calendarEvent,
_onLoadingStarted,
_onDeleteFinished
);
}
}
);
},
icon: Icon(Icons.delete),
),
Expand Down
42 changes: 29 additions & 13 deletions device_calendar/example/lib/presentation/pages/calendar_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import 'package:intl/intl.dart';
import 'package:collection/collection.dart';

import '../date_time_picker.dart';
import '../recurring_event_dialog.dart';
import 'event_reminders.dart';

enum RecurrenceRuleEndType { Indefinite, MaxOccurrences, SpecifiedEndDate }

class CalendarEventPage extends StatefulWidget {
final Calendar _calendar;
final Event _event;
final RecurringEventDialog _recurringEventDialog;

CalendarEventPage(this._calendar, [this._event]);
CalendarEventPage(this._calendar, [this._event, this._recurringEventDialog]);

@override
_CalendarEventPageState createState() {
return _CalendarEventPageState(_calendar, _event);
return _CalendarEventPageState(_calendar, _event, _recurringEventDialog);
}
}

Expand All @@ -29,6 +31,7 @@ class _CalendarEventPageState extends State<CalendarEventPage> {

Event _event;
DeviceCalendarPlugin _deviceCalendarPlugin;
RecurringEventDialog _recurringEventDialog;

DateTime _startDate;
TimeOfDay _startTime;
Expand Down Expand Up @@ -56,8 +59,9 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
List<Attendee> _attendees = List<Attendee>();
List<Reminder> _reminders = List<Reminder>();

_CalendarEventPageState(this._calendar, this._event) {
_CalendarEventPageState(this._calendar, this._event, this._recurringEventDialog) {
_deviceCalendarPlugin = DeviceCalendarPlugin();
_recurringEventDialog = this._recurringEventDialog;

_attendees = List<Attendee>();
_reminders = List<Reminder>();
Expand Down Expand Up @@ -597,9 +601,23 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
color: Colors.red,
child: Text('Delete'),
onPressed: () async {
await _deviceCalendarPlugin.deleteEvent(
_calendar.id, _event.eventId);
Navigator.pop(context, true);
var result = true;
if (!_isRecurringEvent) {
await _deviceCalendarPlugin.deleteEvent(_calendar.id, _event.eventId);
}
else {
result = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return _recurringEventDialog;
}
);
}

if (result) {
Navigator.pop(context, true);
}
},
),
]
Expand Down Expand Up @@ -722,15 +740,13 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
switch (_dayOfWeekGroup) {
case DayOfWeekGroup.Weekday:
case DayOfWeekGroup.Weekend:
case DayOfWeekGroup.Alldays:
case DayOfWeekGroup.AllDays:
_daysOfWeek.clear();
_daysOfWeek.addAll(days.where((a) => _daysOfWeek.every((b) => a != b)));
break;
case DayOfWeekGroup.None:
_daysOfWeek.clear();
break;
default: // DayOfWeekGroup.Custom, it shouldn't be changed
break;
}
}

Expand All @@ -750,12 +766,12 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
_dayOfWeekGroup = DayOfWeekGroup.Weekend;
}
// If _daysOfWeek contains all days
else if (deepEquality(_daysOfWeek, DayOfWeekGroup.Alldays.getDays) && _dayOfWeekGroup != DayOfWeekGroup.Alldays) {
_dayOfWeekGroup = DayOfWeekGroup.Alldays;
else if (deepEquality(_daysOfWeek, DayOfWeekGroup.AllDays.getDays) && _dayOfWeekGroup != DayOfWeekGroup.AllDays) {
_dayOfWeekGroup = DayOfWeekGroup.AllDays;
}
// Otherwise, set as Custom
// Otherwise null
else {
_dayOfWeekGroup = DayOfWeekGroup.Custom;
_dayOfWeekGroup = null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/material.dart';

import '../event_item.dart';
import '../recurring_event_dialog.dart';
import 'calendar_event.dart';

class CalendarEventsPage extends StatefulWidget {
Expand All @@ -19,7 +20,7 @@ class CalendarEventsPage extends StatefulWidget {

class _CalendarEventsPageState extends State<CalendarEventsPage> {
final Calendar _calendar;
BuildContext _scaffoldContext;
final GlobalKey<ScaffoldState> _scaffoldstate = GlobalKey<ScaffoldState>();

DeviceCalendarPlugin _deviceCalendarPlugin;
List<Event> _calendarEvents;
Expand All @@ -38,6 +39,7 @@ class _CalendarEventsPageState extends State<CalendarEventsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldstate,
appBar: AppBar(title: Text('${_calendar.name} events')),
body: (_calendarEvents?.isNotEmpty ?? false)
? Stack(
Expand Down Expand Up @@ -96,7 +98,7 @@ class _CalendarEventsPageState extends State<CalendarEventsPage> {
if (deleteSucceeded) {
await _retrieveCalendarEvents();
} else {
Scaffold.of(_scaffoldContext).showSnackBar(SnackBar(
_scaffoldstate.currentState.showSnackBar(SnackBar(
content: Text('Oops, we ran into an issue deleting the event'),
backgroundColor: Colors.red,
duration: Duration(seconds: 5),
Expand All @@ -110,7 +112,13 @@ class _CalendarEventsPageState extends State<CalendarEventsPage> {
Future _onTapped(Event event) async {
final refreshEvents = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return CalendarEventPage(_calendar, event);
return CalendarEventPage(_calendar, event,
RecurringEventDialog(
_deviceCalendarPlugin,
event,
_onLoading,
_onDeletedFinished,
),);
}));
if (refreshEvents != null && refreshEvents) {
await _retrieveCalendarEvents();
Expand Down
Loading