From 3cd65992fd52993ddd0832e5063da9489e440063 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 6 Aug 2019 12:04:15 +1000 Subject: [PATCH 01/49] make event title optional --- .../builttoroam/devicecalendar/CalendarDelegate.kt | 13 ++++++------- .../devicecalendar/DeviceCalendarPlugin.kt | 3 ++- .../com/builttoroam/devicecalendar/models/Event.kt | 3 ++- device_calendar/lib/src/device_calendar.dart | 1 - 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 6eb43957..77ee86e0 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -385,7 +385,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result) { if (arePermissionsGranted()) { - var existingCal = retrieveCalendar(calendarId, pendingChannelResult, true) + val existingCal = retrieveCalendar(calendarId, pendingChannelResult, true) if (existingCal == null) { finishWithError(NOT_FOUND, "The calendar with the ID $calendarId could not be found", pendingChannelResult) return @@ -445,8 +445,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val calId = cursor.getLong(CALENDAR_PROJECTION_ID_INDEX) val displayName = cursor.getString(CALENDAR_PROJECTION_DISPLAY_NAME_INDEX) val accessLevel = cursor.getInt(CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX) - val accountName = cursor.getString(CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX) - val ownerName = cursor.getString(CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX) val calendar = Calendar(calId.toString(), displayName) calendar.isReadOnly = isCalendarReadOnly(accessLevel) @@ -468,7 +466,8 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val allDay = cursor.getInt(EVENT_PROJECTION_ALL_DAY_INDEX) > 0 val location = cursor.getString(EVENT_PROJECTION_EVENT_LOCATION_INDEX) - val event = Event(title) + val event = Event() + event.title = title event.eventId = eventId.toString() event.calendarId = calendarId event.description = description @@ -484,15 +483,15 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if(recurrenceRuleString == null) { return null } - var rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString!!) - var frequency = when(rfcRecurrenceRule.freq) { + val rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString!!) + val frequency = when(rfcRecurrenceRule.freq) { Freq.YEARLY -> RecurrenceFrequency.YEARLY Freq.MONTHLY -> RecurrenceFrequency.MONTHLY Freq.WEEKLY -> RecurrenceFrequency.WEEKLY Freq.DAILY -> RecurrenceFrequency.DAILY else -> null } - var recurrenceRule = RecurrenceRule(frequency!!) + val recurrenceRule = RecurrenceRule(frequency!!) if(rfcRecurrenceRule.count != null) { recurrenceRule.totalOccurrences = rfcRecurrenceRule.count } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 71244fb5..d5c0eb6d 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -97,7 +97,8 @@ class DeviceCalendarPlugin() : MethodCallHandler { val eventStart = call.argument(EVENT_START_DATE_ARGUMENT) val eventEnd = call.argument(EVENT_END_DATE_ARGUMENT) - val event = Event(eventTitle!!) + val event = Event() + event.title = eventTitle event.calendarId = calendarId event.eventId = eventId event.description = eventDescription diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt index 0ccf252e..2aafba05 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt @@ -1,6 +1,7 @@ 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 diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index a127397f..f0414cf9 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -163,7 +163,6 @@ class DeviceCalendarPlugin { final res = new Result(); if ((event?.calendarId?.isEmpty ?? true) || - (event?.title?.isEmpty ?? true) || event.start == null || event.end == null || event.start.isAfter(event.end)) { From 233f5e052454b34ddf08aaca5bf51b65b6f79335 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 6 Aug 2019 12:12:32 +1000 Subject: [PATCH 02/49] remove optional new --- device_calendar/example/lib/main.dart | 16 +-- .../lib/presentation/date_time_picker.dart | 18 +-- .../example/lib/presentation/event_item.dart | 114 +++++++++--------- .../lib/presentation/input_dropdown.dart | 12 +- .../presentation/pages/calendar_event.dart | 33 ++--- .../presentation/pages/calendar_events.dart | 61 +++++----- .../lib/presentation/pages/calendars.dart | 36 +++--- device_calendar/lib/src/device_calendar.dart | 18 +-- device_calendar/lib/src/models/attendee.dart | 4 +- device_calendar/lib/src/models/calendar.dart | 2 +- device_calendar/lib/src/models/event.dart | 13 +- .../lib/src/models/recurrence_rule.dart | 9 +- device_calendar/lib/src/models/result.dart | 2 +- .../test/device_calendar_test.dart | 18 +-- 14 files changed, 180 insertions(+), 176 deletions(-) diff --git a/device_calendar/example/lib/main.dart b/device_calendar/example/lib/main.dart index e198ee50..0c5a402f 100644 --- a/device_calendar/example/lib/main.dart +++ b/device_calendar/example/lib/main.dart @@ -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 { @override Widget build(BuildContext context) { - return new MaterialApp(routes: { - AppRoutes.calendars: (context) { - return new CalendarsPage(); - } - }); + return MaterialApp( + routes: { + AppRoutes.calendars: (context) { + return CalendarsPage(); + } + }, + ); } } diff --git a/device_calendar/example/lib/presentation/date_time_picker.dart b/device_calendar/example/lib/presentation/date_time_picker.dart index 31b3929a..4fd3ef3e 100644 --- a/device_calendar/example/lib/presentation/date_time_picker.dart +++ b/device_calendar/example/lib/presentation/date_time_picker.dart @@ -25,8 +25,8 @@ class DateTimePicker extends StatelessWidget { final DateTime picked = await showDatePicker( context: context, initialDate: selectedDate, - firstDate: new DateTime(2015, 8), - lastDate: new DateTime(2101)); + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101)); if (picked != null && picked != selectedDate) selectDate(picked); } @@ -39,14 +39,16 @@ class DateTimePicker extends StatelessWidget { @override Widget build(BuildContext context) { final TextStyle valueStyle = Theme.of(context).textTheme.title; - return new Row( + return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - new Expanded( + Expanded( flex: 4, - child: new InputDropdown( + child: InputDropdown( labelText: labelText, - valueText: selectedDate == null ? '': new DateFormat.yMMMd().format(selectedDate), + valueText: selectedDate == null + ? '' + : DateFormat.yMMMd().format(selectedDate), valueStyle: valueStyle, onPressed: () { _selectDate(context); @@ -54,9 +56,9 @@ class DateTimePicker extends StatelessWidget { ), ), const SizedBox(width: 12.0), - new Expanded( + Expanded( flex: 3, - child: new InputDropdown( + child: InputDropdown( valueText: selectedTime?.format(context) ?? '', valueStyle: valueStyle, onPressed: () { diff --git a/device_calendar/example/lib/presentation/event_item.dart b/device_calendar/example/lib/presentation/event_item.dart index 7904c8f8..725101ea 100644 --- a/device_calendar/example/lib/presentation/event_item.dart +++ b/device_calendar/example/lib/presentation/event_item.dart @@ -17,91 +17,91 @@ class EventItem extends StatelessWidget { @override Widget build(BuildContext context) { - return new GestureDetector( + return GestureDetector( onTap: () { _onTapped(_calendarEvent); }, - child: new Card( - child: new Column( + child: Card( + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - new Padding( + Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), - child: new FlutterLogo(), + child: FlutterLogo(), ), - new ListTile( - title: new Text(_calendarEvent.title ?? ''), - subtitle: new Text(_calendarEvent.description ?? '')), - new Container( - padding: new EdgeInsets.symmetric(horizontal: 16.0), - child: new Column( + ListTile( + title: Text(_calendarEvent.title ?? ''), + subtitle: Text(_calendarEvent.description ?? '')), + Container( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Column( children: [ - new Align( + Align( alignment: Alignment.topLeft, - child: new Row( + child: Row( children: [ - new Container( + Container( width: _eventFieldNameWidth, - child: new Text('Starts'), + child: Text('Starts'), ), - new Text(_calendarEvent == null + Text(_calendarEvent == null ? '' - : new DateFormat.yMd() + : DateFormat.yMd() .add_jm() .format(_calendarEvent.start)), ], ), ), - new Padding( - padding: new EdgeInsets.symmetric(vertical: 5.0), + Padding( + padding: EdgeInsets.symmetric(vertical: 5.0), ), - new Align( + Align( alignment: Alignment.topLeft, - child: new Row( + child: Row( children: [ - new Container( + Container( width: _eventFieldNameWidth, - child: new Text('Ends'), + child: Text('Ends'), ), - new Text(_calendarEvent.end == null + Text(_calendarEvent.end == null ? '' - : new DateFormat.yMd() + : DateFormat.yMd() .add_jm() .format(_calendarEvent.end)), ], ), ), - new SizedBox( + SizedBox( height: 10.0, ), - new Align( + Align( alignment: Alignment.topLeft, - child: new Row( + child: Row( children: [ - new Container( + Container( width: _eventFieldNameWidth, - child: new Text('All day?'), + child: Text('All day?'), ), - new Text(_calendarEvent.allDay != null && + Text(_calendarEvent.allDay != null && _calendarEvent.allDay ? 'Yes' : 'No') ], ), ), - new SizedBox( + SizedBox( height: 10.0, ), - new Align( + Align( alignment: Alignment.topLeft, - child: new Row( + child: Row( children: [ - new Container( + Container( width: _eventFieldNameWidth, - child: new Text('Location'), + child: Text('Location'), ), - new Expanded( - child: new Text( + Expanded( + child: Text( _calendarEvent?.location ?? '', overflow: TextOverflow.ellipsis, ), @@ -109,19 +109,19 @@ class EventItem extends StatelessWidget { ], ), ), - new SizedBox( + SizedBox( height: 10.0, ), - new Align( + Align( alignment: Alignment.topLeft, - child: new Row( + child: Row( children: [ - new Container( + Container( width: _eventFieldNameWidth, - child: new Text('Attendees'), + child: Text('Attendees'), ), - new Expanded( - child: new Text( + Expanded( + child: Text( _calendarEvent?.attendees ?.where((a) => a.name?.isNotEmpty ?? false) ?.map((a) => a.name) @@ -136,32 +136,32 @@ class EventItem extends StatelessWidget { ], ), ), - new ButtonTheme.bar( - child: new ButtonBar( + ButtonTheme.bar( + child: ButtonBar( children: [ - new IconButton( + IconButton( onPressed: () { _onTapped(_calendarEvent); }, - icon: new Icon(Icons.edit), + icon: Icon(Icons.edit), ), - new IconButton( + IconButton( onPressed: () async { await showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { - return new AlertDialog( - title: new Text( + return AlertDialog( + title: Text( 'Are you sure you want to delete this event?'), actions: [ - new FlatButton( + FlatButton( onPressed: () { Navigator.of(context).pop(); }, - child: new Text('Cancel'), + child: Text('Cancel'), ), - new FlatButton( + FlatButton( onPressed: () async { Navigator.of(context).pop(); _onLoadingStarted(); @@ -172,13 +172,13 @@ class EventItem extends StatelessWidget { _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); }, - child: new Text('Ok'), + child: Text('Ok'), ), ], ); }); }, - icon: new Icon(Icons.delete), + icon: Icon(Icons.delete), ), ], )) diff --git a/device_calendar/example/lib/presentation/input_dropdown.dart b/device_calendar/example/lib/presentation/input_dropdown.dart index 11086499..b6056b74 100644 --- a/device_calendar/example/lib/presentation/input_dropdown.dart +++ b/device_calendar/example/lib/presentation/input_dropdown.dart @@ -18,19 +18,19 @@ class InputDropdown extends StatelessWidget { @override Widget build(BuildContext context) { - return new InkWell( + return InkWell( onTap: onPressed, - child: new InputDecorator( - decoration: new InputDecoration( + child: InputDecorator( + decoration: InputDecoration( labelText: labelText, ), baseStyle: valueStyle, - child: new Row( + child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ - new Text(valueText, style: valueStyle), - new Icon(Icons.arrow_drop_down, + Text(valueText, style: valueStyle), + Icon(Icons.arrow_drop_down, color: Theme.of(context).brightness == Brightness.light ? Colors.grey.shade700 : Colors.white70), diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index db606b9c..d4f97a64 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -54,11 +54,11 @@ class _CalendarEventPageState extends State { _startDate = _event.start; _endDate = _event.end; _isRecurringEvent = _event.recurrenceRule != null; - if(_isRecurringEvent) { + if (_isRecurringEvent) { _interval = _event.recurrenceRule.interval; _totalOccurrences = _event.recurrenceRule.totalOccurrences; _recurrenceFrequency = _event.recurrenceRule.recurrenceFrequency; - if(_totalOccurrences != null) { + if (_totalOccurrences != null) { _recurrenceRuleEndType = RecurrenceRuleEndType.MaxOccurrences; } if (_event.recurrenceRule.endDate != null) { @@ -66,15 +66,14 @@ class _CalendarEventPageState extends State { _recurrenceEndDate = _event.recurrenceRule.endDate; _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } - } } _startTime = TimeOfDay(hour: _startDate.hour, minute: _startDate.minute); _endTime = TimeOfDay(hour: _endDate.hour, minute: _endDate.minute); - if(_recurrenceEndDate != null) { - _recurrenceEndTime = TimeOfDay( - hour: _recurrenceEndDate.hour, minute: _recurrenceEndDate.minute); + if (_recurrenceEndDate != null) { + _recurrenceEndTime = TimeOfDay( + hour: _recurrenceEndDate.hour, minute: _recurrenceEndDate.minute); } } @@ -84,7 +83,7 @@ class _CalendarEventPageState extends State { key: _scaffoldKey, appBar: AppBar( title: Text(_event.eventId?.isEmpty ?? true - ? 'Create new event' + ? 'Create event' : 'Edit event ${_event.title}'), ), body: SingleChildScrollView( @@ -194,9 +193,9 @@ class _CalendarEventPageState extends State { items: RecurrenceFrequency.values .map( (f) => DropdownMenuItem( - value: f, - child: _recurrenceFrequencyToText(f), - ), + value: f, + child: _recurrenceFrequencyToText(f), + ), ) .toList(), ), @@ -227,10 +226,9 @@ class _CalendarEventPageState extends State { items: RecurrenceRuleEndType.values .map( (f) => DropdownMenuItem( - value: f, - child: - _recurrenceRuleEndTypeToText(f), - ), + value: f, + child: _recurrenceRuleEndTypeToText(f), + ), ) .toList(), ), @@ -291,8 +289,11 @@ class _CalendarEventPageState extends State { _event.recurrenceRule = RecurrenceRule(_recurrenceFrequency, interval: _interval, totalOccurrences: _totalOccurrences, - endDate: _recurrenceRuleEndType == RecurrenceRuleEndType.SpecifiedEndDate ? _combineDateWithTime( - _recurrenceEndDate, _recurrenceEndTime) : null); + endDate: _recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate + ? _combineDateWithTime( + _recurrenceEndDate, _recurrenceEndTime) + : null); } var createEventResult = await _deviceCalendarPlugin.createOrUpdateEvent(_event); diff --git a/device_calendar/example/lib/presentation/pages/calendar_events.dart b/device_calendar/example/lib/presentation/pages/calendar_events.dart index c728c05c..893b3399 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_events.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_events.dart @@ -13,7 +13,7 @@ class CalendarEventsPage extends StatefulWidget { @override _CalendarEventsPageState createState() { - return new _CalendarEventsPageState(_calendar); + return _CalendarEventsPageState(_calendar); } } @@ -26,7 +26,7 @@ class _CalendarEventsPageState extends State { bool _isLoading = true; _CalendarEventsPageState(this._calendar) { - _deviceCalendarPlugin = new DeviceCalendarPlugin(); + _deviceCalendarPlugin = DeviceCalendarPlugin(); } @override @@ -39,16 +39,16 @@ class _CalendarEventsPageState extends State { Widget build(BuildContext context) { final hasAnyEvents = _calendarEvents?.isNotEmpty ?? false; Widget body = hasAnyEvents - ? new Stack( + ? Stack( children: [ - new Column( + Column( children: [ - new Expanded( + Expanded( flex: 1, - child: new ListView.builder( + child: ListView.builder( itemCount: _calendarEvents?.length ?? 0, itemBuilder: (BuildContext context, int index) { - return new EventItem( + return EventItem( _calendarEvents[index], _deviceCalendarPlugin, _onLoading, @@ -59,33 +59,32 @@ class _CalendarEventsPageState extends State { ) ], ), - new Offstage( + Offstage( offstage: !_isLoading, - child: new Container( - decoration: new BoxDecoration( - color: new Color.fromARGB(155, 192, 192, 192)), - child: - new Center(child: new CircularProgressIndicator()))) + child: Container( + decoration: BoxDecoration( + color: Color.fromARGB(155, 192, 192, 192)), + child: Center(child: CircularProgressIndicator()))) ], ) - : new Center(child: new Text('No events found')); - return new Scaffold( - appBar: new AppBar(title: new Text('${_calendar.name} events')), - body: new Builder(builder: (BuildContext context) { + : Center(child: Text('No events found')); + return Scaffold( + appBar: AppBar(title: Text('${_calendar.name} events')), + body: Builder(builder: (BuildContext context) { _scaffoldContext = context; return body; }), - floatingActionButton: new FloatingActionButton( + floatingActionButton: FloatingActionButton( onPressed: () async { final refreshEvents = await Navigator.push(context, - new MaterialPageRoute(builder: (BuildContext context) { - return new CalendarEventPage(_calendar); + MaterialPageRoute(builder: (BuildContext context) { + return CalendarEventPage(_calendar); })); if (refreshEvents == true) { _retrieveCalendarEvents(); } }, - child: new Icon(Icons.add), + child: Icon(Icons.add), ), ); } @@ -100,11 +99,11 @@ class _CalendarEventsPageState extends State { if (deleteSucceeded) { await _retrieveCalendarEvents(); } else { - Scaffold.of(_scaffoldContext).showSnackBar(new SnackBar( - content: new Text('Oops, we ran into an issue deleting the event'), - backgroundColor: Colors.red, - duration: new Duration(seconds: 5), - )); + Scaffold.of(_scaffoldContext).showSnackBar(SnackBar( + content: Text('Oops, we ran into an issue deleting the event'), + backgroundColor: Colors.red, + duration: Duration(seconds: 5), + )); setState(() { _isLoading = false; }); @@ -113,8 +112,8 @@ class _CalendarEventsPageState extends State { Future _onTapped(Event event) async { final refreshEvents = await Navigator.push(context, - new MaterialPageRoute(builder: (BuildContext context) { - return new CalendarEventPage(_calendar, event); + MaterialPageRoute(builder: (BuildContext context) { + return CalendarEventPage(_calendar, event); })); if (refreshEvents != null && refreshEvents) { _retrieveCalendarEvents(); @@ -122,11 +121,11 @@ class _CalendarEventsPageState extends State { } Future _retrieveCalendarEvents() async { - final startDate = new DateTime.now().add(new Duration(days: -30)); - final endDate = new DateTime.now().add(new Duration(days: 30)); + final startDate = DateTime.now().add(Duration(days: -30)); + final endDate = DateTime.now().add(Duration(days: 30)); var calendarEventsResult = await _deviceCalendarPlugin.retrieveEvents( _calendar.id, - new RetrieveEventsParams(startDate: startDate, endDate: endDate)); + RetrieveEventsParams(startDate: startDate, endDate: endDate)); setState(() { _calendarEvents = calendarEventsResult?.data; _isLoading = false; diff --git a/device_calendar/example/lib/presentation/pages/calendars.dart b/device_calendar/example/lib/presentation/pages/calendars.dart index 8f26586b..39787602 100644 --- a/device_calendar/example/lib/presentation/pages/calendars.dart +++ b/device_calendar/example/lib/presentation/pages/calendars.dart @@ -7,7 +7,7 @@ import 'calendar_events.dart'; class CalendarsPage extends StatefulWidget { @override _CalendarsPageState createState() { - return new _CalendarsPageState(); + return _CalendarsPageState(); } } @@ -16,7 +16,7 @@ class _CalendarsPageState extends State { List _calendars; _CalendarsPageState() { - _deviceCalendarPlugin = new DeviceCalendarPlugin(); + _deviceCalendarPlugin = DeviceCalendarPlugin(); } @override @@ -27,36 +27,36 @@ class _CalendarsPageState extends State { @override Widget build(BuildContext context) { - return new Scaffold( - appBar: new AppBar( - title: new Text('Calendars'), + return Scaffold( + appBar: AppBar( + title: Text('Calendars'), ), - body: new Column( + body: Column( children: [ - new Expanded( + Expanded( flex: 1, - child: new ListView.builder( + child: ListView.builder( itemCount: _calendars?.length ?? 0, itemBuilder: (BuildContext context, int index) { - return new GestureDetector( + return GestureDetector( onTap: () async { - await Navigator.push(context, new MaterialPageRoute( - builder: (BuildContext context) { - return new CalendarEventsPage(_calendars[index]); + await Navigator.push(context, + MaterialPageRoute(builder: (BuildContext context) { + return CalendarEventsPage(_calendars[index]); })); }, - child: new Padding( + child: Padding( padding: const EdgeInsets.all(10.0), - child: new Row( + child: Row( children: [ - new Expanded( + Expanded( flex: 1, - child: new Text( + child: Text( _calendars[index].name, - style: new TextStyle(fontSize: 25.0), + style: TextStyle(fontSize: 25.0), ), ), - new Icon(_calendars[index].isReadOnly + Icon(_calendars[index].isReadOnly ? Icons.lock : Icons.lock_open) ], diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index f0414cf9..9b193680 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -16,7 +16,7 @@ class DeviceCalendarPlugin { const MethodChannel('plugins.builttoroam.com/device_calendar'); static final DeviceCalendarPlugin _instance = - new DeviceCalendarPlugin._createInstance(); + DeviceCalendarPlugin._createInstance(); factory DeviceCalendarPlugin() { return _instance; @@ -30,7 +30,7 @@ class DeviceCalendarPlugin { /// Returns a [Result] indicating if calendar READ and WRITE permissions /// have (true) or have not (false) been granted Future> requestPermissions() async { - final res = new Result(); + final res = Result(); try { res.data = await channel.invokeMethod('requestPermissions'); @@ -46,7 +46,7 @@ class DeviceCalendarPlugin { /// Returns a [Result] indicating if calendar READ and WRITE permissions /// have (true) or have not (false) been granted Future> hasPermissions() async { - final res = new Result(); + final res = Result(); try { res.data = await channel.invokeMethod('hasPermissions'); @@ -61,13 +61,13 @@ class DeviceCalendarPlugin { /// /// Returns a [Result] containing a list of device [Calendar] Future>> retrieveCalendars() async { - final res = new Result>(); + final res = Result>(); try { var calendarsJson = await channel.invokeMethod('retrieveCalendars'); res.data = json.decode(calendarsJson).map((decodedCalendar) { - return new Calendar.fromJson(decodedCalendar); + return Calendar.fromJson(decodedCalendar); }).toList(); } catch (e) { _parsePlatformExceptionAndUpdateResult>(e, res); @@ -87,7 +87,7 @@ class DeviceCalendarPlugin { /// into the specified parameters Future>> retrieveEvents( String calendarId, RetrieveEventsParams retrieveEventsParams) async { - final res = new Result>(); + final res = Result>(); if ((calendarId?.isEmpty ?? true)) { res.errorMessages.add( @@ -117,7 +117,7 @@ class DeviceCalendarPlugin { }); res.data = json.decode(eventsJson).map((decodedEvent) { - return new Event.fromJson(decodedEvent); + return Event.fromJson(decodedEvent); }).toList(); } catch (e) { _parsePlatformExceptionAndUpdateResult>(e, res); @@ -134,7 +134,7 @@ class DeviceCalendarPlugin { /// /// Returns a [Result] indicating if the event has (true) or has not (false) been deleted from the calendar Future> deleteEvent(String calendarId, String eventId) async { - final res = new Result(); + final res = Result(); if ((calendarId?.isEmpty ?? true) || (eventId?.isEmpty ?? true)) { res.errorMessages.add( @@ -160,7 +160,7 @@ class DeviceCalendarPlugin { /// /// Returns a [Result] with the newly created or updated [Event.eventId] Future> createOrUpdateEvent(Event event) async { - final res = new Result(); + final res = Result(); if ((event?.calendarId?.isEmpty ?? true) || event.start == null || diff --git a/device_calendar/lib/src/models/attendee.dart b/device_calendar/lib/src/models/attendee.dart index 77d851c6..532ce7f8 100644 --- a/device_calendar/lib/src/models/attendee.dart +++ b/device_calendar/lib/src/models/attendee.dart @@ -9,14 +9,14 @@ class Attendee { Attendee.fromJson(Map json) { if (json == null) { - throw new ArgumentError(ErrorMessages.fromJsonMapIsNull); + throw ArgumentError(ErrorMessages.fromJsonMapIsNull); } name = json['name']; } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['name'] = this.name; return data; } diff --git a/device_calendar/lib/src/models/calendar.dart b/device_calendar/lib/src/models/calendar.dart index e6bcd929..cb9b48bf 100644 --- a/device_calendar/lib/src/models/calendar.dart +++ b/device_calendar/lib/src/models/calendar.dart @@ -18,7 +18,7 @@ class Calendar { } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['id'] = this.id; data['name'] = this.name; data['isReadOnly'] = this.isReadOnly; diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index 8063cb74..66ea729d 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -44,7 +44,7 @@ class Event { Event.fromJson(Map json) { if (json == null) { - throw new ArgumentError(ErrorMessages.fromJsonMapIsNull); + throw ArgumentError(ErrorMessages.fromJsonMapIsNull); } eventId = json['eventId']; @@ -53,18 +53,17 @@ class Event { description = json['description']; int startMillisecondsSinceEpoch = json['start']; if (startMillisecondsSinceEpoch != null) { - start = - new DateTime.fromMillisecondsSinceEpoch(startMillisecondsSinceEpoch); + start = DateTime.fromMillisecondsSinceEpoch(startMillisecondsSinceEpoch); } int endMillisecondsSinceEpoch = json['end']; if (endMillisecondsSinceEpoch != null) { - end = new DateTime.fromMillisecondsSinceEpoch(endMillisecondsSinceEpoch); + end = DateTime.fromMillisecondsSinceEpoch(endMillisecondsSinceEpoch); } allDay = json['allDay']; location = json['location']; if (json['attendees'] != null) { attendees = json['attendees'].map((decodedAttendee) { - return new Attendee.fromJson(decodedAttendee); + return Attendee.fromJson(decodedAttendee); }).toList(); } if (json['recurrenceRule'] != null) { @@ -73,7 +72,7 @@ class Event { } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['eventId'] = this.eventId; data['calendarId'] = this.calendarId; data['title'] = this.title; @@ -83,7 +82,7 @@ class Event { data['allDay'] = this.allDay; data['location'] = this.location; if (attendees != null) { - List> attendeesJson = new List(); + List> attendeesJson = List(); for (var attendee in attendees) { var attendeeJson = attendee.toJson(); attendeesJson.add(attendeeJson); diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index 83d34aa1..ee4a0bbe 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -22,16 +22,17 @@ class RecurrenceRule { this.totalOccurrences, this.interval, this.endDate, - }) : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'); + }) : assert(!(endDate != null && totalOccurrences != null), + 'Cannot specify both an end date and total occurrences for a recurring event'); RecurrenceRule.fromJson(Map json) { if (json == null) { - throw new ArgumentError(ErrorMessages.fromJsonMapIsNull); + throw ArgumentError(ErrorMessages.fromJsonMapIsNull); } int recurrenceFrequencyIndex = json[_recurrenceFrequencyKey]; if (recurrenceFrequencyIndex == null && recurrenceFrequencyIndex >= RecurrenceFrequency.values.length) { - throw new ArgumentError(ErrorMessages.invalidRecurrencyFrequency); + throw ArgumentError(ErrorMessages.invalidRecurrencyFrequency); } recurrenceFrequency = RecurrenceFrequency.values[recurrenceFrequencyIndex]; @@ -45,7 +46,7 @@ class RecurrenceRule { } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data[_recurrenceFrequencyKey] = recurrenceFrequency.index; if (interval != null) { data[_intervalKey] = interval; diff --git a/device_calendar/lib/src/models/result.dart b/device_calendar/lib/src/models/result.dart index 2c2e8bf0..0fa54306 100644 --- a/device_calendar/lib/src/models/result.dart +++ b/device_calendar/lib/src/models/result.dart @@ -14,5 +14,5 @@ class Result { } T data; - List errorMessages = new List(); + List errorMessages = List(); } diff --git a/device_calendar/test/device_calendar_test.dart b/device_calendar/test/device_calendar_test.dart index d3d603e8..b039cd75 100644 --- a/device_calendar/test/device_calendar_test.dart +++ b/device_calendar/test/device_calendar_test.dart @@ -7,7 +7,7 @@ import '../lib/src/common/error_codes.dart'; void main() { MethodChannel channel = const MethodChannel('plugins.builttoroam.com/device_calendar'); - DeviceCalendarPlugin deviceCalendarPlugin = new DeviceCalendarPlugin(); + DeviceCalendarPlugin deviceCalendarPlugin = DeviceCalendarPlugin(); final List log = []; @@ -60,7 +60,7 @@ void main() { test('RetrieveEvents_CalendarId_IsRequired', () async { final String calendarId = null; - final RetrieveEventsParams params = new RetrieveEventsParams(); + final RetrieveEventsParams params = RetrieveEventsParams(); final result = await deviceCalendarPlugin.retrieveEvents(calendarId, params); @@ -107,7 +107,7 @@ void main() { test('CreateEvent_Arguments_Invalid', () async { final String fakeCalendarId = null; - final Event event = new Event(fakeCalendarId); + final Event event = Event(fakeCalendarId); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); expect(result.isSuccess, false); @@ -123,10 +123,10 @@ void main() { }); final String fakeCalendarId = "fakeCalendarId"; - final Event event = new Event(fakeCalendarId); + final Event event = Event(fakeCalendarId); event.title = "fakeEventTitle"; - event.start = new DateTime.now(); - event.end = event.start.add(new Duration(hours: 1)); + event.start = DateTime.now(); + event.end = event.start.add(Duration(hours: 1)); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); expect(result.isSuccess, true); @@ -147,11 +147,11 @@ void main() { }); final String fakeCalendarId = "fakeCalendarId"; - final Event event = new Event(fakeCalendarId); + final Event event = Event(fakeCalendarId); event.eventId = "fakeEventId"; event.title = "fakeEventTitle"; - event.start = new DateTime.now(); - event.end = event.start.add(new Duration(hours: 1)); + event.start = DateTime.now(); + event.end = event.start.add(Duration(hours: 1)); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); expect(result.isSuccess, true); From 19945be247136b03799bb4509b972c4f583dbd99 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 6 Aug 2019 16:56:46 +1000 Subject: [PATCH 03/49] WIP of setting day of week on Android --- .../devicecalendar/CalendarDelegate.kt | 35 +- .../devicecalendar/DeviceCalendarPlugin.kt | 11 +- .../devicecalendar/common/DayOfWeek.kt | 5 + .../devicecalendar/models/Event.kt | 2 +- .../devicecalendar/models/RecurrenceRule.kt | 2 + .../presentation/pages/calendar_event.dart | 405 +++++++++--------- device_calendar/lib/device_calendar.dart | 1 + .../lib/src/common/day_of_week.dart | 1 + .../lib/src/models/recurrence_rule.dart | 17 +- 9 files changed, 266 insertions(+), 213 deletions(-) create mode 100644 device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt create mode 100644 device_calendar/lib/src/common/day_of_week.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 77ee86e0..2ef5bee7 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -20,10 +20,8 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJEC import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX -import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_DISPLAY_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ID_INDEX -import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_ALL_DAY_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_BEGIN_INDEX @@ -33,6 +31,7 @@ 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.DayOfWeek import com.builttoroam.devicecalendar.common.ErrorCodes.Companion.GENERIC_ERROR import com.builttoroam.devicecalendar.common.ErrorCodes.Companion.INVALID_ARGUMENT import com.builttoroam.devicecalendar.common.ErrorCodes.Companion.NOT_ALLOWED @@ -50,6 +49,7 @@ import com.google.gson.GsonBuilder import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry import org.dmfs.rfc5545.DateTime +import org.dmfs.rfc5545.Weekday import org.dmfs.rfc5545.recur.Freq import java.text.SimpleDateFormat import java.util.* @@ -480,11 +480,11 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } private fun parseRecurrenceRuleString(recurrenceRuleString: String?): RecurrenceRule? { - if(recurrenceRuleString == null) { + if (recurrenceRuleString == null) { return null } val rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString!!) - val frequency = when(rfcRecurrenceRule.freq) { + val frequency = when (rfcRecurrenceRule.freq) { Freq.YEARLY -> RecurrenceFrequency.YEARLY Freq.MONTHLY -> RecurrenceFrequency.MONTHLY Freq.WEEKLY -> RecurrenceFrequency.WEEKLY @@ -492,15 +492,20 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { else -> null } val recurrenceRule = RecurrenceRule(frequency!!) - if(rfcRecurrenceRule.count != null) { + if (rfcRecurrenceRule.count != null) { recurrenceRule.totalOccurrences = rfcRecurrenceRule.count } - if(rfcRecurrenceRule.interval != null) { - recurrenceRule.interval = rfcRecurrenceRule.interval - } - if(rfcRecurrenceRule.until != null) { + recurrenceRule.interval = rfcRecurrenceRule.interval + if (rfcRecurrenceRule.until != null) { recurrenceRule.endDate = rfcRecurrenceRule.until.timestamp } + + rfcRecurrenceRule.byDayPart?.forEach { weekdayNum -> + val dayOfWeek = DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == weekdayNum.weekday.ordinal } + if(dayOfWeek != null) { + recurrenceRule.daysOfWeek.add(dayOfWeek) + } + } return recurrenceRule } @@ -615,6 +620,18 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if(recurrenceRule.interval != null) { rr.interval = recurrenceRule.interval!! } + + if (recurrenceRule.daysOfWeek.isNotEmpty()) { + rr.byDayPart = mutableListOf() + for (dayOfWeek in recurrenceRule.daysOfWeek) { + val dayOfWeekCode = Weekday.values().find { + it.ordinal == dayOfWeek.ordinal + } ?: continue + + rr.byDayPart.add(org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, dayOfWeekCode)) + } + } + if(recurrenceRule.totalOccurrences != null) { rr.count = recurrenceRule.totalOccurrences!! } else if(recurrenceRule.endDate != null) { diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index d5c0eb6d..e1d91d17 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -2,6 +2,7 @@ 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.Event import com.builttoroam.devicecalendar.models.RecurrenceRule @@ -44,6 +45,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { private val TOTAL_OCCURRENCES_ARGUMENT = "totalOccurrences" private val INTERVAL_ARGUMENT = "interval" private val END_DATE_ARGUMENT = "endDate" + private val DAYS_OF_WEEK_ARGUMENT = "daysOfWeek" private constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -70,7 +72,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { } } - override fun onMethodCall(call: MethodCall, result: Result): Unit { + override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { REQUEST_PERMISSIONS_METHOD -> { _calendarDelegate.requestPermissions(result) @@ -121,6 +123,13 @@ class DeviceCalendarPlugin() : MethodCallHandler { recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long } + if (recurrenceRuleArgs.containsKey(DAYS_OF_WEEK_ARGUMENT)) { + val daysOfWeek = recurrenceRuleArgs[DAYS_OF_WEEK_ARGUMENT] as List + for (dayOfWeek in daysOfWeek) { + recurrenceRule.daysOfWeek.add(DayOfWeek.values()[dayOfWeek]) + } + } + event.recurrenceRule = recurrenceRule } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt new file mode 100644 index 00000000..07352517 --- /dev/null +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt @@ -0,0 +1,5 @@ +package com.builttoroam.devicecalendar.common + +enum class DayOfWeek { + SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY +} \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt index 2aafba05..bde9f186 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt @@ -1,6 +1,6 @@ package com.builttoroam.devicecalendar.models -class Event() { +class Event { var title: String? = null var eventId: String? = null var calendarId: String? = null diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt index 75af807d..ac9546da 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt @@ -1,5 +1,6 @@ package com.builttoroam.devicecalendar.models +import com.builttoroam.devicecalendar.common.DayOfWeek import com.builttoroam.devicecalendar.common.RecurrenceFrequency @@ -7,4 +8,5 @@ class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) { var totalOccurrences: Int? = null var interval: Int? = null var endDate: Long? = null + val daysOfWeek: MutableList = mutableListOf() } diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index d4f97a64..89ad4109 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -80,232 +80,235 @@ class _CalendarEventPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: Text(_event.eventId?.isEmpty ?? true - ? 'Create event' - : 'Edit event ${_event.title}'), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Form( - autovalidate: _autovalidate, - key: _formKey, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _event.title, - decoration: const InputDecoration( - labelText: 'Title', - hintText: 'Meeting with Gloria...'), - validator: _validateTitle, - onSaved: (String value) { - _event.title = value; - }, - ), + key: _scaffoldKey, + appBar: AppBar( + title: Text(_event.eventId?.isEmpty ?? true + ? 'Create event' + : 'Edit event ${_event.title}'), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Form( + autovalidate: _autovalidate, + key: _formKey, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event.title, + decoration: const InputDecoration( + labelText: 'Title', + hintText: 'Meeting with Gloria...'), + validator: _validateTitle, + onSaved: (String value) { + _event.title = value; + }, ), - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _event.description, - decoration: const InputDecoration( - labelText: 'Description', - hintText: 'Remember to buy flowers...'), - onSaved: (String value) { - _event.description = value; - }, - ), + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event.description, + decoration: const InputDecoration( + labelText: 'Description', + hintText: 'Remember to buy flowers...'), + onSaved: (String value) { + _event.description = value; + }, ), - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'From', - selectedDate: _startDate, - selectedTime: _startTime, - selectDate: (DateTime date) { - setState(() { - _startDate = date; + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'From', + selectedDate: _startDate, + selectedTime: _startTime, + selectDate: (DateTime date) { + setState(() { + _startDate = date; + _event.start = + _combineDateWithTime(_startDate, _startTime); + }); + }, + selectTime: (TimeOfDay time) { + setState( + () { + _startTime = time; _event.start = _combineDateWithTime(_startDate, _startTime); - }); - }, - selectTime: (TimeOfDay time) { - setState( - () { - _startTime = time; - _event.start = - _combineDateWithTime(_startDate, _startTime); - }, - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'To', - selectedDate: _endDate, - selectedTime: _endTime, - selectDate: (DateTime date) { - setState( - () { - _endDate = date; - _event.end = - _combineDateWithTime(_endDate, _endTime); - }, - ); - }, - selectTime: (TimeOfDay time) { - setState( - () { - _endTime = time; - _event.end = - _combineDateWithTime(_endDate, _endTime); - }, - ); - }, - ), + }, + ); + }, ), - CheckboxListTile( - value: _isRecurringEvent, - title: Text('Is recurring'), - onChanged: (isChecked) { - setState(() { - _isRecurringEvent = isChecked; - }); + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'To', + selectedDate: _endDate, + selectedTime: _endTime, + selectDate: (DateTime date) { + setState( + () { + _endDate = date; + _event.end = + _combineDateWithTime(_endDate, _endTime); + }, + ); + }, + selectTime: (TimeOfDay time) { + setState( + () { + _endTime = time; + _event.end = + _combineDateWithTime(_endDate, _endTime); + }, + ); }, ), - if (_isRecurringEvent) - Column( - children: [ - ListTile( - leading: Text('Frequency'), - trailing: DropdownButton( - onChanged: (selectedFrequency) { - setState(() { - _recurrenceFrequency = selectedFrequency; - }); - }, - value: _recurrenceFrequency, - items: RecurrenceFrequency.values - .map( - (f) => DropdownMenuItem( - value: f, - child: _recurrenceFrequencyToText(f), - ), - ) - .toList(), - ), + ), + CheckboxListTile( + value: _isRecurringEvent, + title: Text('Is recurring'), + onChanged: (isChecked) { + setState(() { + _isRecurringEvent = isChecked; + }); + }, + ), + if (_isRecurringEvent) + Column( + children: [ + ListTile( + leading: Text('Frequency'), + trailing: DropdownButton( + onChanged: (selectedFrequency) { + setState(() { + _recurrenceFrequency = selectedFrequency; + }); + }, + value: _recurrenceFrequency, + items: RecurrenceFrequency.values + .map( + (f) => DropdownMenuItem( + value: f, + child: _recurrenceFrequencyToText(f), + ), + ) + .toList(), + ), + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _interval?.toString(), + decoration: const InputDecoration( + labelText: 'Interval between events', + hintText: '1'), + keyboardType: TextInputType.number, + validator: _validateInterval, + onSaved: (String value) { + _interval = int.tryParse(value); + }, + ), + ), + ListTile( + leading: Text('Event ends'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _recurrenceRuleEndType = value; + }); + }, + value: _recurrenceRuleEndType, + items: RecurrenceRuleEndType.values + .map( + (f) => DropdownMenuItem( + value: f, + child: _recurrenceRuleEndTypeToText(f), + ), + ) + .toList(), ), + ), + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.MaxOccurrences) Padding( padding: const EdgeInsets.all(10.0), child: TextFormField( - initialValue: _interval?.toString(), + initialValue: _totalOccurrences?.toString(), decoration: const InputDecoration( - labelText: 'Interval between events', - hintText: '1'), + labelText: 'Max occurrences', hintText: '1'), keyboardType: TextInputType.number, - validator: _validateInterval, + validator: _validateTotalOccurrences, onSaved: (String value) { - _interval = int.tryParse(value); + _totalOccurrences = int.tryParse(value); }, ), ), - ListTile( - leading: Text('Event ends'), - trailing: DropdownButton( - onChanged: (value) { + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate) + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'Date', + selectedDate: _recurrenceEndDate, + selectedTime: _recurrenceEndTime, + selectDate: (DateTime date) { + setState(() { + _recurrenceEndDate = date; + }); + }, + selectTime: (TimeOfDay time) { setState(() { - _recurrenceRuleEndType = value; + _recurrenceEndTime = time; }); }, - value: _recurrenceRuleEndType, - items: RecurrenceRuleEndType.values - .map( - (f) => DropdownMenuItem( - value: f, - child: _recurrenceRuleEndTypeToText(f), - ), - ) - .toList(), ), ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.MaxOccurrences) - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _totalOccurrences?.toString(), - decoration: const InputDecoration( - labelText: 'Max occurrences', - hintText: '1'), - keyboardType: TextInputType.number, - validator: _validateTotalOccurrences, - onSaved: (String value) { - _totalOccurrences = int.tryParse(value); - }, - ), - ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.SpecifiedEndDate) - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'Date', - selectedDate: _recurrenceEndDate, - selectedTime: _recurrenceEndTime, - selectDate: (DateTime date) { - setState(() { - _recurrenceEndDate = date; - }); - }, - selectTime: (TimeOfDay time) { - setState(() { - _recurrenceEndTime = time; - }); - }, - ), - ), - ], - ) - ], - ), - ) - ], - ), + ], + ) + ], + ), + ) + ], ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - final FormState form = _formKey.currentState; - if (!form.validate()) { - _autovalidate = true; // Start validating on every change. - showInSnackBar('Please fix the errors in red before submitting.'); + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + final FormState form = _formKey.currentState; + if (!form.validate()) { + _autovalidate = true; // Start validating on every change. + showInSnackBar('Please fix the errors in red before submitting.'); + } else { + form.save(); + if (_isRecurringEvent) { + _event.recurrenceRule = RecurrenceRule( + _recurrenceFrequency, + interval: _interval, + totalOccurrences: _totalOccurrences, + daysOfWeek: [DayOfWeek.Monday], + endDate: _recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate + ? _combineDateWithTime( + _recurrenceEndDate, _recurrenceEndTime) + : null, + ); + } + var createEventResult = + await _deviceCalendarPlugin.createOrUpdateEvent(_event); + if (createEventResult.isSuccess) { + Navigator.pop(context, true); } else { - form.save(); - if (_isRecurringEvent) { - _event.recurrenceRule = RecurrenceRule(_recurrenceFrequency, - interval: _interval, - totalOccurrences: _totalOccurrences, - endDate: _recurrenceRuleEndType == - RecurrenceRuleEndType.SpecifiedEndDate - ? _combineDateWithTime( - _recurrenceEndDate, _recurrenceEndTime) - : null); - } - var createEventResult = - await _deviceCalendarPlugin.createOrUpdateEvent(_event); - if (createEventResult.isSuccess) { - Navigator.pop(context, true); - } else { - showInSnackBar(createEventResult.errorMessages.join(' | ')); - } + showInSnackBar(createEventResult.errorMessages.join(' | ')); } - }, - child: Icon(Icons.check), - )); + } + }, + child: Icon(Icons.check), + ), + ); } Text _recurrenceFrequencyToText(RecurrenceFrequency recurrenceFrequency) { diff --git a/device_calendar/lib/device_calendar.dart b/device_calendar/lib/device_calendar.dart index 76ccff68..6501f008 100644 --- a/device_calendar/lib/device_calendar.dart +++ b/device_calendar/lib/device_calendar.dart @@ -1,5 +1,6 @@ library device_calendar; +export 'src/common/day_of_week.dart'; export 'src/common/recurrence_frequency.dart'; export 'src/models/attendee.dart'; export 'src/models/calendar.dart'; diff --git a/device_calendar/lib/src/common/day_of_week.dart b/device_calendar/lib/src/common/day_of_week.dart new file mode 100644 index 00000000..54538f25 --- /dev/null +++ b/device_calendar/lib/src/common/day_of_week.dart @@ -0,0 +1 @@ +enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday } diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index ee4a0bbe..98f9494b 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -1,3 +1,5 @@ +import 'package:device_calendar/src/common/day_of_week.dart'; + import '../common/error_messages.dart'; import '../common/recurrence_frequency.dart'; @@ -12,16 +14,21 @@ class RecurrenceRule { /// The frequency of recurring events RecurrenceFrequency recurrenceFrequency; + + List daysOfWeek; + final String _totalOccurrencesKey = 'totalOccurrences'; final String _recurrenceFrequencyKey = 'recurrenceFrequency'; final String _intervalKey = 'interval'; final String _endDateKey = 'endDate'; + final String _daysOfWeekKey = 'daysOfWeek'; RecurrenceRule( this.recurrenceFrequency, { this.totalOccurrences, this.interval, this.endDate, + this.daysOfWeek, }) : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'); @@ -43,6 +50,11 @@ class RecurrenceRule { endDate = DateTime.fromMillisecondsSinceEpoch(endDateMillisecondsSinceEpoch); } + /*List daysOfWeekIndices = json[_daysOfWeekKey]; + if (daysOfWeekIndices != null) { + daysOfWeek = + daysOfWeekIndices.map((index) => DayOfWeek.values[index]).toList(); + }*/ } Map toJson() { @@ -55,7 +67,10 @@ class RecurrenceRule { data[_totalOccurrencesKey] = totalOccurrences; } if (endDate != null) { - data[_endDateKey] = endDate?.millisecondsSinceEpoch; + data[_endDateKey] = endDate.millisecondsSinceEpoch; + } + if (daysOfWeek != null) { + data[_daysOfWeekKey] = daysOfWeek.map((d) => d.index).toList(); } return data; } From c7873581091dceedeb97d16f9299527b83bc9621 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 7 Aug 2019 11:35:37 +1000 Subject: [PATCH 04/49] handle parsing days of week from android --- .../builttoroam/devicecalendar/CalendarDelegate.kt | 3 ++- .../devicecalendar/DayOfWeekSerializer.kt | 14 ++++++++++++++ .../lib/presentation/pages/calendar_event.dart | 1 - .../lib/src/models/recurrence_rule.dart | 12 +++++++----- 4 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 2ef5bee7..9a6f6a11 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -73,8 +73,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { constructor(activity: Activity?, context: Context) { _activity = activity _context = context - var gsonBuilder = GsonBuilder() + val gsonBuilder = GsonBuilder() gsonBuilder.registerTypeAdapter(RecurrenceFrequency::class.java, RecurrenceFrequencySerializer()) + gsonBuilder.registerTypeAdapter(DayOfWeek::class.java, DayOfWeekSerializer()) _gson = gsonBuilder.create() } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt new file mode 100644 index 00000000..02358a06 --- /dev/null +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt @@ -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 { + override fun serialize(src: DayOfWeek?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + if(src != null) { + return JsonPrimitive(src.ordinal) + } + return JsonObject()//To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 89ad4109..f195e83d 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -289,7 +289,6 @@ class _CalendarEventPageState extends State { _recurrenceFrequency, interval: _interval, totalOccurrences: _totalOccurrences, - daysOfWeek: [DayOfWeek.Monday], endDate: _recurrenceRuleEndType == RecurrenceRuleEndType.SpecifiedEndDate ? _combineDateWithTime( diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index 98f9494b..f1a4ba49 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -50,11 +50,13 @@ class RecurrenceRule { endDate = DateTime.fromMillisecondsSinceEpoch(endDateMillisecondsSinceEpoch); } - /*List daysOfWeekIndices = json[_daysOfWeekKey]; - if (daysOfWeekIndices != null) { - daysOfWeek = - daysOfWeekIndices.map((index) => DayOfWeek.values[index]).toList(); - }*/ + List daysOfWeekIndices = json[_daysOfWeekKey]; + if (daysOfWeekIndices != null && daysOfWeekIndices is! List) { + daysOfWeek = daysOfWeekIndices + .cast() + .map((index) => DayOfWeek.values[index]) + .toList(); + } } Map toJson() { From 4d49ea03bf12a6db575dd35aeb21732b704a9501 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 8 Aug 2019 10:13:06 +1000 Subject: [PATCH 05/49] support days of week on iOS, tidy up example a bit --- .../presentation/pages/calendar_event.dart | 4 +- .../Classes/SwiftDeviceCalendarPlugin.swift | 76 ++++++++++++------- device_calendar/lib/src/device_calendar.dart | 2 +- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index f195e83d..e7766030 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -337,14 +337,14 @@ class _CalendarEventPageState extends State { } String _validateTotalOccurrences(String value) { - if (!value.isEmpty && int.tryParse(value) == null) { + if (value.isNotEmpty && int.tryParse(value) == null) { return 'Total occurrences needs to be a valid number'; } return null; } String _validateInterval(String value) { - if (!value.isEmpty && int.tryParse(value) == null) { + if (value.isNotEmpty && int.tryParse(value) == null) { return 'Interval needs to be a valid number'; } return null; diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 6b2ff6dc..fcebb705 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -32,6 +32,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let totalOccurrences: Int? let interval: Int let endDate: Int64? + let daysOfWeek: [Int]? } struct Attendee: Codable { @@ -72,6 +73,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let recurrenceFrequencyArgument = "recurrenceFrequency" let totalOccurrencesArgument = "totalOccurrences" let intervalArgument = "interval" + let daysOfWeekArgument = "daysOfWeek" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] @@ -178,8 +180,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let attendee = Attendee(name: ekParticipant.name!) attendees.append(attendee) } - } + var recurrenceRule: RecurrenceRule? if(ekEvent.hasRecurrenceRules) { let ekRecurrenceRule = ekEvent.recurrenceRules![0] @@ -208,7 +210,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { endDate = Int64(exactly: endDateMs!) } - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate) + var daysOfWeek: [Int]? = nil + if(ekRecurrenceRule.daysOfTheWeek != nil) { + daysOfWeek = [] + for dayOfWeek in ekRecurrenceRule.daysOfTheWeek! { + daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) + } + } + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfWeek: daysOfWeek) } let event = Event( @@ -226,6 +235,44 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return event } + private func createRecurrenceRule(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { + let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary + if (recurrenceRuleArguments != nil) { + let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger + let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger + let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger + var recurrenceInterval = 1 + let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber + let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] + + var recurrenceEnd:EKRecurrenceEnd? + if(endDate != nil) { + recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) + } else if(totalOccurrences != nil && totalOccurrences! > 0) { + recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) + } + + if(interval != nil && interval! > 1) { + recurrenceInterval = interval! + } + + let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? Array + var daysOfWeek : [EKRecurrenceDayOfWeek]? = nil + + if(daysOfWeekIndices != nil) { + daysOfWeek = [] + for dayOfWeekIndex in daysOfWeekIndices! { + daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) + } + } + + ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] + //ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, end: recurrenceEnd)] + } else { + ekEvent!.recurrenceRules = nil + } + } + private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary @@ -265,30 +312,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { ekEvent!.endDate = endDate ekEvent!.calendar = ekCalendar! - let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if (recurrenceRuleArguments != nil) { - let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger - let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger - let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger - var recurrenceInterval = 1 - let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber - let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] - - var recurrenceEnd:EKRecurrenceEnd? - if(endDate != nil) { - recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) - } else if(totalOccurrences != nil && totalOccurrences! > 0) { - recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) - } - - if(interval != nil && interval! > 1) { - recurrenceInterval = interval! - } - - ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, end: recurrenceEnd)] - } else { - ekEvent!.recurrenceRules = nil - } + createRecurrenceRule(arguments, ekEvent) do { try self.eventStore.save(ekEvent!, span: .futureEvents) diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 9b193680..813119c7 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -94,7 +94,7 @@ class DeviceCalendarPlugin { "[${ErrorCodes.invalidArguments}] ${ErrorMessages.invalidMissingCalendarId}"); } - // TODO: Extend capability to handle null start or null end (eg all events after a certain date (null end date) or all events prior to a certain date (null start date)) + // TODO: Extend capability to handle null start or null end (e.g. all events after a certain date (null end date) or all events prior to a certain date (null start date)) if ((retrieveEventsParams?.eventIds?.isEmpty ?? true) && ((retrieveEventsParams?.startDate == null || retrieveEventsParams?.endDate == null) || From 36cf74aa7f0d7ea9e743df1b8b94bf362b7967e3 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 8 Aug 2019 10:45:31 +1000 Subject: [PATCH 06/49] refactor ios code for creating and reading recurrence rules --- .../Classes/SwiftDeviceCalendarPlugin.swift | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index fcebb705..f539f1c6 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -182,6 +182,23 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } + let recurrenceRule = parseEKRecurrenceRules(ekEvent) + let event = Event( + eventId: ekEvent.eventIdentifier, + calendarId: calendarId, + title: ekEvent.title, + description: ekEvent.notes, + start: Int64(ekEvent.startDate.millisecondsSinceEpoch), + end: Int64(ekEvent.endDate.millisecondsSinceEpoch), + allDay: ekEvent.isAllDay, + attendees: attendees, + location: ekEvent.location, + recurrenceRule: recurrenceRule + ) + return event + } + + private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { var recurrenceRule: RecurrenceRule? if(ekEvent.hasRecurrenceRules) { let ekRecurrenceRule = ekEvent.recurrenceRules![0] @@ -219,58 +236,44 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfWeek: daysOfWeek) } - - let event = Event( - eventId: ekEvent.eventIdentifier, - calendarId: calendarId, - title: ekEvent.title, - description: ekEvent.notes, - start: Int64(ekEvent.startDate.millisecondsSinceEpoch), - end: Int64(ekEvent.endDate.millisecondsSinceEpoch), - allDay: ekEvent.isAllDay, - attendees: attendees, - location: ekEvent.location, - recurrenceRule: recurrenceRule - ) - return event + return recurrenceRule } - private func createRecurrenceRule(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { + private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if (recurrenceRuleArguments != nil) { - let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger - let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger - let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger - var recurrenceInterval = 1 - let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber - let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] - - var recurrenceEnd:EKRecurrenceEnd? - if(endDate != nil) { - recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) - } else if(totalOccurrences != nil && totalOccurrences! > 0) { - recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) - } - - if(interval != nil && interval! > 1) { - recurrenceInterval = interval! - } - - let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? Array - var daysOfWeek : [EKRecurrenceDayOfWeek]? = nil - - if(daysOfWeekIndices != nil) { - daysOfWeek = [] - for dayOfWeekIndex in daysOfWeekIndices! { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) - } + if (recurrenceRuleArguments == nil) { + return nil; + } + + let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger + let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger + let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger + var recurrenceInterval = 1 + let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber + let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] + + var recurrenceEnd:EKRecurrenceEnd? + if(endDate != nil) { + recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) + } else if(totalOccurrences != nil && totalOccurrences! > 0) { + recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) + } + + if(interval != nil && interval! > 1) { + recurrenceInterval = interval! + } + + let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? Array + var daysOfWeek : [EKRecurrenceDayOfWeek]? = nil + + if(daysOfWeekIndices != nil) { + daysOfWeek = [] + for dayOfWeekIndex in daysOfWeekIndices! { + daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) } - - ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] - //ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, end: recurrenceEnd)] - } else { - ekEvent!.recurrenceRules = nil } + + return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] } private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { @@ -311,8 +314,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { ekEvent!.startDate = startDate ekEvent!.endDate = endDate ekEvent!.calendar = ekCalendar! - - createRecurrenceRule(arguments, ekEvent) + ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) do { try self.eventStore.save(ekEvent!, span: .futureEvents) From b2bad4d5e6899dbcbc5e6ffe85058bcf17e840d8 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 8 Aug 2019 13:57:40 +1000 Subject: [PATCH 07/49] update example app to be able to configure days of the week --- .../presentation/pages/calendar_event.dart | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index e7766030..d28fc0fd 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -36,6 +36,8 @@ class _CalendarEventPageState extends State { bool _isRecurringEvent = false; RecurrenceRuleEndType _recurrenceRuleEndType; + List _daysOfWeek = List(); + int _totalOccurrences; int _interval; DateTime _recurrenceEndDate; @@ -66,6 +68,7 @@ class _CalendarEventPageState extends State { _recurrenceEndDate = _event.recurrenceRule.endDate; _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } + _daysOfWeek = _event.recurrenceRule.daysOfWeek; } } @@ -83,7 +86,7 @@ class _CalendarEventPageState extends State { key: _scaffoldKey, appBar: AppBar( title: Text(_event.eventId?.isEmpty ?? true - ? 'Create event' + ? 'Create event' : 'Edit event ${_event.title}'), ), body: SingleChildScrollView( @@ -233,6 +236,28 @@ class _CalendarEventPageState extends State { .toList(), ), ), + ListTile( + leading: Text('Days of week'), + ), + ...DayOfWeek.values.map( + (d) { + return CheckboxListTile( + title: _dayOfWeekToText(d), + value: _daysOfWeek?.any((dow) => dow == d), + onChanged: (selected) { + setState( + () { + if (selected) { + _daysOfWeek.add(d); + } else { + _daysOfWeek.remove(d); + } + }, + ); + }, + ); + }, + ), if (_recurrenceRuleEndType == RecurrenceRuleEndType.MaxOccurrences) Padding( @@ -294,6 +319,7 @@ class _CalendarEventPageState extends State { ? _combineDateWithTime( _recurrenceEndDate, _recurrenceEndTime) : null, + daysOfWeek: _daysOfWeek, ); } var createEventResult = @@ -310,6 +336,25 @@ class _CalendarEventPageState extends State { ); } + Text _dayOfWeekToText(DayOfWeek dayOfWeek) { + switch (dayOfWeek) { + case DayOfWeek.Sunday: + return Text('Sunday'); + case DayOfWeek.Monday: + return Text('Monday'); + case DayOfWeek.Tuesday: + return Text('Tuesday'); + case DayOfWeek.Wednesday: + return Text('Wednesday'); + case DayOfWeek.Thursday: + return Text('Thursday'); + case DayOfWeek.Friday: + return Text('Friday'); + default: + return Text(''); + } + } + Text _recurrenceFrequencyToText(RecurrenceFrequency recurrenceFrequency) { switch (recurrenceFrequency) { case RecurrenceFrequency.Daily: From b954cd0abde96c36fc2ce400d25c73616b950dbc Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Fri, 9 Aug 2019 15:33:22 +1000 Subject: [PATCH 08/49] rename daysOfWeek to daysOfTheWeek --- .../builttoroam/devicecalendar/CalendarDelegate.kt | 6 +++--- .../devicecalendar/DeviceCalendarPlugin.kt | 10 +++++----- .../devicecalendar/models/RecurrenceRule.kt | 2 +- .../lib/presentation/pages/calendar_event.dart | 13 +++++++------ .../ios/Classes/SwiftDeviceCalendarPlugin.swift | 8 ++++---- device_calendar/lib/src/device_calendar.dart | 2 +- .../lib/src/models/recurrence_rule.dart | 14 +++++++------- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 9a6f6a11..3b82117d 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -504,7 +504,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { rfcRecurrenceRule.byDayPart?.forEach { weekdayNum -> val dayOfWeek = DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == weekdayNum.weekday.ordinal } if(dayOfWeek != null) { - recurrenceRule.daysOfWeek.add(dayOfWeek) + recurrenceRule.daysOfTheWeek.add(dayOfWeek) } } return recurrenceRule @@ -622,9 +622,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { rr.interval = recurrenceRule.interval!! } - if (recurrenceRule.daysOfWeek.isNotEmpty()) { + if (recurrenceRule.daysOfTheWeek.isNotEmpty()) { rr.byDayPart = mutableListOf() - for (dayOfWeek in recurrenceRule.daysOfWeek) { + for (dayOfWeek in recurrenceRule.daysOfTheWeek) { val dayOfWeekCode = Weekday.values().find { it.ordinal == dayOfWeek.ordinal } ?: continue diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index e1d91d17..867fc5c2 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -45,7 +45,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { private val TOTAL_OCCURRENCES_ARGUMENT = "totalOccurrences" private val INTERVAL_ARGUMENT = "interval" private val END_DATE_ARGUMENT = "endDate" - private val DAYS_OF_WEEK_ARGUMENT = "daysOfWeek" + private val DAYS_OF_THE_WEEK_ARGUMENT = "daysOfTheWeek" private constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -55,7 +55,7 @@ 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() @@ -123,10 +123,10 @@ class DeviceCalendarPlugin() : MethodCallHandler { recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long } - if (recurrenceRuleArgs.containsKey(DAYS_OF_WEEK_ARGUMENT)) { - val daysOfWeek = recurrenceRuleArgs[DAYS_OF_WEEK_ARGUMENT] as List + if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_WEEK_ARGUMENT)) { + val daysOfWeek = recurrenceRuleArgs[DAYS_OF_THE_WEEK_ARGUMENT] as List for (dayOfWeek in daysOfWeek) { - recurrenceRule.daysOfWeek.add(DayOfWeek.values()[dayOfWeek]) + recurrenceRule.daysOfTheWeek.add(DayOfWeek.values()[dayOfWeek]) } } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt index ac9546da..3c021837 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt @@ -8,5 +8,5 @@ class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) { var totalOccurrences: Int? = null var interval: Int? = null var endDate: Long? = null - val daysOfWeek: MutableList = mutableListOf() + val daysOfTheWeek: MutableList = mutableListOf() } diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index d28fc0fd..463ae757 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -36,7 +36,7 @@ class _CalendarEventPageState extends State { bool _isRecurringEvent = false; RecurrenceRuleEndType _recurrenceRuleEndType; - List _daysOfWeek = List(); + List _daysOfTheWeek = List(); int _totalOccurrences; int _interval; @@ -68,7 +68,7 @@ class _CalendarEventPageState extends State { _recurrenceEndDate = _event.recurrenceRule.endDate; _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } - _daysOfWeek = _event.recurrenceRule.daysOfWeek; + _daysOfTheWeek = _event.recurrenceRule.daysOfTheWeek; } } @@ -243,14 +243,15 @@ class _CalendarEventPageState extends State { (d) { return CheckboxListTile( title: _dayOfWeekToText(d), - value: _daysOfWeek?.any((dow) => dow == d), + value: _daysOfTheWeek?.any((dow) => dow == d) ?? + false, onChanged: (selected) { setState( () { if (selected) { - _daysOfWeek.add(d); + _daysOfTheWeek.add(d); } else { - _daysOfWeek.remove(d); + _daysOfTheWeek.remove(d); } }, ); @@ -319,7 +320,7 @@ class _CalendarEventPageState extends State { ? _combineDateWithTime( _recurrenceEndDate, _recurrenceEndTime) : null, - daysOfWeek: _daysOfWeek, + daysOfTheWeek: _daysOfTheWeek, ); } var createEventResult = diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index f539f1c6..cbeb30b2 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -32,7 +32,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let totalOccurrences: Int? let interval: Int let endDate: Int64? - let daysOfWeek: [Int]? + let daysOfTheWeek: [Int]? } struct Attendee: Codable { @@ -73,7 +73,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let recurrenceFrequencyArgument = "recurrenceFrequency" let totalOccurrencesArgument = "totalOccurrences" let intervalArgument = "interval" - let daysOfWeekArgument = "daysOfWeek" + let daysOfTheWeekArgument = "daysOfTheWeek" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] @@ -234,7 +234,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) } } - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfWeek: daysOfWeek) + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfWeek) } return recurrenceRule } @@ -263,7 +263,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { recurrenceInterval = interval! } - let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? Array + let daysOfWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? Array var daysOfWeek : [EKRecurrenceDayOfWeek]? = nil if(daysOfWeekIndices != nil) { diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 813119c7..21a9a37e 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -180,7 +180,7 @@ class DeviceCalendarPlugin { 'eventDescription': event.description, 'eventStartDate': event.start.millisecondsSinceEpoch, 'eventEndDate': event.end.millisecondsSinceEpoch, - 'recurrenceRule': event.recurrenceRule?.toJson() + 'recurrenceRule': event.recurrenceRule?.toJson(), }); } catch (e) { _parsePlatformExceptionAndUpdateResult(e, res); diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index f1a4ba49..d23c418b 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -15,20 +15,20 @@ class RecurrenceRule { /// The frequency of recurring events RecurrenceFrequency recurrenceFrequency; - List daysOfWeek; + List daysOfTheWeek; final String _totalOccurrencesKey = 'totalOccurrences'; final String _recurrenceFrequencyKey = 'recurrenceFrequency'; final String _intervalKey = 'interval'; final String _endDateKey = 'endDate'; - final String _daysOfWeekKey = 'daysOfWeek'; + final String _daysOfTheWeekKey = 'daysOfTheWeek'; RecurrenceRule( this.recurrenceFrequency, { this.totalOccurrences, this.interval, this.endDate, - this.daysOfWeek, + this.daysOfTheWeek, }) : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'); @@ -50,9 +50,9 @@ class RecurrenceRule { endDate = DateTime.fromMillisecondsSinceEpoch(endDateMillisecondsSinceEpoch); } - List daysOfWeekIndices = json[_daysOfWeekKey]; + List daysOfWeekIndices = json[_daysOfTheWeekKey]; if (daysOfWeekIndices != null && daysOfWeekIndices is! List) { - daysOfWeek = daysOfWeekIndices + daysOfTheWeek = daysOfWeekIndices .cast() .map((index) => DayOfWeek.values[index]) .toList(); @@ -71,8 +71,8 @@ class RecurrenceRule { if (endDate != null) { data[_endDateKey] = endDate.millisecondsSinceEpoch; } - if (daysOfWeek != null) { - data[_daysOfWeekKey] = daysOfWeek.map((d) => d.index).toList(); + if (daysOfTheWeek != null) { + data[_daysOfTheWeekKey] = daysOfTheWeek.map((d) => d.index).toList(); } return data; } From ce317b947ff1f9c4c5b16e9fe2e268809f2ed290 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Fri, 9 Aug 2019 16:09:21 +1000 Subject: [PATCH 09/49] add assert statement for days of the week --- .../presentation/pages/calendar_event.dart | 38 +++++++++---------- .../lib/src/models/recurrence_rule.dart | 11 +++++- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 463ae757..f93bea71 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -217,25 +217,6 @@ class _CalendarEventPageState extends State { }, ), ), - ListTile( - leading: Text('Event ends'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _recurrenceRuleEndType = value; - }); - }, - value: _recurrenceRuleEndType, - items: RecurrenceRuleEndType.values - .map( - (f) => DropdownMenuItem( - value: f, - child: _recurrenceRuleEndTypeToText(f), - ), - ) - .toList(), - ), - ), ListTile( leading: Text('Days of week'), ), @@ -259,6 +240,25 @@ class _CalendarEventPageState extends State { ); }, ), + ListTile( + leading: Text('Event ends'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _recurrenceRuleEndType = value; + }); + }, + value: _recurrenceRuleEndType, + items: RecurrenceRuleEndType.values + .map( + (f) => DropdownMenuItem( + value: f, + child: _recurrenceRuleEndTypeToText(f), + ), + ) + .toList(), + ), + ), if (_recurrenceRuleEndType == RecurrenceRuleEndType.MaxOccurrences) Padding( diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index d23c418b..d28d0616 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -15,6 +15,7 @@ class RecurrenceRule { /// The frequency of recurring events RecurrenceFrequency recurrenceFrequency; + /// The days of the week associated with the recurring event should List daysOfTheWeek; final String _totalOccurrencesKey = 'totalOccurrences'; @@ -29,8 +30,14 @@ class RecurrenceRule { this.interval, this.endDate, this.daysOfTheWeek, - }) : assert(!(endDate != null && totalOccurrences != null), - 'Cannot specify both an end date and total occurrences for a recurring event'); + }) : assert(!(endDate != null && totalOccurrences != null), + 'Cannot specify both an end date and total occurrences for a recurring event'), + assert( + daysOfTheWeek.isNotEmpty && + (recurrenceFrequency == RecurrenceFrequency.Weekly || + recurrenceFrequency == RecurrenceFrequency.Monthly && + recurrenceFrequency == RecurrenceFrequency.Yearly), + 'Days of the week can only be specified for recurrence rules with a weekly, monthly or yearly frequency'); RecurrenceRule.fromJson(Map json) { if (json == null) { From b93e295904676f35c674be676f772bf886ed4564 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Fri, 9 Aug 2019 17:02:11 +1000 Subject: [PATCH 10/49] support for days of the month on ios --- .../Classes/SwiftDeviceCalendarPlugin.swift | 37 ++++++++----- .../lib/src/models/recurrence_rule.dart | 52 +++++++++++++------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index cbeb30b2..1493d2c2 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -33,6 +33,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let interval: Int let endDate: Int64? let daysOfTheWeek: [Int]? + let daysOfTheMonth: [Int]? } struct Attendee: Codable { @@ -74,6 +75,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let totalOccurrencesArgument = "totalOccurrences" let intervalArgument = "interval" let daysOfTheWeekArgument = "daysOfTheWeek" + let daysOfTheMonthArgument = "daysOfTheMonth" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] @@ -227,14 +229,22 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { endDate = Int64(exactly: endDateMs!) } - var daysOfWeek: [Int]? = nil + var daysOfTheWeek: [Int]? = nil if(ekRecurrenceRule.daysOfTheWeek != nil) { - daysOfWeek = [] - for dayOfWeek in ekRecurrenceRule.daysOfTheWeek! { - daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) + daysOfTheWeek = [] + for dayOfTheWeek in ekRecurrenceRule.daysOfTheWeek! { + daysOfTheWeek!.append(dayOfTheWeek.dayOfTheWeek.rawValue - 1) } } - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfWeek) + + var daysOfTheMonth: [Int]? = nil + if(ekRecurrenceRule.daysOfTheMonth != nil) { + daysOfTheMonth = [] + for dayOfTheMonth in ekRecurrenceRule.daysOfTheMonth! { + daysOfTheMonth!.append(dayOfTheMonth.intValue) + } + } + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth) } return recurrenceRule } @@ -263,17 +273,20 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { recurrenceInterval = interval! } - let daysOfWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? Array - var daysOfWeek : [EKRecurrenceDayOfWeek]? = nil + let daysOfTheWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? [Int] + var daysOfTheWeek : [EKRecurrenceDayOfWeek]? = nil - if(daysOfWeekIndices != nil) { - daysOfWeek = [] - for dayOfWeekIndex in daysOfWeekIndices! { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) + if(daysOfTheWeekIndices != nil) { + daysOfTheWeek = [] + for dayOfWeekIndex in daysOfTheWeekIndices! { + daysOfTheWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) } } - return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfWeek, daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] + let daysOfTheMonth = recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber] + //var daysOfTheMonthNum: [NSNumber]? = nil + + return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] } private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index d28d0616..b03a1db5 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -15,29 +15,42 @@ class RecurrenceRule { /// The frequency of recurring events RecurrenceFrequency recurrenceFrequency; - /// The days of the week associated with the recurring event should + /// The days of the week that this event occurs on. Only applicable to rules with a weekly, monthly or yearly frequency List daysOfTheWeek; + /// The days of the month that this event occurs on. Only applicable to recurrence rules with a monthly frequency + List daysOfTheMonth; + final String _totalOccurrencesKey = 'totalOccurrences'; final String _recurrenceFrequencyKey = 'recurrenceFrequency'; final String _intervalKey = 'interval'; final String _endDateKey = 'endDate'; final String _daysOfTheWeekKey = 'daysOfTheWeek'; + final String _daysOfTheMonthKey = 'daysOfTheMonth'; - RecurrenceRule( - this.recurrenceFrequency, { - this.totalOccurrences, - this.interval, - this.endDate, - this.daysOfTheWeek, - }) : assert(!(endDate != null && totalOccurrences != null), + RecurrenceRule(this.recurrenceFrequency, + {this.totalOccurrences, + this.interval, + this.endDate, + this.daysOfTheWeek, + this.daysOfTheMonth}) + : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'), assert( - daysOfTheWeek.isNotEmpty && - (recurrenceFrequency == RecurrenceFrequency.Weekly || - recurrenceFrequency == RecurrenceFrequency.Monthly && - recurrenceFrequency == RecurrenceFrequency.Yearly), - 'Days of the week can only be specified for recurrence rules with a weekly, monthly or yearly frequency'); + (daysOfTheWeek?.isEmpty ?? true) || + ((daysOfTheWeek?.isNotEmpty ?? false) && + (recurrenceFrequency == RecurrenceFrequency.Daily)), + 'Days of the week can only be specified for recurrence rules with a weekly, monthly or yearly frequency'), + assert( + (daysOfTheMonth?.isEmpty ?? true) || + ((daysOfTheMonth?.isNotEmpty ?? false) && + recurrenceFrequency == RecurrenceFrequency.Monthly), + 'Days of the month can only be specified for recurrence rules with a monthly frequency'), + assert( + (daysOfTheMonth?.isEmpty ?? true) || + ((daysOfTheMonth?.isNotEmpty ?? false) && + daysOfTheMonth.any((d) => d >= 1 || d <= 31)), + 'Days of the month must be between 1 and 31 inclusive'); RecurrenceRule.fromJson(Map json) { if (json == null) { @@ -57,13 +70,17 @@ class RecurrenceRule { endDate = DateTime.fromMillisecondsSinceEpoch(endDateMillisecondsSinceEpoch); } - List daysOfWeekIndices = json[_daysOfTheWeekKey]; - if (daysOfWeekIndices != null && daysOfWeekIndices is! List) { - daysOfTheWeek = daysOfWeekIndices + List daysOfTheWeekIndices = json[_daysOfTheWeekKey]; + if (daysOfTheWeekIndices != null && daysOfTheWeekIndices is! List) { + daysOfTheWeek = daysOfTheWeekIndices .cast() .map((index) => DayOfWeek.values[index]) .toList(); } + List daysOfTheMonthObj = json[_daysOfTheMonthKey]; + if (daysOfTheMonthObj != null && daysOfTheMonthObj is! List) { + daysOfTheMonth = daysOfTheMonthObj.cast().toList(); + } } Map toJson() { @@ -81,6 +98,9 @@ class RecurrenceRule { if (daysOfTheWeek != null) { data[_daysOfTheWeekKey] = daysOfTheWeek.map((d) => d.index).toList(); } + if (daysOfTheMonth != null) { + data[_daysOfTheMonthKey] = daysOfTheMonth; + } return data; } } From 5c5d4879d9cc034175229942470d2832016f895a Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 12 Aug 2019 13:58:58 +1000 Subject: [PATCH 11/49] add support for days of the month on android --- .../devicecalendar/CalendarDelegate.kt | 64 ++++++++--- .../devicecalendar/DeviceCalendarPlugin.kt | 5 + .../devicecalendar/models/RecurrenceRule.kt | 1 + .../presentation/pages/calendar_event.dart | 106 +++++++++--------- .../widgets/days_of_the_week_form_entry.dart | 62 ++++++++++ .../lib/src/common/day_of_week.dart | 2 +- .../lib/src/models/recurrence_rule.dart | 13 ++- 7 files changed, 175 insertions(+), 78 deletions(-) create mode 100644 device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 3b82117d..f8f1a42a 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -358,7 +358,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val calendar: java.util.Calendar = java.util.Calendar.getInstance() val currentTimeZone: TimeZone = calendar.timeZone values.put(Events.EVENT_TIMEZONE, currentTimeZone.displayName) - if(event.recurrenceRule != null) { + if (event.recurrenceRule != null) { val recurrenceRuleParams = buildRecurrenceRuleParams(event.recurrenceRule!!) values.put(Events.RRULE, recurrenceRuleParams) } @@ -501,12 +501,29 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { recurrenceRule.endDate = rfcRecurrenceRule.until.timestamp } - rfcRecurrenceRule.byDayPart?.forEach { weekdayNum -> - val dayOfWeek = DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == weekdayNum.weekday.ordinal } - if(dayOfWeek != null) { - recurrenceRule.daysOfTheWeek.add(dayOfWeek) + when (rfcRecurrenceRule.freq) { + Freq.WEEKLY, Freq.MONTHLY, Freq.YEARLY -> { + rfcRecurrenceRule.byDayPart?.forEach { weekdayNum -> + val dayOfWeek = DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == weekdayNum.weekday.ordinal } + if (dayOfWeek != null) { + recurrenceRule.daysOfTheWeek.add(dayOfWeek) + } + } + } + } + + val rfcRecurrenceRuleString = rfcRecurrenceRule.toString() + if (rfcRecurrenceRule.freq == Freq.MONTHLY) { + val byMonthDayIndex = rfcRecurrenceRuleString.indexOf("BYMONTHDAY", 0, true) + if (byMonthDayIndex >= 0) { + val endIndex = rfcRecurrenceRuleString.indexOf(";", byMonthDayIndex, true) + val byMonthString = if (endIndex == -1) rfcRecurrenceRuleString.substring(byMonthDayIndex) else rfcRecurrenceRuleString.substring(byMonthDayIndex, endIndex) + recurrenceRule.daysOfTheMonth.addAll(byMonthString.split("=")[1].split(",").map { + it.toInt() + }) } } + return recurrenceRule } @@ -610,38 +627,49 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return api <= android.os.Build.VERSION.SDK_INT } - private fun buildRecurrenceRuleParams(recurrenceRule: RecurrenceRule) : String { - val frequencyParam = when(recurrenceRule.recurrenceFrequency) { + private fun buildRecurrenceRuleParams(recurrenceRule: RecurrenceRule): String { + val frequencyParam = when (recurrenceRule.recurrenceFrequency) { RecurrenceFrequency.DAILY -> Freq.DAILY RecurrenceFrequency.WEEKLY -> Freq.WEEKLY RecurrenceFrequency.MONTHLY -> Freq.MONTHLY RecurrenceFrequency.YEARLY -> Freq.YEARLY } val rr = org.dmfs.rfc5545.recur.RecurrenceRule(frequencyParam) - if(recurrenceRule.interval != null) { + if (recurrenceRule.interval != null) { rr.interval = recurrenceRule.interval!! } - if (recurrenceRule.daysOfTheWeek.isNotEmpty()) { - rr.byDayPart = mutableListOf() - for (dayOfWeek in recurrenceRule.daysOfTheWeek) { - val dayOfWeekCode = Weekday.values().find { - it.ordinal == dayOfWeek.ordinal - } ?: continue - rr.byDayPart.add(org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, dayOfWeekCode)) + when (recurrenceRule.recurrenceFrequency) { + RecurrenceFrequency.WEEKLY, RecurrenceFrequency.MONTHLY, RecurrenceFrequency.YEARLY -> { + if (recurrenceRule.daysOfTheWeek.isNotEmpty()) { + rr.byDayPart = mutableListOf() + for (dayOfWeek in recurrenceRule.daysOfTheWeek) { + val dayOfWeekCode = Weekday.values().find { + it.ordinal == dayOfWeek.ordinal + } ?: continue + + rr.byDayPart.add(org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, dayOfWeekCode)) + } + } } } - if(recurrenceRule.totalOccurrences != null) { + if (recurrenceRule.totalOccurrences != null) { rr.count = recurrenceRule.totalOccurrences!! - } else if(recurrenceRule.endDate != null) { + } else if (recurrenceRule.endDate != null) { val calendar = java.util.Calendar.getInstance(); calendar.timeInMillis = recurrenceRule.endDate!! val dateFormat = SimpleDateFormat("yyyyMMdd") dateFormat.timeZone = calendar.timeZone rr.until = DateTime(calendar.timeZone, recurrenceRule.endDate!!) } - return rr.toString() + + var rrString = rr.toString() + if (frequencyParam == Freq.MONTHLY && recurrenceRule.daysOfTheMonth.isNotEmpty()) { + rrString += ";BYMONTHDAY=" + recurrenceRule.daysOfTheMonth.joinToString(separator = ",") + } + + return rrString } } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 867fc5c2..7a9de1ae 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -46,6 +46,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -130,6 +131,10 @@ class DeviceCalendarPlugin() : MethodCallHandler { } } + if(recurrenceRuleArgs.containsKey(DAYS_OF_THE_MONTH_ARGUMENT)) { + recurrenceRule.daysOfTheMonth.addAll(recurrenceRuleArgs[DAYS_OF_THE_MONTH_ARGUMENT] as List) + } + event.recurrenceRule = recurrenceRule } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt index 3c021837..f4bf01a2 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt @@ -9,4 +9,5 @@ class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) { var interval: Int? = null var endDate: Long? = null val daysOfTheWeek: MutableList = mutableListOf() + val daysOfTheMonth: MutableList = mutableListOf() } diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index f93bea71..408143c1 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -1,4 +1,5 @@ import 'package:device_calendar/device_calendar.dart'; +import 'package:device_calendar_example/presentation/widgets/days_of_the_week_form_entry.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -36,8 +37,9 @@ class _CalendarEventPageState extends State { bool _isRecurringEvent = false; RecurrenceRuleEndType _recurrenceRuleEndType; - List _daysOfTheWeek = List(); - + List _daysOfTheWeek = List(); + List _daysOfTheMonth = List(); + List _validDaysOfTheMonth = List(); int _totalOccurrences; int _interval; DateTime _recurrenceEndDate; @@ -47,6 +49,9 @@ class _CalendarEventPageState extends State { _CalendarEventPageState(this._calendar, this._event) { _deviceCalendarPlugin = DeviceCalendarPlugin(); + for (var i = 1; i <= 31; i++) { + _validDaysOfTheMonth.add(i); + } if (this._event == null) { _startDate = DateTime.now(); _endDate = DateTime.now().add(Duration(hours: 1)); @@ -69,6 +74,7 @@ class _CalendarEventPageState extends State { _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } _daysOfTheWeek = _event.recurrenceRule.daysOfTheWeek; + _daysOfTheMonth = _event.recurrenceRule.daysOfTheMonth; } } @@ -217,29 +223,41 @@ class _CalendarEventPageState extends State { }, ), ), - ListTile( - leading: Text('Days of week'), - ), - ...DayOfWeek.values.map( - (d) { - return CheckboxListTile( - title: _dayOfWeekToText(d), - value: _daysOfTheWeek?.any((dow) => dow == d) ?? - false, - onChanged: (selected) { - setState( - () { - if (selected) { - _daysOfTheWeek.add(d); - } else { - _daysOfTheWeek.remove(d); - } + if (_recurrenceFrequency == + RecurrenceFrequency.Weekly || + _recurrenceFrequency == + RecurrenceFrequency.Monthly || + _recurrenceFrequency == RecurrenceFrequency.Yearly) + DaysOfTheWeekFormEntry(daysOfTheWeek: _daysOfTheWeek), + if (_recurrenceFrequency == RecurrenceFrequency.Monthly) + Column( + children: [ + ListTile( + leading: Text('Days of the month'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _daysOfTheMonth.clear(); + _daysOfTheMonth.add(value); + }); }, - ); - }, - ); - }, - ), + value: _daysOfTheMonth.isEmpty + ? null + : _daysOfTheMonth[0], + items: _validDaysOfTheMonth + .map( + (m) => DropdownMenuItem( + value: m, + child: Text( + m.toString(), + ), + ), + ) + .toList(), + ), + ), + ], + ), ListTile( leading: Text('Event ends'), trailing: DropdownButton( @@ -311,17 +329,16 @@ class _CalendarEventPageState extends State { } else { form.save(); if (_isRecurringEvent) { - _event.recurrenceRule = RecurrenceRule( - _recurrenceFrequency, - interval: _interval, - totalOccurrences: _totalOccurrences, - endDate: _recurrenceRuleEndType == - RecurrenceRuleEndType.SpecifiedEndDate - ? _combineDateWithTime( - _recurrenceEndDate, _recurrenceEndTime) - : null, - daysOfTheWeek: _daysOfTheWeek, - ); + _event.recurrenceRule = RecurrenceRule(_recurrenceFrequency, + interval: _interval, + totalOccurrences: _totalOccurrences, + endDate: _recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate + ? _combineDateWithTime( + _recurrenceEndDate, _recurrenceEndTime) + : null, + daysOfTheWeek: _daysOfTheWeek, + daysOfTheMonth: _daysOfTheMonth); } var createEventResult = await _deviceCalendarPlugin.createOrUpdateEvent(_event); @@ -337,25 +354,6 @@ class _CalendarEventPageState extends State { ); } - Text _dayOfWeekToText(DayOfWeek dayOfWeek) { - switch (dayOfWeek) { - case DayOfWeek.Sunday: - return Text('Sunday'); - case DayOfWeek.Monday: - return Text('Monday'); - case DayOfWeek.Tuesday: - return Text('Tuesday'); - case DayOfWeek.Wednesday: - return Text('Wednesday'); - case DayOfWeek.Thursday: - return Text('Thursday'); - case DayOfWeek.Friday: - return Text('Friday'); - default: - return Text(''); - } - } - Text _recurrenceFrequencyToText(RecurrenceFrequency recurrenceFrequency) { switch (recurrenceFrequency) { case RecurrenceFrequency.Daily: diff --git a/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart b/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart new file mode 100644 index 00000000..b514d2fd --- /dev/null +++ b/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:device_calendar/device_calendar.dart'; + +class DaysOfTheWeekFormEntry extends StatefulWidget { + final List daysOfTheWeek; + const DaysOfTheWeekFormEntry({@required this.daysOfTheWeek, Key key}) + : super(key: key); + + @override + _DaysOfTheWeekFormEntryState createState() => _DaysOfTheWeekFormEntryState(); +} + +class _DaysOfTheWeekFormEntryState extends State { + Text _dayOfWeekToText(DayOfTheWeek dayOfWeek) { + switch (dayOfWeek) { + case DayOfTheWeek.Sunday: + return Text('Sunday'); + case DayOfTheWeek.Monday: + return Text('Monday'); + case DayOfTheWeek.Tuesday: + return Text('Tuesday'); + case DayOfTheWeek.Wednesday: + return Text('Wednesday'); + case DayOfTheWeek.Thursday: + return Text('Thursday'); + case DayOfTheWeek.Friday: + return Text('Friday'); + default: + return Text(''); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ListTile( + leading: Text('Days of the week'), + ), + ...DayOfTheWeek.values.map( + (d) { + return CheckboxListTile( + title: _dayOfWeekToText(d), + value: widget.daysOfTheWeek?.any((dow) => dow == d) ?? false, + onChanged: (selected) { + setState( + () { + if (selected) { + widget.daysOfTheWeek.add(d); + } else { + widget.daysOfTheWeek.remove(d); + } + }, + ); + }, + ); + }, + ), + ], + ); + } +} diff --git a/device_calendar/lib/src/common/day_of_week.dart b/device_calendar/lib/src/common/day_of_week.dart index 54538f25..8a97b10d 100644 --- a/device_calendar/lib/src/common/day_of_week.dart +++ b/device_calendar/lib/src/common/day_of_week.dart @@ -1 +1 @@ -enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday } +enum DayOfTheWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday } diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index b03a1db5..08928c29 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -16,7 +16,7 @@ class RecurrenceRule { RecurrenceFrequency recurrenceFrequency; /// The days of the week that this event occurs on. Only applicable to rules with a weekly, monthly or yearly frequency - List daysOfTheWeek; + List daysOfTheWeek; /// The days of the month that this event occurs on. Only applicable to recurrence rules with a monthly frequency List daysOfTheMonth; @@ -39,7 +39,9 @@ class RecurrenceRule { assert( (daysOfTheWeek?.isEmpty ?? true) || ((daysOfTheWeek?.isNotEmpty ?? false) && - (recurrenceFrequency == RecurrenceFrequency.Daily)), + (recurrenceFrequency == RecurrenceFrequency.Weekly || + recurrenceFrequency == RecurrenceFrequency.Monthly || + recurrenceFrequency == RecurrenceFrequency.Yearly)), 'Days of the week can only be specified for recurrence rules with a weekly, monthly or yearly frequency'), assert( (daysOfTheMonth?.isEmpty ?? true) || @@ -49,8 +51,9 @@ class RecurrenceRule { assert( (daysOfTheMonth?.isEmpty ?? true) || ((daysOfTheMonth?.isNotEmpty ?? false) && - daysOfTheMonth.any((d) => d >= 1 || d <= 31)), - 'Days of the month must be between 1 and 31 inclusive'); + (daysOfTheMonth.any((d) => d >= 1 || d <= 31) || + (daysOfTheMonth.any((d) => d >= -31 && d <= -1)))), + 'Days of the month must be between 1 and 31 or -1 and -31 inclusive'); RecurrenceRule.fromJson(Map json) { if (json == null) { @@ -74,7 +77,7 @@ class RecurrenceRule { if (daysOfTheWeekIndices != null && daysOfTheWeekIndices is! List) { daysOfTheWeek = daysOfTheWeekIndices .cast() - .map((index) => DayOfWeek.values[index]) + .map((index) => DayOfTheWeek.values[index]) .toList(); } List daysOfTheMonthObj = json[_daysOfTheMonthKey]; From 7484f323eca3334d6c03ea1f9e8b8e6ac982ae10 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:34:54 +1000 Subject: [PATCH 12/49] add ios support for months of the year --- .../presentation/pages/calendar_event.dart | 38 ++++++++++++++++++- .../Classes/SwiftDeviceCalendarPlugin.swift | 18 ++++++--- .../lib/src/models/recurrence_rule.dart | 28 ++++++++++---- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 408143c1..fbc13d87 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -40,6 +40,8 @@ class _CalendarEventPageState extends State { List _daysOfTheWeek = List(); List _daysOfTheMonth = List(); List _validDaysOfTheMonth = List(); + List _monthsOfTheYear = List(); + List _validMonthsOfTheYear = List(); int _totalOccurrences; int _interval; DateTime _recurrenceEndDate; @@ -52,6 +54,9 @@ class _CalendarEventPageState extends State { for (var i = 1; i <= 31; i++) { _validDaysOfTheMonth.add(i); } + for (var i = 1; i <= 12; i++) { + _validMonthsOfTheYear.add(i); + } if (this._event == null) { _startDate = DateTime.now(); _endDate = DateTime.now().add(Duration(hours: 1)); @@ -75,6 +80,7 @@ class _CalendarEventPageState extends State { } _daysOfTheWeek = _event.recurrenceRule.daysOfTheWeek; _daysOfTheMonth = _event.recurrenceRule.daysOfTheMonth; + _monthsOfTheYear = _event.recurrenceRule.monthsOfTheYear; } } @@ -245,6 +251,35 @@ class _CalendarEventPageState extends State { ? null : _daysOfTheMonth[0], items: _validDaysOfTheMonth + .map( + (d) => DropdownMenuItem( + value: d, + child: Text( + d.toString(), + ), + ), + ) + .toList(), + ), + ), + ], + ), + if (_recurrenceFrequency == RecurrenceFrequency.Yearly) + Column( + children: [ + ListTile( + leading: Text('Months of the year'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _monthsOfTheYear.clear(); + _monthsOfTheYear.add(value); + }); + }, + value: _monthsOfTheYear.isEmpty + ? null + : _monthsOfTheYear[0], + items: _validMonthsOfTheYear .map( (m) => DropdownMenuItem( value: m, @@ -338,7 +373,8 @@ class _CalendarEventPageState extends State { _recurrenceEndDate, _recurrenceEndTime) : null, daysOfTheWeek: _daysOfTheWeek, - daysOfTheMonth: _daysOfTheMonth); + daysOfTheMonth: _daysOfTheMonth, + monthsOfTheYear: _monthsOfTheYear); } var createEventResult = await _deviceCalendarPlugin.createOrUpdateEvent(_event); diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 1493d2c2..e6ed4306 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -34,6 +34,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let endDate: Int64? let daysOfTheWeek: [Int]? let daysOfTheMonth: [Int]? + let monthsOfTheYear: [Int]? } struct Attendee: Codable { @@ -76,6 +77,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let intervalArgument = "interval" let daysOfTheWeekArgument = "daysOfTheWeek" let daysOfTheMonthArgument = "daysOfTheMonth" + let monthsOfTheYearArgument = "monthsOfTheYear" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] @@ -244,7 +246,16 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { daysOfTheMonth!.append(dayOfTheMonth.intValue) } } - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth) + + var monthsOfTheYear: [Int]? = nil + if(ekRecurrenceRule.monthsOfTheYear != nil) { + monthsOfTheYear = [] + for monthOfTheYear in ekRecurrenceRule.monthsOfTheYear! { + monthsOfTheYear!.append(monthOfTheYear.intValue) + } + } + + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth, monthsOfTheYear: monthsOfTheYear) } return recurrenceRule } @@ -283,10 +294,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - let daysOfTheMonth = recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber] - //var daysOfTheMonthNum: [NSNumber]? = nil - - return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] + return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: recurrenceRuleArguments![monthsOfTheYearArgument] as? [NSNumber], weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] } private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index 08928c29..05522f20 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -21,19 +21,24 @@ class RecurrenceRule { /// The days of the month that this event occurs on. Only applicable to recurrence rules with a monthly frequency List daysOfTheMonth; + /// The months of the year that the event occurs on. Only applicable to recurrence rules with a yearly frequency + List monthsOfTheYear; + final String _totalOccurrencesKey = 'totalOccurrences'; final String _recurrenceFrequencyKey = 'recurrenceFrequency'; final String _intervalKey = 'interval'; final String _endDateKey = 'endDate'; final String _daysOfTheWeekKey = 'daysOfTheWeek'; final String _daysOfTheMonthKey = 'daysOfTheMonth'; + final String _monthsOfTheYearKey = 'monthsOfTheYear'; RecurrenceRule(this.recurrenceFrequency, {this.totalOccurrences, this.interval, this.endDate, this.daysOfTheWeek, - this.daysOfTheMonth}) + this.daysOfTheMonth, + this.monthsOfTheYear}) : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'), assert( @@ -46,14 +51,16 @@ class RecurrenceRule { assert( (daysOfTheMonth?.isEmpty ?? true) || ((daysOfTheMonth?.isNotEmpty ?? false) && - recurrenceFrequency == RecurrenceFrequency.Monthly), - 'Days of the month can only be specified for recurrence rules with a monthly frequency'), - assert( - (daysOfTheMonth?.isEmpty ?? true) || - ((daysOfTheMonth?.isNotEmpty ?? false) && + recurrenceFrequency == RecurrenceFrequency.Monthly && (daysOfTheMonth.any((d) => d >= 1 || d <= 31) || (daysOfTheMonth.any((d) => d >= -31 && d <= -1)))), - 'Days of the month must be between 1 and 31 or -1 and -31 inclusive'); + 'Days of the month must be between 1 and 31 or -1 and -31 inclusive and can only be specified for recurrence rules with a monthly frequency'), + assert( + (monthsOfTheYear?.isEmpty ?? true) || + ((monthsOfTheYear?.isNotEmpty ?? false) && + recurrenceFrequency == RecurrenceFrequency.Yearly && + monthsOfTheYear.any((d) => d >= 1 || d <= 12)), + 'Months of the year must be between 1 and 12 inclusive and can only be specified for recurrence rules with a yearly frequency'); RecurrenceRule.fromJson(Map json) { if (json == null) { @@ -84,6 +91,10 @@ class RecurrenceRule { if (daysOfTheMonthObj != null && daysOfTheMonthObj is! List) { daysOfTheMonth = daysOfTheMonthObj.cast().toList(); } + List monthsOfTheYearObj = json[_monthsOfTheYearKey]; + if (monthsOfTheYearObj != null && monthsOfTheYearObj is! List) { + monthsOfTheYear = monthsOfTheYearObj.cast().toList(); + } } Map toJson() { @@ -104,6 +115,9 @@ class RecurrenceRule { if (daysOfTheMonth != null) { data[_daysOfTheMonthKey] = daysOfTheMonth; } + if (monthsOfTheYear != null) { + data[_monthsOfTheYearKey] = monthsOfTheYear; + } return data; } } From 203fd2566b389274e890e612174bf6f2daa59282 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 12 Aug 2019 17:37:35 +1000 Subject: [PATCH 13/49] handle months of the year on android and refactor code --- .../devicecalendar/CalendarDelegate.kt | 64 +++++++---- .../devicecalendar/DayOfWeekSerializer.kt | 2 +- .../devicecalendar/DeviceCalendarPlugin.kt | 107 +++++++++++------- .../devicecalendar/models/RecurrenceRule.kt | 5 +- 4 files changed, 108 insertions(+), 70 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index f8f1a42a..3aae85e5 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -63,8 +63,11 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val CREATE_OR_UPDATE_EVENT_METHOD_CODE = RETRIEVE_CALENDAR_METHOD_CODE + 1 private val DELETE_EVENT_METHOD_CODE = CREATE_OR_UPDATE_EVENT_METHOD_CODE + 1 private val REQUEST_PERMISSIONS_METHOD_CODE = DELETE_EVENT_METHOD_CODE + 1 + private val PART_TEMPLATE = ";%s=" + private val BYMONTHDAY_PART = "BYMONTHDAY" + private val BYMONTH_PART = "BYMONTH" - private val _cachedParametersMap: MutableMap = mutableMapOf() + private val _cachedParametersMap: MutableMap = mutableMapOf() private var _activity: Activity? = null private var _context: Context? = null @@ -503,30 +506,35 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { when (rfcRecurrenceRule.freq) { Freq.WEEKLY, Freq.MONTHLY, Freq.YEARLY -> { - rfcRecurrenceRule.byDayPart?.forEach { weekdayNum -> - val dayOfWeek = DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == weekdayNum.weekday.ordinal } - if (dayOfWeek != null) { - recurrenceRule.daysOfTheWeek.add(dayOfWeek) - } - } + recurrenceRule.daysOfTheWeek = (rfcRecurrenceRule.byDayPart?.map { + DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == it.weekday.ordinal } + })?.filterNotNull()?.toMutableList() } } val rfcRecurrenceRuleString = rfcRecurrenceRule.toString() if (rfcRecurrenceRule.freq == Freq.MONTHLY) { - val byMonthDayIndex = rfcRecurrenceRuleString.indexOf("BYMONTHDAY", 0, true) - if (byMonthDayIndex >= 0) { - val endIndex = rfcRecurrenceRuleString.indexOf(";", byMonthDayIndex, true) - val byMonthString = if (endIndex == -1) rfcRecurrenceRuleString.substring(byMonthDayIndex) else rfcRecurrenceRuleString.substring(byMonthDayIndex, endIndex) - recurrenceRule.daysOfTheMonth.addAll(byMonthString.split("=")[1].split(",").map { - it.toInt() - }) - } + recurrenceRule.daysOfTheMonth = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTHDAY_PART) + } + + if (rfcRecurrenceRule.freq == Freq.YEARLY) { + recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTH_PART) } return recurrenceRule } + private fun convertCalendarPartToNumericValues(rfcRecurrenceRuleString: String, partName: String): MutableList? { + val partIndex = rfcRecurrenceRuleString.indexOf(partName) + if (partIndex == -1) { + return null + } + + return (rfcRecurrenceRuleString.substring(partIndex).split(";").firstOrNull()?.split("=")?.lastOrNull()?.split(",")?.map { + it.toInt() + })?.toMutableList() + } + private fun parseAttendee(cursor: Cursor?): Attendee? { if (cursor == null) { return null @@ -642,14 +650,15 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { when (recurrenceRule.recurrenceFrequency) { RecurrenceFrequency.WEEKLY, RecurrenceFrequency.MONTHLY, RecurrenceFrequency.YEARLY -> { - if (recurrenceRule.daysOfTheWeek.isNotEmpty()) { - rr.byDayPart = mutableListOf() - for (dayOfWeek in recurrenceRule.daysOfTheWeek) { - val dayOfWeekCode = Weekday.values().find { + if(recurrenceRule.daysOfTheWeek?.isEmpty() == true) { + rr.byDayPart = null + } else { + rr.byDayPart = recurrenceRule.daysOfTheWeek?.mapNotNull { dayOfWeek -> + Weekday.values().firstOrNull { it.ordinal == dayOfWeek.ordinal - } ?: continue - - rr.byDayPart.add(org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, dayOfWeekCode)) + } + }?.map { + org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, it) } } } @@ -665,11 +674,18 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { rr.until = DateTime(calendar.timeZone, recurrenceRule.endDate!!) } + var rrString = rr.toString() - if (frequencyParam == Freq.MONTHLY && recurrenceRule.daysOfTheMonth.isNotEmpty()) { - rrString += ";BYMONTHDAY=" + recurrenceRule.daysOfTheMonth.joinToString(separator = ",") + + if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY && recurrenceRule.daysOfTheMonth != null && recurrenceRule.daysOfTheMonth!!.isNotEmpty()) { + rrString += PART_TEMPLATE.format(BYMONTHDAY_PART) + recurrenceRule.daysOfTheMonth!!.joinToString(separator = ",") + } + + if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY && recurrenceRule.monthsOfTheYear != null && recurrenceRule.monthsOfTheYear!!.isNotEmpty()) { + rrString += PART_TEMPLATE.format(BYMONTH_PART) + recurrenceRule.monthsOfTheYear!!.joinToString(separator = ",") } + return rrString } } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt index 02358a06..b2bbce05 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DayOfWeekSerializer.kt @@ -9,6 +9,6 @@ class DayOfWeekSerializer: JsonSerializer { if(src != null) { return JsonPrimitive(src.ordinal) } - return JsonObject()//To change body of created functions use File | Settings | File Templates. + return JsonObject() } } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 7a9de1ae..4c282244 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -47,6 +47,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -73,6 +74,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { } } + override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { REQUEST_PERMISSIONS_METHOD -> { @@ -94,49 +96,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { } CREATE_OR_UPDATE_EVENT_METHOD -> { val calendarId = call.argument(CALENDAR_ID_ARGUMENT) - val eventId = call.argument(EVENT_ID_ARGUMENT) - val eventTitle = call.argument(EVENT_TITLE_ARGUMENT) - val eventDescription = call.argument(EVENT_DESCRIPTION_ARGUMENT) - val eventStart = call.argument(EVENT_START_DATE_ARGUMENT) - val eventEnd = call.argument(EVENT_END_DATE_ARGUMENT) - - val event = Event() - event.title = eventTitle - event.calendarId = calendarId - event.eventId = eventId - event.description = eventDescription - event.start = eventStart!! - event.end = eventEnd!! - - if(call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument>(RECURRENCE_RULE_ARGUMENT) != null) { - val recurrenceRuleArgs = call.argument>(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)) { - val daysOfWeek = recurrenceRuleArgs[DAYS_OF_THE_WEEK_ARGUMENT] as List - for (dayOfWeek in daysOfWeek) { - recurrenceRule.daysOfTheWeek.add(DayOfWeek.values()[dayOfWeek]) - } - } - - if(recurrenceRuleArgs.containsKey(DAYS_OF_THE_MONTH_ARGUMENT)) { - recurrenceRule.daysOfTheMonth.addAll(recurrenceRuleArgs[DAYS_OF_THE_MONTH_ARGUMENT] as List) - } - - event.recurrenceRule = recurrenceRule - } + val event = parseEventArgs(call, calendarId) _calendarDelegate.createOrUpdateEvent(calendarId!!, event, result) } @@ -151,4 +111,65 @@ class DeviceCalendarPlugin() : MethodCallHandler { } } } + + private fun parseEventArgs(call: MethodCall, calendarId: String?): Event { + val eventId = call.argument(EVENT_ID_ARGUMENT) + val eventTitle = call.argument(EVENT_TITLE_ARGUMENT) + val eventDescription = call.argument(EVENT_DESCRIPTION_ARGUMENT) + val eventStart = call.argument(EVENT_START_DATE_ARGUMENT) + val eventEnd = call.argument(EVENT_END_DATE_ARGUMENT) + + val event = Event() + event.title = eventTitle + event.calendarId = calendarId + event.eventId = eventId + event.description = eventDescription + event.start = eventStart!! + event.end = eventEnd!! + + if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument>(RECURRENCE_RULE_ARGUMENT) != null) { + val recurrenceRule = parseRecurrenceRuleArgs(call) + + event.recurrenceRule = recurrenceRule + } + return event + } + + private fun parseRecurrenceRuleArgs(call: MethodCall): RecurrenceRule { + val recurrenceRuleArgs = call.argument>(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()?.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() + } + return recurrenceRule + } + + private inline fun Any?.toListOf(): List? { + return (this as List<*>?)?.filterIsInstance()?.toList() + } + + private inline fun Any?.toMutableListOf(): MutableList? { + return this?.toListOf()?.toMutableList() + } } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt index f4bf01a2..59f8d918 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt @@ -8,6 +8,7 @@ class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) { var totalOccurrences: Int? = null var interval: Int? = null var endDate: Long? = null - val daysOfTheWeek: MutableList = mutableListOf() - val daysOfTheMonth: MutableList = mutableListOf() + var daysOfTheWeek: MutableList? = null + var daysOfTheMonth: MutableList? = null + var monthsOfTheYear: MutableList? = null } From 8acaca6141de144aff87334d89ca128ebda7d566 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 13:53:03 +1000 Subject: [PATCH 14/49] support weekOfTheYear and setPositions --- device_calendar/CHANGELOG.md | 3 + device_calendar/README.md | 2 + .../devicecalendar/CalendarDelegate.kt | 35 +- .../devicecalendar/DeviceCalendarPlugin.kt | 13 +- .../devicecalendar/models/RecurrenceRule.kt | 2 + .../presentation/pages/calendar_event.dart | 366 ++++++++++-------- .../widgets/days_of_the_week_form_entry.dart | 2 + .../Classes/SwiftDeviceCalendarPlugin.swift | 32 +- .../lib/src/common/day_of_week.dart | 10 +- .../lib/src/models/recurrence_rule.dart | 57 ++- device_calendar/pubspec.yaml | 2 +- 11 files changed, 334 insertions(+), 190 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index ecbe9f0b..62106a43 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0 TBD +* Support for more advanced recurrence rules + # 0.2.1+1 5th August 2019 * Fixing date in changelog for version 0.2.1 diff --git a/device_calendar/README.md b/device_calendar/README.md index 9c77b02c..56d45d0d 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -12,6 +12,8 @@ A cross platform plugin for modifying calendars on the user's device. * 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) +**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 + ## Android Integration The following will need to be added to the manifest file for your application to indicate permissions to modify calendars a needed diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 3aae85e5..4b674a6e 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -66,6 +66,8 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val PART_TEMPLATE = ";%s=" private val BYMONTHDAY_PART = "BYMONTHDAY" private val BYMONTH_PART = "BYMONTH" + private val BYWEEKNO_PART = "BYWEEKNO" + private val BYSETPOS_PART = "BYSETPOS" private val _cachedParametersMap: MutableMap = mutableMapOf() @@ -202,7 +204,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @SuppressLint("MissingPermission") fun retrieveCalendars(pendingChannelResult: MethodChannel.Result) { if (arePermissionsGranted()) { - val contentResolver: ContentResolver? = _context?.getContentResolver() val uri: Uri = CalendarContract.Calendars.CONTENT_URI val cursor: Cursor? = contentResolver?.query(uri, CALENDAR_PROJECTION, null, null, null) @@ -230,8 +231,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_CALENDARS_METHOD_CODE) requestPermissions(parameters) } - - return } fun retrieveCalendar(calendarId: String, pendingChannelResult: MethodChannel.Result, isInternalCall: Boolean = false): Calendar? { @@ -289,7 +288,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } val contentResolver: ContentResolver? = _context?.getContentResolver() - val eventsUriBuilder = CalendarContract.Instances.CONTENT_URI.buildUpon() ContentUris.appendId(eventsUriBuilder, startDate ?: Date(0).time) ContentUris.appendId(eventsUriBuilder, endDate ?: Date(Long.MAX_VALUE).time) @@ -519,8 +517,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (rfcRecurrenceRule.freq == Freq.YEARLY) { recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTH_PART) + recurrenceRule.weeksOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYWEEKNO_PART) } + recurrenceRule.setPositions = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYSETPOS_PART) return recurrenceRule } @@ -650,7 +650,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { when (recurrenceRule.recurrenceFrequency) { RecurrenceFrequency.WEEKLY, RecurrenceFrequency.MONTHLY, RecurrenceFrequency.YEARLY -> { - if(recurrenceRule.daysOfTheWeek?.isEmpty() == true) { + if (recurrenceRule.daysOfTheWeek?.isEmpty() == true) { rr.byDayPart = null } else { rr.byDayPart = recurrenceRule.daysOfTheWeek?.mapNotNull { dayOfWeek -> @@ -674,18 +674,31 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { rr.until = DateTime(calendar.timeZone, recurrenceRule.endDate!!) } - var rrString = rr.toString() - if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY && recurrenceRule.daysOfTheMonth != null && recurrenceRule.daysOfTheMonth!!.isNotEmpty()) { - rrString += PART_TEMPLATE.format(BYMONTHDAY_PART) + recurrenceRule.daysOfTheMonth!!.joinToString(separator = ",") + rrString = rrString.addPartWithValues(BYMONTHDAY_PART, recurrenceRule.daysOfTheMonth) } - if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY && recurrenceRule.monthsOfTheYear != null && recurrenceRule.monthsOfTheYear!!.isNotEmpty()) { - rrString += PART_TEMPLATE.format(BYMONTH_PART) + recurrenceRule.monthsOfTheYear!!.joinToString(separator = ",") - } + if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY) { + if (recurrenceRule.monthsOfTheYear != null && recurrenceRule.monthsOfTheYear!!.isNotEmpty()) { + rrString = rrString.addPartWithValues(BYMONTH_PART, recurrenceRule.monthsOfTheYear) + } + if (recurrenceRule.weeksOfTheYear != null && recurrenceRule.weeksOfTheYear!!.isNotEmpty()) { + rrString = rrString.addPartWithValues(BYWEEKNO_PART, recurrenceRule.weeksOfTheYear) + } + } + rrString = rrString.addPartWithValues(BYSETPOS_PART, recurrenceRule.setPositions) return rrString } + + + private fun String.addPartWithValues(partName: String, values: List?): String { + if (values != null && values.isNotEmpty()) { + return this + PART_TEMPLATE.format(partName) + values.joinToString(",") + } + + return this + } } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 4c282244..e1c2b86b 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -48,6 +48,8 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -129,9 +131,9 @@ class DeviceCalendarPlugin() : MethodCallHandler { if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument>(RECURRENCE_RULE_ARGUMENT) != null) { val recurrenceRule = parseRecurrenceRuleArgs(call) - event.recurrenceRule = recurrenceRule } + return event } @@ -162,6 +164,15 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt index 59f8d918..c98eb164 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/RecurrenceRule.kt @@ -11,4 +11,6 @@ class RecurrenceRule(val recurrenceFrequency : RecurrenceFrequency) { var daysOfTheWeek: MutableList? = null var daysOfTheMonth: MutableList? = null var monthsOfTheYear: MutableList? = null + var weeksOfTheYear: MutableList? = null + var setPositions: MutableList? = null } diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index fbc13d87..d0b21922 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -39,9 +39,12 @@ class _CalendarEventPageState extends State { List _daysOfTheWeek = List(); List _daysOfTheMonth = List(); - List _validDaysOfTheMonth = List(); List _monthsOfTheYear = List(); + List _weeksOfTheYear = List(); + List _setPositions = List(); + List _validDaysOfTheMonth = List(); List _validMonthsOfTheYear = List(); + List _validWeeksOfTheYear = List(); int _totalOccurrences; int _interval; DateTime _recurrenceEndDate; @@ -51,12 +54,21 @@ class _CalendarEventPageState extends State { _CalendarEventPageState(this._calendar, this._event) { _deviceCalendarPlugin = DeviceCalendarPlugin(); + for (var i = -31; i <= -1; i++) { + _validDaysOfTheMonth.add(i); + } for (var i = 1; i <= 31; i++) { _validDaysOfTheMonth.add(i); } for (var i = 1; i <= 12; i++) { _validMonthsOfTheYear.add(i); } + for (var i = -53; i <= -1; i++) { + _validWeeksOfTheYear.add(i); + } + for (var i = 1; i <= 53; i++) { + _validWeeksOfTheYear.add(i); + } if (this._event == null) { _startDate = DateTime.now(); _endDate = DateTime.now().add(Duration(hours: 1)); @@ -78,9 +90,15 @@ class _CalendarEventPageState extends State { _recurrenceEndDate = _event.recurrenceRule.endDate; _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } - _daysOfTheWeek = _event.recurrenceRule.daysOfTheWeek; - _daysOfTheMonth = _event.recurrenceRule.daysOfTheMonth; - _monthsOfTheYear = _event.recurrenceRule.monthsOfTheYear; + _daysOfTheWeek = + _event.recurrenceRule.daysOfTheWeek ?? new List(); + _daysOfTheMonth = + _event.recurrenceRule.daysOfTheMonth ?? new List(); + _monthsOfTheYear = + _event.recurrenceRule.monthsOfTheYear ?? new List(); + _weeksOfTheYear = + _event.recurrenceRule.weeksOfTheYear ?? new List(); + _setPositions = _event.recurrenceRule.setPositions ?? new List(); } } @@ -193,162 +211,185 @@ class _CalendarEventPageState extends State { }); }, ), - if (_isRecurringEvent) - Column( - children: [ - ListTile( - leading: Text('Frequency'), - trailing: DropdownButton( - onChanged: (selectedFrequency) { - setState(() { - _recurrenceFrequency = selectedFrequency; - }); - }, - value: _recurrenceFrequency, - items: RecurrenceFrequency.values - .map( - (f) => DropdownMenuItem( - value: f, - child: _recurrenceFrequencyToText(f), - ), - ) - .toList(), - ), + if (_isRecurringEvent) ...[ + ListTile( + leading: Text('Frequency'), + trailing: DropdownButton( + onChanged: (selectedFrequency) { + setState(() { + _recurrenceFrequency = selectedFrequency; + }); + }, + value: _recurrenceFrequency, + items: RecurrenceFrequency.values + .map((f) => DropdownMenuItem( + value: f, + child: _recurrenceFrequencyToText(f), + )) + .toList(), + ), + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _interval?.toString(), + decoration: const InputDecoration( + labelText: 'Interval between events', + hintText: '1'), + keyboardType: TextInputType.number, + validator: _validateInterval, + onSaved: (String value) { + _interval = int.tryParse(value); + }, + ), + ), + if (_recurrenceFrequency == RecurrenceFrequency.Weekly || + _recurrenceFrequency == RecurrenceFrequency.Monthly || + _recurrenceFrequency == RecurrenceFrequency.Yearly) + DaysOfTheWeekFormEntry(daysOfTheWeek: _daysOfTheWeek), + if (_recurrenceFrequency == + RecurrenceFrequency.Monthly) ...[ + ListTile( + leading: Text('Days of the month'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _daysOfTheMonth.clear(); + _daysOfTheMonth.add(value); + }); + }, + value: _daysOfTheMonth.isEmpty + ? null + : _daysOfTheMonth[0], + items: _validDaysOfTheMonth + .map((d) => DropdownMenuItem( + value: d, + child: Text( + d.toString(), + ), + )) + .toList(), ), - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _interval?.toString(), - decoration: const InputDecoration( - labelText: 'Interval between events', - hintText: '1'), - keyboardType: TextInputType.number, - validator: _validateInterval, - onSaved: (String value) { - _interval = int.tryParse(value); - }, - ), + ), + ], + if (_recurrenceFrequency == RecurrenceFrequency.Yearly) ...[ + ListTile( + leading: Text('Weeks of the year'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _weeksOfTheYear.clear(); + _weeksOfTheYear.add(value); + }); + }, + value: _weeksOfTheYear.isEmpty + ? null + : _weeksOfTheYear[0], + items: _validWeeksOfTheYear + .map((w) => DropdownMenuItem( + value: w, + child: Text( + w.toString(), + ), + )) + .toList(), ), - if (_recurrenceFrequency == - RecurrenceFrequency.Weekly || - _recurrenceFrequency == - RecurrenceFrequency.Monthly || - _recurrenceFrequency == RecurrenceFrequency.Yearly) - DaysOfTheWeekFormEntry(daysOfTheWeek: _daysOfTheWeek), - if (_recurrenceFrequency == RecurrenceFrequency.Monthly) - Column( - children: [ - ListTile( - leading: Text('Days of the month'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _daysOfTheMonth.clear(); - _daysOfTheMonth.add(value); - }); - }, - value: _daysOfTheMonth.isEmpty - ? null - : _daysOfTheMonth[0], - items: _validDaysOfTheMonth - .map( - (d) => DropdownMenuItem( - value: d, - child: Text( - d.toString(), - ), - ), - ) - .toList(), - ), - ), - ], - ), - if (_recurrenceFrequency == RecurrenceFrequency.Yearly) - Column( - children: [ - ListTile( - leading: Text('Months of the year'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _monthsOfTheYear.clear(); - _monthsOfTheYear.add(value); - }); - }, - value: _monthsOfTheYear.isEmpty - ? null - : _monthsOfTheYear[0], - items: _validMonthsOfTheYear - .map( - (m) => DropdownMenuItem( - value: m, - child: Text( - m.toString(), - ), - ), - ) - .toList(), - ), - ), - ], - ), - ListTile( - leading: Text('Event ends'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _recurrenceRuleEndType = value; - }); - }, - value: _recurrenceRuleEndType, - items: RecurrenceRuleEndType.values - .map( - (f) => DropdownMenuItem( - value: f, - child: _recurrenceRuleEndTypeToText(f), - ), - ) - .toList(), - ), + ), + ListTile( + leading: Text('Months of the year'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _monthsOfTheYear.clear(); + _monthsOfTheYear.add(value); + }); + }, + value: _monthsOfTheYear.isEmpty + ? null + : _monthsOfTheYear[0], + items: _validMonthsOfTheYear + .map((m) => DropdownMenuItem( + value: m, + child: Text( + m.toString(), + ), + )) + .toList(), ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.MaxOccurrences) - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _totalOccurrences?.toString(), - decoration: const InputDecoration( - labelText: 'Max occurrences', hintText: '1'), - keyboardType: TextInputType.number, - validator: _validateTotalOccurrences, - onSaved: (String value) { - _totalOccurrences = int.tryParse(value); - }, - ), - ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.SpecifiedEndDate) - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'Date', - selectedDate: _recurrenceEndDate, - selectedTime: _recurrenceEndTime, - selectDate: (DateTime date) { - setState(() { - _recurrenceEndDate = date; - }); - }, - selectTime: (TimeOfDay time) { - setState(() { - _recurrenceEndTime = time; - }); - }, - ), - ), - ], - ) + ), + ], + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _setPositions.isEmpty + ? null + : _setPositions.first.toString(), + decoration: + const InputDecoration(labelText: 'Set positions'), + keyboardType: TextInputType.number, + validator: _validateSetPositions, + onSaved: (String value) { + _setPositions.clear(); + if (value == null || value.isEmpty) { + return; + } + _setPositions.add(int.parse(value)); + }, + ), + ), + ListTile( + leading: Text('Event ends'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _recurrenceRuleEndType = value; + }); + }, + value: _recurrenceRuleEndType, + items: RecurrenceRuleEndType.values + .map((f) => DropdownMenuItem( + value: f, + child: _recurrenceRuleEndTypeToText(f), + )) + .toList(), + ), + ), + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.MaxOccurrences) + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _totalOccurrences?.toString(), + decoration: const InputDecoration( + labelText: 'Max occurrences', hintText: '1'), + keyboardType: TextInputType.number, + validator: _validateTotalOccurrences, + onSaved: (String value) { + _totalOccurrences = int.tryParse(value); + }, + ), + ), + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate) + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'Date', + selectedDate: _recurrenceEndDate, + selectedTime: _recurrenceEndTime, + selectDate: (DateTime date) { + setState(() { + _recurrenceEndDate = date; + }); + }, + selectTime: (TimeOfDay time) { + setState(() { + _recurrenceEndTime = time; + }); + }, + ), + ), + ], ], ), ) @@ -374,7 +415,9 @@ class _CalendarEventPageState extends State { : null, daysOfTheWeek: _daysOfTheWeek, daysOfTheMonth: _daysOfTheMonth, - monthsOfTheYear: _monthsOfTheYear); + monthsOfTheYear: _monthsOfTheYear, + weeksOfTheYear: _weeksOfTheYear, + setPositions: _setPositions); } var createEventResult = await _deviceCalendarPlugin.createOrUpdateEvent(_event); @@ -423,6 +466,13 @@ class _CalendarEventPageState extends State { return null; } + String _validateSetPositions(String value) { + if (value.isNotEmpty && int.tryParse(value) == null) { + return 'Set position needs to be a valid number'; + } + return null; + } + String _validateInterval(String value) { if (value.isNotEmpty && int.tryParse(value) == null) { return 'Interval needs to be a valid number'; diff --git a/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart b/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart index b514d2fd..e53f3790 100644 --- a/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart +++ b/device_calendar/example/lib/presentation/widgets/days_of_the_week_form_entry.dart @@ -25,6 +25,8 @@ class _DaysOfTheWeekFormEntryState extends State { return Text('Thursday'); case DayOfTheWeek.Friday: return Text('Friday'); + case DayOfTheWeek.Saturday: + return Text('Saturday'); default: return Text(''); } diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index e6ed4306..92c8e8e7 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -35,6 +35,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let daysOfTheWeek: [Int]? let daysOfTheMonth: [Int]? let monthsOfTheYear: [Int]? + let weeksOfTheYear: [Int]? + let setPositions: [Int]? } struct Attendee: Codable { @@ -78,6 +80,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let daysOfTheWeekArgument = "daysOfTheWeek" let daysOfTheMonthArgument = "daysOfTheMonth" let monthsOfTheYearArgument = "monthsOfTheYear" + let weeksOfTheYearArgument = "weeksOfTheYear" + let setPositionsArgument = "setPositions" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] @@ -232,7 +236,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } var daysOfTheWeek: [Int]? = nil - if(ekRecurrenceRule.daysOfTheWeek != nil) { + if(ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty) { daysOfTheWeek = [] for dayOfTheWeek in ekRecurrenceRule.daysOfTheWeek! { daysOfTheWeek!.append(dayOfTheWeek.dayOfTheWeek.rawValue - 1) @@ -240,7 +244,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } var daysOfTheMonth: [Int]? = nil - if(ekRecurrenceRule.daysOfTheMonth != nil) { + if(ekRecurrenceRule.daysOfTheMonth != nil && !ekRecurrenceRule.daysOfTheMonth!.isEmpty) { daysOfTheMonth = [] for dayOfTheMonth in ekRecurrenceRule.daysOfTheMonth! { daysOfTheMonth!.append(dayOfTheMonth.intValue) @@ -248,14 +252,30 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } var monthsOfTheYear: [Int]? = nil - if(ekRecurrenceRule.monthsOfTheYear != nil) { + if(ekRecurrenceRule.monthsOfTheYear != nil && !ekRecurrenceRule.monthsOfTheYear!.isEmpty) { monthsOfTheYear = [] for monthOfTheYear in ekRecurrenceRule.monthsOfTheYear! { monthsOfTheYear!.append(monthOfTheYear.intValue) } } - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth, monthsOfTheYear: monthsOfTheYear) + var weeksOfTheYear: [Int]? = nil + if(ekRecurrenceRule.weeksOfTheYear != nil && !ekRecurrenceRule.weeksOfTheYear!.isEmpty) { + weeksOfTheYear = [] + for weekOfTheYear in ekRecurrenceRule.weeksOfTheYear! { + weeksOfTheYear!.append(weekOfTheYear.intValue) + } + } + + var setPositions: [Int]? = nil + if(ekRecurrenceRule.setPositions != nil && !ekRecurrenceRule.setPositions!.isEmpty) { + setPositions = [] + for setPosition in ekRecurrenceRule.setPositions! { + setPositions!.append(setPosition.intValue) + } + } + + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth, monthsOfTheYear: monthsOfTheYear, weeksOfTheYear: weeksOfTheYear, setPositions: setPositions) } return recurrenceRule } @@ -287,14 +307,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let daysOfTheWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? [Int] var daysOfTheWeek : [EKRecurrenceDayOfWeek]? = nil - if(daysOfTheWeekIndices != nil) { + if(daysOfTheWeekIndices != nil && !daysOfTheWeekIndices!.isEmpty) { daysOfTheWeek = [] for dayOfWeekIndex in daysOfTheWeekIndices! { daysOfTheWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) } } - return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: recurrenceRuleArguments![monthsOfTheYearArgument] as? [NSNumber], weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: recurrenceEnd)] + return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: recurrenceRuleArguments![monthsOfTheYearArgument] as? [NSNumber], weeksOfTheYear: recurrenceRuleArguments![weeksOfTheYearArgument] as? [NSNumber], daysOfTheYear: nil, setPositions: recurrenceRuleArguments![setPositionsArgument] as? [NSNumber], end: recurrenceEnd)] } private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { diff --git a/device_calendar/lib/src/common/day_of_week.dart b/device_calendar/lib/src/common/day_of_week.dart index 8a97b10d..0a3156af 100644 --- a/device_calendar/lib/src/common/day_of_week.dart +++ b/device_calendar/lib/src/common/day_of_week.dart @@ -1 +1,9 @@ -enum DayOfTheWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday } +enum DayOfTheWeek { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday +} diff --git a/device_calendar/lib/src/models/recurrence_rule.dart b/device_calendar/lib/src/models/recurrence_rule.dart index 05522f20..d7b30184 100644 --- a/device_calendar/lib/src/models/recurrence_rule.dart +++ b/device_calendar/lib/src/models/recurrence_rule.dart @@ -24,6 +24,12 @@ class RecurrenceRule { /// The months of the year that the event occurs on. Only applicable to recurrence rules with a yearly frequency List monthsOfTheYear; + /// The weeks of the year that the event occurs on. Only applicable to recurrence rules with a yearly frequency + List weeksOfTheYear; + + /// Filters which recurrences to include in the recurrence rule’s frequency. Only applicable when either [daysOfTheWeek], [daysOfTheMonth], [weeksOfTheYear] or [monthsOfTheYear] are specified + List setPositions; + final String _totalOccurrencesKey = 'totalOccurrences'; final String _recurrenceFrequencyKey = 'recurrenceFrequency'; final String _intervalKey = 'interval'; @@ -31,6 +37,8 @@ class RecurrenceRule { final String _daysOfTheWeekKey = 'daysOfTheWeek'; final String _daysOfTheMonthKey = 'daysOfTheMonth'; final String _monthsOfTheYearKey = 'monthsOfTheYear'; + final String _weeksOfTheYearKey = 'weeksOfTheYear'; + final String _setPositionsKey = 'setPositions'; RecurrenceRule(this.recurrenceFrequency, {this.totalOccurrences, @@ -38,7 +46,9 @@ class RecurrenceRule { this.endDate, this.daysOfTheWeek, this.daysOfTheMonth, - this.monthsOfTheYear}) + this.monthsOfTheYear, + this.weeksOfTheYear, + this.setPositions}) : assert(!(endDate != null && totalOccurrences != null), 'Cannot specify both an end date and total occurrences for a recurring event'), assert( @@ -52,15 +62,29 @@ class RecurrenceRule { (daysOfTheMonth?.isEmpty ?? true) || ((daysOfTheMonth?.isNotEmpty ?? false) && recurrenceFrequency == RecurrenceFrequency.Monthly && - (daysOfTheMonth.any((d) => d >= 1 || d <= 31) || - (daysOfTheMonth.any((d) => d >= -31 && d <= -1)))), + (daysOfTheMonth.every( + (d) => (d >= 1 && d <= 31) || (d >= -31 && d <= -1)))), 'Days of the month must be between 1 and 31 or -1 and -31 inclusive and can only be specified for recurrence rules with a monthly frequency'), assert( (monthsOfTheYear?.isEmpty ?? true) || ((monthsOfTheYear?.isNotEmpty ?? false) && recurrenceFrequency == RecurrenceFrequency.Yearly && - monthsOfTheYear.any((d) => d >= 1 || d <= 12)), - 'Months of the year must be between 1 and 12 inclusive and can only be specified for recurrence rules with a yearly frequency'); + monthsOfTheYear.every((d) => d >= 1 && d <= 12)), + 'Months of the year must be between 1 and 12 inclusive and can only be specified for recurrence rules with a yearly frequency'), + assert( + (weeksOfTheYear?.isEmpty ?? true) || + ((weeksOfTheYear?.isNotEmpty ?? false) && + recurrenceFrequency == RecurrenceFrequency.Yearly && + weeksOfTheYear.every( + (d) => (d >= 1 && d <= 53) || (d >= -53 || d <= -1))), + 'Weeks of the year must be between 1 and 53 or between -1 and -53 inclusive, and can only be specified for recurrence rules with a yearly frequency'), + assert( + (setPositions.isEmpty ?? true) || + ((setPositions?.isNotEmpty ?? false) && + ((daysOfTheMonth?.isNotEmpty ?? false) || + (monthsOfTheYear?.isNotEmpty ?? false) || + (weeksOfTheYear?.isNotEmpty ?? false))), + 'Set positions can only be specified along with either days of the week, days of the month, weeks of the year or months of the year'); RecurrenceRule.fromJson(Map json) { if (json == null) { @@ -87,14 +111,17 @@ class RecurrenceRule { .map((index) => DayOfTheWeek.values[index]) .toList(); } - List daysOfTheMonthObj = json[_daysOfTheMonthKey]; - if (daysOfTheMonthObj != null && daysOfTheMonthObj is! List) { - daysOfTheMonth = daysOfTheMonthObj.cast().toList(); - } - List monthsOfTheYearObj = json[_monthsOfTheYearKey]; - if (monthsOfTheYearObj != null && monthsOfTheYearObj is! List) { - monthsOfTheYear = monthsOfTheYearObj.cast().toList(); + daysOfTheMonth = convertToIntList(json[_daysOfTheMonthKey]); + monthsOfTheYear = convertToIntList(json[_monthsOfTheYearKey]); + weeksOfTheYear = convertToIntList(json[_weeksOfTheYearKey]); + setPositions = convertToIntList(json[_setPositionsKey]); + } + + List convertToIntList(List objList) { + if (objList != null && objList is! List) { + return objList.cast().toList(); } + return null; } Map toJson() { @@ -118,6 +145,12 @@ class RecurrenceRule { if (monthsOfTheYear != null) { data[_monthsOfTheYearKey] = monthsOfTheYear; } + if (weeksOfTheYear != null) { + data[_weeksOfTheYearKey] = weeksOfTheYear; + } + if (setPositions != null) { + data[_setPositionsKey] = setPositions; + } return data; } } diff --git a/device_calendar/pubspec.yaml b/device_calendar/pubspec.yaml index 92ac7ec3..eab1854b 100644 --- a/device_calendar/pubspec.yaml +++ b/device_calendar/pubspec.yaml @@ -1,6 +1,6 @@ name: device_calendar description: A cross platform plugin for modifying calendars on the user's device. -version: 0.2.1+1 +version: 1.0.0 author: Built to Roam homepage: https://github.com/builttoroam/flutter_plugins/tree/develop/device_calendar From b4673e898685dea96f6655118527882c164fef33 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 14:27:06 +1000 Subject: [PATCH 15/49] add notes around proguard --- device_calendar/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/device_calendar/README.md b/device_calendar/README.md index 56d45d0d..13d3c996 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -23,6 +23,12 @@ The following will need to be added to the manifest file for your application to ``` +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 From 3d67c4918320a6eada0c300ba5030978583d58b3 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 14:42:20 +1000 Subject: [PATCH 16/49] add saturday to android DayOfWeek enum --- .../kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt index 07352517..be4e809a 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/DayOfWeek.kt @@ -1,5 +1,5 @@ package com.builttoroam.devicecalendar.common enum class DayOfWeek { - SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY + SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } \ No newline at end of file From fe1ac085dc73eb52c8621727fe84e87f56a4a7c4 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 15:06:38 +1000 Subject: [PATCH 17/49] address code analysis problem --- device_calendar/lib/src/device_calendar.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 21a9a37e..78be42d7 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -15,15 +15,14 @@ class DeviceCalendarPlugin { static const MethodChannel channel = const MethodChannel('plugins.builttoroam.com/device_calendar'); - static final DeviceCalendarPlugin _instance = - DeviceCalendarPlugin._createInstance(); + static final DeviceCalendarPlugin _instance = DeviceCalendarPlugin.private(); factory DeviceCalendarPlugin() { return _instance; } @visibleForTesting - DeviceCalendarPlugin._createInstance(); + DeviceCalendarPlugin.private(); /// Requests permissions to modify the calendars on the device /// From 433656c5fa4e2208b9d4d2b38090ea291d65e020 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 15:25:53 +1000 Subject: [PATCH 18/49] rename constants --- .../devicecalendar/CalendarDelegate.kt | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 4b674a6e..a08edc9e 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -57,17 +57,17 @@ import java.util.* class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { - private val RETRIEVE_CALENDARS_METHOD_CODE = 0 - private val RETRIEVE_EVENTS_METHOD_CODE = RETRIEVE_CALENDARS_METHOD_CODE + 1 - private val RETRIEVE_CALENDAR_METHOD_CODE = RETRIEVE_EVENTS_METHOD_CODE + 1 - private val CREATE_OR_UPDATE_EVENT_METHOD_CODE = RETRIEVE_CALENDAR_METHOD_CODE + 1 - private val DELETE_EVENT_METHOD_CODE = CREATE_OR_UPDATE_EVENT_METHOD_CODE + 1 - private val REQUEST_PERMISSIONS_METHOD_CODE = DELETE_EVENT_METHOD_CODE + 1 - private val PART_TEMPLATE = ";%s=" - private val BYMONTHDAY_PART = "BYMONTHDAY" - private val BYMONTH_PART = "BYMONTH" - private val BYWEEKNO_PART = "BYWEEKNO" - private val BYSETPOS_PART = "BYSETPOS" + private val retrieveCalendarsMethodCode = 0 + private val retrieveEventsMethodCode = retrieveCalendarsMethodCode + 1 + private val retrieveCalendarMethodCode = retrieveEventsMethodCode + 1 + private val createOrUpdateMethodCode = retrieveCalendarMethodCode + 1 + private val deleteEventMethodCode = createOrUpdateMethodCode + 1 + private val requestPermissionsMethodCode = deleteEventMethodCode + 1 + private val partTemplate = ";%s=" + private val byMonthDayPart = "BYMONTHDAY" + private val byMonthPart = "BYMONTH" + private val byWeekNoPart = "BYWEEKNO" + private val bySetPosPart = "BYSETPOS" private val _cachedParametersMap: MutableMap = mutableMapOf() @@ -100,22 +100,22 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } when (cachedValues.calendarDelegateMethodCode) { - RETRIEVE_CALENDARS_METHOD_CODE -> { + retrieveCalendarsMethodCode -> { return handleRetrieveCalendarsRequest(permissionGranted, cachedValues, requestCode) } - RETRIEVE_EVENTS_METHOD_CODE -> { + retrieveEventsMethodCode -> { return handleRetrieveEventsRequest(permissionGranted, cachedValues, requestCode) } - RETRIEVE_CALENDAR_METHOD_CODE -> { + retrieveCalendarMethodCode -> { return handleRetrieveCalendarRequest(permissionGranted, cachedValues, requestCode) } - CREATE_OR_UPDATE_EVENT_METHOD_CODE -> { + createOrUpdateMethodCode -> { return handleCreateOrUpdateEventRequest(permissionGranted, cachedValues, requestCode) } - DELETE_EVENT_METHOD_CODE -> { + deleteEventMethodCode -> { return handleDeleteEventRequest(permissionGranted, cachedValues, requestCode) } - REQUEST_PERMISSIONS_METHOD_CODE -> { + requestPermissionsMethodCode -> { return handlePermissionsRequest(permissionGranted, cachedValues) } } @@ -192,7 +192,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (arePermissionsGranted()) { finishWithSuccess(true, pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, REQUEST_PERMISSIONS_METHOD_CODE) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, requestPermissionsMethodCode) requestPermissions(parameters) } } @@ -228,7 +228,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { cursor?.close() } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_CALENDARS_METHOD_CODE) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveCalendarsMethodCode) requestPermissions(parameters) } } @@ -266,7 +266,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { cursor?.close() } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_CALENDAR_METHOD_CODE, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveCalendarMethodCode, calendarId) requestPermissions(parameters) } @@ -330,7 +330,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(_gson?.toJson(events), pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_EVENTS_METHOD_CODE, calendarId, startDate, endDate) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveEventsMethodCode, calendarId, startDate, endDate) requestPermissions(parameters) } @@ -379,7 +379,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { println(e.message) } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, CREATE_OR_UPDATE_EVENT_METHOD_CODE, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, createOrUpdateMethodCode, calendarId) parameters.event = event requestPermissions(parameters) } @@ -411,7 +411,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, DELETE_EVENT_METHOD_CODE, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, deleteEventMethodCode, calendarId) parameters.eventId = eventId requestPermissions(parameters) } @@ -512,15 +512,15 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val rfcRecurrenceRuleString = rfcRecurrenceRule.toString() if (rfcRecurrenceRule.freq == Freq.MONTHLY) { - recurrenceRule.daysOfTheMonth = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTHDAY_PART) + recurrenceRule.daysOfTheMonth = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byMonthDayPart) } if (rfcRecurrenceRule.freq == Freq.YEARLY) { - recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTH_PART) - recurrenceRule.weeksOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYWEEKNO_PART) + recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byMonthPart) + recurrenceRule.weeksOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byWeekNoPart) } - recurrenceRule.setPositions = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYSETPOS_PART) + recurrenceRule.setPositions = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, bySetPosPart) return recurrenceRule } @@ -676,27 +676,27 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { var rrString = rr.toString() if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY && recurrenceRule.daysOfTheMonth != null && recurrenceRule.daysOfTheMonth!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(BYMONTHDAY_PART, recurrenceRule.daysOfTheMonth) + rrString = rrString.addPartWithValues(byMonthDayPart, recurrenceRule.daysOfTheMonth) } if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY) { if (recurrenceRule.monthsOfTheYear != null && recurrenceRule.monthsOfTheYear!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(BYMONTH_PART, recurrenceRule.monthsOfTheYear) + rrString = rrString.addPartWithValues(byMonthPart, recurrenceRule.monthsOfTheYear) } if (recurrenceRule.weeksOfTheYear != null && recurrenceRule.weeksOfTheYear!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(BYWEEKNO_PART, recurrenceRule.weeksOfTheYear) + rrString = rrString.addPartWithValues(byWeekNoPart, recurrenceRule.weeksOfTheYear) } } - rrString = rrString.addPartWithValues(BYSETPOS_PART, recurrenceRule.setPositions) + rrString = rrString.addPartWithValues(bySetPosPart, recurrenceRule.setPositions) return rrString } private fun String.addPartWithValues(partName: String, values: List?): String { if (values != null && values.isNotEmpty()) { - return this + PART_TEMPLATE.format(partName) + values.joinToString(",") + return this + partTemplate.format(partName) + values.joinToString(",") } return this From b24093e8c00ab991e30a13ebafe433cb6cd39a62 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 15:31:16 +1000 Subject: [PATCH 19/49] rename constants in DeviceCalendarPlugin.kt file --- .../devicecalendar/DeviceCalendarPlugin.kt | 124 +++++++++--------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index e1c2b86b..b0377ac3 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -23,33 +23,33 @@ class DeviceCalendarPlugin() : MethodCallHandler { private lateinit var _calendarDelegate: CalendarDelegate // Methods - private val REQUEST_PERMISSIONS_METHOD = "requestPermissions" - private val HAS_PERMISSIONS_METHOD = "hasPermissions" - private val RETRIEVE_CALENDARS_METHOD = "retrieveCalendars" - private val RETRIEVE_EVENTS_METHOD = "retrieveEvents" - private val DELETE_EVENT_METHOD = "deleteEvent" - private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent" + private val requestPermissionsMethod = "requestPermissions" + private val hasPermissionsMethod = "hasPermissions" + private val retrieveCalendarsMethod = "retrieveCalendars" + private val retrieveEventsMethod = "retrieveEvents" + private val deleteEventMethod = "deleteEvent" + private val createOrUpdateEventMethod = "createOrUpdateEvent" // 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 EVENT_ID_ARGUMENT = "eventId" - private val EVENT_TITLE_ARGUMENT = "eventTitle" - private val EVENT_DESCRIPTION_ARGUMENT = "eventDescription" - private val EVENT_START_DATE_ARGUMENT = "eventStartDate" - private val EVENT_END_DATE_ARGUMENT = "eventEndDate" - 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 calendarIdArgument = "calendarId" + private val calendarEventsStartDateArgument = "startDate" + private val calendarEventsEndDateArgument = "endDate" + private val calendarEventIdsArgument = "eventIds" + private val eventIdArgument = "eventId" + private val eventTitleArgument = "eventTitle" + private val eventDescriptionArgument = "eventDescription" + private val eventStartDateArgument = "eventStartDate" + private val eventEndDateArgument = "eventEndDate" + private val recurrenceRuleArgument = "recurrenceRule" + private val recurrenceFrequencyArgument = "recurrenceFrequency" + private val totalOccurrencesArgument = "totalOccurrences" + private val intervalArgument = "interval" + private val endDateArgument = "endDate" + private val daysOfTheWeekArgument = "daysOfTheWeek" + private val daysOfTheMonthArgument = "daysOfTheMonth" + private val monthsOfTheYearArgument = "monthsOfTheYear" + private val weeksOfTheYearArgument = "weeksOfTheYear" + private val setPositionsArgument = "setPositions" private constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { @@ -79,32 +79,32 @@ class DeviceCalendarPlugin() : MethodCallHandler { override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { - REQUEST_PERMISSIONS_METHOD -> { + requestPermissionsMethod -> { _calendarDelegate.requestPermissions(result) } - HAS_PERMISSIONS_METHOD -> { + hasPermissionsMethod -> { _calendarDelegate.hasPermissions(result) } - RETRIEVE_CALENDARS_METHOD -> { + retrieveCalendarsMethod -> { _calendarDelegate.retrieveCalendars(result) } - RETRIEVE_EVENTS_METHOD -> { - val calendarId = call.argument(CALENDAR_ID_ARGUMENT) - val startDate = call.argument(CALENDAR_EVENTS_START_DATE_ARGUMENT) - val endDate = call.argument(CALENDAR_EVENTS_END_DATE_ARGUMENT) - val eventIds = call.argument>(CALENDAR_EVENTS_IDS_ARGUMENT) ?: listOf() + retrieveEventsMethod -> { + val calendarId = call.argument(calendarIdArgument) + val startDate = call.argument(calendarEventsStartDateArgument) + val endDate = call.argument(calendarEventsEndDateArgument) + val eventIds = call.argument>(calendarEventIdsArgument) ?: listOf() _calendarDelegate.retrieveEvents(calendarId!!, startDate, endDate, eventIds, result) } - CREATE_OR_UPDATE_EVENT_METHOD -> { - val calendarId = call.argument(CALENDAR_ID_ARGUMENT) + createOrUpdateEventMethod -> { + val calendarId = call.argument(calendarIdArgument) val event = parseEventArgs(call, calendarId) _calendarDelegate.createOrUpdateEvent(calendarId!!, event, result) } - DELETE_EVENT_METHOD -> { - val calendarId = call.argument(CALENDAR_ID_ARGUMENT) - val eventId = call.argument(EVENT_ID_ARGUMENT) + deleteEventMethod -> { + val calendarId = call.argument(calendarIdArgument) + val eventId = call.argument(eventIdArgument) _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result) } @@ -115,11 +115,11 @@ class DeviceCalendarPlugin() : MethodCallHandler { } private fun parseEventArgs(call: MethodCall, calendarId: String?): Event { - val eventId = call.argument(EVENT_ID_ARGUMENT) - val eventTitle = call.argument(EVENT_TITLE_ARGUMENT) - val eventDescription = call.argument(EVENT_DESCRIPTION_ARGUMENT) - val eventStart = call.argument(EVENT_START_DATE_ARGUMENT) - val eventEnd = call.argument(EVENT_END_DATE_ARGUMENT) + val eventId = call.argument(eventIdArgument) + val eventTitle = call.argument(eventTitleArgument) + val eventDescription = call.argument(eventDescriptionArgument) + val eventStart = call.argument(eventStartDateArgument) + val eventEnd = call.argument(eventEndDateArgument) val event = Event() event.title = eventTitle @@ -129,7 +129,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { event.start = eventStart!! event.end = eventEnd!! - if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument>(RECURRENCE_RULE_ARGUMENT) != null) { + if (call.hasArgument(recurrenceRuleArgument) && call.argument>(recurrenceRuleArgument) != null) { val recurrenceRule = parseRecurrenceRuleArgs(call) event.recurrenceRule = recurrenceRule } @@ -138,39 +138,39 @@ class DeviceCalendarPlugin() : MethodCallHandler { } private fun parseRecurrenceRuleArgs(call: MethodCall): RecurrenceRule { - val recurrenceRuleArgs = call.argument>(RECURRENCE_RULE_ARGUMENT)!! - val recurrenceFrequencyIndex = recurrenceRuleArgs[RECURRENCE_FREQUENCY_ARGUMENT] as Int + val recurrenceRuleArgs = call.argument>(recurrenceRuleArgument)!! + val recurrenceFrequencyIndex = recurrenceRuleArgs[recurrenceFrequencyArgument] 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(totalOccurrencesArgument)) { + recurrenceRule.totalOccurrences = recurrenceRuleArgs[totalOccurrencesArgument] as Int } - if (recurrenceRuleArgs.containsKey(INTERVAL_ARGUMENT)) { - recurrenceRule.interval = recurrenceRuleArgs[INTERVAL_ARGUMENT] as Int + if (recurrenceRuleArgs.containsKey(intervalArgument)) { + recurrenceRule.interval = recurrenceRuleArgs[intervalArgument] as Int } - if (recurrenceRuleArgs.containsKey(END_DATE_ARGUMENT)) { - recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long + if (recurrenceRuleArgs.containsKey(endDateArgument)) { + recurrenceRule.endDate = recurrenceRuleArgs[endDateArgument] as Long } - if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_WEEK_ARGUMENT)) { - recurrenceRule.daysOfTheWeek = recurrenceRuleArgs[DAYS_OF_THE_WEEK_ARGUMENT].toListOf()?.map { DayOfWeek.values()[it] }?.toMutableList() + if (recurrenceRuleArgs.containsKey(daysOfTheWeekArgument)) { + recurrenceRule.daysOfTheWeek = recurrenceRuleArgs[daysOfTheWeekArgument].toListOf()?.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(daysOfTheMonthArgument)) { + recurrenceRule.daysOfTheMonth = recurrenceRuleArgs[daysOfTheMonthArgument].toMutableListOf() } - if (recurrenceRuleArgs.containsKey(MONTHS_OF_THE_YEAR_ARGUMENT)) { - recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[MONTHS_OF_THE_YEAR_ARGUMENT].toMutableListOf() + if (recurrenceRuleArgs.containsKey(monthsOfTheYearArgument)) { + recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[monthsOfTheYearArgument].toMutableListOf() } - if(recurrenceRuleArgs.containsKey(WEEKS_OF_THE_YEAR_ARGUMENT)) { - recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[WEEKS_OF_THE_YEAR_ARGUMENT].toMutableListOf() + if(recurrenceRuleArgs.containsKey(weeksOfTheYearArgument)) { + recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[weeksOfTheYearArgument].toMutableListOf() } - if(recurrenceRuleArgs.containsKey(SET_POSITIONS_ARGUMENT)) { - recurrenceRule.setPositions = recurrenceRuleArgs[SET_POSITIONS_ARGUMENT].toMutableListOf() + if(recurrenceRuleArgs.containsKey(setPositionsArgument)) { + recurrenceRule.setPositions = recurrenceRuleArgs[setPositionsArgument].toMutableListOf() } return recurrenceRule From e646d23812a7d551660a22cb75771ed202cafe3b Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 15:51:07 +1000 Subject: [PATCH 20/49] remove unnecessary variable initialisation --- .../ios/Classes/SwiftDeviceCalendarPlugin.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 92c8e8e7..75deb696 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -224,8 +224,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { frequency = 0 } - var totalOccurrences: Int? = nil - var endDate: Int64? = nil + var totalOccurrences: Int? + var endDate: Int64? if(ekRecurrenceRule.recurrenceEnd?.occurrenceCount != nil) { totalOccurrences = ekRecurrenceRule.recurrenceEnd?.occurrenceCount } @@ -235,7 +235,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { endDate = Int64(exactly: endDateMs!) } - var daysOfTheWeek: [Int]? = nil + var daysOfTheWeek: [Int]? if(ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty) { daysOfTheWeek = [] for dayOfTheWeek in ekRecurrenceRule.daysOfTheWeek! { @@ -243,7 +243,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - var daysOfTheMonth: [Int]? = nil + var daysOfTheMonth: [Int]? if(ekRecurrenceRule.daysOfTheMonth != nil && !ekRecurrenceRule.daysOfTheMonth!.isEmpty) { daysOfTheMonth = [] for dayOfTheMonth in ekRecurrenceRule.daysOfTheMonth! { @@ -251,7 +251,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - var monthsOfTheYear: [Int]? = nil + var monthsOfTheYear: [Int]? if(ekRecurrenceRule.monthsOfTheYear != nil && !ekRecurrenceRule.monthsOfTheYear!.isEmpty) { monthsOfTheYear = [] for monthOfTheYear in ekRecurrenceRule.monthsOfTheYear! { @@ -259,7 +259,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - var weeksOfTheYear: [Int]? = nil + var weeksOfTheYear: [Int]? if(ekRecurrenceRule.weeksOfTheYear != nil && !ekRecurrenceRule.weeksOfTheYear!.isEmpty) { weeksOfTheYear = [] for weekOfTheYear in ekRecurrenceRule.weeksOfTheYear! { @@ -267,7 +267,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - var setPositions: [Int]? = nil + var setPositions: [Int]? if(ekRecurrenceRule.setPositions != nil && !ekRecurrenceRule.setPositions!.isEmpty) { setPositions = [] for setPosition in ekRecurrenceRule.setPositions! { @@ -305,7 +305,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } let daysOfTheWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? [Int] - var daysOfTheWeek : [EKRecurrenceDayOfWeek]? = nil + var daysOfTheWeek : [EKRecurrenceDayOfWeek]? if(daysOfTheWeekIndices != nil && !daysOfTheWeekIndices!.isEmpty) { daysOfTheWeek = [] From c63e1eec7ccbbde395a182cd304167ee5fd8b200 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 16:19:35 +1000 Subject: [PATCH 21/49] refactor parsing native recurrence rule --- .../Classes/SwiftDeviceCalendarPlugin.swift | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 75deb696..9c2fd921 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -83,7 +83,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let weeksOfTheYearArgument = "weeksOfTheYear" let setPositionsArgument = "setPositions" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] - + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) @@ -243,47 +243,29 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - var daysOfTheMonth: [Int]? - if(ekRecurrenceRule.daysOfTheMonth != nil && !ekRecurrenceRule.daysOfTheMonth!.isEmpty) { - daysOfTheMonth = [] - for dayOfTheMonth in ekRecurrenceRule.daysOfTheMonth! { - daysOfTheMonth!.append(dayOfTheMonth.intValue) - } - } - - var monthsOfTheYear: [Int]? - if(ekRecurrenceRule.monthsOfTheYear != nil && !ekRecurrenceRule.monthsOfTheYear!.isEmpty) { - monthsOfTheYear = [] - for monthOfTheYear in ekRecurrenceRule.monthsOfTheYear! { - monthsOfTheYear!.append(monthOfTheYear.intValue) - } - } - - var weeksOfTheYear: [Int]? - if(ekRecurrenceRule.weeksOfTheYear != nil && !ekRecurrenceRule.weeksOfTheYear!.isEmpty) { - weeksOfTheYear = [] - for weekOfTheYear in ekRecurrenceRule.weeksOfTheYear! { - weeksOfTheYear!.append(weekOfTheYear.intValue) - } - } - - var setPositions: [Int]? - if(ekRecurrenceRule.setPositions != nil && !ekRecurrenceRule.setPositions!.isEmpty) { - setPositions = [] - for setPosition in ekRecurrenceRule.setPositions! { - setPositions!.append(setPosition.intValue) - } - } - - recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: daysOfTheMonth, monthsOfTheYear: monthsOfTheYear, weeksOfTheYear: weeksOfTheYear, setPositions: setPositions) + recurrenceRule = RecurrenceRule(recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, interval: ekRecurrenceRule.interval, endDate: endDate, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: convertToIntArray(ekRecurrenceRule.daysOfTheMonth), monthsOfTheYear: convertToIntArray(ekRecurrenceRule.monthsOfTheYear), weeksOfTheYear: convertToIntArray(ekRecurrenceRule.weeksOfTheYear), setPositions: convertToIntArray(ekRecurrenceRule.setPositions)) } + return recurrenceRule } + private func convertToIntArray(_ arguments: [NSNumber]?) -> [Int]? { + if(arguments?.isEmpty ?? true) { + return nil + } + + var result: [Int] = [] + for element in arguments! { + result.append(element.intValue) + } + + return result + } + private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary if (recurrenceRuleArguments == nil) { - return nil; + return nil } let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger From d7a5ecfc87ba0a435c439fffbf06cbd06402192e Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 16:31:41 +1000 Subject: [PATCH 22/49] tidy up kotlin code --- .../devicecalendar/CalendarDelegate.kt | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index a08edc9e..531fd0ba 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -233,7 +233,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } - fun retrieveCalendar(calendarId: String, pendingChannelResult: MethodChannel.Result, isInternalCall: Boolean = false): Calendar? { + private fun retrieveCalendar(calendarId: String, pendingChannelResult: MethodChannel.Result, isInternalCall: Boolean = false): Calendar? { if (isInternalCall || arePermissionsGranted()) { val calendarIdNumber = calendarId.toLongOrNull() if (calendarIdNumber == null) { @@ -504,9 +504,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { when (rfcRecurrenceRule.freq) { Freq.WEEKLY, Freq.MONTHLY, Freq.YEARLY -> { - recurrenceRule.daysOfTheWeek = (rfcRecurrenceRule.byDayPart?.map { + recurrenceRule.daysOfTheWeek = rfcRecurrenceRule.byDayPart?.map { DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == it.weekday.ordinal } - })?.filterNotNull()?.toMutableList() + }?.filterNotNull()?.toMutableList() } } @@ -530,9 +530,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - return (rfcRecurrenceRuleString.substring(partIndex).split(";").firstOrNull()?.split("=")?.lastOrNull()?.split(",")?.map { + return rfcRecurrenceRuleString.substring(partIndex).split(";").firstOrNull()?.split("=")?.lastOrNull()?.split(",")?.map { it.toInt() - })?.toMutableList() + }?.toMutableList() } private fun parseAttendee(cursor: Cursor?): Attendee? { @@ -568,11 +568,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @SuppressLint("MissingPermission") private fun updateEventAttendees(events: MutableList, contentResolver: ContentResolver?, pendingChannelResult: MethodChannel.Result) { - - if (events == null) { - return - } - val eventsMapById = events.associateBy { it.eventId } val attendeesQueryEventIds = eventsMapById.values.map { "(${CalendarContract.Attendees.EVENT_ID} = ${it.eventId})" } val attendeesQuery = attendeesQueryEventIds.joinToString(" OR ") @@ -597,7 +592,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) println(e.message) } finally { - attendeesCursor?.close(); + attendeesCursor?.close() } } @@ -605,7 +600,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @Synchronized private fun generateUniqueRequestCodeAndCacheParameters(parameters: CalendarMethodsParametersCacheModel): Int { // TODO we can ran out of Int's at some point so this probably should re-use some of the freed ones - val uniqueRequestCode: Int = (_cachedParametersMap.keys?.max() ?: 0) + 1 + val uniqueRequestCode: Int = (_cachedParametersMap.keys.max() ?: 0) + 1 parameters.ownCacheKey = uniqueRequestCode _cachedParametersMap[uniqueRequestCode] = parameters @@ -648,26 +643,14 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } - when (recurrenceRule.recurrenceFrequency) { - RecurrenceFrequency.WEEKLY, RecurrenceFrequency.MONTHLY, RecurrenceFrequency.YEARLY -> { - if (recurrenceRule.daysOfTheWeek?.isEmpty() == true) { - rr.byDayPart = null - } else { - rr.byDayPart = recurrenceRule.daysOfTheWeek?.mapNotNull { dayOfWeek -> - Weekday.values().firstOrNull { - it.ordinal == dayOfWeek.ordinal - } - }?.map { - org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, it) - } - } - } + if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.WEEKLY || recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY || recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY) { + rr.byDayPart = buildByDayPart(recurrenceRule) } if (recurrenceRule.totalOccurrences != null) { rr.count = recurrenceRule.totalOccurrences!! } else if (recurrenceRule.endDate != null) { - val calendar = java.util.Calendar.getInstance(); + val calendar = java.util.Calendar.getInstance() calendar.timeInMillis = recurrenceRule.endDate!! val dateFormat = SimpleDateFormat("yyyyMMdd") dateFormat.timeZone = calendar.timeZone @@ -693,6 +676,20 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return rrString } + private fun buildByDayPart(recurrenceRule: RecurrenceRule): List? { + if (recurrenceRule.daysOfTheWeek?.isEmpty() == true) { + return null + } + + return recurrenceRule.daysOfTheWeek?.mapNotNull { dayOfWeek -> + Weekday.values().firstOrNull { + it.ordinal == dayOfWeek.ordinal + } + }?.map { + org.dmfs.rfc5545.recur.RecurrenceRule.WeekdayNum(0, it) + } + } + private fun String.addPartWithValues(partName: String, values: List?): String { if (values != null && values.isNotEmpty()) { From 339cfd7b49f36ccc7a88a8ec423cf82776cc1a55 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 13 Aug 2019 17:25:47 +1000 Subject: [PATCH 23/49] update note in readme about the example app --- device_calendar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/README.md b/device_calendar/README.md index 13d3c996..5626c9c9 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -12,7 +12,7 @@ A cross platform plugin for modifying calendars on the user's device. * 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) -**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 +**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 From 2341f4aa933547cb775466256de798aa437c5a52 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Fri, 16 Aug 2019 13:02:25 +1000 Subject: [PATCH 24/49] make retrieveCalendars and retrieveEvents return read-only lists --- device_calendar/CHANGELOG.md | 1 + device_calendar/lib/src/device_calendar.dart | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 62106a43..eb40dc9f 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -1,5 +1,6 @@ # 1.0.0 TBD * Support for more advanced recurrence rules +* **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified # 0.2.1+1 5th August 2019 * Fixing date in changelog for version 0.2.1 diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 78be42d7..c61c807b 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -67,7 +67,7 @@ class DeviceCalendarPlugin { res.data = json.decode(calendarsJson).map((decodedCalendar) { return Calendar.fromJson(decodedCalendar); - }).toList(); + }).toList(growable: false); } catch (e) { _parsePlatformExceptionAndUpdateResult>(e, res); } @@ -117,7 +117,7 @@ class DeviceCalendarPlugin { res.data = json.decode(eventsJson).map((decodedEvent) { return Event.fromJson(decodedEvent); - }).toList(); + }).toList(growable: false); } catch (e) { _parsePlatformExceptionAndUpdateResult>(e, res); } From 8b2a87cae7f013f0fa306cd647b42d0af5d60f6f Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 20 Aug 2019 10:38:24 +1000 Subject: [PATCH 25/49] remove more usages of new operator in example code --- .../lib/presentation/pages/calendar_event.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 84eb8511..d0d75c1c 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -91,14 +91,11 @@ class _CalendarEventPageState extends State { _recurrenceEndTime = TimeOfDay.fromDateTime(_recurrenceEndDate); } _daysOfTheWeek = - _event.recurrenceRule.daysOfTheWeek ?? new List(); - _daysOfTheMonth = - _event.recurrenceRule.daysOfTheMonth ?? new List(); - _monthsOfTheYear = - _event.recurrenceRule.monthsOfTheYear ?? new List(); - _weeksOfTheYear = - _event.recurrenceRule.weeksOfTheYear ?? new List(); - _setPositions = _event.recurrenceRule.setPositions ?? new List(); + _event.recurrenceRule.daysOfTheWeek ?? List(); + _daysOfTheMonth = _event.recurrenceRule.daysOfTheMonth ?? List(); + _monthsOfTheYear = _event.recurrenceRule.monthsOfTheYear ?? List(); + _weeksOfTheYear = _event.recurrenceRule.weeksOfTheYear ?? List(); + _setPositions = _event.recurrenceRule.setPositions ?? List(); } } From 8471ad28b374d15abeb42d6adbf09c015068b564 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 20 Aug 2019 11:07:30 +1000 Subject: [PATCH 26/49] validate calendar exists prior to trying to add/update an event --- .../builttoroam/devicecalendar/CalendarDelegate.kt | 9 +++++++-- .../devicecalendar/DeviceCalendarPlugin.kt | 11 +++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 2b63d25f..b387aa83 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -345,6 +345,12 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return } + val calendar = retrieveCalendar(calendarId, pendingChannelResult, true) + if (calendar == null) { + finishWithError(NOT_FOUND, "Couldn't retrieve the Calendar with ID $calendarId", pendingChannelResult) + return + } + val contentResolver: ContentResolver? = _context?.getContentResolver() val values = ContentValues() val duration: String? = null @@ -357,8 +363,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { values.put(Events.DURATION, duration) // MK using current device time zone - val calendar: java.util.Calendar = java.util.Calendar.getInstance() - val currentTimeZone: TimeZone = calendar.timeZone + val currentTimeZone: TimeZone = java.util.Calendar.getInstance().timeZone values.put(Events.EVENT_TIMEZONE, currentTimeZone.displayName) if (event.recurrenceRule != null) { val recurrenceRuleParams = buildRecurrenceRuleParams(event.recurrenceRule!!) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index c0302443..098ae9f4 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -75,8 +75,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { registrar.addRequestPermissionsResultListener(calendarDelegate) } } - - + override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { requestPermissionsMethod -> { @@ -160,22 +159,22 @@ class DeviceCalendarPlugin() : MethodCallHandler { recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[monthsOfTheYearArgument].toMutableListOf() } - if(recurrenceRuleArgs.containsKey(weeksOfTheYearArgument)) { + if (recurrenceRuleArgs.containsKey(weeksOfTheYearArgument)) { recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[weeksOfTheYearArgument].toMutableListOf() } - if(recurrenceRuleArgs.containsKey(setPositionsArgument)) { + if (recurrenceRuleArgs.containsKey(setPositionsArgument)) { recurrenceRule.setPositions = recurrenceRuleArgs[setPositionsArgument].toMutableListOf() } return recurrenceRule } - private inline fun Any?.toListOf(): List? { + private inline fun Any?.toListOf(): List? { return (this as List<*>?)?.filterIsInstance()?.toList() } - private inline fun Any?.toMutableListOf(): MutableList? { + private inline fun Any?.toMutableListOf(): MutableList? { return this?.toListOf()?.toMutableList() } } From d4e39e0a22dd62ac289298c720767550475fc9d4 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 20 Aug 2019 17:55:31 +1000 Subject: [PATCH 27/49] handle parsing email address of attendees and organiser info --- .../devicecalendar/CalendarDelegate.kt | 18 +++++-------- .../devicecalendar/models/Attendee.kt | 6 +---- .../devicecalendar/models/Event.kt | 1 + .../Classes/SwiftDeviceCalendarPlugin.swift | 26 +++++++++++++------ device_calendar/lib/src/models/attendee.dart | 6 ++++- device_calendar/lib/src/models/event.dart | 6 +++++ 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index b387aa83..927264c3 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -17,6 +17,7 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EVENT_ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_ID_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION +import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_RELATIONSHIP_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX @@ -546,18 +547,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - val id = cursor.getLong(ATTENDEE_ID_INDEX) - val eventId = cursor.getLong(ATTENDEE_EVENT_ID_INDEX) - val name = cursor.getString(ATTENDEE_NAME_INDEX) - val email = cursor.getString(ATTENDEE_EMAIL_INDEX) - val type = cursor.getInt(ATTENDEE_TYPE_INDEX) - - val attendee = Attendee(name) - attendee.id = id - attendee.eventId = eventId - attendee.email = email - attendee.attendanceRequired = type == CalendarContract.Attendees.TYPE_REQUIRED - + val attendee = Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) return attendee } @@ -589,6 +579,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (eventsMapById.containsKey(attendee.eventId.toString())) { val attendeeEvent = eventsMapById[attendee.eventId.toString()] + if (attendee.isOrganizer) { + attendeeEvent?.organizer = attendee + continue + } attendeeEvent?.attendees?.add(attendee) } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt index 654c8793..639a9040 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt @@ -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 eventId: Long, val emailAddress: String, val name: String?, val isOrganizer: Boolean) { } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt index bde9f186..b5535f8a 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt @@ -11,4 +11,5 @@ class Event { var location: String? = null var attendees: MutableList = mutableListOf() var recurrenceRule: RecurrenceRule? = null + var organizer: Attendee? = null } \ No newline at end of file diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index d7d1b218..c3a52261 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -25,6 +25,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let attendees: [Attendee] let location: String? let recurrenceRule: RecurrenceRule? + let organizer: Attendee? } struct RecurrenceRule: Codable { @@ -40,7 +41,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } struct Attendee: Codable { - let name: String + let name: String? + let emailAddress: String } static let channelName = "plugins.builttoroam.com/device_calendar" @@ -176,12 +178,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { var attendees = [Attendee]() if (ekEvent.attendees != nil) { for ekParticipant in ekEvent.attendees! { - if(ekParticipant.name == nil) { - continue - } - - let attendee = Attendee(name: ekParticipant.name!) - attendees.append(attendee) + attendees.append(convertEkParticipantToAttendee(ekParticipant: ekParticipant)!) } } @@ -196,11 +193,24 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { allDay: ekEvent.isAllDay, attendees: attendees, location: ekEvent.location, - recurrenceRule: recurrenceRule + recurrenceRule: recurrenceRule, + organizer: convertEkParticipantToAttendee(ekParticipant: ekEvent.organizer) ) return event } + private func convertEkParticipantToAttendee(ekParticipant: EKParticipant?) -> Attendee? { + if(ekParticipant == nil) { + return nil + } + + let emailAddress = ekParticipant!.url.absoluteString.replacingOccurrences(of: ekParticipant!.url.scheme! + ":", with: "") + let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress) + return attendee + } + + + private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { var recurrenceRule: RecurrenceRule? if(ekEvent.hasRecurrenceRules) { diff --git a/device_calendar/lib/src/models/attendee.dart b/device_calendar/lib/src/models/attendee.dart index 532ce7f8..8ccc2cda 100644 --- a/device_calendar/lib/src/models/attendee.dart +++ b/device_calendar/lib/src/models/attendee.dart @@ -5,7 +5,10 @@ class Attendee { /// The name of the attendee String name; - Attendee(this.name); + /// The email address of the attendee + String emailAddress; + + Attendee({this.name, this.emailAddress}); Attendee.fromJson(Map json) { if (json == null) { @@ -13,6 +16,7 @@ class Attendee { } name = json['name']; + emailAddress = json['emailAddress']; } Map toJson() { diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index 66ea729d..4d6e14e0 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -31,6 +31,9 @@ class Event { /// A list of attendees for this event List attendees; + /// The organizer of this event + Attendee organizer; + /// The recurrence rule for this event RecurrenceRule recurrenceRule; @@ -69,6 +72,9 @@ class Event { if (json['recurrenceRule'] != null) { recurrenceRule = RecurrenceRule.fromJson(json['recurrenceRule']); } + if (json['organizer'] != null) { + organizer = Attendee.fromJson(json['organizer']); + } } Map toJson() { From b953ec34e0f42b6d0ed16d3b4a1f6cf9fd2dab35 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 20 Aug 2019 18:00:17 +1000 Subject: [PATCH 28/49] update changelog --- device_calendar/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 4f12a6f5..1f6ea887 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -1,6 +1,9 @@ # 1.0.0 TBD * Support for more advanced recurrence rules -* **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified +* 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) +* **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified as part of address 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 From 5bedc442e71d0558f19aaca05fab62c64670d2ab Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 10:07:07 +1000 Subject: [PATCH 29/49] include organiser in list of attendees for consistency across platforms --- .../kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 927264c3..cc423b9b 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -581,8 +581,8 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val attendeeEvent = eventsMapById[attendee.eventId.toString()] if (attendee.isOrganizer) { attendeeEvent?.organizer = attendee - continue } + attendeeEvent?.attendees?.add(attendee) } From 5dd100c262677bdd297b5499f2edc722fa012e58 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 10:44:36 +1000 Subject: [PATCH 30/49] format code and apply some of the suggestions --- .../devicecalendar/CalendarDelegate.kt | 57 ++++++------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index cc423b9b..82db27e3 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -14,11 +14,9 @@ import android.provider.CalendarContract import android.provider.CalendarContract.Events import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EMAIL_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EVENT_ID_INDEX -import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_ID_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_RELATIONSHIP_INDEX -import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_DISPLAY_NAME_INDEX @@ -93,12 +91,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return false } - val cachedValues: CalendarMethodsParametersCacheModel? = _cachedParametersMap[requestCode] - if (cachedValues == null) { - // unlikely scenario where another plugin is potentially using the same request code but it's not one we are tracking so return to - // indicate we're not handling the request - return false - } + val cachedValues: CalendarMethodsParametersCacheModel = _cachedParametersMap[requestCode] + ?: // unlikely scenario where another plugin is potentially using the same request code but it's not one we are tracking so return to + // indicate we're not handling the request + return false when (cachedValues.calendarDelegateMethodCode) { retrieveCalendarsMethodCode -> { @@ -173,7 +169,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } _cachedParametersMap.remove(requestCode) - return true } @@ -185,7 +180,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } _cachedParametersMap.remove(requestCode) - return true } @@ -208,16 +202,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val contentResolver: ContentResolver? = _context?.getContentResolver() val uri: Uri = CalendarContract.Calendars.CONTENT_URI val cursor: Cursor? = contentResolver?.query(uri, CALENDAR_PROJECTION, null, null, null) - val calendars: MutableList = mutableListOf() - try { - while (cursor?.moveToNext() ?: false) { - - val calendar = parseCalendar(cursor) - if (calendar == null) { - continue - } + while (cursor?.moveToNext() == true) { + val calendar = parseCalendar(cursor) ?: continue calendars.add(calendar) } @@ -249,7 +237,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val cursor: Cursor? = contentResolver?.query(ContentUris.withAppendedId(uri, calendarIdNumber), CALENDAR_PROJECTION, null, null, null) try { - if (cursor?.moveToFirst() ?: false) { + if (cursor?.moveToFirst() == true) { val calendar = parseCalendar(cursor) if (isInternalCall) { return calendar @@ -309,16 +297,12 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val events: MutableList = mutableListOf() try { - if (eventsCursor?.moveToFirst() ?: false) { + if (eventsCursor?.moveToFirst() == true) { do { - val event = parseEvent(calendarId, eventsCursor) - if (event == null) { - continue - } - + val event = parseEvent(calendarId, eventsCursor) ?: continue events.add(event) - } while (eventsCursor?.moveToNext() ?: false) + } while (eventsCursor.moveToNext()) updateEventAttendees(events, contentResolver, pendingChannelResult) } @@ -352,7 +336,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return } - val contentResolver: ContentResolver? = _context?.getContentResolver() + val contentResolver: ContentResolver? = _context?.contentResolver val values = ContentValues() val duration: String? = null values.put(Events.DTSTART, event.start) @@ -375,7 +359,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (eventId == null) { val uri = contentResolver?.insert(Events.CONTENT_URI, values) // get the event ID that is the last element in the Uri - eventId = java.lang.Long.parseLong(uri?.getLastPathSegment()) + eventId = java.lang.Long.parseLong(uri?.lastPathSegment) } else { contentResolver?.update(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), values, null, null) } @@ -411,11 +395,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return } - val contentResolver: ContentResolver? = _context?.getContentResolver() - + 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) } else { val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, deleteEventMethodCode, calendarId) @@ -428,7 +410,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (atLeastAPI(23)) { val writeCalendarPermissionGranted = _activity?.checkSelfPermission(Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED val readCalendarPermissionGranted = _activity?.checkSelfPermission(Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED - return writeCalendarPermissionGranted && readCalendarPermissionGranted } @@ -547,8 +528,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - val attendee = Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) - return attendee + return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { @@ -570,12 +550,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null); try { - if (attendeesCursor?.moveToFirst() ?: false) { + if (attendeesCursor?.moveToFirst() == true) { do { - val attendee = parseAttendee(attendeesCursor) - if (attendee == null) { - continue - } + val attendee = parseAttendee(attendeesCursor) ?: continue if (eventsMapById.containsKey(attendee.eventId.toString())) { val attendeeEvent = eventsMapById[attendee.eventId.toString()] @@ -586,7 +563,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { attendeeEvent?.attendees?.add(attendee) } - } while (attendeesCursor?.moveToNext() ?: false) + } while (attendeesCursor.moveToNext()) } } catch (e: Exception) { finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) From 244a9a8f12b04b909f2335bd2af5bff8e5394854 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 11:18:54 +1000 Subject: [PATCH 31/49] reapply kotlin naming conventions for constants --- .../devicecalendar/CalendarDelegate.kt | 66 +++++---- .../devicecalendar/DeviceCalendarPlugin.kt | 136 +++++++++--------- 2 files changed, 98 insertions(+), 104 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 82db27e3..b4f8147a 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -55,21 +55,19 @@ import java.util.* class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { - - private val retrieveCalendarsMethodCode = 0 - private val retrieveEventsMethodCode = retrieveCalendarsMethodCode + 1 - private val retrieveCalendarMethodCode = retrieveEventsMethodCode + 1 - private val createOrUpdateMethodCode = retrieveCalendarMethodCode + 1 - private val deleteEventMethodCode = createOrUpdateMethodCode + 1 - private val requestPermissionsMethodCode = deleteEventMethodCode + 1 - private val partTemplate = ";%s=" - private val byMonthDayPart = "BYMONTHDAY" - private val byMonthPart = "BYMONTH" - private val byWeekNoPart = "BYWEEKNO" - private val bySetPosPart = "BYSETPOS" + private val RETRIEVE_CALENDARS_REQUEST_CODE = 0 + private val RETRIEVE_EVENTS_REQUEST_CODE = RETRIEVE_CALENDARS_REQUEST_CODE + 1 + private val RETRIEVE_CALENDAR_REQUEST_CODE = RETRIEVE_EVENTS_REQUEST_CODE + 1 + private val CREATE_OR_UPDATE_EVENT_REQUEST_CODE = RETRIEVE_CALENDAR_REQUEST_CODE + 1 + private val DELETE_EVENT_REQUEST_CODE = CREATE_OR_UPDATE_EVENT_REQUEST_CODE + 1 + private val REQUEST_PERMISSIONS_REQUEST_CODE = DELETE_EVENT_REQUEST_CODE + 1 + private val PART_TEMPLATE = ";%s=" + private val BYMONTHDAY_PART = "BYMONTHDAY" + private val BYMONTH_PART = "BYMONTH" + private val BYWEEKNO_PART = "BYWEEKNO" + private val BYSETPOS_PART = "BYSETPOS" private val _cachedParametersMap: MutableMap = mutableMapOf() - private var _activity: Activity? = null private var _context: Context? = null private var _gson: Gson? = null @@ -97,22 +95,22 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return false when (cachedValues.calendarDelegateMethodCode) { - retrieveCalendarsMethodCode -> { + RETRIEVE_CALENDARS_REQUEST_CODE -> { return handleRetrieveCalendarsRequest(permissionGranted, cachedValues, requestCode) } - retrieveEventsMethodCode -> { + RETRIEVE_EVENTS_REQUEST_CODE -> { return handleRetrieveEventsRequest(permissionGranted, cachedValues, requestCode) } - retrieveCalendarMethodCode -> { + RETRIEVE_CALENDAR_REQUEST_CODE -> { return handleRetrieveCalendarRequest(permissionGranted, cachedValues, requestCode) } - createOrUpdateMethodCode -> { + CREATE_OR_UPDATE_EVENT_REQUEST_CODE -> { return handleCreateOrUpdateEventRequest(permissionGranted, cachedValues, requestCode) } - deleteEventMethodCode -> { + DELETE_EVENT_REQUEST_CODE -> { return handleDeleteEventRequest(permissionGranted, cachedValues, requestCode) } - requestPermissionsMethodCode -> { + REQUEST_PERMISSIONS_REQUEST_CODE -> { return handlePermissionsRequest(permissionGranted, cachedValues) } } @@ -187,7 +185,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (arePermissionsGranted()) { finishWithSuccess(true, pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, requestPermissionsMethodCode) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, REQUEST_PERMISSIONS_REQUEST_CODE) requestPermissions(parameters) } } @@ -217,7 +215,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { cursor?.close() } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveCalendarsMethodCode) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_CALENDARS_REQUEST_CODE) requestPermissions(parameters) } } @@ -255,7 +253,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { cursor?.close() } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveCalendarMethodCode, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_CALENDAR_REQUEST_CODE, calendarId) requestPermissions(parameters) } @@ -315,7 +313,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(_gson?.toJson(events), pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, retrieveEventsMethodCode, calendarId, startDate, endDate) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, RETRIEVE_EVENTS_REQUEST_CODE, calendarId, startDate, endDate) requestPermissions(parameters) } @@ -370,7 +368,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { println(e.message) } } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, createOrUpdateMethodCode, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, CREATE_OR_UPDATE_EVENT_REQUEST_CODE, calendarId) parameters.event = event requestPermissions(parameters) } @@ -400,7 +398,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0 finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) } else { - val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, deleteEventMethodCode, calendarId) + val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, DELETE_EVENT_REQUEST_CODE, calendarId) parameters.eventId = eventId requestPermissions(parameters) } @@ -500,15 +498,15 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val rfcRecurrenceRuleString = rfcRecurrenceRule.toString() if (rfcRecurrenceRule.freq == Freq.MONTHLY) { - recurrenceRule.daysOfTheMonth = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byMonthDayPart) + recurrenceRule.daysOfTheMonth = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTHDAY_PART) } if (rfcRecurrenceRule.freq == Freq.YEARLY) { - recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byMonthPart) - recurrenceRule.weeksOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, byWeekNoPart) + recurrenceRule.monthsOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYMONTH_PART) + recurrenceRule.weeksOfTheYear = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYWEEKNO_PART) } - recurrenceRule.setPositions = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, bySetPosPart) + recurrenceRule.setPositions = convertCalendarPartToNumericValues(rfcRecurrenceRuleString, BYSETPOS_PART) return recurrenceRule } @@ -636,20 +634,20 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { var rrString = rr.toString() if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY && recurrenceRule.daysOfTheMonth != null && recurrenceRule.daysOfTheMonth!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(byMonthDayPart, recurrenceRule.daysOfTheMonth) + rrString = rrString.addPartWithValues(BYMONTHDAY_PART, recurrenceRule.daysOfTheMonth) } if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY) { if (recurrenceRule.monthsOfTheYear != null && recurrenceRule.monthsOfTheYear!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(byMonthPart, recurrenceRule.monthsOfTheYear) + rrString = rrString.addPartWithValues(BYMONTH_PART, recurrenceRule.monthsOfTheYear) } if (recurrenceRule.weeksOfTheYear != null && recurrenceRule.weeksOfTheYear!!.isNotEmpty()) { - rrString = rrString.addPartWithValues(byWeekNoPart, recurrenceRule.weeksOfTheYear) + rrString = rrString.addPartWithValues(BYWEEKNO_PART, recurrenceRule.weeksOfTheYear) } } - rrString = rrString.addPartWithValues(bySetPosPart, recurrenceRule.setPositions) + rrString = rrString.addPartWithValues(BYSETPOS_PART, recurrenceRule.setPositions) return rrString } @@ -670,7 +668,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private fun String.addPartWithValues(partName: String, values: List?): String { if (values != null && values.isNotEmpty()) { - return this + partTemplate.format(partName) + values.joinToString(",") + return this + PART_TEMPLATE.format(partName) + values.joinToString(",") } return this diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 098ae9f4..07b45938 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -13,44 +13,40 @@ 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 requestPermissionsMethod = "requestPermissions" - private val hasPermissionsMethod = "hasPermissions" - private val retrieveCalendarsMethod = "retrieveCalendars" - private val retrieveEventsMethod = "retrieveEvents" - private val deleteEventMethod = "deleteEvent" - private val createOrUpdateEventMethod = "createOrUpdateEvent" + private val REQUEST_PERMISSIONS_METHOD = "requestPermissions" + private val HAS_PERMISSIONS_METHOD = "hasPermissions" + private val RETRIEVE_CALENDARS_METHOD = "retrieveCalendars" + private val RETRIEVE_EVENTS_METHOD = "retrieveEvents" + private val DELETE_EVENT_METHOD = "deleteEvent" + private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent" // Method arguments - private val calendarIdArgument = "calendarId" - private val calendarEventsStartDateArgument = "startDate" - private val calendarEventsEndDateArgument = "endDate" - private val calendarEventIdsArgument = "eventIds" - private val eventIdArgument = "eventId" - private val eventTitleArgument = "eventTitle" - private val eventLocationArgument = "eventLocation" - private val eventDescriptionArgument = "eventDescription" - private val eventStartDateArgument = "eventStartDate" - private val eventEndDateArgument = "eventEndDate" - private val recurrenceRuleArgument = "recurrenceRule" - private val recurrenceFrequencyArgument = "recurrenceFrequency" - private val totalOccurrencesArgument = "totalOccurrences" - private val intervalArgument = "interval" - private val endDateArgument = "endDate" - private val daysOfTheWeekArgument = "daysOfTheWeek" - private val daysOfTheMonthArgument = "daysOfTheMonth" - private val monthsOfTheYearArgument = "monthsOfTheYear" - private val weeksOfTheYearArgument = "weeksOfTheYear" - private val setPositionsArgument = "setPositions" + private val CALENDAR_ID_ARGUMENT = "calendarId" + 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 RECURRENCE_RULE_ARGUMENT = "recurrenceRule" + private val RECURRENCE_FREQUENCY_ARGUMENT = "recurrenceFrequency" + private val TOTAL_OCCURRENCES_ARGUMENT = "totalOccurrences" + private val INTERVAL_ARGUMENT = "interval" + 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 lateinit var _registrar: Registrar + private lateinit var _calendarDelegate: CalendarDelegate private constructor(registrar: Registrar, calendarDelegate: CalendarDelegate) : this() { _registrar = registrar @@ -78,32 +74,32 @@ class DeviceCalendarPlugin() : MethodCallHandler { override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { - requestPermissionsMethod -> { + REQUEST_PERMISSIONS_METHOD -> { _calendarDelegate.requestPermissions(result) } - hasPermissionsMethod -> { + HAS_PERMISSIONS_METHOD -> { _calendarDelegate.hasPermissions(result) } - retrieveCalendarsMethod -> { + RETRIEVE_CALENDARS_METHOD -> { _calendarDelegate.retrieveCalendars(result) } - retrieveEventsMethod -> { - val calendarId = call.argument(calendarIdArgument) - val startDate = call.argument(calendarEventsStartDateArgument) - val endDate = call.argument(calendarEventsEndDateArgument) - val eventIds = call.argument>(calendarEventIdsArgument) ?: listOf() + RETRIEVE_EVENTS_METHOD -> { + val calendarId = call.argument(CALENDAR_ID_ARGUMENT) + val startDate = call.argument(START_DATE_ARGUMENT) + val endDate = call.argument(END_DATE_ARGUMENT) + val eventIds = call.argument>(EVENT_IDS_ARGUMENT) ?: listOf() _calendarDelegate.retrieveEvents(calendarId!!, startDate, endDate, eventIds, result) } - createOrUpdateEventMethod -> { - val calendarId = call.argument(calendarIdArgument) + CREATE_OR_UPDATE_EVENT_METHOD -> { + val calendarId = call.argument(CALENDAR_ID_ARGUMENT) val event = parseEventArgs(call, calendarId) _calendarDelegate.createOrUpdateEvent(calendarId!!, event, result) } - deleteEventMethod -> { - val calendarId = call.argument(calendarIdArgument) - val eventId = call.argument(eventIdArgument) + DELETE_EVENT_METHOD -> { + val calendarId = call.argument(CALENDAR_ID_ARGUMENT) + val eventId = call.argument(EVENT_ID_ARGUMENT) _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result) } @@ -115,15 +111,15 @@ class DeviceCalendarPlugin() : MethodCallHandler { private fun parseEventArgs(call: MethodCall, calendarId: String?): Event { val event = Event() - event.title = call.argument(eventTitleArgument) + event.title = call.argument(EVENT_TITLE_ARGUMENT) event.calendarId = calendarId - event.eventId = call.argument(eventIdArgument) - event.description = call.argument(eventDescriptionArgument) - event.start = call.argument(eventStartDateArgument)!! - event.end = call.argument(eventEndDateArgument)!! - event.location = call.argument(eventLocationArgument) + event.eventId = call.argument(EVENT_ID_ARGUMENT) + event.description = call.argument(EVENT_DESCRIPTION_ARGUMENT) + event.start = call.argument(EVENT_START_DATE_ARGUMENT)!! + event.end = call.argument(EVENT_END_DATE_ARGUMENT)!! + event.location = call.argument(EVENT_LOCATION_ARGUMENT) - if (call.hasArgument(recurrenceRuleArgument) && call.argument>(recurrenceRuleArgument) != null) { + if (call.hasArgument(RECURRENCE_RULE_ARGUMENT) && call.argument>(RECURRENCE_RULE_ARGUMENT) != null) { val recurrenceRule = parseRecurrenceRuleArgs(call) event.recurrenceRule = recurrenceRule } @@ -132,39 +128,39 @@ class DeviceCalendarPlugin() : MethodCallHandler { } private fun parseRecurrenceRuleArgs(call: MethodCall): RecurrenceRule { - val recurrenceRuleArgs = call.argument>(recurrenceRuleArgument)!! - val recurrenceFrequencyIndex = recurrenceRuleArgs[recurrenceFrequencyArgument] as Int + val recurrenceRuleArgs = call.argument>(RECURRENCE_RULE_ARGUMENT)!! + val recurrenceFrequencyIndex = recurrenceRuleArgs[RECURRENCE_FREQUENCY_ARGUMENT] as Int val recurrenceRule = RecurrenceRule(RecurrenceFrequency.values()[recurrenceFrequencyIndex]) - if (recurrenceRuleArgs.containsKey(totalOccurrencesArgument)) { - recurrenceRule.totalOccurrences = recurrenceRuleArgs[totalOccurrencesArgument] as Int + if (recurrenceRuleArgs.containsKey(TOTAL_OCCURRENCES_ARGUMENT)) { + recurrenceRule.totalOccurrences = recurrenceRuleArgs[TOTAL_OCCURRENCES_ARGUMENT] as Int } - if (recurrenceRuleArgs.containsKey(intervalArgument)) { - recurrenceRule.interval = recurrenceRuleArgs[intervalArgument] as Int + if (recurrenceRuleArgs.containsKey(INTERVAL_ARGUMENT)) { + recurrenceRule.interval = recurrenceRuleArgs[INTERVAL_ARGUMENT] as Int } - if (recurrenceRuleArgs.containsKey(endDateArgument)) { - recurrenceRule.endDate = recurrenceRuleArgs[endDateArgument] as Long + if (recurrenceRuleArgs.containsKey(END_DATE_ARGUMENT)) { + recurrenceRule.endDate = recurrenceRuleArgs[END_DATE_ARGUMENT] as Long } - if (recurrenceRuleArgs.containsKey(daysOfTheWeekArgument)) { - recurrenceRule.daysOfTheWeek = recurrenceRuleArgs[daysOfTheWeekArgument].toListOf()?.map { DayOfWeek.values()[it] }?.toMutableList() + if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_WEEK_ARGUMENT)) { + recurrenceRule.daysOfTheWeek = recurrenceRuleArgs[DAYS_OF_THE_WEEK_ARGUMENT].toListOf()?.map { DayOfWeek.values()[it] }?.toMutableList() } - if (recurrenceRuleArgs.containsKey(daysOfTheMonthArgument)) { - recurrenceRule.daysOfTheMonth = recurrenceRuleArgs[daysOfTheMonthArgument].toMutableListOf() + if (recurrenceRuleArgs.containsKey(DAYS_OF_THE_MONTH_ARGUMENT)) { + recurrenceRule.daysOfTheMonth = recurrenceRuleArgs[DAYS_OF_THE_MONTH_ARGUMENT].toMutableListOf() } - if (recurrenceRuleArgs.containsKey(monthsOfTheYearArgument)) { - recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[monthsOfTheYearArgument].toMutableListOf() + if (recurrenceRuleArgs.containsKey(MONTHS_OF_THE_YEAR_ARGUMENT)) { + recurrenceRule.monthsOfTheYear = recurrenceRuleArgs[MONTHS_OF_THE_YEAR_ARGUMENT].toMutableListOf() } - if (recurrenceRuleArgs.containsKey(weeksOfTheYearArgument)) { - recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[weeksOfTheYearArgument].toMutableListOf() + if (recurrenceRuleArgs.containsKey(WEEKS_OF_THE_YEAR_ARGUMENT)) { + recurrenceRule.weeksOfTheYear = recurrenceRuleArgs[WEEKS_OF_THE_YEAR_ARGUMENT].toMutableListOf() } - if (recurrenceRuleArgs.containsKey(setPositionsArgument)) { - recurrenceRule.setPositions = recurrenceRuleArgs[setPositionsArgument].toMutableListOf() + if (recurrenceRuleArgs.containsKey(SET_POSITIONS_ARGUMENT)) { + recurrenceRule.setPositions = recurrenceRuleArgs[SET_POSITIONS_ARGUMENT].toMutableListOf() } return recurrenceRule From 95d5dd0fdcb545d98a4727942a1b359e19b8d0e1 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 11:31:17 +1000 Subject: [PATCH 32/49] additional code formatting/cleanup --- .../builttoroam/devicecalendar/CalendarDelegate.kt | 14 +++++++------- .../ios/Classes/SwiftDeviceCalendarPlugin.swift | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index b4f8147a..db5fead4 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -197,7 +197,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @SuppressLint("MissingPermission") fun retrieveCalendars(pendingChannelResult: MethodChannel.Result) { if (arePermissionsGranted()) { - val contentResolver: ContentResolver? = _context?.getContentResolver() + val contentResolver: ContentResolver? = _context?.contentResolver val uri: Uri = CalendarContract.Calendars.CONTENT_URI val cursor: Cursor? = contentResolver?.query(uri, CALENDAR_PROJECTION, null, null, null) val calendars: MutableList = mutableListOf() @@ -274,7 +274,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return } - val contentResolver: ContentResolver? = _context?.getContentResolver() + val contentResolver: ContentResolver? = _context?.contentResolver val eventsUriBuilder = CalendarContract.Instances.CONTENT_URI.buildUpon() ContentUris.appendId(eventsUriBuilder, startDate ?: Date(0).time) ContentUris.appendId(eventsUriBuilder, endDate ?: Date(Long.MAX_VALUE).time) @@ -286,7 +286,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val eventsIdsQuery = eventsIdsQueryElements.joinToString(" OR ") var eventsSelectionQuery = "$eventsCalendarQuery AND $eventsNotDeletedQuery" - if (!eventsIdsQuery.isNullOrEmpty()) { + if (eventsIdsQuery.isNotEmpty()) { eventsSelectionQuery += " AND ($eventsIdsQuery)" } val eventsSortOrder = Events.DTSTART + " ASC" @@ -471,7 +471,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (recurrenceRuleString == null) { return null } - val rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString!!) + val rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString) val frequency = when (rfcRecurrenceRule.freq) { Freq.YEARLY -> RecurrenceFrequency.YEARLY Freq.MONTHLY -> RecurrenceFrequency.MONTHLY @@ -490,9 +490,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { when (rfcRecurrenceRule.freq) { Freq.WEEKLY, Freq.MONTHLY, Freq.YEARLY -> { - recurrenceRule.daysOfTheWeek = rfcRecurrenceRule.byDayPart?.map { + recurrenceRule.daysOfTheWeek = rfcRecurrenceRule.byDayPart?.mapNotNull { DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == it.weekday.ordinal } - }?.filterNotNull()?.toMutableList() + }?.toMutableList() } } @@ -545,7 +545,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val eventsMapById = events.associateBy { it.eventId } val attendeesQueryEventIds = eventsMapById.values.map { "(${CalendarContract.Attendees.EVENT_ID} = ${it.eventId})" } val attendeesQuery = attendeesQueryEventIds.joinToString(" OR ") - val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null); + val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null) try { if (attendeesCursor?.moveToFirst() == true) { diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index c3a52261..d7f033e8 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -430,8 +430,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let errorMessage = String(format: self.eventNotFoundErrorMessageFormat, eventId) result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) } - - + private func encodeJsonAndFinish(codable: T, result: @escaping FlutterResult) { do { let jsonEncoder = JSONEncoder() From 141371cb3a8c02485c9f5f77b986c65208c7892f Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 15:36:43 +1000 Subject: [PATCH 33/49] add ability to read info on if attendee is required for an event --- .../devicecalendar/CalendarDelegate.kt | 13 ++++++---- .../devicecalendar/models/Attendee.kt | 2 +- .../devicecalendar/models/Event.kt | 4 ++-- .../Classes/SwiftDeviceCalendarPlugin.swift | 3 ++- device_calendar/lib/src/models/attendee.dart | 24 +++++++++++++++++-- device_calendar/lib/src/models/event.dart | 4 ++-- .../android/attendee_details.dart | 13 ++++++++++ .../ios/attendee_details.dart | 16 +++++++++++++ .../models/platform_specifics/ios/role.dart | 3 +++ device_calendar/pubspec.yaml | 1 + 10 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart create mode 100644 device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart create mode 100644 device_calendar/lib/src/models/platform_specifics/ios/role.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index db5fead4..81e28610 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -17,6 +17,7 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EVENT_ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_RELATIONSHIP_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_DISPLAY_NAME_INDEX @@ -230,7 +231,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - val contentResolver: ContentResolver? = _context?.getContentResolver() + val contentResolver: ContentResolver? = _context?.contentResolver val uri: Uri = CalendarContract.Calendars.CONTENT_URI val cursor: Cursor? = contentResolver?.query(ContentUris.withAppendedId(uri, calendarIdNumber), CALENDAR_PROJECTION, null, null, null) @@ -337,8 +338,8 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val contentResolver: ContentResolver? = _context?.contentResolver val values = ContentValues() val duration: String? = null - values.put(Events.DTSTART, event.start) - values.put(Events.DTEND, event.end) + values.put(Events.DTSTART, event.start!!) + values.put(Events.DTEND, event.end!!) values.put(Events.TITLE, event.title) values.put(Events.DESCRIPTION, event.description) values.put(Events.EVENT_LOCATION, event.location) @@ -471,6 +472,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (recurrenceRuleString == null) { return null } + val rfcRecurrenceRule = org.dmfs.rfc5545.recur.RecurrenceRule(recurrenceRuleString) val frequency = when (rfcRecurrenceRule.freq) { Freq.YEARLY -> RecurrenceFrequency.YEARLY @@ -479,10 +481,12 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { Freq.DAILY -> RecurrenceFrequency.DAILY else -> null } + val recurrenceRule = RecurrenceRule(frequency!!) if (rfcRecurrenceRule.count != null) { recurrenceRule.totalOccurrences = rfcRecurrenceRule.count } + recurrenceRule.interval = rfcRecurrenceRule.interval if (rfcRecurrenceRule.until != null) { recurrenceRule.endDate = rfcRecurrenceRule.until.timestamp @@ -526,7 +530,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED ,cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { @@ -665,7 +669,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } - private fun String.addPartWithValues(partName: String, values: List?): String { if (values != null && values.isNotEmpty()) { return this + PART_TEMPLATE.format(partName) + values.joinToString(",") diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt index 639a9040..e33cee6f 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt @@ -1,4 +1,4 @@ package com.builttoroam.devicecalendar.models -class Attendee(val eventId: Long, val emailAddress: String, val name: String?, val isOrganizer: Boolean) { +class Attendee(val eventId: Long, val emailAddress: String, val name: String?, val isRequired: Boolean, val isOrganizer: Boolean) { } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt index b5535f8a..db9881df 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt @@ -5,8 +5,8 @@ class Event { 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 = mutableListOf() diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index d7f033e8..01303c86 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -43,6 +43,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { struct Attendee: Codable { let name: String? let emailAddress: String + let role: Int } static let channelName = "plugins.builttoroam.com/device_calendar" @@ -205,7 +206,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } let emailAddress = ekParticipant!.url.absoluteString.replacingOccurrences(of: ekParticipant!.url.scheme! + ":", with: "") - let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress) + let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress, role: ekParticipant!.participantRole.rawValue) return attendee } diff --git a/device_calendar/lib/src/models/attendee.dart b/device_calendar/lib/src/models/attendee.dart index 8ccc2cda..cff8a3c6 100644 --- a/device_calendar/lib/src/models/attendee.dart +++ b/device_calendar/lib/src/models/attendee.dart @@ -1,4 +1,8 @@ +import 'package:platform/platform.dart'; + import '../common/error_messages.dart'; +import 'platform_specifics/android/attendee_details.dart'; +import 'platform_specifics/ios/attendee_details.dart'; /// A person attending an event class Attendee { @@ -8,6 +12,14 @@ class Attendee { /// The email address of the attendee String emailAddress; + /// Details about the attendee that are specific to iOS. + /// When reading details for an existing event, this will only be populated on iOS devices. + IosAttendeeDetails iosAttendeeDetails; + + /// Details about the attendee that are specific to Android. + /// When reading details for an existing event, this will only be populated on Android devices. + AndroidAttendeeDetails androidAttendeeDetails; + Attendee({this.name, this.emailAddress}); Attendee.fromJson(Map json) { @@ -17,11 +29,19 @@ class Attendee { name = json['name']; emailAddress = json['emailAddress']; + final platform = LocalPlatform(); + if (platform.isAndroid) { + androidAttendeeDetails = AndroidAttendeeDetails.fromJson(json); + } + + if (platform.isIOS) { + iosAttendeeDetails = IosAttendeeDetails.fromJson(json); + } } - Map toJson() { + /*Map toJson() { final Map data = Map(); data['name'] = this.name; return data; - } + }*/ } diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index 4d6e14e0..9fd1fcb4 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -87,14 +87,14 @@ class Event { data['end'] = this.end.millisecondsSinceEpoch; data['allDay'] = this.allDay; data['location'] = this.location; - if (attendees != null) { + /*if (attendees != null) { List> attendeesJson = List(); for (var attendee in attendees) { var attendeeJson = attendee.toJson(); attendeesJson.add(attendeeJson); } data['attendees'] = attendeesJson; - } + }*/ if (recurrenceRule != null) { data['recurrenceRule'] = recurrenceRule.toJson(); } diff --git a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart new file mode 100644 index 00000000..0199c596 --- /dev/null +++ b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart @@ -0,0 +1,13 @@ +import '../../../common/error_messages.dart'; + +class AndroidAttendeeDetails { + /// Indicates if the attendee is required for this event + bool isRequired; + + AndroidAttendeeDetails.fromJson(Map json) { + if (json == null) { + throw ArgumentError(ErrorMessages.fromJsonMapIsNull); + } + isRequired = json['isRequired']; + } +} diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart new file mode 100644 index 00000000..6c1fef18 --- /dev/null +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart @@ -0,0 +1,16 @@ +import '../../../common/error_messages.dart'; +import 'role.dart'; + +class IosAttendeeDetails { + // The attendee's role at an event + Role role; + + IosAttendeeDetails.fromJson(Map json) { + if (json == null) { + throw ArgumentError(ErrorMessages.fromJsonMapIsNull); + } + if (json['role'] != null && json['role'] is int) { + role = Role.values[json['role']]; + } + } +} diff --git a/device_calendar/lib/src/models/platform_specifics/ios/role.dart b/device_calendar/lib/src/models/platform_specifics/ios/role.dart new file mode 100644 index 00000000..7e566313 --- /dev/null +++ b/device_calendar/lib/src/models/platform_specifics/ios/role.dart @@ -0,0 +1,3 @@ +/// The available roles of an attendee at an event. +/// This is iOS specific and based on the values from https://developer.apple.com/documentation/eventkit/ekparticipantrole +enum Role { Unknown, Required, Optional, Chair, NonParticipant } diff --git a/device_calendar/pubspec.yaml b/device_calendar/pubspec.yaml index eab1854b..8267ad20 100644 --- a/device_calendar/pubspec.yaml +++ b/device_calendar/pubspec.yaml @@ -7,6 +7,7 @@ homepage: https://github.com/builttoroam/flutter_plugins/tree/develop/device_cal dependencies: flutter: sdk: flutter + platform: ^2.2.1 dev_dependencies: flutter_test: From 1076241ea398f1eef3755feed125b1d32739873f Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 15:54:33 +1000 Subject: [PATCH 34/49] format code --- .../kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt | 2 +- .../com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 81e28610..19085149 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -530,7 +530,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED ,cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 07b45938..1ae72468 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -71,7 +71,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { registrar.addRequestPermissionsResultListener(calendarDelegate) } } - + override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { REQUEST_PERMISSIONS_METHOD -> { From d3ad3d1fd27dc5930d20499c08893d003c991f42 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 21 Aug 2019 16:51:00 +1000 Subject: [PATCH 35/49] handle parsing of attendance status on ios and android --- .../com/builttoroam/devicecalendar/CalendarDelegate.kt | 3 ++- .../com/builttoroam/devicecalendar/models/Attendee.kt | 2 +- .../ios/Classes/SwiftDeviceCalendarPlugin.swift | 8 +++----- .../platform_specifics/android/attendance_status.dart | 1 + .../platform_specifics/android/attendee_details.dart | 8 ++++++++ .../platform_specifics/ios/attendance_status.dart | 10 ++++++++++ .../platform_specifics/ios/attendee_details.dart | 7 +++++++ 7 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 device_calendar/lib/src/models/platform_specifics/android/attendance_status.dart create mode 100644 device_calendar/lib/src/models/platform_specifics/ios/attendance_status.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 19085149..eaa10335 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -17,6 +17,7 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EVENT_ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_RELATIONSHIP_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_STATUS_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX @@ -530,7 +531,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_STATUS_INDEX),cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt index e33cee6f..45ba9872 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt @@ -1,4 +1,4 @@ package com.builttoroam.devicecalendar.models -class Attendee(val eventId: Long, val emailAddress: String, val name: String?, val isRequired: Boolean, val isOrganizer: Boolean) { +class Attendee(val eventId: Long, val emailAddress: String, val name: String?, val isRequired: Boolean, val attendanceStatus: Int, val isOrganizer: Boolean) { } \ No newline at end of file diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 01303c86..08bbdec8 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -7,7 +7,6 @@ extension Date { } public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { - struct Calendar: Codable { let id: String let name: String @@ -44,6 +43,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let name: String? let emailAddress: String let role: Int + let attendanceStatus: Int } static let channelName = "plugins.builttoroam.com/device_calendar" @@ -206,12 +206,10 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } let emailAddress = ekParticipant!.url.absoluteString.replacingOccurrences(of: ekParticipant!.url.scheme! + ":", with: "") - let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress, role: ekParticipant!.participantRole.rawValue) + let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue) return attendee } - - - + private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { var recurrenceRule: RecurrenceRule? if(ekEvent.hasRecurrenceRules) { diff --git a/device_calendar/lib/src/models/platform_specifics/android/attendance_status.dart b/device_calendar/lib/src/models/platform_specifics/android/attendance_status.dart new file mode 100644 index 00000000..9c3956c2 --- /dev/null +++ b/device_calendar/lib/src/models/platform_specifics/android/attendance_status.dart @@ -0,0 +1 @@ +enum AndroidAttendanceStatus { None, Accepted, Declined, Invited, Tentative } diff --git a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart index 0199c596..b13ec658 100644 --- a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart @@ -1,13 +1,21 @@ +import 'package:device_calendar/src/models/platform_specifics/android/attendance_status.dart'; + import '../../../common/error_messages.dart'; class AndroidAttendeeDetails { /// Indicates if the attendee is required for this event bool isRequired; + AndroidAttendanceStatus attendanceStatus; + AndroidAttendeeDetails.fromJson(Map json) { if (json == null) { throw ArgumentError(ErrorMessages.fromJsonMapIsNull); } isRequired = json['isRequired']; + if (json['attendanceStatus'] != null && json['attendanceStatus'] is int) { + attendanceStatus = + AndroidAttendanceStatus.values[json['attendanceStatus']]; + } } } diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendance_status.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendance_status.dart new file mode 100644 index 00000000..05d83324 --- /dev/null +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendance_status.dart @@ -0,0 +1,10 @@ +enum IosAttendanceStatus { + Unknown, + Pending, + Accepted, + Declined, + Tentative, + Delegated, + Completed, + InProcess +} diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart index 6c1fef18..bc4209d6 100644 --- a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart @@ -1,3 +1,5 @@ +import 'package:device_calendar/src/models/platform_specifics/ios/attendance_status.dart'; + import '../../../common/error_messages.dart'; import 'role.dart'; @@ -5,6 +7,8 @@ class IosAttendeeDetails { // The attendee's role at an event Role role; + IosAttendanceStatus attendanceStatus; + IosAttendeeDetails.fromJson(Map json) { if (json == null) { throw ArgumentError(ErrorMessages.fromJsonMapIsNull); @@ -12,5 +16,8 @@ class IosAttendeeDetails { if (json['role'] != null && json['role'] is int) { role = Role.values[json['role']]; } + if (json['attendanceStatus'] != null && json['attendanceStatus'] is int) { + attendanceStatus = IosAttendanceStatus.values[json['attendanceStatus']]; + } } } From b73bcaad5ca6d386a7c62a907a98e2937a2df272 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 22 Aug 2019 09:45:10 +1000 Subject: [PATCH 36/49] update changelog about returning attendee status and if they're required --- device_calendar/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 1f6ea887..f2d7053e 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -3,6 +3,7 @@ * 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 * **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified as part of address issue [113](https://github.com/builttoroam/flutter_plugins/issues/113) # 0.2.2 19th August 2019 From b61804c14320853c607e8e183853ec9a98065dd2 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Thu, 22 Aug 2019 10:09:41 +1000 Subject: [PATCH 37/49] remove dependency on platform package --- device_calendar/lib/src/models/attendee.dart | 7 +++---- device_calendar/pubspec.yaml | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/device_calendar/lib/src/models/attendee.dart b/device_calendar/lib/src/models/attendee.dart index cff8a3c6..80da4dfd 100644 --- a/device_calendar/lib/src/models/attendee.dart +++ b/device_calendar/lib/src/models/attendee.dart @@ -1,4 +1,4 @@ -import 'package:platform/platform.dart'; +import 'dart:io' show Platform; import '../common/error_messages.dart'; import 'platform_specifics/android/attendee_details.dart'; @@ -29,12 +29,11 @@ class Attendee { name = json['name']; emailAddress = json['emailAddress']; - final platform = LocalPlatform(); - if (platform.isAndroid) { + if (Platform.isAndroid) { androidAttendeeDetails = AndroidAttendeeDetails.fromJson(json); } - if (platform.isIOS) { + if (Platform.isIOS) { iosAttendeeDetails = IosAttendeeDetails.fromJson(json); } } diff --git a/device_calendar/pubspec.yaml b/device_calendar/pubspec.yaml index 8267ad20..eab1854b 100644 --- a/device_calendar/pubspec.yaml +++ b/device_calendar/pubspec.yaml @@ -7,7 +7,6 @@ homepage: https://github.com/builttoroam/flutter_plugins/tree/develop/device_cal dependencies: flutter: sdk: flutter - platform: ^2.2.1 dev_dependencies: flutter_test: From 163f75e70b6989afb4fbc538e028cca6a0962ff3 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Fri, 23 Aug 2019 10:52:06 +1000 Subject: [PATCH 38/49] remove old code on create recurrence rules --- .../Classes/SwiftDeviceCalendarPlugin.swift | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 08bbdec8..4fea5b93 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -345,30 +345,6 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) ekEvent!.location = location - let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if (recurrenceRuleArguments != nil) { - let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger - let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger - let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger - var recurrenceInterval = 1 - let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber - let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] - - var recurrenceEnd:EKRecurrenceEnd? - if(endDate != nil) { - recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) - } else if(totalOccurrences != nil && totalOccurrences! > 0) { - recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) - } - - if(interval != nil && interval! > 1) { - recurrenceInterval = interval! - } - - ekEvent!.recurrenceRules = [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, end: recurrenceEnd)] - } else { - ekEvent!.recurrenceRules = nil - } do { try self.eventStore.save(ekEvent!, span: .futureEvents) result(ekEvent!.eventIdentifier) From 30cfa5afbf93d7fef5b7a2bab37b6349a43b3a1f Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 26 Aug 2019 09:38:52 +1000 Subject: [PATCH 39/49] support modifying attendees on iOS --- .../Classes/SwiftDeviceCalendarPlugin.swift | 107 +++++++++++++----- device_calendar/lib/device_calendar.dart | 3 + device_calendar/lib/src/device_calendar.dart | 3 +- device_calendar/lib/src/models/attendee.dart | 23 +++- device_calendar/lib/src/models/event.dart | 29 +++-- .../android/attendee_details.dart | 11 +- .../ios/attendee_details.dart | 14 ++- 7 files changed, 136 insertions(+), 54 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 4fea5b93..9665ca7d 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -6,6 +6,15 @@ extension Date { var millisecondsSinceEpoch: Double { return self.timeIntervalSince1970 * 1000.0 } } +extension EKParticipant { + var emailAddress: String? { + if(self.url.scheme == nil) { + return nil + } + + return url.absoluteString.replacingOccurrences(of: self.url.scheme! + ":", with: "") } +} + public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { struct Calendar: Codable { let id: String @@ -72,6 +81,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let eventStartDateArgument = "eventStartDate" let eventEndDateArgument = "eventEndDate" let eventLocationArgument = "eventLocation" + let attendeesArgument = "attendees" let recurrenceRuleArgument = "recurrenceRule" let recurrenceFrequencyArgument = "recurrenceFrequency" let totalOccurrencesArgument = "totalOccurrences" @@ -135,7 +145,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let eventIds = arguments[eventIdsArgument] as? [String] var events = [Event]() let specifiedStartEndDates = startDateMillisecondsSinceEpoch != nil && endDateDateMillisecondsSinceEpoch != nil - if (specifiedStartEndDates) { + if specifiedStartEndDates { let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch!.doubleValue / 1000.0) let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch!.doubleValue / 1000.0) let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) @@ -147,12 +157,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } - if (eventIds == nil) { + if eventIds == nil { self.encodeJsonAndFinish(codable: events, result: result) return } - if (specifiedStartEndDates) { + if specifiedStartEndDates { events = events.filter({ (e) -> Bool in e.calendarId == calendarId && eventIds!.contains(e.eventId) }) @@ -163,7 +173,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { for eventId in eventIds! { let ekEvent = self.eventStore.event(withIdentifier: eventId) - if(ekEvent == nil) { + if ekEvent == nil { continue } @@ -177,9 +187,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { private func createEventFromEkEvent(calendarId: String, ekEvent: EKEvent) -> Event { var attendees = [Attendee]() - if (ekEvent.attendees != nil) { + if ekEvent.attendees != nil { for ekParticipant in ekEvent.attendees! { - attendees.append(convertEkParticipantToAttendee(ekParticipant: ekParticipant)!) + let attendee = convertEkParticipantToAttendee(ekParticipant: ekParticipant); + if attendee == nil { + continue + } + + attendees.append(attendee!) } } @@ -201,18 +216,17 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } private func convertEkParticipantToAttendee(ekParticipant: EKParticipant?) -> Attendee? { - if(ekParticipant == nil) { + if ekParticipant == nil || ekParticipant?.emailAddress == nil { return nil } - - let emailAddress = ekParticipant!.url.absoluteString.replacingOccurrences(of: ekParticipant!.url.scheme! + ":", with: "") - let attendee = Attendee(name: ekParticipant!.name, emailAddress: emailAddress, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue) + + let attendee = Attendee(name: ekParticipant!.name, emailAddress: ekParticipant!.emailAddress!, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue) return attendee } private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { var recurrenceRule: RecurrenceRule? - if(ekEvent.hasRecurrenceRules) { + if ekEvent.hasRecurrenceRules { let ekRecurrenceRule = ekEvent.recurrenceRules![0] var frequency: Int switch ekRecurrenceRule.frequency { @@ -240,7 +254,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } var daysOfTheWeek: [Int]? - if(ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty) { + if ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty { daysOfTheWeek = [] for dayOfTheWeek in ekRecurrenceRule.daysOfTheWeek! { daysOfTheWeek!.append(dayOfTheWeek.dayOfTheWeek.rawValue - 1) @@ -254,7 +268,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } private func convertToIntArray(_ arguments: [NSNumber]?) -> [Int]? { - if(arguments?.isEmpty ?? true) { + if arguments?.isEmpty ?? true { return nil } @@ -268,7 +282,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if (recurrenceRuleArguments == nil) { + if recurrenceRuleArguments == nil { return nil } @@ -280,20 +294,20 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] var recurrenceEnd:EKRecurrenceEnd? - if(endDate != nil) { + if endDate != nil { recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) } else if(totalOccurrences != nil && totalOccurrences! > 0) { recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) } - if(interval != nil && interval! > 1) { + if interval != nil && interval! > 1 { recurrenceInterval = interval! } let daysOfTheWeekIndices = recurrenceRuleArguments![daysOfTheWeekArgument] as? [Int] var daysOfTheWeek : [EKRecurrenceDayOfWeek]? - if(daysOfTheWeekIndices != nil && !daysOfTheWeekIndices!.isEmpty) { + if daysOfTheWeekIndices != nil && !daysOfTheWeekIndices!.isEmpty { daysOfTheWeek = [] for dayOfWeekIndex in daysOfTheWeekIndices! { daysOfTheWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) @@ -303,6 +317,34 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: recurrenceRuleArguments![monthsOfTheYearArgument] as? [NSNumber], weeksOfTheYear: recurrenceRuleArguments![weeksOfTheYearArgument] as? [NSNumber], daysOfTheYear: nil, setPositions: recurrenceRuleArguments![setPositionsArgument] as? [NSNumber], end: recurrenceEnd)] } + fileprivate func createAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { + let attendeesArguments = arguments[attendeesArgument] as? [Dictionary] + if attendeesArguments != nil { + var attendees = [EKParticipant]() + for attendeeArguments in attendeesArguments! { + let emailAddress = attendeeArguments["emailAddress"] as! String + if(ekEvent!.attendees != nil) { + let existingAttendee = ekEvent!.attendees!.first { element in + return element.emailAddress == emailAddress + } + if existingAttendee != nil && ekEvent!.organizer?.emailAddress != existingAttendee?.emailAddress{ + attendees.append(existingAttendee!) + continue + } + } + + let attendee = createParticipant(emailAddress: emailAddress) + if(attendee == nil) { + continue + } + + attendees.append(attendee!) + } + + ekEvent!.setValue(attendees, forKey: "attendees") + } + } + private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary @@ -321,13 +363,13 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return } - if (!(ekCalendar!.allowsContentModifications)) { + if !(ekCalendar!.allowsContentModifications) { self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) return } var ekEvent: EKEvent? - if(eventId == nil) { + if eventId == nil { ekEvent = EKEvent.init(eventStore: self.eventStore) } else { ekEvent = self.eventStore.event(withIdentifier: eventId!) @@ -342,9 +384,10 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { ekEvent!.startDate = startDate ekEvent!.endDate = endDate ekEvent!.calendar = ekCalendar! - ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) ekEvent!.location = location - + ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) + createAttendees(arguments, ekEvent) + do { try self.eventStore.save(ekEvent!, span: .futureEvents) result(ekEvent!.eventIdentifier) @@ -355,24 +398,34 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { }, result: result) } + private func createParticipant(emailAddress: String) -> EKParticipant? { + let ekAttendeeClass: AnyClass? = NSClassFromString("EKAttendee") + if let type = ekAttendeeClass as? NSObject.Type { + let participant = type.init() + participant.setValue(emailAddress, forKey: "emailAddress") + return participant as? EKParticipant + } + return nil + } + private func deleteEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary let calendarId = arguments[calendarIdArgument] as! String let eventId = arguments[eventIdArgument] as! String let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) - if (ekCalendar == nil) { + if ekCalendar == nil { self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) return } - if (!(ekCalendar!.allowsContentModifications)) { + if !(ekCalendar!.allowsContentModifications) { self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) return } let ekEvent = self.eventStore.event(withIdentifier: eventId) - if (ekEvent == nil) { + if ekEvent == nil { self.finishWithEventNotFoundError(result: result, eventId: eventId) return } @@ -418,7 +471,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } private func checkPermissionsThenExecute(permissionsGrantedAction: () -> Void, result: @escaping FlutterResult) { - if(hasPermissions()) { + if hasPermissions() { permissionsGrantedAction() return } @@ -426,7 +479,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } private func requestPermissions(completion: @escaping (Bool) -> Void) { - if(hasPermissions()) { + if hasPermissions() { completion(true) return } @@ -442,7 +495,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } private func requestPermissions(_ result: @escaping FlutterResult) { - if(hasPermissions()) { + if hasPermissions() { result(true) } eventStore.requestAccess(to: .event, completion: { diff --git a/device_calendar/lib/device_calendar.dart b/device_calendar/lib/device_calendar.dart index 6501f008..674e3fc0 100644 --- a/device_calendar/lib/device_calendar.dart +++ b/device_calendar/lib/device_calendar.dart @@ -8,4 +8,7 @@ export 'src/models/result.dart'; export 'src/models/event.dart'; export 'src/models/retrieve_events_params.dart'; export 'src/models/recurrence_rule.dart'; +export 'src/models/platform_specifics/ios/attendee_details.dart'; +export 'src/models/platform_specifics/ios/role.dart'; +export 'src/models/platform_specifics/android/attendee_details.dart'; export 'src/device_calendar.dart'; diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 9e7a77e9..9410748a 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -181,7 +181,8 @@ class DeviceCalendarPlugin { 'eventStartDate': event.start.millisecondsSinceEpoch, 'eventEndDate': event.end.millisecondsSinceEpoch, 'eventLocation': event.location, - 'recurrenceRule': event.recurrenceRule?.toJson() + 'recurrenceRule': event.recurrenceRule?.toJson(), + 'attendees': event.attendees?.map((a) => a.toJson())?.toList(), }); } catch (e) { _parsePlatformExceptionAndUpdateResult(e, res); diff --git a/device_calendar/lib/src/models/attendee.dart b/device_calendar/lib/src/models/attendee.dart index 80da4dfd..095c7c90 100644 --- a/device_calendar/lib/src/models/attendee.dart +++ b/device_calendar/lib/src/models/attendee.dart @@ -6,13 +6,13 @@ import 'platform_specifics/ios/attendee_details.dart'; /// A person attending an event class Attendee { - /// The name of the attendee + /// The name of the attendee. Currently has no effect when saving attendees on iOS. String name; /// The email address of the attendee String emailAddress; - /// Details about the attendee that are specific to iOS. + /// Details about the attendee that are specific to iOS. Currently has no effect when saving attendees on iOS. /// When reading details for an existing event, this will only be populated on iOS devices. IosAttendeeDetails iosAttendeeDetails; @@ -20,7 +20,11 @@ class Attendee { /// When reading details for an existing event, this will only be populated on Android devices. AndroidAttendeeDetails androidAttendeeDetails; - Attendee({this.name, this.emailAddress}); + Attendee( + {this.name, + this.emailAddress, + this.iosAttendeeDetails, + this.androidAttendeeDetails}); Attendee.fromJson(Map json) { if (json == null) { @@ -38,9 +42,16 @@ class Attendee { } } - /*Map toJson() { + Map toJson() { final Map data = Map(); - data['name'] = this.name; + data['name'] = name; + data['emailAddress'] = emailAddress; + if (Platform.isIOS && iosAttendeeDetails != null) { + data.addEntries(iosAttendeeDetails.toJson().entries); + } + if (Platform.isAndroid && androidAttendeeDetails != null) { + data.addEntries(androidAttendeeDetails.toJson().entries); + } return data; - }*/ + } } diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index 9fd1fcb4..abda62e3 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -43,6 +43,7 @@ class Event { this.start, this.end, this.description, + this.attendees, this.recurrenceRule}); Event.fromJson(Map json) { @@ -79,22 +80,18 @@ class Event { Map toJson() { final Map data = Map(); - data['eventId'] = this.eventId; - data['calendarId'] = this.calendarId; - data['title'] = this.title; - data['description'] = this.description; - data['start'] = this.start.millisecondsSinceEpoch; - data['end'] = this.end.millisecondsSinceEpoch; - data['allDay'] = this.allDay; - data['location'] = this.location; - /*if (attendees != null) { - List> attendeesJson = List(); - for (var attendee in attendees) { - var attendeeJson = attendee.toJson(); - attendeesJson.add(attendeeJson); - } - data['attendees'] = attendeesJson; - }*/ + data['eventId'] = eventId; + data['calendarId'] = calendarId; + data['title'] = title; + data['description'] = description; + data['start'] = start.millisecondsSinceEpoch; + data['end'] = end.millisecondsSinceEpoch; + data['allDay'] = allDay; + data['location'] = location; + if (attendees != null) { + print('have attendees'); + data['attendees'] = attendees.map((a) => a.toJson()).toList(); + } if (recurrenceRule != null) { data['recurrenceRule'] = recurrenceRule.toJson(); } diff --git a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart index b13ec658..a9d1475e 100644 --- a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart @@ -3,10 +3,13 @@ import 'package:device_calendar/src/models/platform_specifics/android/attendance import '../../../common/error_messages.dart'; class AndroidAttendeeDetails { + AndroidAttendanceStatus _attendanceStatus; + /// Indicates if the attendee is required for this event bool isRequired; - AndroidAttendanceStatus attendanceStatus; + /// The attendee's status for the event. This is read-only + AndroidAttendanceStatus get attendanceStatus => _attendanceStatus; AndroidAttendeeDetails.fromJson(Map json) { if (json == null) { @@ -14,8 +17,12 @@ class AndroidAttendeeDetails { } isRequired = json['isRequired']; if (json['attendanceStatus'] != null && json['attendanceStatus'] is int) { - attendanceStatus = + _attendanceStatus = AndroidAttendanceStatus.values[json['attendanceStatus']]; } } + + Map toJson() { + return {'isRequired': isRequired}; + } } diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart index bc4209d6..37937005 100644 --- a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart @@ -4,10 +4,14 @@ import '../../../common/error_messages.dart'; import 'role.dart'; class IosAttendeeDetails { + IosAttendanceStatus _attendanceStatus; // The attendee's role at an event Role role; - IosAttendanceStatus attendanceStatus; + /// The attendee's status for the event. This is read-only + IosAttendanceStatus get attendanceStatus => _attendanceStatus; + + IosAttendeeDetails(this.role); IosAttendeeDetails.fromJson(Map json) { if (json == null) { @@ -17,7 +21,13 @@ class IosAttendeeDetails { role = Role.values[json['role']]; } if (json['attendanceStatus'] != null && json['attendanceStatus'] is int) { - attendanceStatus = IosAttendanceStatus.values[json['attendanceStatus']]; + _attendanceStatus = IosAttendanceStatus.values[json['attendanceStatus']]; } } + + Map toJson() { + return { + 'role': role?.index, + }; + } } From 5070399734260fd3befcbafebeed2d2b397b39a2 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 26 Aug 2019 15:48:29 +1000 Subject: [PATCH 40/49] add ability to modify attendees on android --- device_calendar/CHANGELOG.md | 1 + device_calendar/README.md | 1 + .../devicecalendar/CalendarDelegate.kt | 123 +++++++++++------- .../devicecalendar/DeviceCalendarPlugin.kt | 13 ++ .../devicecalendar/models/Attendee.kt | 2 +- .../presentation/pages/calendar_event.dart | 13 ++ .../lib/presentation/pages/calendars.dart | 11 +- device_calendar/lib/src/models/event.dart | 9 +- .../android/attendee_details.dart | 3 + .../ios/attendee_details.dart | 5 +- 10 files changed, 125 insertions(+), 56 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index f2d7053e..b50a6a1d 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -4,6 +4,7 @@ * 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 * **BREAKING CHANGE** `retrieveCalendars` and `retrieveEvents` now return lists that cannot be modified as part of address issue [113](https://github.com/builttoroam/flutter_plugins/issues/113) # 0.2.2 19th August 2019 diff --git a/device_calendar/README.md b/device_calendar/README.md index 5626c9c9..2e8a0e28 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -11,6 +11,7 @@ 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) **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. diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index eaa10335..b9ed1aa2 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -212,7 +212,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(_gson?.toJson(calendars), pendingChannelResult) } catch (e: Exception) { finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) - println(e.message) } finally { cursor?.close() } @@ -250,7 +249,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } } catch (e: Exception) { - println(e.message) + finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) } finally { cursor?.close() } @@ -304,11 +303,14 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } while (eventsCursor.moveToNext()) - updateEventAttendees(events, contentResolver, pendingChannelResult) + for (event in events) { + val attendees = retrieveAttendees(event.eventId!!, contentResolver) + event.organizer = attendees.firstOrNull { it.isOrganizer != null && it.isOrganizer } + event.attendees = attendees + } } } catch (e: Exception) { finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) - println(e.message) } finally { eventsCursor?.close() } @@ -337,37 +339,28 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } val contentResolver: ContentResolver? = _context?.contentResolver - val values = ContentValues() - val duration: String? = null - values.put(Events.DTSTART, event.start!!) - values.put(Events.DTEND, event.end!!) - values.put(Events.TITLE, event.title) - values.put(Events.DESCRIPTION, event.description) - values.put(Events.EVENT_LOCATION, event.location) - values.put(Events.CALENDAR_ID, calendarId) - values.put(Events.DURATION, duration) - - // MK using current device time zone - val currentTimeZone: TimeZone = java.util.Calendar.getInstance().timeZone - values.put(Events.EVENT_TIMEZONE, currentTimeZone.displayName) - if (event.recurrenceRule != null) { - val recurrenceRuleParams = buildRecurrenceRuleParams(event.recurrenceRule!!) - values.put(Events.RRULE, recurrenceRuleParams) - } + val values = buildEventContentValues(event, calendarId) try { var eventId: Long? = event.eventId?.toLongOrNull() if (eventId == null) { val uri = contentResolver?.insert(Events.CONTENT_URI, values) // get the event ID that is the last element in the Uri - eventId = java.lang.Long.parseLong(uri?.lastPathSegment) + eventId = java.lang.Long.parseLong(uri?.lastPathSegment!!) + insertAttendees(event.attendees, eventId, contentResolver) } else { contentResolver?.update(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), values, null, null) + val existingAttendees = retrieveAttendees(eventId.toString(), contentResolver) + val attendeesToDelete = if (event.attendees.isNotEmpty()) existingAttendees.filter { existingAttendee -> event.attendees.all { it.emailAddress != existingAttendee.emailAddress } } else existingAttendees + for (attendeeToDelete in attendeesToDelete) { + deleteAttendee(eventId, attendeeToDelete, contentResolver) + } + val attendeesToInsert = event.attendees.filter { existingAttendees.all { existingAttendee -> existingAttendee.emailAddress != it.emailAddress }} + insertAttendees(attendeesToInsert, eventId, contentResolver) } finishWithSuccess(eventId.toString(), pendingChannelResult) } catch (e: Exception) { finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) - println(e.message) } } else { val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, CREATE_OR_UPDATE_EVENT_REQUEST_CODE, calendarId) @@ -376,6 +369,58 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } + private fun buildEventContentValues(event: Event, calendarId: String): ContentValues { + val values = ContentValues() + val duration: String? = null + values.put(Events.DTSTART, event.start!!) + values.put(Events.DTEND, event.end!!) + values.put(Events.TITLE, event.title) + values.put(Events.DESCRIPTION, event.description) + values.put(Events.EVENT_LOCATION, event.location) + values.put(Events.CALENDAR_ID, calendarId) + values.put(Events.DURATION, duration) + + // MK using current device time zone + val currentTimeZone: TimeZone = java.util.Calendar.getInstance().timeZone + values.put(Events.EVENT_TIMEZONE, currentTimeZone.displayName) + if (event.recurrenceRule != null) { + val recurrenceRuleParams = buildRecurrenceRuleParams(event.recurrenceRule!!) + values.put(Events.RRULE, recurrenceRuleParams) + } + return values + } + + @SuppressLint("MissingPermission") + private fun insertAttendees(attendees: List, eventId: Long?, contentResolver: ContentResolver?) { + val attendeesValues: MutableList = mutableListOf() + for (attendee in attendees) { + val attendeeValues = ContentValues().apply { + put(CalendarContract.Attendees.ATTENDEE_EMAIL, attendee.emailAddress) + put( + CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, + CalendarContract.Attendees.RELATIONSHIP_ATTENDEE + ) + put(CalendarContract.Attendees.ATTENDEE_TYPE, if (attendee.isRequired != null && attendee.isRequired) CalendarContract.Attendees.TYPE_REQUIRED else CalendarContract.Attendees.TYPE_OPTIONAL) + put( + CalendarContract.Attendees.ATTENDEE_STATUS, + CalendarContract.Attendees.ATTENDEE_STATUS_INVITED + ) + put(CalendarContract.Attendees.EVENT_ID, eventId) + } + attendeesValues.add(attendeeValues) + } + + contentResolver?.bulkInsert(CalendarContract.Attendees.CONTENT_URI, attendeesValues.toTypedArray()) + } + + @SuppressLint("MissingPermission") + private fun deleteAttendee(eventId: Long, attendee: Attendee, contentResolver: ContentResolver?) { + val selection = "(" + CalendarContract.Attendees.EVENT_ID + " = ?) AND (" + CalendarContract.Attendees.ATTENDEE_EMAIL + " = ?)" + val selectionArgs = arrayOf(eventId.toString() + "", attendee.emailAddress) + contentResolver?.delete(CalendarContract.Attendees.CONTENT_URI, selection, selectionArgs) + + } + fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result) { if (arePermissionsGranted()) { val existingCal = retrieveCalendar(calendarId, pendingChannelResult, true) @@ -546,35 +591,20 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } @SuppressLint("MissingPermission") - private fun updateEventAttendees(events: MutableList, contentResolver: ContentResolver?, pendingChannelResult: MethodChannel.Result) { - val eventsMapById = events.associateBy { it.eventId } - val attendeesQueryEventIds = eventsMapById.values.map { "(${CalendarContract.Attendees.EVENT_ID} = ${it.eventId})" } - val attendeesQuery = attendeesQueryEventIds.joinToString(" OR ") + private fun retrieveAttendees(eventId: String, contentResolver: ContentResolver?): MutableList { + val attendees:MutableList = mutableListOf() + val attendeesQuery = "(${CalendarContract.Attendees.EVENT_ID} = ${eventId})" val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null) - - try { - if (attendeesCursor?.moveToFirst() == true) { + attendeesCursor.use { cursor -> + if (cursor?.moveToFirst() == true) { do { val attendee = parseAttendee(attendeesCursor) ?: continue - - if (eventsMapById.containsKey(attendee.eventId.toString())) { - val attendeeEvent = eventsMapById[attendee.eventId.toString()] - if (attendee.isOrganizer) { - attendeeEvent?.organizer = attendee - } - - attendeeEvent?.attendees?.add(attendee) - } - - } while (attendeesCursor.moveToNext()) + attendees.add(attendee) + } while (cursor.moveToNext()) } - } catch (e: Exception) { - finishWithError(GENERIC_ERROR, e.message, pendingChannelResult) - println(e.message) - } finally { - attendeesCursor?.close() } + return attendees } @Synchronized @@ -622,7 +652,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { rr.interval = recurrenceRule.interval!! } - if (recurrenceRule.recurrenceFrequency == RecurrenceFrequency.WEEKLY || recurrenceRule.recurrenceFrequency == RecurrenceFrequency.MONTHLY || recurrenceRule.recurrenceFrequency == RecurrenceFrequency.YEARLY) { rr.byDayPart = buildByDayPart(recurrenceRule) } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 1ae72468..fff158cf 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -4,6 +4,7 @@ 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 @@ -44,6 +45,10 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 lateinit var _registrar: Registrar private lateinit var _calendarDelegate: CalendarDelegate @@ -124,6 +129,14 @@ class DeviceCalendarPlugin() : MethodCallHandler { event.recurrenceRule = recurrenceRule } + if (call.hasArgument(ATTENDEES_ARGUMENT) && call.argument>>(ATTENDEES_ARGUMENT) != null) { + event.attendees = mutableListOf() + val attendeesArgs = call.argument>>(ATTENDEES_ARGUMENT)!! + for (attendeeArgs in attendeesArgs) { + event.attendees.add(Attendee(null, attendeeArgs[EMAIL_ADDRESS_ARGUMENT] as String, attendeeArgs[NAME_ARGUMENT] as String?, attendeeArgs[IS_REQUIRED_ARGUMENT] as Boolean?, null, null)) + } + } + return event } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt index 45ba9872..9927e0d6 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt @@ -1,4 +1,4 @@ package com.builttoroam.devicecalendar.models -class Attendee(val eventId: Long, val emailAddress: String, val name: String?, val isRequired: Boolean, val attendanceStatus: Int, val isOrganizer: Boolean) { +class Attendee(var eventId: Long?, val emailAddress: String, val name: String?, val isRequired: Boolean?, val attendanceStatus: Int?, val isOrganizer: Boolean?) { } \ No newline at end of file diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index d0d75c1c..29c005ae 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -78,6 +78,10 @@ class _CalendarEventPageState extends State { _startDate = _event.start; _endDate = _event.end; _isRecurringEvent = _event.recurrenceRule != null; + if (_event.organizer != null) { + printAttendeeDetails(_event.organizer); + } + _event.attendees?.forEach((a) => printAttendeeDetails(a)); if (_isRecurringEvent) { _interval = _event.recurrenceRule.interval; _totalOccurrences = _event.recurrenceRule.totalOccurrences; @@ -107,6 +111,15 @@ class _CalendarEventPageState extends State { } } + void printAttendeeDetails(Attendee attendee) { + print( + 'attendee name: ${attendee.name}, email address: ${attendee.emailAddress}'); + print( + 'ios specifics - status: ${attendee.iosAttendeeDetails?.attendanceStatus}, role: ${attendee.iosAttendeeDetails?.role}'); + print( + 'android specifics - status ${attendee.androidAttendeeDetails?.attendanceStatus}, is required: ${attendee.androidAttendeeDetails?.isRequired}'); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/device_calendar/example/lib/presentation/pages/calendars.dart b/device_calendar/example/lib/presentation/pages/calendars.dart index 39787602..c2f32f44 100644 --- a/device_calendar/example/lib/presentation/pages/calendars.dart +++ b/device_calendar/example/lib/presentation/pages/calendars.dart @@ -32,7 +32,14 @@ class _CalendarsPageState extends State { title: Text('Calendars'), ), body: Column( - children: [ + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + 'WARNING: some aspects of saving events are hardcoded in this example app. As such we recommend you do not modify existing events as this may result in loss of information', + style: Theme.of(context).textTheme.title, + ), + ), Expanded( flex: 1, child: ListView.builder( @@ -53,7 +60,7 @@ class _CalendarsPageState extends State { flex: 1, child: Text( _calendars[index].name, - style: TextStyle(fontSize: 25.0), + style: Theme.of(context).textTheme.subhead, ), ), Icon(_calendars[index].isReadOnly diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index abda62e3..19011c91 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -4,6 +4,8 @@ import 'recurrence_rule.dart'; /// An event associated with a calendar class Event { + Attendee _organizer; + /// The unique identifier for this event String eventId; @@ -31,8 +33,8 @@ class Event { /// A list of attendees for this event List attendees; - /// The organizer of this event - Attendee organizer; + /// The organizer of this event. This property is read-only + Attendee get organizer => _organizer; /// The recurrence rule for this event RecurrenceRule recurrenceRule; @@ -74,7 +76,7 @@ class Event { recurrenceRule = RecurrenceRule.fromJson(json['recurrenceRule']); } if (json['organizer'] != null) { - organizer = Attendee.fromJson(json['organizer']); + _organizer = Attendee.fromJson(json['organizer']); } } @@ -89,7 +91,6 @@ class Event { data['allDay'] = allDay; data['location'] = location; if (attendees != null) { - print('have attendees'); data['attendees'] = attendees.map((a) => a.toJson()).toList(); } if (recurrenceRule != null) { diff --git a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart index a9d1475e..57dfe5c3 100644 --- a/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/android/attendee_details.dart @@ -1,4 +1,5 @@ import 'package:device_calendar/src/models/platform_specifics/android/attendance_status.dart'; +import 'package:flutter/foundation.dart'; import '../../../common/error_messages.dart'; @@ -11,6 +12,8 @@ class AndroidAttendeeDetails { /// The attendee's status for the event. This is read-only AndroidAttendanceStatus get attendanceStatus => _attendanceStatus; + AndroidAttendeeDetails({@required this.isRequired}); + AndroidAttendeeDetails.fromJson(Map json) { if (json == null) { throw ArgumentError(ErrorMessages.fromJsonMapIsNull); diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart index 37937005..45b56f75 100644 --- a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart @@ -1,6 +1,7 @@ -import 'package:device_calendar/src/models/platform_specifics/ios/attendance_status.dart'; +import 'package:flutter/foundation.dart'; import '../../../common/error_messages.dart'; +import 'attendance_status.dart'; import 'role.dart'; class IosAttendeeDetails { @@ -11,7 +12,7 @@ class IosAttendeeDetails { /// The attendee's status for the event. This is read-only IosAttendanceStatus get attendanceStatus => _attendanceStatus; - IosAttendeeDetails(this.role); + IosAttendeeDetails({this.role}); IosAttendeeDetails.fromJson(Map json) { if (json == null) { From e697ee3832c5a939ff14a174489c216b68a7d24e Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 26 Aug 2019 15:48:39 +1000 Subject: [PATCH 41/49] fix reading email address of attendees on ios --- .../ios/Classes/SwiftDeviceCalendarPlugin.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 9665ca7d..be0380b1 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -8,11 +8,8 @@ extension Date { extension EKParticipant { var emailAddress: String? { - if(self.url.scheme == nil) { - return nil - } - - return url.absoluteString.replacingOccurrences(of: self.url.scheme! + ":", with: "") } + return self.value(forKey: "emailAddress") as? String + } } public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { From 36924c327af5871f75c64ab184993e767f585859 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 26 Aug 2019 16:39:29 +1000 Subject: [PATCH 42/49] remove event id from android attendee ctor --- .../kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt | 3 +-- .../com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt | 2 +- .../kotlin/com/builttoroam/devicecalendar/models/Attendee.kt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index b9ed1aa2..25e840b8 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -13,7 +13,6 @@ import android.net.Uri import android.provider.CalendarContract import android.provider.CalendarContract.Events import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EMAIL_INDEX -import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EVENT_ID_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_RELATIONSHIP_INDEX @@ -576,7 +575,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } - return Attendee(cursor.getLong(ATTENDEE_EVENT_ID_INDEX), cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_STATUS_INDEX),cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + return Attendee(cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_STATUS_INDEX),cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index fff158cf..6ab7a809 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -133,7 +133,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { event.attendees = mutableListOf() val attendeesArgs = call.argument>>(ATTENDEES_ARGUMENT)!! for (attendeeArgs in attendeesArgs) { - event.attendees.add(Attendee(null, attendeeArgs[EMAIL_ADDRESS_ARGUMENT] as String, attendeeArgs[NAME_ARGUMENT] as String?, attendeeArgs[IS_REQUIRED_ARGUMENT] as Boolean?, null, null)) + event.attendees.add(Attendee(attendeeArgs[EMAIL_ADDRESS_ARGUMENT] as String, attendeeArgs[NAME_ARGUMENT] as String?, attendeeArgs[IS_REQUIRED_ARGUMENT] as Boolean?, null, null)) } } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt index 9927e0d6..a5662ab1 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Attendee.kt @@ -1,4 +1,4 @@ package com.builttoroam.devicecalendar.models -class Attendee(var eventId: Long?, val emailAddress: String, val name: String?, val isRequired: Boolean?, val attendanceStatus: Int?, val isOrganizer: Boolean?) { +class Attendee(val emailAddress: String, val name: String?, val isRequired: Boolean?, val attendanceStatus: Int?, val isOrganizer: Boolean?) { } \ No newline at end of file From 7d64c12131db4d39284640c4f749bd65f3aafad7 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Mon, 26 Aug 2019 17:53:27 +1000 Subject: [PATCH 43/49] support reading/writing of event reminders on android --- .../devicecalendar/CalendarDelegate.kt | 100 +++++++++++++++--- .../devicecalendar/DeviceCalendarPlugin.kt | 14 ++- .../devicecalendar/common/Constants.kt | 6 ++ .../devicecalendar/models/Event.kt | 1 + .../devicecalendar/models/Reminder.kt | 4 + .../presentation/pages/calendar_event.dart | 1 + device_calendar/lib/device_calendar.dart | 1 + device_calendar/lib/src/device_calendar.dart | 1 + device_calendar/lib/src/models/event.dart | 16 ++- device_calendar/lib/src/models/reminder.dart | 15 +++ 10 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Reminder.kt create mode 100644 device_calendar/lib/src/models/reminder.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 25e840b8..15c8a9f4 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -31,6 +31,8 @@ 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.REMINDER_MINUTES_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_PROJECTION import com.builttoroam.devicecalendar.common.DayOfWeek import com.builttoroam.devicecalendar.common.ErrorCodes.Companion.GENERIC_ERROR import com.builttoroam.devicecalendar.common.ErrorCodes.Companion.INVALID_ARGUMENT @@ -53,7 +55,7 @@ import org.dmfs.rfc5545.Weekday import org.dmfs.rfc5545.recur.Freq import java.text.SimpleDateFormat import java.util.* - +import com.builttoroam.devicecalendar.models.CalendarMethodsParametersCacheModel class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val RETRIEVE_CALENDARS_REQUEST_CODE = 0 @@ -204,7 +206,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val calendars: MutableList = mutableListOf() try { while (cursor?.moveToNext() == true) { - val calendar = parseCalendar(cursor) ?: continue + val calendar = parseCalendarRow(cursor) ?: continue calendars.add(calendar) } @@ -236,7 +238,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { try { if (cursor?.moveToFirst() == true) { - val calendar = parseCalendar(cursor) + val calendar = parseCalendarRow(cursor) if (isInternalCall) { return calendar } else { @@ -306,6 +308,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val attendees = retrieveAttendees(event.eventId!!, contentResolver) event.organizer = attendees.firstOrNull { it.isOrganizer != null && it.isOrganizer } event.attendees = attendees + event.reminders = retrieveReminders(event.eventId!!, contentResolver) } } } catch (e: Exception) { @@ -346,6 +349,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { // get the event ID that is the last element in the Uri eventId = java.lang.Long.parseLong(uri?.lastPathSegment!!) insertAttendees(event.attendees, eventId, contentResolver) + insertReminders(event.reminders, eventId, contentResolver) } else { contentResolver?.update(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), values, null, null) val existingAttendees = retrieveAttendees(eventId.toString(), contentResolver) @@ -353,8 +357,11 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { for (attendeeToDelete in attendeesToDelete) { deleteAttendee(eventId, attendeeToDelete, contentResolver) } - val attendeesToInsert = event.attendees.filter { existingAttendees.all { existingAttendee -> existingAttendee.emailAddress != it.emailAddress }} + + val attendeesToInsert = event.attendees.filter { existingAttendees.all { existingAttendee -> existingAttendee.emailAddress != it.emailAddress } } insertAttendees(attendeesToInsert, eventId, contentResolver) + deleteExistingReminders(contentResolver, eventId) + insertReminders(event.reminders, eventId, contentResolver!!) } finishWithSuccess(eventId.toString(), pendingChannelResult) @@ -368,6 +375,38 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } + private fun deleteExistingReminders(contentResolver: ContentResolver?, eventId: Long) { + val cursor = CalendarContract.Reminders.query(contentResolver, eventId, arrayOf( + CalendarContract.Reminders._ID + )) + while (cursor != null && cursor.moveToNext()) { + var reminderUri: Uri? = null + val reminderId = cursor.getLong(0) + if (reminderId > 0) { + reminderUri = ContentUris.withAppendedId(CalendarContract.Reminders.CONTENT_URI, reminderId) + } + if (reminderUri != null) { + contentResolver?.delete(reminderUri, null, null) + } + } + cursor?.close() + } + + @SuppressLint("MissingPermission") + private fun insertReminders(reminders: List, eventId: Long?, contentResolver: ContentResolver) { + if (reminders.isEmpty()) { + return + } + val remindersContentValues = reminders.map { + ContentValues().apply { + put(CalendarContract.Reminders.EVENT_ID, eventId) + put(CalendarContract.Reminders.MINUTES, it.minutes) + put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT) + } + }.toTypedArray() + contentResolver.bulkInsert(CalendarContract.Reminders.CONTENT_URI, remindersContentValues) + } + private fun buildEventContentValues(event: Event, calendarId: String): ContentValues { val values = ContentValues() val duration: String? = null @@ -391,25 +430,27 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @SuppressLint("MissingPermission") private fun insertAttendees(attendees: List, eventId: Long?, contentResolver: ContentResolver?) { - val attendeesValues: MutableList = mutableListOf() - for (attendee in attendees) { - val attendeeValues = ContentValues().apply { - put(CalendarContract.Attendees.ATTENDEE_EMAIL, attendee.emailAddress) + if (attendees.isEmpty()) { + return + } + + val attendeesValues = attendees.map { + ContentValues().apply { + put(CalendarContract.Attendees.ATTENDEE_EMAIL, it.emailAddress) put( CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, CalendarContract.Attendees.RELATIONSHIP_ATTENDEE ) - put(CalendarContract.Attendees.ATTENDEE_TYPE, if (attendee.isRequired != null && attendee.isRequired) CalendarContract.Attendees.TYPE_REQUIRED else CalendarContract.Attendees.TYPE_OPTIONAL) + put(CalendarContract.Attendees.ATTENDEE_TYPE, if (it.isRequired != null && it.isRequired) CalendarContract.Attendees.TYPE_REQUIRED else CalendarContract.Attendees.TYPE_OPTIONAL) put( CalendarContract.Attendees.ATTENDEE_STATUS, CalendarContract.Attendees.ATTENDEE_STATUS_INVITED ) put(CalendarContract.Attendees.EVENT_ID, eventId) } - attendeesValues.add(attendeeValues) - } + }.toTypedArray() - contentResolver?.bulkInsert(CalendarContract.Attendees.CONTENT_URI, attendeesValues.toTypedArray()) + contentResolver?.bulkInsert(CalendarContract.Attendees.CONTENT_URI, attendeesValues) } @SuppressLint("MissingPermission") @@ -471,7 +512,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } } - private fun parseCalendar(cursor: Cursor?): Calendar? { + private fun parseCalendarRow(cursor: Cursor?): Calendar? { if (cursor == null) { return null } @@ -570,12 +611,20 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { }?.toMutableList() } - private fun parseAttendee(cursor: Cursor?): Attendee? { + private fun parseAttendeeRow(cursor: Cursor?): Attendee? { + if (cursor == null) { + return null + } + + return Attendee(cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_STATUS_INDEX), cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + } + + private fun parseReminderRow(cursor: Cursor?): Reminder? { if (cursor == null) { return null } - return Attendee(cursor.getString(ATTENDEE_EMAIL_INDEX), cursor.getString(ATTENDEE_NAME_INDEX), cursor.getInt(ATTENDEE_TYPE_INDEX) == CalendarContract.Attendees.TYPE_REQUIRED, cursor.getInt(ATTENDEE_STATUS_INDEX),cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER) + return Reminder(cursor.getInt(REMINDER_MINUTES_INDEX)) } private fun isCalendarReadOnly(accessLevel: Int): Boolean { @@ -591,13 +640,13 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { @SuppressLint("MissingPermission") private fun retrieveAttendees(eventId: String, contentResolver: ContentResolver?): MutableList { - val attendees:MutableList = mutableListOf() + val attendees: MutableList = mutableListOf() val attendeesQuery = "(${CalendarContract.Attendees.EVENT_ID} = ${eventId})" val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null) attendeesCursor.use { cursor -> if (cursor?.moveToFirst() == true) { do { - val attendee = parseAttendee(attendeesCursor) ?: continue + val attendee = parseAttendeeRow(attendeesCursor) ?: continue attendees.add(attendee) } while (cursor.moveToNext()) } @@ -606,6 +655,23 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return attendees } + @SuppressLint("MissingPermission") + private fun retrieveReminders(eventId: String, contentResolver: ContentResolver?) : MutableList { + val reminders: MutableList = mutableListOf() + val remindersQuery = "(${CalendarContract.Reminders.EVENT_ID} = ${eventId})" + val remindersCursor = contentResolver?.query(CalendarContract.Reminders.CONTENT_URI, REMINDER_PROJECTION, remindersQuery, null, null) + remindersCursor.use { cursor -> + if (cursor?.moveToFirst() == true) { + do { + val reminder = parseReminderRow(remindersCursor) ?: continue + reminders.add(reminder) + } while (cursor.moveToNext()) + } + } + + return reminders + } + @Synchronized private fun generateUniqueRequestCodeAndCacheParameters(parameters: CalendarMethodsParametersCacheModel): Int { // TODO we can ran out of Int's at some point so this probably should re-use some of the freed ones diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 6ab7a809..15de3268 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -7,11 +7,11 @@ 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" @@ -49,6 +49,8 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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 @@ -137,6 +139,14 @@ class DeviceCalendarPlugin() : MethodCallHandler { } } + if (call.hasArgument(REMINDERS_ARGUMENT) && call.argument>>(REMINDERS_ARGUMENT) != null) { + event.reminders = mutableListOf() + val remindersArgs = call.argument>>(REMINDERS_ARGUMENT)!! + for (reminderArgs in remindersArgs) { + event.reminders.add(Reminder(reminderArgs[MINUTES_ARGUMENT] as Int)) + } + } + return event } diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt index e64ba4ce..29e8ffe7 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt @@ -57,5 +57,11 @@ class Constants { CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, CalendarContract.Attendees.ATTENDEE_STATUS ) + + const val REMINDER_MINUTES_INDEX = 1 + val REMINDER_PROJECTION: Array = arrayOf( + CalendarContract.Reminders.EVENT_ID, + CalendarContract.Reminders.MINUTES + ) } } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt index db9881df..f7c42808 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Event.kt @@ -12,4 +12,5 @@ class Event { var attendees: MutableList = mutableListOf() var recurrenceRule: RecurrenceRule? = null var organizer: Attendee? = null + var reminders: MutableList = mutableListOf() } \ No newline at end of file diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Reminder.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Reminder.kt new file mode 100644 index 00000000..2ae22f85 --- /dev/null +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Reminder.kt @@ -0,0 +1,4 @@ +package com.builttoroam.devicecalendar.models + +class Reminder(val minutes: Int) { +} \ No newline at end of file diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 29c005ae..c9412dce 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -82,6 +82,7 @@ class _CalendarEventPageState extends State { printAttendeeDetails(_event.organizer); } _event.attendees?.forEach((a) => printAttendeeDetails(a)); + _event.reminders?.forEach((r) => print('reminder ${r.minutes}')); if (_isRecurringEvent) { _interval = _event.recurrenceRule.interval; _totalOccurrences = _event.recurrenceRule.totalOccurrences; diff --git a/device_calendar/lib/device_calendar.dart b/device_calendar/lib/device_calendar.dart index 674e3fc0..461568b8 100644 --- a/device_calendar/lib/device_calendar.dart +++ b/device_calendar/lib/device_calendar.dart @@ -5,6 +5,7 @@ export 'src/common/recurrence_frequency.dart'; export 'src/models/attendee.dart'; export 'src/models/calendar.dart'; export 'src/models/result.dart'; +export 'src/models/reminder.dart'; export 'src/models/event.dart'; export 'src/models/retrieve_events_params.dart'; export 'src/models/recurrence_rule.dart'; diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index 9410748a..b537e565 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -183,6 +183,7 @@ class DeviceCalendarPlugin { 'eventLocation': event.location, 'recurrenceRule': event.recurrenceRule?.toJson(), 'attendees': event.attendees?.map((a) => a.toJson())?.toList(), + 'reminders': event.reminders?.map((r) => r.toJson())?.toList() }); } catch (e) { _parsePlatformExceptionAndUpdateResult(e, res); diff --git a/device_calendar/lib/src/models/event.dart b/device_calendar/lib/src/models/event.dart index 19011c91..6e0d38b8 100644 --- a/device_calendar/lib/src/models/event.dart +++ b/device_calendar/lib/src/models/event.dart @@ -1,3 +1,4 @@ +import '../../device_calendar.dart'; import '../common/error_messages.dart'; import 'attendee.dart'; import 'recurrence_rule.dart'; @@ -39,6 +40,8 @@ class Event { /// The recurrence rule for this event RecurrenceRule recurrenceRule; + List reminders; + Event(this.calendarId, {this.eventId, this.title, @@ -78,9 +81,15 @@ class Event { if (json['organizer'] != null) { _organizer = Attendee.fromJson(json['organizer']); } + if (json['reminders'] != null) { + reminders = json['reminders'].map((decodedReminder) { + return Reminder.fromJson(decodedReminder); + }).toList(); + } } - Map toJson() { + // TODO: look at using this method + /* Map toJson() { final Map data = Map(); data['eventId'] = eventId; data['calendarId'] = calendarId; @@ -96,6 +105,9 @@ class Event { if (recurrenceRule != null) { data['recurrenceRule'] = recurrenceRule.toJson(); } + if (reminders != null) { + data['reminders'] = reminders.map((r) => r.toJson()).toList(); + } return data; - } + }*/ } diff --git a/device_calendar/lib/src/models/reminder.dart b/device_calendar/lib/src/models/reminder.dart new file mode 100644 index 00000000..26ab5939 --- /dev/null +++ b/device_calendar/lib/src/models/reminder.dart @@ -0,0 +1,15 @@ +import 'package:flutter/foundation.dart'; + +class Reminder { + int minutes; + + Reminder({@required this.minutes}); + + Reminder.fromJson(Map json) { + minutes = json['minutes'] as int; + } + + Map toJson() { + return {'minutes': minutes}; + } +} From e01373a0d18a91f2b55512a79091101be360b7ea Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 27 Aug 2019 11:06:47 +1000 Subject: [PATCH 44/49] add ability to create reminders on ios relative to start of event --- device_calendar/CHANGELOG.md | 1 + device_calendar/README.md | 1 + .../Classes/SwiftDeviceCalendarPlugin.swift | 76 ++++++++++++++----- .../ios/attendee_details.dart | 2 - device_calendar/lib/src/models/reminder.dart | 4 +- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index b50a6a1d..90f93c69 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -5,6 +5,7 @@ * 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 as part of address issue [113](https://github.com/builttoroam/flutter_plugins/issues/113) # 0.2.2 19th August 2019 diff --git a/device_calendar/README.md b/device_calendar/README.md index 2e8a0e28..3ded41ff 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -12,6 +12,7 @@ A cross platform plugin for modifying calendars on the user's device. * 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. diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index be0380b1..90d0afaa 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -31,6 +31,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let location: String? let recurrenceRule: RecurrenceRule? let organizer: Attendee? + let reminders: [Reminder] } struct RecurrenceRule: Codable { @@ -52,6 +53,10 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let attendanceStatus: Int } + struct Reminder: Codable { + let minutes: Int + } + static let channelName = "plugins.builttoroam.com/device_calendar" let notFoundErrorCode = "404" let notAllowed = "405" @@ -88,6 +93,9 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let monthsOfTheYearArgument = "monthsOfTheYear" let weeksOfTheYearArgument = "weeksOfTheYear" let setPositionsArgument = "setPositions" + let emailAddressArgument = "emailAddress" + let remindersArgument = "reminders" + let minutesArgument = "minutes" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] public static func register(with registrar: FlutterPluginRegistrar) { @@ -195,6 +203,13 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { } } + var reminders = [Reminder]() + if ekEvent.alarms != nil { + for alarm in ekEvent.alarms! { + reminders.append(Reminder(minutes: Int(-alarm.relativeOffset / 60))) + } + } + let recurrenceRule = parseEKRecurrenceRules(ekEvent) let event = Event( eventId: ekEvent.eventIdentifier, @@ -207,7 +222,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { attendees: attendees, location: ekEvent.location, recurrenceRule: recurrenceRule, - organizer: convertEkParticipantToAttendee(ekParticipant: ekEvent.organizer) + organizer: convertEkParticipantToAttendee(ekParticipant: ekEvent.organizer), + reminders: reminders ) return event } @@ -314,32 +330,49 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return [EKRecurrenceRule(recurrenceWith: namedFrequency, interval: recurrenceInterval, daysOfTheWeek: daysOfTheWeek, daysOfTheMonth: recurrenceRuleArguments![daysOfTheMonthArgument] as? [NSNumber], monthsOfTheYear: recurrenceRuleArguments![monthsOfTheYearArgument] as? [NSNumber], weeksOfTheYear: recurrenceRuleArguments![weeksOfTheYearArgument] as? [NSNumber], daysOfTheYear: nil, setPositions: recurrenceRuleArguments![setPositionsArgument] as? [NSNumber], end: recurrenceEnd)] } - fileprivate func createAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { + private func setAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { let attendeesArguments = arguments[attendeesArgument] as? [Dictionary] - if attendeesArguments != nil { - var attendees = [EKParticipant]() - for attendeeArguments in attendeesArguments! { - let emailAddress = attendeeArguments["emailAddress"] as! String - if(ekEvent!.attendees != nil) { - let existingAttendee = ekEvent!.attendees!.first { element in - return element.emailAddress == emailAddress - } - if existingAttendee != nil && ekEvent!.organizer?.emailAddress != existingAttendee?.emailAddress{ - attendees.append(existingAttendee!) - continue - } + if attendeesArguments == nil { + return + } + + var attendees = [EKParticipant]() + for attendeeArguments in attendeesArguments! { + let emailAddress = attendeeArguments[emailAddressArgument] as! String + if(ekEvent!.attendees != nil) { + let existingAttendee = ekEvent!.attendees!.first { element in + return element.emailAddress == emailAddress } - - let attendee = createParticipant(emailAddress: emailAddress) - if(attendee == nil) { + if existingAttendee != nil && ekEvent!.organizer?.emailAddress != existingAttendee?.emailAddress{ + attendees.append(existingAttendee!) continue } - - attendees.append(attendee!) } - ekEvent!.setValue(attendees, forKey: "attendees") + let attendee = createParticipant(emailAddress: emailAddress) + if(attendee == nil) { + continue + } + + attendees.append(attendee!) + } + + ekEvent!.setValue(attendees, forKey: "attendees") + } + + private func createReminders(_ arguments: [String : AnyObject]) -> [EKAlarm]?{ + let remindersArguments = arguments[remindersArgument] as? [Dictionary] + if remindersArguments == nil { + return nil } + + var reminders = [EKAlarm]() + for reminderArguments in remindersArguments! { + let minutes = reminderArguments[minutesArgument] as! Int + reminders.append(EKAlarm.init(relativeOffset: 60 * Double(-minutes))) + } + + return reminders } private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { @@ -383,7 +416,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { ekEvent!.calendar = ekCalendar! ekEvent!.location = location ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) - createAttendees(arguments, ekEvent) + setAttendees(arguments, ekEvent) + ekEvent!.alarms = createReminders(arguments) do { try self.eventStore.save(ekEvent!, span: .futureEvents) diff --git a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart index 45b56f75..1eb2ead2 100644 --- a/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart +++ b/device_calendar/lib/src/models/platform_specifics/ios/attendee_details.dart @@ -1,5 +1,3 @@ -import 'package:flutter/foundation.dart'; - import '../../../common/error_messages.dart'; import 'attendance_status.dart'; import 'role.dart'; diff --git a/device_calendar/lib/src/models/reminder.dart b/device_calendar/lib/src/models/reminder.dart index 26ab5939..8fd6d142 100644 --- a/device_calendar/lib/src/models/reminder.dart +++ b/device_calendar/lib/src/models/reminder.dart @@ -1,9 +1,11 @@ import 'package:flutter/foundation.dart'; class Reminder { + /// The time when the reminder should be triggered expressed in terms of minutes before the start of the event int minutes; - Reminder({@required this.minutes}); + Reminder({@required this.minutes}) + : assert(minutes >= 0, 'Minutes must be greater than or equal than zero'); Reminder.fromJson(Map json) { minutes = json['minutes'] as int; From c297f4a938e373eaf29da2e770df280d65dc85af Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Tue, 27 Aug 2019 16:38:43 +1000 Subject: [PATCH 45/49] update example app for modifying attendees and reminders --- .../presentation/pages/calendar_event.dart | 75 ++++++++++++- .../presentation/pages/event_attendees.dart | 100 ++++++++++++++++++ .../presentation/pages/event_reminders.dart | 100 ++++++++++++++++++ 3 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 device_calendar/example/lib/presentation/pages/event_attendees.dart create mode 100644 device_calendar/example/lib/presentation/pages/event_reminders.dart diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index c9412dce..b5674f35 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -1,9 +1,11 @@ import 'package:device_calendar/device_calendar.dart'; +import 'package:device_calendar_example/presentation/pages/event_attendees.dart'; import 'package:device_calendar_example/presentation/widgets/days_of_the_week_form_entry.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../date_time_picker.dart'; +import 'event_reminders.dart'; enum RecurrenceRuleEndType { MaxOccurrences, SpecifiedEndDate } @@ -45,6 +47,8 @@ class _CalendarEventPageState extends State { List _validDaysOfTheMonth = List(); List _validMonthsOfTheYear = List(); List _validWeeksOfTheYear = List(); + List _attendees = List(); + List _reminders = List(); int _totalOccurrences; int _interval; DateTime _recurrenceEndDate; @@ -69,6 +73,8 @@ class _CalendarEventPageState extends State { for (var i = 1; i <= 53; i++) { _validWeeksOfTheYear.add(i); } + _attendees = List(); + _reminders = List(); if (this._event == null) { _startDate = DateTime.now(); _endDate = DateTime.now().add(Duration(hours: 1)); @@ -78,11 +84,12 @@ class _CalendarEventPageState extends State { _startDate = _event.start; _endDate = _event.end; _isRecurringEvent = _event.recurrenceRule != null; - if (_event.organizer != null) { - printAttendeeDetails(_event.organizer); + if (_event.attendees.isNotEmpty) { + _attendees.addAll(_event.attendees); + } + if (_event.reminders.isNotEmpty) { + _reminders.addAll(_event.reminders); } - _event.attendees?.forEach((a) => printAttendeeDetails(a)); - _event.reminders?.forEach((r) => print('reminder ${r.minutes}')); if (_isRecurringEvent) { _interval = _event.recurrenceRule.interval; _totalOccurrences = _event.recurrenceRule.totalOccurrences; @@ -224,6 +231,64 @@ class _CalendarEventPageState extends State { }, ), ), + GestureDetector( + onTap: () async { + List result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + EventAttendees(_attendees))); + if (result == null) { + return; + } + _attendees = result; + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Align( + alignment: Alignment.centerLeft, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10.0, + children: [ + Icon(Icons.people), + if (_attendees.isEmpty) Text('Add people'), + for (var attendee in _attendees) + Text('${attendee.emailAddress};') + ], + ), + ), + ), + ), + GestureDetector( + onTap: () async { + List result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + EventReminders(_reminders))); + if (result == null) { + return; + } + _reminders = result; + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Align( + alignment: Alignment.centerLeft, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10.0, + children: [ + Icon(Icons.alarm), + if (_reminders.isEmpty) Text('Add reminders'), + for (var reminder in _reminders) + Text('${reminder.minutes} minutes before; ') + ], + ), + ), + ), + ), CheckboxListTile( value: _isRecurringEvent, title: Text('Is recurring'), @@ -441,6 +506,8 @@ class _CalendarEventPageState extends State { weeksOfTheYear: _weeksOfTheYear, setPositions: _setPositions); } + _event.attendees = _attendees; + _event.reminders = _reminders; var createEventResult = await _deviceCalendarPlugin.createOrUpdateEvent(_event); if (createEventResult.isSuccess) { diff --git a/device_calendar/example/lib/presentation/pages/event_attendees.dart b/device_calendar/example/lib/presentation/pages/event_attendees.dart new file mode 100644 index 00000000..36faff57 --- /dev/null +++ b/device_calendar/example/lib/presentation/pages/event_attendees.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:device_calendar/device_calendar.dart'; + +class EventAttendees extends StatefulWidget { + final List _attendees; + const EventAttendees(this._attendees, {Key key}) : super(key: key); + + @override + _EventAttendeesState createState() => + _EventAttendeesState(_attendees ?? List()); +} + +class _EventAttendeesState extends State { + List _attendees; + final _formKey = GlobalKey(); + final _emailAddressController = TextEditingController(); + + _EventAttendeesState(List attendees) { + _attendees = List()..addAll(attendees); + } + + @override + void dispose() { + _emailAddressController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Attendees'), + ), + body: Column( + children: [ + Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: _emailAddressController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter an email address'; + } + return null; + }, + decoration: + const InputDecoration(labelText: 'Email address'), + ), + ), + RaisedButton( + child: Text('Add'), + onPressed: () { + if (_formKey.currentState.validate()) { + setState(() { + _attendees.add(Attendee( + emailAddress: _emailAddressController.text)); + _emailAddressController.clear(); + }); + } + }, + ), + ], + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: _attendees.length, + itemBuilder: (context, index) { + return ListTile( + title: Text('${_attendees[index].emailAddress}'), + trailing: RaisedButton( + onPressed: () { + setState(() { + _attendees.removeWhere((a) => + a.emailAddress == _attendees[index].emailAddress); + }); + }, + child: Text('Delete'), + ), + ); + }, + ), + ), + RaisedButton( + onPressed: () { + Navigator.pop(context, _attendees); + }, + child: Text('Done'), + ) + ], + ), + ); + } +} diff --git a/device_calendar/example/lib/presentation/pages/event_reminders.dart b/device_calendar/example/lib/presentation/pages/event_reminders.dart new file mode 100644 index 00000000..cfba82b0 --- /dev/null +++ b/device_calendar/example/lib/presentation/pages/event_reminders.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:device_calendar/device_calendar.dart'; + +class EventReminders extends StatefulWidget { + final List _reminders; + EventReminders(this._reminders, {Key key}) : super(key: key); + + _EventRemindersState createState() => _EventRemindersState(_reminders); +} + +class _EventRemindersState extends State { + List _reminders; + final _formKey = GlobalKey(); + final _minutesController = TextEditingController(); + + _EventRemindersState(List reminders) { + _reminders = List()..addAll(reminders); + } + + @override + void dispose() { + _minutesController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Reminders'), + ), + body: Column( + children: [ + Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: _minutesController, + validator: (value) { + if (value == null || + value.isEmpty || + int.tryParse(value) == null) { + return 'Please enter a reminder time in minutes'; + } + return null; + }, + decoration: const InputDecoration( + labelText: 'Minutes before start'), + ), + ), + RaisedButton( + child: Text('Add'), + onPressed: () { + if (_formKey.currentState.validate()) { + setState(() { + _reminders.add(Reminder( + minutes: int.parse(_minutesController.text))); + _minutesController.clear(); + }); + } + }, + ), + ], + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: _reminders.length, + itemBuilder: (context, index) { + return ListTile( + title: Text('${_reminders[index].minutes} minutes'), + trailing: RaisedButton( + onPressed: () { + setState(() { + _reminders.removeWhere( + (a) => a.minutes == _reminders[index].minutes); + }); + }, + child: Text('Delete'), + ), + ); + }, + ), + ), + RaisedButton( + onPressed: () { + Navigator.pop(context, _reminders); + }, + child: Text('Done'), + ) + ], + ), + ); + } +} From 400154d169e60f69275e153b2d67fe68391ad677 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 28 Aug 2019 10:00:21 +1000 Subject: [PATCH 46/49] refactor code around handling permissions --- .../devicecalendar/CalendarDelegate.kt | 107 +++++------------- 1 file changed, 27 insertions(+), 80 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 15c8a9f4..73087c6a 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -97,91 +97,38 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { // indicate we're not handling the request return false - when (cachedValues.calendarDelegateMethodCode) { - RETRIEVE_CALENDARS_REQUEST_CODE -> { - return handleRetrieveCalendarsRequest(permissionGranted, cachedValues, requestCode) - } - RETRIEVE_EVENTS_REQUEST_CODE -> { - return handleRetrieveEventsRequest(permissionGranted, cachedValues, requestCode) - } - RETRIEVE_CALENDAR_REQUEST_CODE -> { - return handleRetrieveCalendarRequest(permissionGranted, cachedValues, requestCode) - } - CREATE_OR_UPDATE_EVENT_REQUEST_CODE -> { - return handleCreateOrUpdateEventRequest(permissionGranted, cachedValues, requestCode) - } - DELETE_EVENT_REQUEST_CODE -> { - return handleDeleteEventRequest(permissionGranted, cachedValues, requestCode) - } - REQUEST_PERMISSIONS_REQUEST_CODE -> { - return handlePermissionsRequest(permissionGranted, cachedValues) + try { + if (!permissionGranted) { + finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) + return false } - } - - return false - } - - private fun handlePermissionsRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel): Boolean { - finishWithSuccess(permissionGranted, cachedValues.pendingChannelResult) - return true - } - private fun handleDeleteEventRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel, requestCode: Int): Boolean { - if (permissionGranted) { - deleteEvent(cachedValues.eventId, cachedValues.calendarId, cachedValues.pendingChannelResult) - } else { - finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) - } - - _cachedParametersMap.remove(requestCode) - - return true - } - - private fun handleCreateOrUpdateEventRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel, requestCode: Int): Boolean { - if (permissionGranted) { - createOrUpdateEvent(cachedValues.calendarId, cachedValues.event, cachedValues.pendingChannelResult) - } else { - finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) - } - - _cachedParametersMap.remove(requestCode) - - return true - } - - private fun handleRetrieveCalendarRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel, requestCode: Int): Boolean { - if (permissionGranted) { - retrieveCalendar(cachedValues.calendarId, cachedValues.pendingChannelResult) - } else { - finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) - } - - _cachedParametersMap.remove(requestCode) - - return true - } + when (cachedValues.calendarDelegateMethodCode) { + RETRIEVE_CALENDARS_REQUEST_CODE -> { + retrieveCalendars(cachedValues.pendingChannelResult) + } + RETRIEVE_EVENTS_REQUEST_CODE -> { + retrieveEvents(cachedValues.calendarId, cachedValues.calendarEventsStartDate, cachedValues.calendarEventsEndDate, cachedValues.calendarEventsIds, cachedValues.pendingChannelResult) + } + RETRIEVE_CALENDAR_REQUEST_CODE -> { + retrieveCalendar(cachedValues.calendarId, cachedValues.pendingChannelResult) + } + CREATE_OR_UPDATE_EVENT_REQUEST_CODE -> { + createOrUpdateEvent(cachedValues.calendarId, cachedValues.event, cachedValues.pendingChannelResult) + } + DELETE_EVENT_REQUEST_CODE -> { + deleteEvent(cachedValues.eventId, cachedValues.calendarId, cachedValues.pendingChannelResult) + } + REQUEST_PERMISSIONS_REQUEST_CODE -> { + finishWithSuccess(permissionGranted, cachedValues.pendingChannelResult) + } + } - private fun handleRetrieveEventsRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel, requestCode: Int): Boolean { - if (permissionGranted) { - retrieveEvents(cachedValues.calendarId, cachedValues.calendarEventsStartDate, cachedValues.calendarEventsEndDate, cachedValues.calendarEventsIds, cachedValues.pendingChannelResult) - } else { - finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) + return true } - - _cachedParametersMap.remove(requestCode) - return true - } - - private fun handleRetrieveCalendarsRequest(permissionGranted: Boolean, cachedValues: CalendarMethodsParametersCacheModel, requestCode: Int): Boolean { - if (permissionGranted) { - retrieveCalendars(cachedValues.pendingChannelResult) - } else { - finishWithError(NOT_AUTHORIZED, NOT_AUTHORIZED_MESSAGE, cachedValues.pendingChannelResult) + finally { + _cachedParametersMap.remove(cachedValues.calendarDelegateMethodCode) } - - _cachedParametersMap.remove(requestCode) - return true } fun requestPermissions(pendingChannelResult: MethodChannel.Result) { From 016f919c40c5a07816b3c1c35317f3ab43284de4 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 28 Aug 2019 10:18:25 +1000 Subject: [PATCH 47/49] remove redundant channel creation code --- .../com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 15de3268..a79839ea 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -69,9 +69,6 @@ class DeviceCalendarPlugin() : MethodCallHandler { 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) From 7dcdacd66df17acda7f5819d09fa6de187104b79 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 28 Aug 2019 10:27:34 +1000 Subject: [PATCH 48/49] return UnmodifiableListView types for retrieveEvents and retrieveCalendars --- device_calendar/CHANGELOG.md | 2 +- device_calendar/lib/src/device_calendar.dart | 25 ++++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 90f93c69..fffea7c2 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -6,7 +6,7 @@ * 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 as part of address issue [113](https://github.com/builttoroam/flutter_plugins/issues/113) +* **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 diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index b537e565..4ba56df5 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:convert'; import 'package:flutter/services.dart'; @@ -59,17 +60,19 @@ class DeviceCalendarPlugin { /// Retrieves all of the device defined calendars /// /// Returns a [Result] containing a list of device [Calendar] - Future>> retrieveCalendars() async { - final res = Result>(); + Future>> retrieveCalendars() async { + final res = Result>(); try { var calendarsJson = await channel.invokeMethod('retrieveCalendars'); - res.data = json.decode(calendarsJson).map((decodedCalendar) { + res.data = UnmodifiableListView( + json.decode(calendarsJson).map((decodedCalendar) { return Calendar.fromJson(decodedCalendar); - }).toList(growable: false); + })); } catch (e) { - _parsePlatformExceptionAndUpdateResult>(e, res); + _parsePlatformExceptionAndUpdateResult>( + e, res); } return res; @@ -84,9 +87,9 @@ class DeviceCalendarPlugin { /// /// Returns a [Result] containing a list [Event], that fall /// into the specified parameters - Future>> retrieveEvents( + Future>> retrieveEvents( String calendarId, RetrieveEventsParams retrieveEventsParams) async { - final res = Result>(); + final res = Result>(); if ((calendarId?.isEmpty ?? true)) { res.errorMessages.add( @@ -115,11 +118,13 @@ class DeviceCalendarPlugin { 'eventIds': retrieveEventsParams.eventIds }); - res.data = json.decode(eventsJson).map((decodedEvent) { + res.data = UnmodifiableListView( + json.decode(eventsJson).map((decodedEvent) { return Event.fromJson(decodedEvent); - }).toList(growable: false); + })); } catch (e) { - _parsePlatformExceptionAndUpdateResult>(e, res); + _parsePlatformExceptionAndUpdateResult>( + e, res); } } From b1e146033e4e80dbfb05ecbc30c327c77c2c26e5 Mon Sep 17 00:00:00 2001 From: Michael Bui <25263378+MaikuB@users.noreply.github.com> Date: Wed, 28 Aug 2019 12:36:39 +1000 Subject: [PATCH 49/49] update changelog to include date for 1.0.0 release --- device_calendar/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index fffea7c2..bb2291ac 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -1,4 +1,4 @@ -# 1.0.0 TBD +# 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)