Skip to content

Commit

Permalink
Exporting to Google Calendar (#159)
Browse files Browse the repository at this point in the history
* added a non-functional floating action button for exporting google calendar

trying to work with a stream of university events

* experimented with iterating through a stream of events

* started to create a Google Calendar Event

* added ending date for an event and a sample list of strings for recurrence

* Obtaining Client ID credentials for Google Calendar API

* Managed to export non-recurrent events

Recurrent events seem to output the error "Invalid recurrence rule"

Based on:
https://medium.com/flutter-community/flutter-use-google-calendar-api-adding-the-events-to-calendar-3d8fcb008493

Additional resources used:
https://developers.google.com/calendar/concepts/events-calendars
https://developers.google.com/calendar/recurringevents
https://tools.ietf.org/html/rfc5545#section-3.8.5

* Added some TODOs for my future commits

* Sending all events in the same client instance instead of opening one for each event

* Temporary(?) fix - removed the time in the "until" field of events' rrule

* implemented custom getter for correct recurrence rule

this new rrule should take into account the holidays when dealing with even/odd events

* testing the addition of a new secondary calendar in GCal for timetable events

* managed to add events to a custom GCal calendar

* Cleaned up the code

Avoided "cascading then-s" which could make the code unavoidable, in my opinion. Now there is an awaited function call for insertion of a new calendar, after which the adding of events is processed.

* added missing parenthesis

* trailing comma

* Implemented update

The current method is probably not final, and can be described as a workaround.
 Every time the user exports his events to Google Calendar (GCal) they are added in a separate secondary calendar with the summary "ACS UPB Mobile".

 Re-exporting the calendar to GCal works by deleting the previous "ACS UPB Mobile" named entry and reinserting the new version.

* Added default value for rrule calculation return

* Added different colors for each event

Google Calendar requires a color id (from 1 to 11) for an event color. Currently I am just using the id from the type of the event (there are less than 11 types of events, hence each type will be associated to a different color).

However, you will probably see different colors between, for example, a Lecture in the ACS UPB Mobile calendar and its Google Calendar export. I might need to think about a solution to this.

* Added a TODO regarding GCal notifications

* Reformat

* Organized GCal code

Moved Google Calendar color details to google_apis.dart.
Created getter which assigns an event type to a Google Calendar color (which has a color ID and a name). This is done manually, with regards to how event type colors are assigned in the ACS UPB Mobile Timetable.

* Fixed bug which prevented GCal imported events from being edited. Organized code

My guess is that the aforementioned bug was caused by RRule inconsistencies. According to RFC5545 (https://tools.ietf.org/html/rfc5545) [BYWEEKNO] MUST NOT be used when the FREQ rule must not be used when the FREQ part is set to anything other than YEARLY.', so I replaed 'Daily' with 'Yearly'. On my test events, things seem to work well, but I will further investigate this.

* Organized GCal code

Renamed eventInstance to uniEvent to avoid confusion with objects of UniEventInstance class type. Added TODO related to event descriptions, added onError for callback exceptions;

 Made client AutoRefreshing thinking that this might allow  refresh tokens. Yet, the user still has to re-accept permissions in an external browser tab (forced webview is not allowed, according to my attempts), so remembering user's consent might not be possible.

* Added default case for Google API Client ID

* Added flutter_inappwebview, since default url_launcher webview doesn't work with OAuth

* Replaced some colors, so they could contrast more.

* Added widget to exportToGoogleCalendar function

* Renamed field (possibly required by a package update)

* Added flutter_web_browser package

Used for asking user for Google Calendar permission in a webview. This way they can easily close it and go back to the app (without tapping back multiple times or switching apps from chrome).

* Replaced previous Google Calendar access prompt with new one; changed print messages

* Modified rrule, see google/googleapis.dart#223 (comment)

* Moved Google Calendar functions to a separate extension

* Moved TODOs

* Replaced cascade invocations, clarified code, removed unnecessary comments

* Removed unnecessary imports

* Replaced normal FAB with an empty one for anonymous users

* Stopped adding widget as parameter to function

* Renamed variable for clarification

* Removed duplicate code

* Refactoring; changed scope of variables from private to public

* Properly replaced the frequency for rruleBasedOnCalendar getter

* Removed todo

* Commented reminder implementation, will get back to it some other time

* camelCase

* Added convenience method for converting Period to Duration

Also reformatted and removed unnecessary imports.

* Commented on some todos

* Added todo related to rrule based on calendar string

* Explained functionality of
clientViaUserConsent function and parameters

* Removed whitespace

* Replaced string list add with string list declaration

* toString and replaceAll are now done in the same operation

* Removed comments

* Removed prints in prompt function, made return value Future<void> instead of void (it is async!)

* Export to GCal is now done in settings instead of FAB

* Renamed hex ids for compliance with lower camelCase requirements

These are not currently used, but I decided to include them for a future PR, related to choosing a closest event color from this list.

* just a question mark :D

* Added issue related to this TODO

#175

* Added issue related to that TODO

#176

* TODO converted to project

TODO converted to project

TODO already mentioned in issue

[issue](#168)

Removed irrelevant comments and TODOs

* Removed unnecessary hex codes

* Update lib/resources/google_apis.dart

Co-authored-by: Ioana Alexandru <[email protected]>

* Removed unnecessary extension

* Update lib/pages/timetable/service/google_calendar_services.dart

Co-authored-by: Ioana Alexandru <[email protected]>

* Code cleanup

* Code cleanup

* Specific version for package

* Removed accidentally inserted preferences title

* Removed pronouns in localization files

* Replaced constant with value taken from the generated localization files

* Replaced S.of(context) with S.current

* Ensured visibility of editing permissions widget

* Removed pronouns for Romanian

* Update lib/pages/timetable/service/google_calendar_services.dart

Co-authored-by: Ioana Alexandru <[email protected]>

* Removed old prompt function

* Turned then callback in await function

* Reverted old pubspec.yaml formatting

* Explicitly specified package versions in pubspec.yaml

* Replaced default Platform selecting
implementation

* Made some widgets invisible when on Flutter Web

* Made some widgets invisible when on Flutter Web

Made widget display toast when user is not logged in

* Reformat

* String for error in inserting GCal events in RO and EN

* Wrapped all operations in a single try catch, with an AppToast in catch block

* pubspec.yaml reformat

* Child widget after visible property

* Reformat

* Preparing for release

* enabled property for GCal widget is different now

* enabled property

* Update lib/l10n/intl_en.arb

Co-authored-by: Ioana Alexandru <[email protected]>

* dot after error

* ctrl alt l

* Accidentally deleted '}' in l10n.dart

* Enabled property

* Removed enabled property so that  corresponding toasts are displayed

Co-authored-by: Ioana Alexandru <[email protected]>
  • Loading branch information
bogpie and IoanaAlexandru authored May 1, 2021
1 parent ab8b1be commit 6c29017
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 27 deletions.
2 changes: 2 additions & 0 deletions android/fastlane/metadata/android/en-GB/changelogs/10012.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added
- You can now export timetable events in Google Calendar 📆
2 changes: 2 additions & 0 deletions android/fastlane/metadata/android/en-US/changelogs/10012.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added
- You can now export timetable events in Google Calendar 📆
2 changes: 2 additions & 0 deletions android/fastlane/metadata/android/ro/changelogs/10012.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added
- Acum poți exporta evenimentele din orar în Google Calendar 📆
10 changes: 7 additions & 3 deletions lib/generated/intl/messages_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class MessageLookup extends MessageLookupByLibrary {
"errorEventTypeCannotBeEmpty" : MessageLookupByLibrary.simpleMessage("Event type cannot be empty."),
"errorImage" : MessageLookupByLibrary.simpleMessage("The image could not be loaded."),
"errorIncorrectPassword" : MessageLookupByLibrary.simpleMessage("The password you entered is incorrect."),
"errorInsertGoogleEvents" : MessageLookupByLibrary.simpleMessage("Unable to insert events in Google Calendar."),
"errorInvalidEmail" : MessageLookupByLibrary.simpleMessage("You need to provide a valid e-mail address."),
"errorMissingFirstName" : MessageLookupByLibrary.simpleMessage("Please provide your first name(s)."),
"errorMissingLastName" : MessageLookupByLibrary.simpleMessage("Please provide your last name(s)."),
Expand Down Expand Up @@ -119,6 +120,7 @@ class MessageLookup extends MessageLookupByLibrary {
"infoAppIsOpenSource" : m2,
"infoClasses" : MessageLookupByLibrary.simpleMessage("classes you are interested in"),
"infoEmail" : m3,
"infoExportToGoogleCalendar" : MessageLookupByLibrary.simpleMessage("Export filtered events from Timetable"),
"infoLoading" : MessageLookupByLibrary.simpleMessage("Loading..."),
"infoMakeSureGroupIsSelected" : MessageLookupByLibrary.simpleMessage("Make sure your group/subgroup is selected in the"),
"infoPassword" : MessageLookupByLibrary.simpleMessage("It must contain lower and uppercase letters, one number and one special character, and have a minimum length of 8."),
Expand All @@ -137,9 +139,6 @@ class MessageLookup extends MessageLookupByLibrary {
"labelConfirmPassword" : MessageLookupByLibrary.simpleMessage("Confirm password"),
"labelCustom" : MessageLookupByLibrary.simpleMessage("Custom"),
"labelDay" : MessageLookupByLibrary.simpleMessage("Day"),
"labelToday" : MessageLookupByLibrary.simpleMessage("Today"),
"labelTomorrow" : MessageLookupByLibrary.simpleMessage("Tomorrow"),
"labelNow" : MessageLookupByLibrary.simpleMessage("Now"),
"labelDescription" : MessageLookupByLibrary.simpleMessage("Description"),
"labelEmail" : MessageLookupByLibrary.simpleMessage("Email"),
"labelEnd" : MessageLookupByLibrary.simpleMessage("End"),
Expand All @@ -153,6 +152,7 @@ class MessageLookup extends MessageLookupByLibrary {
"labelLocation" : MessageLookupByLibrary.simpleMessage("Location"),
"labelName" : MessageLookupByLibrary.simpleMessage("Name"),
"labelNewPassword" : MessageLookupByLibrary.simpleMessage("New password"),
"labelNow" : MessageLookupByLibrary.simpleMessage("Now"),
"labelOdd" : MessageLookupByLibrary.simpleMessage("Odd"),
"labelOldPassword" : MessageLookupByLibrary.simpleMessage("Old password"),
"labelPassword" : MessageLookupByLibrary.simpleMessage("Password"),
Expand All @@ -165,6 +165,8 @@ class MessageLookup extends MessageLookupByLibrary {
"labelSemester" : MessageLookupByLibrary.simpleMessage("Semester"),
"labelStart" : MessageLookupByLibrary.simpleMessage("Start"),
"labelTeam" : m5,
"labelToday" : MessageLookupByLibrary.simpleMessage("Today"),
"labelTomorrow" : MessageLookupByLibrary.simpleMessage("Tomorrow"),
"labelType" : MessageLookupByLibrary.simpleMessage("Type"),
"labelUniversityYear" : MessageLookupByLibrary.simpleMessage("University year"),
"labelUnknown" : MessageLookupByLibrary.simpleMessage("Unknown"),
Expand Down Expand Up @@ -235,6 +237,7 @@ class MessageLookup extends MessageLookupByLibrary {
"sectionFrequentlyAccessedWebsites" : MessageLookupByLibrary.simpleMessage("Favourite websites"),
"sectionGrading" : MessageLookupByLibrary.simpleMessage("Grading"),
"sectionShortcuts" : MessageLookupByLibrary.simpleMessage("Shortcuts"),
"settingsExportToGoogleCalendar" : MessageLookupByLibrary.simpleMessage("Export events to Google Calendar"),
"settingsItemDarkMode" : MessageLookupByLibrary.simpleMessage("Dark Mode"),
"settingsItemEditingPermissions" : MessageLookupByLibrary.simpleMessage("Your editing permissions"),
"settingsItemLanguage" : MessageLookupByLibrary.simpleMessage("Language"),
Expand All @@ -249,6 +252,7 @@ class MessageLookup extends MessageLookupByLibrary {
"settingsTitleDataControl" : MessageLookupByLibrary.simpleMessage("Data control"),
"settingsTitleLocalization" : MessageLookupByLibrary.simpleMessage("Localization"),
"settingsTitlePersonalization" : MessageLookupByLibrary.simpleMessage("Personalization"),
"settingsTitleTimetable" : MessageLookupByLibrary.simpleMessage("Timetable"),
"shortcutTypeClassbook" : MessageLookupByLibrary.simpleMessage("Classbook"),
"shortcutTypeMain" : MessageLookupByLibrary.simpleMessage("Main page"),
"shortcutTypeOther" : MessageLookupByLibrary.simpleMessage("Other"),
Expand Down
10 changes: 7 additions & 3 deletions lib/generated/intl/messages_ro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class MessageLookup extends MessageLookupByLibrary {
"errorEventTypeCannotBeEmpty" : MessageLookupByLibrary.simpleMessage("Tipul de eveniment trebuie precizat."),
"errorImage" : MessageLookupByLibrary.simpleMessage("Imaginea nu putut fi încărcată."),
"errorIncorrectPassword" : MessageLookupByLibrary.simpleMessage("Parola introdusă nu este corectă."),
"errorInsertGoogleEvents" : MessageLookupByLibrary.simpleMessage("Evenimentele nu au putut fi inserate în Google Calendar."),
"errorInvalidEmail" : MessageLookupByLibrary.simpleMessage("Trebuie să introduceți un e-mail valid."),
"errorMissingFirstName" : MessageLookupByLibrary.simpleMessage("Introduceți prenumele."),
"errorMissingLastName" : MessageLookupByLibrary.simpleMessage("Introduceți numele de familie."),
Expand Down Expand Up @@ -119,6 +120,7 @@ class MessageLookup extends MessageLookupByLibrary {
"infoAppIsOpenSource" : m2,
"infoClasses" : MessageLookupByLibrary.simpleMessage("materiile care vă interesează"),
"infoEmail" : m3,
"infoExportToGoogleCalendar" : MessageLookupByLibrary.simpleMessage("Exportă evenimentele filtrate din Orar"),
"infoLoading" : MessageLookupByLibrary.simpleMessage("Se încarcă..."),
"infoMakeSureGroupIsSelected" : MessageLookupByLibrary.simpleMessage("Asigurați-vă că aveți grupa/semigrupa selectată în"),
"infoPassword" : MessageLookupByLibrary.simpleMessage("Aceasta trebuie să conțină majuscule, minuscule și cel puțin un număr sau un simbol, având minimum 8 caractere."),
Expand All @@ -137,9 +139,6 @@ class MessageLookup extends MessageLookupByLibrary {
"labelConfirmPassword" : MessageLookupByLibrary.simpleMessage("Confirmare parolă"),
"labelCustom" : MessageLookupByLibrary.simpleMessage("Alta"),
"labelDay" : MessageLookupByLibrary.simpleMessage("Zi"),
"labelToday" : MessageLookupByLibrary.simpleMessage("Astăzi"),
"labelTomorrow" : MessageLookupByLibrary.simpleMessage("Mâine"),
"labelNow" : MessageLookupByLibrary.simpleMessage("Acum"),
"labelDescription" : MessageLookupByLibrary.simpleMessage("Descriere"),
"labelEmail" : MessageLookupByLibrary.simpleMessage("Email"),
"labelEnd" : MessageLookupByLibrary.simpleMessage("Sfârșit"),
Expand All @@ -153,6 +152,7 @@ class MessageLookup extends MessageLookupByLibrary {
"labelLocation" : MessageLookupByLibrary.simpleMessage("Locație"),
"labelName" : MessageLookupByLibrary.simpleMessage("Nume"),
"labelNewPassword" : MessageLookupByLibrary.simpleMessage("Parolă nouă"),
"labelNow" : MessageLookupByLibrary.simpleMessage("Acum"),
"labelOdd" : MessageLookupByLibrary.simpleMessage("Impară"),
"labelOldPassword" : MessageLookupByLibrary.simpleMessage("Parolă veche"),
"labelPassword" : MessageLookupByLibrary.simpleMessage("Parolă"),
Expand All @@ -165,6 +165,8 @@ class MessageLookup extends MessageLookupByLibrary {
"labelSemester" : MessageLookupByLibrary.simpleMessage("Semestrul"),
"labelStart" : MessageLookupByLibrary.simpleMessage("Început"),
"labelTeam" : m5,
"labelToday" : MessageLookupByLibrary.simpleMessage("Astăzi"),
"labelTomorrow" : MessageLookupByLibrary.simpleMessage("Mâine"),
"labelType" : MessageLookupByLibrary.simpleMessage("Tip"),
"labelUniversityYear" : MessageLookupByLibrary.simpleMessage("An universitar"),
"labelUnknown" : MessageLookupByLibrary.simpleMessage("Necunoscut"),
Expand Down Expand Up @@ -235,6 +237,7 @@ class MessageLookup extends MessageLookupByLibrary {
"sectionFrequentlyAccessedWebsites" : MessageLookupByLibrary.simpleMessage("Website-uri favorite"),
"sectionGrading" : MessageLookupByLibrary.simpleMessage("Punctaj"),
"sectionShortcuts" : MessageLookupByLibrary.simpleMessage("Scurtături"),
"settingsExportToGoogleCalendar" : MessageLookupByLibrary.simpleMessage("Exportă evenimentele în Google Calendar"),
"settingsItemDarkMode" : MessageLookupByLibrary.simpleMessage("Mod Întunecat"),
"settingsItemEditingPermissions" : MessageLookupByLibrary.simpleMessage("Permisiunile tale de editare"),
"settingsItemLanguage" : MessageLookupByLibrary.simpleMessage("Limbă"),
Expand All @@ -249,6 +252,7 @@ class MessageLookup extends MessageLookupByLibrary {
"settingsTitleDataControl" : MessageLookupByLibrary.simpleMessage("Control date"),
"settingsTitleLocalization" : MessageLookupByLibrary.simpleMessage("Localizare"),
"settingsTitlePersonalization" : MessageLookupByLibrary.simpleMessage("Personalizare"),
"settingsTitleTimetable" : MessageLookupByLibrary.simpleMessage("Orar"),
"shortcutTypeClassbook" : MessageLookupByLibrary.simpleMessage("Catalog"),
"shortcutTypeMain" : MessageLookupByLibrary.simpleMessage("Pagina principală"),
"shortcutTypeOther" : MessageLookupByLibrary.simpleMessage("Alta"),
Expand Down
62 changes: 51 additions & 11 deletions lib/generated/l10n.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"errorClassCannotBeEmpty": "Class cannot be empty.",
"errorPictureSizeToBig": "Please select a picture that is less than 5MB.",
"errorImage": "The image could not be loaded.",
"errorInsertGoogleEvents": "Unable to insert events in Google Calendar.",

"warningRequestExists": "Request already exists",
"warningInternetConnection": "Please make sure you have an internet connection.",
Expand Down Expand Up @@ -192,6 +193,8 @@
"settingsItemLanguageRomanian": "Romanian",
"settingsItemLanguageAuto": "Auto",
"settingsRelevanceFilter": "Relevance filter",
"settingsExportToGoogleCalendar": "Export events to Google Calendar",
"settingsTitleTimetable": "Timetable",

"websiteCategoryLearning": "Learning",
"websiteCategoryAdministrative": "Administrative",
Expand Down Expand Up @@ -271,11 +274,12 @@
"infoEmail": "This is the same username you use to log in to {forum}.",
"infoLoading": "Loading...",
"infoReadThePolicy": "Read the {appName} policy",
"infoExportToGoogleCalendar": "Export filtered events from Timetable",

"stringEmailDomain": "@stud.acs.upb.ro",
"stringForum": "cs.curs.pub.ro",
"stringAnonymous": "Anonymous",
"stringAnd": "and",

"fileAcsBanner": "assets/images/acs_banner_en.png"
}
}
4 changes: 4 additions & 0 deletions lib/l10n/intl_ro.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"errorClassCannotBeEmpty": "Materia trebuie precizată.",
"errorPictureSizeToBig": "Selectați o fotografie care are mai puțin de 5MB.",
"errorImage": "Imaginea nu putut fi încărcată.",
"errorInsertGoogleEvents": "Evenimentele nu au putut fi inserate în Google Calendar.",

"warningRequestExists": "O cerere deja există",
"warningInternetConnection": "Asigurați-vă că sunteți conectat la internet.",
Expand Down Expand Up @@ -192,6 +193,8 @@
"settingsItemLanguageRomanian": "Română",
"settingsItemLanguageAuto": "Auto",
"settingsRelevanceFilter": "Filtru de relevanță",
"settingsExportToGoogleCalendar": "Exportă evenimentele în Google Calendar",
"settingsTitleTimetable": "Orar",

"websiteCategoryLearning": "Cursuri",
"websiteCategoryAdministrative": "Administrativ",
Expand Down Expand Up @@ -272,6 +275,7 @@
"infoEmail": "Acesta este același username pe care îl folosești să te loghezi pe {forum}.",
"infoLoading": "Se încarcă...",
"infoReadThePolicy": "Citește politica {appName}",
"infoExportToGoogleCalendar": "Exportă evenimentele filtrate din Orar",

"stringEmailDomain": "@stud.acs.upb.ro",
"stringForum": "cs.curs.pub.ro",
Expand Down
23 changes: 23 additions & 0 deletions lib/pages/settings/view/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:acs_upb_mobile/authentication/service/auth_provider.dart';
import 'package:acs_upb_mobile/generated/l10n.dart';
import 'package:acs_upb_mobile/navigation/routes.dart';
import 'package:acs_upb_mobile/pages/settings/service/request_provider.dart';
import 'package:acs_upb_mobile/pages/timetable/service/uni_event_provider.dart';
import 'package:acs_upb_mobile/resources/locale_provider.dart';
import 'package:acs_upb_mobile/resources/utils.dart';
import 'package:acs_upb_mobile/widgets/icon_text.dart';
Expand Down Expand Up @@ -114,6 +115,28 @@ class _SettingsPageState extends State<SettingsPage> {
S.current.infoReadThePolicy(Utils.packageInfo.appName),
),
),
Visibility(
visible: Platform.isAndroid || Platform.isIOS,
child: PreferenceTitle(S.current.settingsTitleTimetable),
),
Visibility(
visible: Platform.isAndroid || Platform.isIOS,
child: ListTile(
key: const ValueKey('google_calendar'),
onTap: () async {
if (authProvider.isAnonymous) {
AppToast.show(S.current.messageNotLoggedIn);
} else {
final eventProvider = Provider.of<UniEventProvider>(
context,
listen: false);
await eventProvider.exportToGoogleCalendar();
}
},
title: Text(S.current.settingsExportToGoogleCalendar),
subtitle: Text(S.current.infoExportToGoogleCalendar),
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Expand Down
17 changes: 11 additions & 6 deletions lib/pages/timetable/model/events/recurring_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ class RecurringUniEvent extends UniEvent {

final RecurrenceRule rrule;

@override
Iterable<UniEventInstance> generateInstances(
{DateInterval intersectingInterval}) sync* {
RecurrenceRule rrule = this.rrule;
RecurrenceRule get rruleBasedOnCalendar {
final RecurrenceRule rrule = this.rrule;
if (calendar != null && rrule.frequency == Frequency.weekly) {
var weeks = calendar.nonHolidayWeeks;

Expand All @@ -67,14 +65,21 @@ class RecurringUniEvent extends UniEvent {
rrule.interval)
.toSet();
}
rrule = rrule.copyWith(
frequency: Frequency.daily,
return rrule.copyWith(
frequency: Frequency.yearly,
interval: 1,
byWeekDays: rrule.byWeekDays.isNotEmpty
? rrule.byWeekDays
: {ByWeekDayEntry(start.dayOfWeek)},
byWeeks: weeks);
}
return rrule;
}

@override
Iterable<UniEventInstance> generateInstances(
{DateInterval intersectingInterval}) sync* {
final RecurrenceRule rrule = rruleBasedOnCalendar;

// Calculate recurrences
int i = 0;
Expand Down
Loading

0 comments on commit 6c29017

Please sign in to comment.