From bc9d4754214848b9d5c53c4f78b2c7e3b03592e1 Mon Sep 17 00:00:00 2001 From: thomassth Date: Fri, 30 Sep 2022 16:38:21 -0400 Subject: [PATCH 01/15] fix for ownerAccount null --- .../com/builttoroam/devicecalendar/models/Calendar.kt | 11 +++++++++-- example/android/app/build.gradle | 2 +- example/android/build.gradle | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Calendar.kt b/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Calendar.kt index da17b0d3..6e10b7fe 100644 --- a/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Calendar.kt +++ b/android/src/main/kotlin/com/builttoroam/devicecalendar/models/Calendar.kt @@ -1,6 +1,13 @@ package com.builttoroam.devicecalendar.models -class Calendar(val id: String, val name: String, val color : Int, val accountName: String, val accountType: String, val ownerAccount: String) { +class Calendar( + val id: String, + val name: String, + val color: Int, + val accountName: String, + val accountType: String, + val ownerAccount: String? +) { var isReadOnly: Boolean = false var isDefault: Boolean = false -} \ No newline at end of file +} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 4af82323..dd924715 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 32 ndkVersion '22.1.7171670' sourceSets { diff --git a/example/android/build.gradle b/example/android/build.gradle index c85ba2ad..d135914c 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 682b43b7073e3e618e932de2bbf4c76a35813b29 Mon Sep 17 00:00:00 2001 From: thomassth Date: Fri, 30 Sep 2022 16:55:45 -0400 Subject: [PATCH 02/15] more calendar data displayed --- example/lib/presentation/pages/calendars.dart | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/example/lib/presentation/pages/calendars.dart b/example/lib/presentation/pages/calendars.dart index e28dbc7f..98601e14 100644 --- a/example/lib/presentation/pages/calendars.dart +++ b/example/lib/presentation/pages/calendars.dart @@ -70,12 +70,20 @@ class _CalendarsPageState extends State { child: Row( children: [ Expanded( - flex: 1, - child: Text( - _calendars[index].name!, - style: Theme.of(context).textTheme.subtitle1, - ), - ), + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${_calendars[index].id}: ${_calendars[index].name!}", + style: + Theme.of(context).textTheme.subtitle1, + ), + Text( + "Account: ${_calendars[index].accountName!}"), + Text( + "type: ${_calendars[index].accountType}"), + ])), Container( width: 15, height: 15, @@ -84,13 +92,14 @@ class _CalendarsPageState extends State { color: Color(_calendars[index].color!)), ), SizedBox(width: 10), - Container( - margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0), - padding: const EdgeInsets.all(3.0), - decoration: BoxDecoration( - border: Border.all(color: Colors.blueAccent)), - child: Text('Default'), - ), + if (_calendars[index].isDefault!) + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0), + padding: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent)), + child: Text('Default'), + ), Icon(_calendars[index].isReadOnly == true ? Icons.lock : Icons.lock_open) From 996283545f9d08dc4006aedef60b3a8f026a1516 Mon Sep 17 00:00:00 2001 From: thomassth Date: Sat, 1 Oct 2022 01:19:38 -0400 Subject: [PATCH 03/15] example app dark mode --- example/lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 043898fc..f24e43ba 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,6 +14,9 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(), + themeMode: ThemeMode.system, + darkTheme: ThemeData.dark(), routes: { AppRoutes.calendars: (context) { return CalendarsPage(key: Key('calendarsPage')); From 23ff57e2764efd66f77c4a7c471dc2c0619ad2eb Mon Sep 17 00:00:00 2001 From: thomassth Date: Thu, 6 Oct 2022 01:54:24 -0400 Subject: [PATCH 04/15] activate flutter lint --- analysis_options.yaml | 30 + example/analysis_options.yaml | 31 +- example/integration_test/app_test.dart | 4 +- .../integration_test/integration_test.dart | 2 +- example/lib/common/app_routes.dart | 2 +- example/lib/main.dart | 6 +- .../lib/presentation/date_time_picker.dart | 4 +- example/lib/presentation/event_item.dart | 68 +- .../lib/presentation/pages/calendar_add.dart | 12 +- .../presentation/pages/calendar_event.dart | 1274 +++++++++-------- .../presentation/pages/calendar_events.dart | 26 +- example/lib/presentation/pages/calendars.dart | 14 +- .../presentation/pages/event_attendee.dart | 8 +- .../presentation/pages/event_reminders.dart | 10 +- .../presentation/recurring_event_dialog.dart | 12 +- example/pubspec.yaml | 1 + lib/src/common/error_messages.dart | 4 +- lib/src/device_calendar.dart | 15 +- lib/src/models/event.dart | 4 +- test/device_calendar_test.dart | 37 +- 20 files changed, 816 insertions(+), 748 deletions(-) create mode 100644 analysis_options.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..68a79339 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + constant_identifier_names: false # TODO: use lowerCamelCases consistently + avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index d4fcc1ad..3e1200f9 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1 +1,30 @@ -include: package:pedantic/analysis_options.yaml \ No newline at end of file +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + constant_identifier_names: false # TODO: use lowerCamelCases consistently + avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/integration_test/app_test.dart b/example/integration_test/app_test.dart index 503a8951..6e4a1908 100644 --- a/example/integration_test/app_test.dart +++ b/example/integration_test/app_test.dart @@ -10,7 +10,7 @@ import 'package:device_calendar_example/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Calendar plugin example', () { - final eventTitle = Uuid().v1(); + final eventTitle = const Uuid().v1(); final saveEventButtonFinder = find.byKey(const Key('saveEventButton')); final eventTitleFinder = find.text(eventTitle); final firstWritableCalendarFinder = @@ -27,7 +27,7 @@ void main() { testWidgets('select first writable calendar', (WidgetTester tester) async { app.main(); - await tester.pumpAndSettle(Duration(milliseconds: 500)); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(firstWritableCalendarFinder, findsOneWidget); }); testWidgets('go to add event page', (WidgetTester tester) async { diff --git a/example/integration_test/integration_test.dart b/example/integration_test/integration_test.dart index d061d76e..ca6e9ef3 100644 --- a/example/integration_test/integration_test.dart +++ b/example/integration_test/integration_test.dart @@ -2,7 +2,7 @@ import 'package:integration_test/integration_test_driver.dart'; /// Instruction for iOS: /// See `ios.sh` -/// Instruction for android: +/// Instruction for android: /// See `integration_test_android.dart` Future main() => integrationDriver(); diff --git a/example/lib/common/app_routes.dart b/example/lib/common/app_routes.dart index 176a028b..991a9d70 100644 --- a/example/lib/common/app_routes.dart +++ b/example/lib/common/app_routes.dart @@ -1,3 +1,3 @@ class AppRoutes { - static final calendars = '/'; + static const calendars = '/'; } diff --git a/example/lib/main.dart b/example/lib/main.dart index f24e43ba..ffb03586 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'common/app_routes.dart'; import 'presentation/pages/calendars.dart'; -void main() => runApp(MyApp()); +void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + @override _MyAppState createState() => _MyAppState(); } @@ -19,7 +21,7 @@ class _MyAppState extends State { darkTheme: ThemeData.dark(), routes: { AppRoutes.calendars: (context) { - return CalendarsPage(key: Key('calendarsPage')); + return const CalendarsPage(key: Key('calendarsPage')); } }, ); diff --git a/example/lib/presentation/date_time_picker.dart b/example/lib/presentation/date_time_picker.dart index cf1692e7..dc11e8d9 100644 --- a/example/lib/presentation/date_time_picker.dart +++ b/example/lib/presentation/date_time_picker.dart @@ -23,7 +23,7 @@ class DateTimePicker extends StatelessWidget { final ValueChanged? selectTime; final bool enableTime; - Future _selectDate(BuildContext context) async { + Future _selectDate(BuildContext context) async { final picked = await showDatePicker( context: context, initialDate: selectedDate != null @@ -36,7 +36,7 @@ class DateTimePicker extends StatelessWidget { } } - Future _selectTime(BuildContext context) async { + Future _selectTime(BuildContext context) async { if (selectedTime == null) return; final picked = await showTimePicker(context: context, initialTime: selectedTime!); diff --git a/example/lib/presentation/event_item.dart b/example/lib/presentation/event_item.dart index 2d9c182d..ec0eb425 100644 --- a/example/lib/presentation/event_item.dart +++ b/example/lib/presentation/event_item.dart @@ -16,7 +16,7 @@ class EventItem extends StatefulWidget { final VoidCallback _onLoadingStarted; final Function(bool) _onDeleteFinished; - EventItem( + const EventItem( this._calendarEvent, this._deviceCalendarPlugin, this._onLoadingStarted, @@ -54,15 +54,15 @@ class _EventItemState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0), child: FlutterLogo(), ), ListTile( title: Text(widget._calendarEvent?.title ?? ''), subtitle: Text(widget._calendarEvent?.description ?? '')), Container( - padding: EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ if (_currentLocation != null) @@ -70,9 +70,9 @@ class _EventItemState extends State { alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Starts'), + child: const Text('Starts'), ), Text( widget._calendarEvent == null @@ -84,7 +84,7 @@ class _EventItemState extends State { ], ), ), - Padding( + const Padding( padding: EdgeInsets.symmetric(vertical: 5.0), ), if (_currentLocation != null) @@ -92,9 +92,9 @@ class _EventItemState extends State { alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Ends'), + child: const Text('Ends'), ), Text( widget._calendarEvent?.end == null @@ -106,16 +106,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('All day?'), + child: const Text('All day?'), ), Text(widget._calendarEvent?.allDay != null && widget._calendarEvent?.allDay == true @@ -124,16 +124,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Location'), + child: const Text('Location'), ), Expanded( child: Text( @@ -144,16 +144,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('URL'), + child: const Text('URL'), ), Expanded( child: Text( @@ -164,16 +164,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Attendees'), + child: const Text('Attendees'), ), Expanded( child: Text( @@ -188,16 +188,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Availability'), + child: const Text('Availability'), ), Expanded( child: Text( @@ -209,16 +209,16 @@ class _EventItemState extends State { ], ), ), - SizedBox( + const SizedBox( height: 10.0, ), Align( alignment: Alignment.topLeft, child: Row( children: [ - Container( + SizedBox( width: _eventFieldNameWidth, - child: Text('Status'), + child: const Text('Status'), ), Expanded( child: Text( @@ -242,7 +242,7 @@ class _EventItemState extends State { widget._onTapped(widget._calendarEvent as Event); } }, - icon: Icon(Icons.edit), + icon: const Icon(Icons.edit), ), IconButton( onPressed: () async { @@ -252,14 +252,14 @@ class _EventItemState extends State { builder: (BuildContext context) { if (widget._calendarEvent?.recurrenceRule == null) { return AlertDialog( - title: Text( + title: const Text( 'Are you sure you want to delete this event?'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, - child: Text('Cancel'), + child: const Text('Cancel'), ), TextButton( onPressed: () async { @@ -274,13 +274,13 @@ class _EventItemState extends State { deleteResult.isSuccess && deleteResult.data != null); }, - child: Text('Delete'), + child: const Text('Delete'), ), ], ); } else { if (widget._calendarEvent == null) { - return SizedBox(); + return const SizedBox(); } return RecurringEventDialog( widget._deviceCalendarPlugin, @@ -291,7 +291,7 @@ class _EventItemState extends State { }, ); }, - icon: Icon(Icons.delete), + icon: const Icon(Icons.delete), ), ] else ...[ IconButton( @@ -300,7 +300,7 @@ class _EventItemState extends State { widget._onTapped(widget._calendarEvent!); } }, - icon: Icon(Icons.remove_red_eye), + icon: const Icon(Icons.remove_red_eye), ), ] ], diff --git a/example/lib/presentation/pages/calendar_add.dart b/example/lib/presentation/pages/calendar_add.dart index abce3788..fc4abbce 100644 --- a/example/lib/presentation/pages/calendar_add.dart +++ b/example/lib/presentation/pages/calendar_add.dart @@ -4,6 +4,8 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; class CalendarAddPage extends StatefulWidget { + const CalendarAddPage({Key? key}) : super(key: key); + @override _CalendarAddPageState createState() { return _CalendarAddPageState(); @@ -29,13 +31,13 @@ class _CalendarAddPageState extends State { return Scaffold( key: _scaffoldKey, appBar: AppBar( - title: Text('Create Calendar'), + title: const Text('Create Calendar'), ), body: Form( autovalidateMode: _autovalidate, key: _formKey, child: Container( - padding: EdgeInsets.all(10), + padding: const EdgeInsets.all(10), child: Column( children: [ TextFormField( @@ -46,11 +48,11 @@ class _CalendarAddPageState extends State { validator: _validateCalendarName, onSaved: (String? value) => _calendarName = value ?? '', ), - SizedBox(height: 10), + const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Calendar Color'), + const Text('Calendar Color'), DropdownButton( onChanged: (selectedColor) { setState(() => _colorChoice = selectedColor); @@ -101,7 +103,7 @@ class _CalendarAddPageState extends State { } } }, - child: Icon(Icons.check), + child: const Icon(Icons.check), ), ); } diff --git a/example/lib/presentation/pages/calendar_event.dart b/example/lib/presentation/pages/calendar_event.dart index 8dcc3cd0..587520ec 100644 --- a/example/lib/presentation/pages/calendar_event.dart +++ b/example/lib/presentation/pages/calendar_event.dart @@ -4,14 +4,14 @@ import 'package:collection/collection.dart'; import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:intl/intl.dart'; +import 'package:timezone/timezone.dart'; import '../date_time_picker.dart'; import '../recurring_event_dialog.dart'; import 'event_attendee.dart'; import 'event_reminders.dart'; -import 'package:timezone/timezone.dart'; -import 'package:flutter_native_timezone/flutter_native_timezone.dart'; enum RecurrenceRuleEndType { Indefinite, MaxOccurrences, SpecifiedEndDate } @@ -89,11 +89,13 @@ class _CalendarEventPageState extends State { var currentLocation = timeZoneDatabase.locations[_timezone]; if (currentLocation != null) { _startDate = TZDateTime.now(currentLocation); - _endDate = TZDateTime.now(currentLocation).add(Duration(hours: 1)); + _endDate = + TZDateTime.now(currentLocation).add(const Duration(hours: 1)); } else { var fallbackLocation = timeZoneDatabase.locations['Etc/UTC']; _startDate = TZDateTime.now(fallbackLocation!); - _endDate = TZDateTime.now(fallbackLocation).add(Duration(hours: 1)); + _endDate = + TZDateTime.now(fallbackLocation).add(const Duration(hours: 1)); } _event = Event(_calendar.id, start: _startDate, end: _endDate); @@ -178,300 +180,275 @@ class _CalendarEventPageState extends State { ? 'View event ${_event?.title}' : 'Edit event ${_event?.title}'), ), - body: SingleChildScrollView( - child: AbsorbPointer( - absorbing: _calendar.isReadOnly ?? false, - child: Column( - children: [ - Form( - autovalidateMode: _autovalidate, - key: _formKey, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - key: Key('titleField'), - 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?.location, - decoration: const InputDecoration( - labelText: 'Location', - hintText: 'Sydney, Australia'), - onSaved: (String? value) { - _event?.location = value; - }, + body: SafeArea( + child: SingleChildScrollView( + child: AbsorbPointer( + absorbing: _calendar.isReadOnly ?? false, + child: Column( + children: [ + Form( + autovalidateMode: _autovalidate, + key: _formKey, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + key: const Key('titleField'), + 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?.url?.data?.contentText ?? '', - decoration: const InputDecoration( - labelText: 'URL', hintText: 'https://google.com'), - onSaved: (String? value) { - if (value != null) { - var uri = Uri.dataFromString(value); - _event?.url = uri; - } - }, + 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; + }, + ), ), - ), - ListTile( - leading: Text( - 'Availability', - style: TextStyle(fontSize: 16), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event?.location, + decoration: const InputDecoration( + labelText: 'Location', + hintText: 'Sydney, Australia'), + onSaved: (String? value) { + _event?.location = value; + }, + ), ), - trailing: DropdownButton( - value: _availability, - onChanged: (Availability? newValue) { - setState(() { - if (newValue != null) { - _availability = newValue; - _event?.availability = newValue; + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event?.url?.data?.contentText ?? '', + decoration: const InputDecoration( + labelText: 'URL', hintText: 'https://google.com'), + onSaved: (String? value) { + if (value != null) { + var uri = Uri.dataFromString(value); + _event?.url = uri; } - }); - }, - items: Availability.values - .map>( - (Availability value) { - return DropdownMenuItem( - value: value, - child: Text(value.enumToString), - ); - }).toList(), + }, + ), ), - ), - if(Platform.isAndroid) ListTile( - leading: Text( - 'Status', + leading: const Text( + 'Availability', style: TextStyle(fontSize: 16), ), - trailing: DropdownButton( - value: _eventStatus, - onChanged: (EventStatus? newValue) { + trailing: DropdownButton( + value: _availability, + onChanged: (Availability? newValue) { setState(() { if (newValue != null) { - _eventStatus = newValue; - _event?.status = newValue; + _availability = newValue; + _event?.availability = newValue; } }); }, - items: EventStatus.values - .map>( - (EventStatus value) { - return DropdownMenuItem( + items: Availability.values + .map>( + (Availability value) { + return DropdownMenuItem( value: value, child: Text(value.enumToString), ); }).toList(), ), ), - SwitchListTile( - value: _event?.allDay ?? false, - onChanged: (value) => - setState(() => _event?.allDay = value), - title: Text('All Day'), - ), - if (_startDate != null) - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'From', - enableTime: _event?.allDay == false, - selectedDate: _startDate, - selectedTime: _startTime, - selectDate: (DateTime date) { - setState(() { - var currentLocation = - timeZoneDatabase.locations[_timezone]; - if (currentLocation != null) { - _startDate = - TZDateTime.from(date, currentLocation); - _event?.start = _combineDateWithTime( - _startDate, _startTime); - } - }); - }, - selectTime: (TimeOfDay time) { - setState( - () { - _startTime = time; - _event?.start = _combineDateWithTime( - _startDate, _startTime); - }, - ); - }, - ), - ), - if ((_event?.allDay == false) && Platform.isAndroid) - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _event?.start?.location.name, - decoration: const InputDecoration( - labelText: 'Start date time zone', - hintText: 'Australia/Sydney'), - onSaved: (String? value) { - _event?.updateStartLocation(value); - }, + if (Platform.isAndroid) + ListTile( + leading: const Text( + 'Status', + style: TextStyle(fontSize: 16), + ), + trailing: DropdownButton( + value: _eventStatus, + onChanged: (EventStatus? newValue) { + setState(() { + if (newValue != null) { + _eventStatus = newValue; + _event?.status = newValue; + } + }); + }, + items: EventStatus.values + .map>( + (EventStatus value) { + return DropdownMenuItem( + value: value, + child: Text(value.enumToString), + ); + }).toList(), + ), ), + SwitchListTile( + value: _event?.allDay ?? false, + onChanged: (value) => + setState(() => _event?.allDay = value), + title: const Text('All Day'), ), - // Only add the 'To' Date for non-allDay events on all - // platforms except Android (which allows multiple-day allDay events) - if (_event?.allDay == false || Platform.isAndroid) - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'To', - selectedDate: _endDate, - selectedTime: _endTime, - enableTime: _event?.allDay == false, - selectDate: (DateTime date) { - setState( - () { + if (_startDate != null) + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'From', + enableTime: _event?.allDay == false, + selectedDate: _startDate, + selectedTime: _startTime, + selectDate: (DateTime date) { + setState(() { var currentLocation = timeZoneDatabase.locations[_timezone]; if (currentLocation != null) { - _endDate = + _startDate = TZDateTime.from(date, currentLocation); + _event?.start = _combineDateWithTime( + _startDate, _startTime); + } + }); + }, + selectTime: (TimeOfDay time) { + setState( + () { + _startTime = time; + _event?.start = _combineDateWithTime( + _startDate, _startTime); + }, + ); + }, + ), + ), + if ((_event?.allDay == false) && Platform.isAndroid) + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event?.start?.location.name, + decoration: const InputDecoration( + labelText: 'Start date time zone', + hintText: 'Australia/Sydney'), + onSaved: (String? value) { + _event?.updateStartLocation(value); + }, + ), + ), + // Only add the 'To' Date for non-allDay events on all + // platforms except Android (which allows multiple-day allDay events) + if (_event?.allDay == false || Platform.isAndroid) + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'To', + selectedDate: _endDate, + selectedTime: _endTime, + enableTime: _event?.allDay == false, + selectDate: (DateTime date) { + setState( + () { + var currentLocation = + timeZoneDatabase.locations[_timezone]; + if (currentLocation != null) { + _endDate = + TZDateTime.from(date, currentLocation); + _event?.end = _combineDateWithTime( + _endDate, _endTime); + } + }, + ); + }, + selectTime: (TimeOfDay time) { + setState( + () { + _endTime = time; _event?.end = _combineDateWithTime(_endDate, _endTime); - } - }, - ); - }, - selectTime: (TimeOfDay time) { - setState( - () { - _endTime = time; - _event?.end = - _combineDateWithTime(_endDate, _endTime); - }, - ); - }, + }, + ); + }, + ), ), - ), - if (_event?.allDay == false && Platform.isAndroid) - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _event?.end?.location.name, - decoration: InputDecoration( - labelText: 'End date time zone', - hintText: 'Australia/Sydney'), - onSaved: (String? value) => - _event?.updateEndLocation(value), + if (_event?.allDay == false && Platform.isAndroid) + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event?.end?.location.name, + decoration: const InputDecoration( + labelText: 'End date time zone', + hintText: 'Australia/Sydney'), + onSaved: (String? value) => + _event?.updateEndLocation(value), + ), ), - ), - ListTile( - onTap: _calendar.isReadOnly == false - ? () async { - var result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - EventAttendeePage())); - if (result != null) { - setState(() { - _attendees.add(result); - }); - } - } - : null, - leading: Icon(Icons.people), - title: Text(_calendar.isReadOnly == false - ? 'Add Attendees' - : 'Attendees'), - ), - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: _attendees.length, - itemBuilder: (context, index) { - return Container( - color: _attendees[index].isOrganiser - ? Colors.greenAccent[100] - : Colors.transparent, - child: ListTile( - onTap: () async { - var result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EventAttendeePage( - attendee: _attendees[index], - eventId: _event?.eventId))); - if (result != null) { - return setState(() { - _attendees[index] = result; - }); + ListTile( + onTap: _calendar.isReadOnly == false + ? () async { + var result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const EventAttendeePage())); + if (result != null) { + setState(() { + _attendees.add(result); + }); + } } - }, - title: Padding( - padding: - const EdgeInsets.symmetric(vertical: 10.0), - child: Text( - '${_attendees[index].name} (${_attendees[index].emailAddress})'), - ), - subtitle: Wrap( - spacing: 10, - direction: Axis.horizontal, - alignment: WrapAlignment.end, - children: [ - Visibility( - visible: _attendees[index] - .androidAttendeeDetails != - null, - child: Container( - margin: const EdgeInsets.symmetric( - vertical: 10.0), - padding: const EdgeInsets.all(3.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.blueAccent)), - child: Text( - 'Android: ${_attendees[index].androidAttendeeDetails?.attendanceStatus?.enumToString}')), - ), - Visibility( - visible: - _attendees[index].iosAttendeeDetails != - null, - child: Container( - margin: const EdgeInsets.symmetric( - vertical: 10.0), - padding: const EdgeInsets.all(3.0), - decoration: BoxDecoration( - border: Border.all( - color: Colors.blueAccent)), - child: Text( - 'iOS: ${_attendees[index].iosAttendeeDetails?.attendanceStatus?.enumToString}')), - ), - Visibility( - visible: _attendees[index].isCurrentUser, + : null, + leading: const Icon(Icons.people), + title: Text(_calendar.isReadOnly == false + ? 'Add Attendees' + : 'Attendees'), + ), + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: _attendees.length, + itemBuilder: (context, index) { + return Container( + color: _attendees[index].isOrganiser + ? Colors.greenAccent[100] + : Colors.transparent, + child: ListTile( + onTap: () async { + var result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EventAttendeePage( + attendee: _attendees[index], + eventId: _event?.eventId))); + if (result != null) { + return setState(() { + _attendees[index] = result; + }); + } + }, + title: Padding( + padding: + const EdgeInsets.symmetric(vertical: 10.0), + child: Text( + '${_attendees[index].name} (${_attendees[index].emailAddress})'), + ), + subtitle: Wrap( + spacing: 10, + direction: Axis.horizontal, + alignment: WrapAlignment.end, + children: [ + Visibility( + visible: _attendees[index] + .androidAttendeeDetails != + null, child: Container( margin: const EdgeInsets.symmetric( vertical: 10.0), @@ -479,9 +456,13 @@ class _CalendarEventPageState extends State { decoration: BoxDecoration( border: Border.all( color: Colors.blueAccent)), - child: Text('current user'))), - Visibility( - visible: _attendees[index].isOrganiser, + child: Text( + 'Android: ${_attendees[index].androidAttendeeDetails?.attendanceStatus?.enumToString}')), + ), + Visibility( + visible: + _attendees[index].iosAttendeeDetails != + null, child: Container( margin: const EdgeInsets.symmetric( vertical: 10.0), @@ -489,406 +470,431 @@ class _CalendarEventPageState extends State { decoration: BoxDecoration( border: Border.all( color: Colors.blueAccent)), - child: Text('Organiser'))), - Container( - margin: const EdgeInsets.symmetric( - vertical: 10.0), - padding: const EdgeInsets.all(3.0), - decoration: BoxDecoration( - border: - Border.all(color: Colors.blueAccent)), - child: Text( - '${_attendees[index].role?.enumToString}'), - ), - IconButton( - padding: const EdgeInsets.all(0), - onPressed: () { - setState(() { - _attendees.removeAt(index); - }); - }, - icon: Icon( - Icons.remove_circle, - color: Colors.redAccent, + child: Text( + 'iOS: ${_attendees[index].iosAttendeeDetails?.attendanceStatus?.enumToString}')), + ), + Visibility( + visible: _attendees[index].isCurrentUser, + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 10.0), + padding: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + border: Border.all( + color: Colors.blueAccent)), + child: const Text('current user'))), + Visibility( + visible: _attendees[index].isOrganiser, + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 10.0), + padding: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + border: Border.all( + color: Colors.blueAccent)), + child: const Text('Organiser'))), + Container( + margin: const EdgeInsets.symmetric( + vertical: 10.0), + padding: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + border: Border.all( + color: Colors.blueAccent)), + child: Text( + '${_attendees[index].role?.enumToString}'), ), - ) + IconButton( + padding: const EdgeInsets.all(0), + onPressed: () { + setState(() { + _attendees.removeAt(index); + }); + }, + icon: const Icon( + Icons.remove_circle, + color: Colors.redAccent, + ), + ) + ], + ), + ), + ); + }, + ), + GestureDetector( + onTap: () async { + var result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + EventRemindersPage(_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: [ + const Icon(Icons.alarm), + if (_reminders.isEmpty) + Text(_calendar.isReadOnly == false + ? 'Add reminders' + : 'Reminders'), + for (var reminder in _reminders) + Text('${reminder.minutes} minutes before; ') ], ), ), - ); - }, - ), - GestureDetector( - onTap: () async { - var result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - EventRemindersPage(_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(_calendar.isReadOnly == false - ? 'Add reminders' - : 'Reminders'), - for (var reminder in _reminders) - Text('${reminder.minutes} minutes before; ') - ], - ), ), ), - ), - CheckboxListTile( - value: _isRecurringEvent, - title: Text('Is recurring'), - onChanged: (isChecked) { - setState(() { - _isRecurringEvent = isChecked ?? false; - }); - }, - ), - if (_isRecurringEvent) ...[ - ListTile( - leading: Text('Select a Recurrence Type'), - trailing: DropdownButton( - onChanged: (selectedFrequency) { - setState(() { - _recurrenceFrequency = selectedFrequency; - _getValidDaysOfMonth(_recurrenceFrequency); - }); - }, - value: _recurrenceFrequency, - items: RecurrenceFrequency.values - .map((frequency) => DropdownMenuItem( - value: frequency, - child: - _recurrenceFrequencyToText(frequency), - )) - .toList(), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), - child: Row( - children: [ - Text('Repeat Every '), - Flexible( - child: TextFormField( - initialValue: _interval?.toString() ?? '1', - decoration: - const InputDecoration(hintText: '1'), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(2) - ], - validator: _validateInterval, - textAlign: TextAlign.right, - onSaved: (String? value) { - if (value != null) { - _interval = int.tryParse(value); - } - }, - ), - ), - _recurrenceFrequencyToIntervalText( - _recurrenceFrequency), - ], - ), + CheckboxListTile( + value: _isRecurringEvent, + title: const Text('Is recurring'), + onChanged: (isChecked) { + setState(() { + _isRecurringEvent = isChecked ?? false; + }); + }, ), - if (_recurrenceFrequency == - RecurrenceFrequency.Weekly) ...[ - Column( - children: [ - ...DayOfWeek.values.map((day) { - return CheckboxListTile( - title: Text(day.enumToString), - value: _daysOfWeek.any((dow) => dow == day), - onChanged: (selected) { - setState(() { - if (selected == true) { - _daysOfWeek.add(day); - } else { - _daysOfWeek.remove(day); - } - _updateDaysOfWeekGroup(selectedDay: day); - }); - }, - ); - }), - Divider(color: Colors.black), - ...DayOfWeekGroup.values.map((group) { - return RadioListTile( - title: Text(group.enumToString), - value: group, - groupValue: _dayOfWeekGroup, - onChanged: (selected) { - setState(() { - _dayOfWeekGroup = - selected as DayOfWeekGroup; - _updateDaysOfWeek(); - }); - }, - controlAffinity: - ListTileControlAffinity.trailing); - }), - ], - ) - ], - if (_recurrenceFrequency == RecurrenceFrequency.Monthly || - _recurrenceFrequency == - RecurrenceFrequency.Yearly) ...[ - SwitchListTile( - value: _isByDayOfMonth, - onChanged: (value) => - setState(() => _isByDayOfMonth = value), - title: Text('By day of the month'), - ) - ], - if (_recurrenceFrequency == RecurrenceFrequency.Yearly && - _isByDayOfMonth) ...[ + if (_isRecurringEvent) ...[ ListTile( - leading: Text('Month of the year'), - trailing: DropdownButton( - onChanged: (value) { + leading: const Text('Select a Recurrence Type'), + trailing: DropdownButton( + onChanged: (selectedFrequency) { setState(() { - _monthOfYear = value; + _recurrenceFrequency = selectedFrequency; _getValidDaysOfMonth(_recurrenceFrequency); }); }, - value: _monthOfYear, - items: MonthOfYear.values - .map((month) => DropdownMenuItem( - value: month, - child: Text(month.enumToString), - )) - .toList(), - ), - ), - ], - if (_isByDayOfMonth && - (_recurrenceFrequency == - RecurrenceFrequency.Monthly || - _recurrenceFrequency == - RecurrenceFrequency.Yearly)) ...[ - ListTile( - leading: Text('Day of the month'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _dayOfMonth = value; - }); - }, - value: _dayOfMonth, - items: _validDaysOfMonth - .map((day) => DropdownMenuItem( - value: day, - child: Text(day.toString()), + value: _recurrenceFrequency, + items: RecurrenceFrequency.values + .map((frequency) => DropdownMenuItem( + value: frequency, + child: + _recurrenceFrequencyToText(frequency), )) .toList(), ), ), - ], - if (!_isByDayOfMonth && - (_recurrenceFrequency == - RecurrenceFrequency.Monthly || - _recurrenceFrequency == - RecurrenceFrequency.Yearly)) ...[ - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 15, 10), - child: Align( - alignment: Alignment.centerLeft, - child: _recurrenceFrequencyToText( - _recurrenceFrequency) - .data != - null - ? Text(_recurrenceFrequencyToText( - _recurrenceFrequency) - .data! + - ' on the ') - : Text('')), - ), Padding( padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( - child: DropdownButton( - onChanged: (value) { - setState(() { - _weekOfMonth = value; - }); - }, - value: _weekOfMonth ?? WeekNumber.First, - items: WeekNumber.values - .map((weekNum) => DropdownMenuItem( - value: weekNum, - child: Text(weekNum.enumToString), - )) - .toList(), - ), - ), - Flexible( - child: DropdownButton( - onChanged: (value) { - setState(() { - _selectedDayOfWeek = value; - }); - }, - value: _selectedDayOfWeek != null - ? DayOfWeek - .values[_selectedDayOfWeek!.index] - : DayOfWeek.values[0], - items: DayOfWeek.values - .map((day) => DropdownMenuItem( - value: day, - child: Text(day.enumToString), - )) - .toList(), - ), - ), - if (_recurrenceFrequency == - RecurrenceFrequency.Yearly) ...[ - Text('of'), - Flexible( - child: DropdownButton( - onChanged: (value) { - setState(() { - _monthOfYear = value; - }); - }, - value: _monthOfYear, - items: MonthOfYear.values - .map((month) => DropdownMenuItem( - value: month, - child: Text(month.enumToString), - )) - .toList(), - ), - ), - ] - ], - ), - ), - ], - ListTile( - leading: Text('Event ends'), - trailing: DropdownButton( - onChanged: (value) { - setState(() { - _recurrenceRuleEndType = value; - }); - }, - value: _recurrenceRuleEndType, - items: RecurrenceRuleEndType.values - .map((frequency) => DropdownMenuItem( - value: frequency, - child: - _recurrenceRuleEndTypeToText(frequency), - )) - .toList(), - ), - ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.MaxOccurrences) - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), - child: Row( - children: [ - Text('For the next '), + const Text('Repeat Every '), Flexible( child: TextFormField( - initialValue: - _totalOccurrences?.toString() ?? '1', + initialValue: _interval?.toString() ?? '1', decoration: const InputDecoration(hintText: '1'), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(3), + LengthLimitingTextInputFormatter(2) ], - validator: _validateTotalOccurrences, + validator: _validateInterval, textAlign: TextAlign.right, onSaved: (String? value) { if (value != null) { - _totalOccurrences = int.tryParse(value); + _interval = int.tryParse(value); } }, ), ), - Text(' occurrences'), + _recurrenceFrequencyToIntervalText( + _recurrenceFrequency), ], ), ), - if (_recurrenceRuleEndType == - RecurrenceRuleEndType.SpecifiedEndDate) - Padding( - padding: const EdgeInsets.all(10.0), - child: DateTimePicker( - labelText: 'Date', - enableTime: false, - selectedDate: _recurrenceEndDate, - selectDate: (DateTime date) { + if (_recurrenceFrequency == + RecurrenceFrequency.Weekly) ...[ + Column( + children: [ + ...DayOfWeek.values.map((day) { + return CheckboxListTile( + title: Text(day.enumToString), + value: _daysOfWeek.any((dow) => dow == day), + onChanged: (selected) { + setState(() { + if (selected == true) { + _daysOfWeek.add(day); + } else { + _daysOfWeek.remove(day); + } + _updateDaysOfWeekGroup(selectedDay: day); + }); + }, + ); + }), + const Divider(color: Colors.black), + ...DayOfWeekGroup.values.map((group) { + return RadioListTile( + title: Text(group.enumToString), + value: group, + groupValue: _dayOfWeekGroup, + onChanged: (selected) { + setState(() { + _dayOfWeekGroup = + selected as DayOfWeekGroup; + _updateDaysOfWeek(); + }); + }, + controlAffinity: + ListTileControlAffinity.trailing); + }), + ], + ) + ], + if (_recurrenceFrequency == + RecurrenceFrequency.Monthly || + _recurrenceFrequency == + RecurrenceFrequency.Yearly) ...[ + SwitchListTile( + value: _isByDayOfMonth, + onChanged: (value) => + setState(() => _isByDayOfMonth = value), + title: const Text('By day of the month'), + ) + ], + if (_recurrenceFrequency == + RecurrenceFrequency.Yearly && + _isByDayOfMonth) ...[ + ListTile( + leading: const Text('Month of the year'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _monthOfYear = value; + _getValidDaysOfMonth(_recurrenceFrequency); + }); + }, + value: _monthOfYear, + items: MonthOfYear.values + .map((month) => DropdownMenuItem( + value: month, + child: Text(month.enumToString), + )) + .toList(), + ), + ), + ], + if (_isByDayOfMonth && + (_recurrenceFrequency == + RecurrenceFrequency.Monthly || + _recurrenceFrequency == + RecurrenceFrequency.Yearly)) ...[ + ListTile( + leading: const Text('Day of the month'), + trailing: DropdownButton( + onChanged: (value) { + setState(() { + _dayOfMonth = value; + }); + }, + value: _dayOfMonth, + items: _validDaysOfMonth + .map((day) => DropdownMenuItem( + value: day, + child: Text(day.toString()), + )) + .toList(), + ), + ), + ], + if (!_isByDayOfMonth && + (_recurrenceFrequency == + RecurrenceFrequency.Monthly || + _recurrenceFrequency == + RecurrenceFrequency.Yearly)) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(15, 10, 15, 10), + child: Align( + alignment: Alignment.centerLeft, + child: _recurrenceFrequencyToText( + _recurrenceFrequency) + .data != + null + ? Text(_recurrenceFrequencyToText( + _recurrenceFrequency) + .data! + + ' on the ') + : const Text('')), + ), + Padding( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: DropdownButton( + onChanged: (value) { + setState(() { + _weekOfMonth = value; + }); + }, + value: _weekOfMonth ?? WeekNumber.First, + items: WeekNumber.values + .map((weekNum) => DropdownMenuItem( + value: weekNum, + child: Text(weekNum.enumToString), + )) + .toList(), + ), + ), + Flexible( + child: DropdownButton( + onChanged: (value) { + setState(() { + _selectedDayOfWeek = value; + }); + }, + value: _selectedDayOfWeek != null + ? DayOfWeek + .values[_selectedDayOfWeek!.index] + : DayOfWeek.values[0], + items: DayOfWeek.values + .map((day) => DropdownMenuItem( + value: day, + child: Text(day.enumToString), + )) + .toList(), + ), + ), + if (_recurrenceFrequency == + RecurrenceFrequency.Yearly) ...[ + const Text('of'), + Flexible( + child: DropdownButton( + onChanged: (value) { + setState(() { + _monthOfYear = value; + }); + }, + value: _monthOfYear, + items: MonthOfYear.values + .map((month) => DropdownMenuItem( + value: month, + child: Text(month.enumToString), + )) + .toList(), + ), + ), + ] + ], + ), + ), + ], + ListTile( + leading: const Text('Event ends'), + trailing: DropdownButton( + onChanged: (value) { setState(() { - _recurrenceEndDate = date; + _recurrenceRuleEndType = value; }); }, + value: _recurrenceRuleEndType, + items: RecurrenceRuleEndType.values + .map((frequency) => DropdownMenuItem( + value: frequency, + child: _recurrenceRuleEndTypeToText( + frequency), + )) + .toList(), ), ), + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.MaxOccurrences) + Padding( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), + child: Row( + children: [ + const Text('For the next '), + Flexible( + child: TextFormField( + initialValue: + _totalOccurrences?.toString() ?? '1', + decoration: + const InputDecoration(hintText: '1'), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(3), + ], + validator: _validateTotalOccurrences, + textAlign: TextAlign.right, + onSaved: (String? value) { + if (value != null) { + _totalOccurrences = int.tryParse(value); + } + }, + ), + ), + const Text(' occurrences'), + ], + ), + ), + if (_recurrenceRuleEndType == + RecurrenceRuleEndType.SpecifiedEndDate) + Padding( + padding: const EdgeInsets.all(10.0), + child: DateTimePicker( + labelText: 'Date', + enableTime: false, + selectedDate: _recurrenceEndDate, + selectDate: (DateTime date) { + setState(() { + _recurrenceEndDate = date; + }); + }, + ), + ), + ], ], - ], + ), ), - ), - if (_calendar.isReadOnly == false && - (_event?.eventId?.isNotEmpty ?? false)) ...[ - ElevatedButton( - key: Key('deleteEventButton'), - style: ElevatedButton.styleFrom( - primary: Colors.red, onPrimary: Colors.white), - onPressed: () async { - bool? result = true; - if (!_isRecurringEvent) { - await _deviceCalendarPlugin.deleteEvent( - _calendar.id, _event?.eventId); - } else { - result = await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return _recurringEventDialog != null - ? _recurringEventDialog as Widget - : SizedBox(); - }); - } + if (_calendar.isReadOnly == false && + (_event?.eventId?.isNotEmpty ?? false)) ...[ + ElevatedButton( + key: const Key('deleteEventButton'), + style: ElevatedButton.styleFrom( + primary: Colors.red, onPrimary: Colors.white), + onPressed: () async { + bool? result = true; + if (!_isRecurringEvent) { + await _deviceCalendarPlugin.deleteEvent( + _calendar.id, _event?.eventId); + } else { + result = await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return _recurringEventDialog != null + ? _recurringEventDialog as Widget + : const SizedBox(); + }); + } - if (result == true) { - Navigator.pop(context, true); - } - }, - child: Text('Delete'), - ), - ] - ], + if (result == true) { + Navigator.pop(context, true); + } + }, + child: const Text('Delete'), + ), + ] + ], + ), ), ), ), floatingActionButton: Visibility( visible: _calendar.isReadOnly == false, child: FloatingActionButton( - key: Key('saveEventButton'), + key: const Key('saveEventButton'), onPressed: () async { final form = _formKey.currentState; if (form?.validate() == false) { @@ -940,7 +946,7 @@ class _CalendarEventPageState extends State { } } }, - child: Icon(Icons.check), + child: const Icon(Icons.check), ), ), ); @@ -949,15 +955,15 @@ class _CalendarEventPageState extends State { Text _recurrenceFrequencyToText(RecurrenceFrequency? recurrenceFrequency) { switch (recurrenceFrequency) { case RecurrenceFrequency.Daily: - return Text('Daily'); + return const Text('Daily'); case RecurrenceFrequency.Weekly: - return Text('Weekly'); + return const Text('Weekly'); case RecurrenceFrequency.Monthly: - return Text('Monthly'); + return const Text('Monthly'); case RecurrenceFrequency.Yearly: - return Text('Yearly'); + return const Text('Yearly'); default: - return Text(''); + return const Text(''); } } @@ -965,28 +971,28 @@ class _CalendarEventPageState extends State { RecurrenceFrequency? recurrenceFrequency) { switch (recurrenceFrequency) { case RecurrenceFrequency.Daily: - return Text(' Day(s)'); + return const Text(' Day(s)'); case RecurrenceFrequency.Weekly: - return Text(' Week(s) on'); + return const Text(' Week(s) on'); case RecurrenceFrequency.Monthly: - return Text(' Month(s)'); + return const Text(' Month(s)'); case RecurrenceFrequency.Yearly: - return Text(' Year(s)'); + return const Text(' Year(s)'); default: - return Text(''); + return const Text(''); } } Text _recurrenceRuleEndTypeToText(RecurrenceRuleEndType endType) { switch (endType) { case RecurrenceRuleEndType.Indefinite: - return Text('Indefinitely'); + return const Text('Indefinitely'); case RecurrenceRuleEndType.MaxOccurrences: - return Text('After a set number of times'); + return const Text('After a set number of times'); case RecurrenceRuleEndType.SpecifiedEndDate: - return Text('Continues until a specified date'); + return const Text('Continues until a specified date'); default: - return Text(''); + return const Text(''); } } diff --git a/example/lib/presentation/pages/calendar_events.dart b/example/lib/presentation/pages/calendar_events.dart index 0cd6f182..f0b6c27e 100644 --- a/example/lib/presentation/pages/calendar_events.dart +++ b/example/lib/presentation/pages/calendar_events.dart @@ -61,19 +61,19 @@ class _CalendarEventsPageState extends State { }, ), if (_isLoading) - Center( + const Center( child: CircularProgressIndicator(), ) ], ) - : Center(child: Text('No events found')), + : const Center(child: Text('No events found')), floatingActionButton: _getAddEventButton(context)); } Widget? _getAddEventButton(BuildContext context) { if (_calendar.isReadOnly == false || _calendar.isReadOnly == null) { return FloatingActionButton( - key: Key('addEventButton'), + key: const Key('addEventButton'), onPressed: () async { final refreshEvents = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { @@ -83,7 +83,7 @@ class _CalendarEventsPageState extends State { await _retrieveCalendarEvents(); } }, - child: Icon(Icons.add), + child: const Icon(Icons.add), ); } else { return null; @@ -100,7 +100,7 @@ class _CalendarEventsPageState extends State { if (deleteSucceeded) { await _retrieveCalendarEvents(); } else { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Oops, we ran into an issue deleting the event'), backgroundColor: Colors.red, duration: Duration(seconds: 5), @@ -131,8 +131,8 @@ class _CalendarEventsPageState extends State { } Future _retrieveCalendarEvents() async { - final startDate = DateTime.now().add(Duration(days: -30)); - final endDate = DateTime.now().add(Duration(days: 30)); + final startDate = DateTime.now().add(const Duration(days: -30)); + final endDate = DateTime.now().add(const Duration(days: 30)); var calendarEventsResult = await _deviceCalendarPlugin.retrieveEvents( _calendar.id, RetrieveEventsParams(startDate: startDate, endDate: endDate)); @@ -144,7 +144,7 @@ class _CalendarEventsPageState extends State { Widget _getDeleteButton() { return IconButton( - icon: Icon(Icons.delete), + icon: const Icon(Icons.delete), onPressed: () async { await _showDeleteDialog(); }); @@ -155,12 +155,12 @@ class _CalendarEventsPageState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Warning'), + title: const Text('Warning'), content: SingleChildScrollView( child: ListBody( children: [ - Text('This will delete this calendar'), - Text('Are you sure?'), + const Text('This will delete this calendar'), + const Text('Are you sure?'), ], ), ), @@ -174,13 +174,13 @@ class _CalendarEventsPageState extends State { Navigator.of(context).pop(); Navigator.of(context).pop(); }, - child: Text('Delete!'), + child: const Text('Delete!'), ), TextButton( onPressed: () { Navigator.of(context).pop(); }, - child: Text('Cancel'), + child: const Text('Cancel'), ), ], ); diff --git a/example/lib/presentation/pages/calendars.dart b/example/lib/presentation/pages/calendars.dart index 98601e14..b09596fe 100644 --- a/example/lib/presentation/pages/calendars.dart +++ b/example/lib/presentation/pages/calendars.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'calendar_events.dart'; class CalendarsPage extends StatefulWidget { - CalendarsPage({Key? key}) : super(key: key); + const CalendarsPage({Key? key}) : super(key: key); @override _CalendarsPageState createState() { @@ -37,7 +37,7 @@ class _CalendarsPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Calendars'), + title: const Text('Calendars'), actions: [_getRefreshButton()], ), body: Column( @@ -62,7 +62,7 @@ class _CalendarsPageState extends State { await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { return CalendarEventsPage(_calendars[index], - key: Key('calendarEventsPage')); + key: const Key('calendarEventsPage')); })); }, child: Padding( @@ -91,14 +91,14 @@ class _CalendarsPageState extends State { shape: BoxShape.circle, color: Color(_calendars[index].color!)), ), - SizedBox(width: 10), + const SizedBox(width: 10), if (_calendars[index].isDefault!) Container( margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0), padding: const EdgeInsets.all(3.0), decoration: BoxDecoration( border: Border.all(color: Colors.blueAccent)), - child: Text('Default'), + child: const Text('Default'), ), Icon(_calendars[index].isReadOnly == true ? Icons.lock @@ -123,7 +123,7 @@ class _CalendarsPageState extends State { _retrieveCalendars(); } }, - child: Icon(Icons.add), + child: const Icon(Icons.add), ), ); } @@ -153,7 +153,7 @@ class _CalendarsPageState extends State { Widget _getRefreshButton() { return IconButton( - icon: Icon(Icons.refresh), + icon: const Icon(Icons.refresh), onPressed: () async { _retrieveCalendars(); }); diff --git a/example/lib/presentation/pages/event_attendee.dart b/example/lib/presentation/pages/event_attendee.dart index 002ba6eb..a2f17aba 100644 --- a/example/lib/presentation/pages/event_attendee.dart +++ b/example/lib/presentation/pages/event_attendee.dart @@ -90,7 +90,7 @@ class _EventAttendeePageState extends State { ), ), ListTile( - leading: Text('Role'), + leading: const Text('Role'), trailing: DropdownButton( onChanged: (value) { setState(() { @@ -118,14 +118,14 @@ class _EventAttendeePageState extends State { context, ModalRoute.withName(AppRoutes.calendars)); //TODO: finish calling and getting attendee details from iOS }, - leading: Icon(Icons.edit), - title: Text('View / edit iOS attendance details'), + leading: const Icon(Icons.edit), + title: const Text('View / edit iOS attendance details'), ), ), Visibility( visible: Platform.isAndroid, child: ListTile( - leading: Text('Android attendee status'), + leading: const Text('Android attendee status'), trailing: DropdownButton( onChanged: (value) { setState(() { diff --git a/example/lib/presentation/pages/event_reminders.dart b/example/lib/presentation/pages/event_reminders.dart index 11619285..4b0a11f3 100644 --- a/example/lib/presentation/pages/event_reminders.dart +++ b/example/lib/presentation/pages/event_reminders.dart @@ -3,7 +3,7 @@ import 'package:device_calendar/device_calendar.dart'; class EventRemindersPage extends StatefulWidget { final List _reminders; - EventRemindersPage(this._reminders, {Key? key}) : super(key: key); + const EventRemindersPage(this._reminders, {Key? key}) : super(key: key); @override _EventRemindersPageState createState() => @@ -29,7 +29,7 @@ class _EventRemindersPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Reminders'), + title: const Text('Reminders'), ), body: Column( children: [ @@ -64,7 +64,7 @@ class _EventRemindersPageState extends State { }); } }, - child: Text('Add'), + child: const Text('Add'), ), ], ), @@ -83,7 +83,7 @@ class _EventRemindersPageState extends State { (a) => a.minutes == _reminders[index].minutes); }); }, - child: Text('Delete'), + child: const Text('Delete'), ), ); }, @@ -93,7 +93,7 @@ class _EventRemindersPageState extends State { onPressed: () { Navigator.pop(context, _reminders); }, - child: Text('Done'), + child: const Text('Done'), ) ], ), diff --git a/example/lib/presentation/recurring_event_dialog.dart b/example/lib/presentation/recurring_event_dialog.dart index b3c63b3d..c8b8ff35 100644 --- a/example/lib/presentation/recurring_event_dialog.dart +++ b/example/lib/presentation/recurring_event_dialog.dart @@ -8,7 +8,7 @@ class RecurringEventDialog extends StatefulWidget { final VoidCallback _onLoadingStarted; final Function(bool) _onDeleteFinished; - RecurringEventDialog(this._deviceCalendarPlugin, this._calendarEvent, + const RecurringEventDialog(this._deviceCalendarPlugin, this._calendarEvent, this._onLoadingStarted, this._onDeleteFinished, {Key? key}) : super(key: key); @@ -38,7 +38,7 @@ class _RecurringEventDialogState extends State { @override Widget build(BuildContext context) { return SimpleDialog( - title: Text('Are you sure you want to delete this event?'), + title: const Text('Are you sure you want to delete this event?'), children: [ SimpleDialogOption( onPressed: () async { @@ -56,7 +56,7 @@ class _RecurringEventDialogState extends State { deleteResult.isSuccess && deleteResult.data != null); } }, - child: Text('This instance only'), + child: const Text('This instance only'), ), SimpleDialogOption( onPressed: () async { @@ -74,7 +74,7 @@ class _RecurringEventDialogState extends State { deleteResult.isSuccess && deleteResult.data != null); } }, - child: Text('This and following instances'), + child: const Text('This and following instances'), ), SimpleDialogOption( onPressed: () async { @@ -87,13 +87,13 @@ class _RecurringEventDialogState extends State { deleteResult.isSuccess && deleteResult.data != null); } }, - child: Text('All instances'), + child: const Text('All instances'), ), SimpleDialogOption( onPressed: () { Navigator.of(context).pop(false); }, - child: Text('Cancel'), + child: const Text('Cancel'), ) ], ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c6eeea43..e840c944 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,7 @@ name: device_calendar_example description: Demonstrates how to use the device_calendar plugin. version: 3.2.0 +publish_to: none environment: sdk: '>=2.12.0 <3.0.0' diff --git a/lib/src/common/error_messages.dart b/lib/src/common/error_messages.dart index dc05d425..f2126006 100644 --- a/lib/src/common/error_messages.dart +++ b/lib/src/common/error_messages.dart @@ -21,7 +21,7 @@ class ErrorMessages { static const String unknownDeviceIssue = 'Device calendar plugin ran into an unknown issue'; static const String unknownDeviceExceptionTemplate = - 'Device calendar plugin ran into an issue. Platform specific exception [%s], with message :\"%s\", has been thrown.'; + 'Device calendar plugin ran into an issue. Platform specific exception [%s], with message :"%s", has been thrown.'; static const String unknownDeviceGenericExceptionTemplate = - 'Device calendar plugin ran into an issue, with message \"%s\"'; + 'Device calendar plugin ran into an issue, with message "%s"'; } diff --git a/lib/src/device_calendar.dart b/lib/src/device_calendar.dart index eb1d9db1..728024c1 100644 --- a/lib/src/device_calendar.dart +++ b/lib/src/device_calendar.dart @@ -214,9 +214,9 @@ class DeviceCalendarPlugin { // allDay events on Android need to be at midnight UTC event.start = Platform.isAndroid ? TZDateTime.utc(event.start!.year, event.start!.month, - event.start!.day, 0, 0, 0) + event.start!.day, 0, 0, 0) : TZDateTime.from(dateStart, - timeZoneDatabase.locations[event.start!.location.name]!); + timeZoneDatabase.locations[event.start!.location.name]!); } if (event.end != null) { var dateEnd = DateTime( @@ -226,10 +226,10 @@ class DeviceCalendarPlugin { // Jan 1 and 2, should be from Jan 1 00:00:00 to Jan 3 00:00:00 event.end = Platform.isAndroid ? TZDateTime.utc(event.end!.year, event.end!.month, - event.end!.day, 0, 0, 0) - .add(Duration(days: 1)) + event.end!.day, 0, 0, 0) + .add(const Duration(days: 1)) : TZDateTime.from(dateEnd, - timeZoneDatabase.locations[event.end!.location.name]!); + timeZoneDatabase.locations[event.end!.location.name]!); } } @@ -322,7 +322,7 @@ class DeviceCalendarPlugin { /// Displays a native iOS view [EKEventViewController] /// https://developer.apple.com/documentation/eventkitui/ekeventviewcontroller - /// + /// /// Allows to change the event's attendance status /// Works only on iOS /// Returns after dismissing EKEventViewController's dialog @@ -337,7 +337,6 @@ class DeviceCalendarPlugin { ); } - Future> _invokeChannelMethod( String channelMethodName, { Function(Result)? assertParameters, @@ -375,7 +374,7 @@ class DeviceCalendarPlugin { Exception? exception, Result result) { if (exception == null) { result.errors.add( - ResultError( + const ResultError( ErrorCodes.unknown, ErrorMessages.unknownDeviceIssue, ), diff --git a/lib/src/models/event.dart b/lib/src/models/event.dart index bc3e2f2d..ff536688 100644 --- a/lib/src/models/event.dart +++ b/lib/src/models/event.dart @@ -138,7 +138,7 @@ class Event { end = end?.subtract(Duration(milliseconds: endOffset)); // The Event End Date for allDay events is midnight of the next day, so // subtract one day - end = end?.subtract(Duration(days: 1)); + end = end?.subtract(const Duration(days: 1)); } location = json['eventLocation']; availability = parseStringToAvailability(json['availability']); @@ -179,7 +179,7 @@ class Event { }).toList(); } if (legacyJSON) { - throw FormatException( + throw const FormatException( 'legacy JSON detected. Please update your current JSONs as they may not be supported later on.'); } } diff --git a/test/device_calendar_test.dart b/test/device_calendar_test.dart index 6c105c0a..3889c44b 100644 --- a/test/device_calendar_test.dart +++ b/test/device_calendar_test.dart @@ -7,8 +7,7 @@ import 'package:timezone/timezone.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final channel = - const MethodChannel('plugins.builttoroam.com/device_calendar'); + const channel = MethodChannel('plugins.builttoroam.com/device_calendar'); var deviceCalendarPlugin = DeviceCalendarPlugin(); final log = []; @@ -47,9 +46,9 @@ void main() { }); test('RetrieveCalendars_Returns_Successfully', () async { - final fakeCalendarName = 'fakeCalendarName'; + const fakeCalendarName = 'fakeCalendarName'; channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '[{\"id\":\"1\",\"isReadOnly\":false,\"name\":\"$fakeCalendarName\"}]'; + return '[{"id":"1","isReadOnly":false,"name":"$fakeCalendarName"}]'; }); final result = await deviceCalendarPlugin.retrieveCalendars(); @@ -61,8 +60,8 @@ void main() { }); test('RetrieveEvents_CalendarId_IsRequired', () async { - final String? calendarId = null; - final params = RetrieveEventsParams(); + const String? calendarId = null; + const params = RetrieveEventsParams(); final result = await deviceCalendarPlugin.retrieveEvents(calendarId, params); @@ -72,8 +71,8 @@ void main() { }); test('DeleteEvent_CalendarId_IsRequired', () async { - final String? calendarId = null; - final eventId = 'fakeEventId'; + const String? calendarId = null; + const eventId = 'fakeEventId'; final result = await deviceCalendarPlugin.deleteEvent(calendarId, eventId); expect(result.isSuccess, false); @@ -82,8 +81,8 @@ void main() { }); test('DeleteEvent_EventId_IsRequired', () async { - final calendarId = 'fakeCalendarId'; - final String? eventId = null; + const calendarId = 'fakeCalendarId'; + const String? eventId = null; final result = await deviceCalendarPlugin.deleteEvent(calendarId, eventId); expect(result.isSuccess, false); @@ -92,8 +91,8 @@ void main() { }); test('DeleteEvent_PassesArguments_Correctly', () async { - final calendarId = 'fakeCalendarId'; - final eventId = 'fakeEventId'; + const calendarId = 'fakeCalendarId'; + const eventId = 'fakeEventId'; await deviceCalendarPlugin.deleteEvent(calendarId, eventId); expect(log, [ @@ -105,7 +104,7 @@ void main() { }); test('CreateEvent_Arguments_Invalid', () async { - final String? fakeCalendarId = null; + const String? fakeCalendarId = null; final event = Event(fakeCalendarId); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); @@ -115,16 +114,16 @@ void main() { }); test('CreateEvent_Returns_Successfully', () async { - final fakeNewEventId = 'fakeNewEventId'; + const fakeNewEventId = 'fakeNewEventId'; channel.setMockMethodCallHandler((MethodCall methodCall) async { return fakeNewEventId; }); - final fakeCalendarId = 'fakeCalendarId'; + const fakeCalendarId = 'fakeCalendarId'; final event = Event(fakeCalendarId); event.title = 'fakeEventTitle'; event.start = TZDateTime.now(local); - event.end = event.start!.add(Duration(hours: 1)); + event.end = event.start!.add(const Duration(hours: 1)); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); expect(result?.isSuccess, true); @@ -134,7 +133,7 @@ void main() { }); test('UpdateEvent_Returns_Successfully', () async { - final fakeNewEventId = 'fakeNewEventId'; + const fakeNewEventId = 'fakeNewEventId'; channel.setMockMethodCallHandler((MethodCall methodCall) async { final arguments = methodCall.arguments as Map; if (!arguments.containsKey('eventId') || arguments['eventId'] == null) { @@ -144,12 +143,12 @@ void main() { return fakeNewEventId; }); - final fakeCalendarId = 'fakeCalendarId'; + const fakeCalendarId = 'fakeCalendarId'; final event = Event(fakeCalendarId); event.eventId = 'fakeEventId'; event.title = 'fakeEventTitle'; event.start = TZDateTime.now(local); - event.end = event.start!.add(Duration(hours: 1)); + event.end = event.start!.add(const Duration(hours: 1)); final result = await deviceCalendarPlugin.createOrUpdateEvent(event); expect(result?.isSuccess, true); From 9147125e7675be9a959a3d7cc2eda80ce2b4a76d Mon Sep 17 00:00:00 2001 From: thomassth Date: Thu, 6 Oct 2022 21:21:22 -0400 Subject: [PATCH 05/15] more linting fixes --- example/lib/main.dart | 2 +- example/lib/presentation/event_item.dart | 2 +- example/lib/presentation/pages/calendar_events.dart | 2 +- example/lib/presentation/pages/calendars.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ffb03586..3b5d61ee 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,7 +9,7 @@ class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override - _MyAppState createState() => _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State { diff --git a/example/lib/presentation/event_item.dart b/example/lib/presentation/event_item.dart index ec0eb425..d0fa800d 100644 --- a/example/lib/presentation/event_item.dart +++ b/example/lib/presentation/event_item.dart @@ -27,7 +27,7 @@ class EventItem extends StatefulWidget { : super(key: key); @override - _EventItemState createState() { + State createState() { return _EventItemState(); } } diff --git a/example/lib/presentation/pages/calendar_events.dart b/example/lib/presentation/pages/calendar_events.dart index f0b6c27e..584458e3 100644 --- a/example/lib/presentation/pages/calendar_events.dart +++ b/example/lib/presentation/pages/calendar_events.dart @@ -10,7 +10,7 @@ import 'calendar_event.dart'; class CalendarEventsPage extends StatefulWidget { final Calendar _calendar; - CalendarEventsPage(this._calendar, {Key? key}) : super(key: key); + const CalendarEventsPage(this._calendar, {Key? key}) : super(key: key); @override _CalendarEventsPageState createState() { diff --git a/example/lib/presentation/pages/calendars.dart b/example/lib/presentation/pages/calendars.dart index b09596fe..4dbf22e3 100644 --- a/example/lib/presentation/pages/calendars.dart +++ b/example/lib/presentation/pages/calendars.dart @@ -116,7 +116,7 @@ class _CalendarsPageState extends State { onPressed: () async { final createCalendar = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { - return CalendarAddPage(); + return const CalendarAddPage(); })); if (createCalendar == true) { From 30a6a5fcce70c2cb30b25d50d3868d3a7929d1a2 Mon Sep 17 00:00:00 2001 From: Thomas Kam Date: Thu, 6 Oct 2022 23:58:59 -0400 Subject: [PATCH 06/15] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 36 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..ffebd121 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +In a few sentence, briefly describe of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +Optional, add screenshots if it helps explain your problem. + +**Device(s) tested** +This can be very important as not all device vendors do calendar in the same way. + - Device: [e.g. Pixel 6] + - OS: [e.g. Android 12.0] + - Plugin version [e.g. 4.2.0 Release] + +**Flutter doctor** +Run a `flutter doctor` so we can rule out env issues + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 145013c29ea35dd697edb6dc50cbfe2eea6b1136 Mon Sep 17 00:00:00 2001 From: thomassth Date: Wed, 12 Oct 2022 01:31:00 -0400 Subject: [PATCH 07/15] packages cleanup --- example/lib/presentation/event_item.dart | 7 +++---- example/lib/presentation/pages/calendar_event.dart | 1 - lib/device_calendar.dart | 1 + lib/src/models/event.dart | 6 +++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/example/lib/presentation/event_item.dart b/example/lib/presentation/event_item.dart index d0fa800d..ca401086 100644 --- a/example/lib/presentation/event_item.dart +++ b/example/lib/presentation/event_item.dart @@ -1,11 +1,11 @@ import 'dart:io'; + import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:intl/intl.dart'; import 'recurring_event_dialog.dart'; -import 'package:timezone/timezone.dart'; -import 'package:flutter_native_timezone/flutter_native_timezone.dart'; class EventItem extends StatefulWidget { final Event? _calendarEvent; @@ -222,8 +222,7 @@ class _EventItemState extends State { ), Expanded( child: Text( - widget._calendarEvent?.status?.enumToString ?? - '', + widget._calendarEvent?.status?.enumToString ?? '', overflow: TextOverflow.ellipsis, ), ) diff --git a/example/lib/presentation/pages/calendar_event.dart b/example/lib/presentation/pages/calendar_event.dart index 587520ec..ff2a65ee 100644 --- a/example/lib/presentation/pages/calendar_event.dart +++ b/example/lib/presentation/pages/calendar_event.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:intl/intl.dart'; -import 'package:timezone/timezone.dart'; import '../date_time_picker.dart'; import '../recurring_event_dialog.dart'; diff --git a/lib/device_calendar.dart b/lib/device_calendar.dart index 15d51182..4811bb72 100644 --- a/lib/device_calendar.dart +++ b/lib/device_calendar.dart @@ -14,3 +14,4 @@ export 'src/models/platform_specifics/ios/attendance_status.dart'; export 'src/models/platform_specifics/android/attendee_details.dart'; export 'src/models/platform_specifics/android/attendance_status.dart'; export 'src/device_calendar.dart'; +export 'package:timezone/timezone.dart'; diff --git a/lib/src/models/event.dart b/lib/src/models/event.dart index ff536688..c7478005 100644 --- a/lib/src/models/event.dart +++ b/lib/src/models/event.dart @@ -1,9 +1,9 @@ import 'dart:io'; +import 'package:collection/collection.dart'; + import '../../device_calendar.dart'; import '../common/error_messages.dart'; -import 'package:timezone/timezone.dart'; -import 'package:collection/collection.dart'; /// An event associated with a calendar class Event { @@ -48,7 +48,7 @@ class Event { /// Indicates if this event is of confirmed, canceled, tentative or none status EventStatus? status; - + ///Note for development: /// ///JSON field names are coded in dart, swift and kotlin to facilitate data exchange. From c4c140c375506b36d015b3ff27661de829e0b2e4 Mon Sep 17 00:00:00 2001 From: Oleksandra Fedotova Date: Thu, 13 Oct 2022 12:11:52 +0300 Subject: [PATCH 08/15] Fix create/update events in IOS Fixed fullDay with DateRange events creation (not only one fullday events possible to create) Timezone should be set only if event is not full day --- ios/Classes/SwiftDeviceCalendarPlugin.swift | 221 ++++++++++---------- 1 file changed, 110 insertions(+), 111 deletions(-) diff --git a/ios/Classes/SwiftDeviceCalendarPlugin.swift b/ios/Classes/SwiftDeviceCalendarPlugin.swift index 59730a93..940a86b4 100644 --- a/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -24,7 +24,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let accountName: String let accountType: String } - + struct Event: Codable { let eventId: String let calendarId: String @@ -43,7 +43,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let availability: Availability? let eventStatus: EventStatus? } - + struct RecurrenceRule: Codable { let recurrenceFrequency: Int let totalOccurrences: Int? @@ -54,7 +54,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let monthOfYear: Int? let weekOfMonth: Int? } - + struct Attendee: Codable { let name: String? let emailAddress: String @@ -62,11 +62,11 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let attendanceStatus: Int let isCurrentUser: Bool } - + struct Reminder: Codable { let minutes: Int } - + enum Availability: String, Codable { case BUSY case FREE @@ -80,7 +80,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele case CANCELED case NONE } - + static let channelName = "plugins.builttoroam.com/device_calendar" let notFoundErrorCode = "404" let notAllowed = "405" @@ -144,7 +144,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let instance = SwiftDeviceCalendarPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } - + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case requestPermissionsMethod: @@ -172,12 +172,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele result(FlutterMethodNotImplemented) } } - + private func hasPermissions(_ result: FlutterResult) { let hasPermissions = hasEventPermissions() result(hasPermissions) } - + private func getSource() -> EKSource? { let localSources = eventStore.sources.filter { $0.sourceType == .local } @@ -204,14 +204,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele do { calendar.title = arguments[calendarNameArgument] as! String let calendarColor = arguments[calendarColorArgument] as? String - + if (calendarColor != nil) { calendar.cgColor = UIColor(hex: calendarColor!)?.cgColor } else { calendar.cgColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0).cgColor // Red colour as a default } - + guard let source = getSource() else { result(FlutterError(code: self.genericError, message: "Local calendar was not found.", details: nil)) return @@ -227,7 +227,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) } } - + private func retrieveCalendars(_ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let ekCalendars = self.eventStore.calendars(for: .event) @@ -244,27 +244,27 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele accountType: getAccountType(ekCalendar.source.sourceType)) calendars.append(calendar) } - + self.encodeJsonAndFinish(codable: calendars, result: result) }, result: result) } - + private func deleteCalendar(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary let calendarId = arguments[calendarIdArgument] as! String - + let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) if ekCalendar == nil { self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) return } - + if !(ekCalendar!.allowsContentModifications) { self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) return } - + do { try self.eventStore.removeCalendar(ekCalendar!, commit: true) result(true) @@ -274,7 +274,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } }, result: result) } - + private func getAccountType(_ sourceType: EKSourceType) -> String { switch (sourceType) { case .local: @@ -293,7 +293,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele return "Unknown"; } } - + private func retrieveEvents(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary @@ -319,35 +319,35 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } } } - + guard let eventIds = eventIdArgs else { self.encodeJsonAndFinish(codable: events, result: result) return } - + if specifiedStartEndDates { events = events.filter({ (e) -> Bool in e.calendarId == calendarId && eventIds.contains(e.eventId) }) - + self.encodeJsonAndFinish(codable: events, result: result) return } - + for eventId in eventIds { let ekEvent = self.eventStore.event(withIdentifier: eventId) if ekEvent == nil { continue } - + let event = createEventFromEkEvent(calendarId: calendarId, ekEvent: ekEvent!) events.append(event) } - + self.encodeJsonAndFinish(codable: events, result: result) }, result: result) } - + private func createEventFromEkEvent(calendarId: String, ekEvent: EKEvent) -> Event { var attendees = [Attendee]() if ekEvent.attendees != nil { @@ -356,18 +356,18 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele if attendee == nil { continue } - + attendees.append(attendee!) } } - + 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, @@ -390,12 +390,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele return event } - + private func convertEkParticipantToAttendee(ekParticipant: EKParticipant?) -> Attendee? { if ekParticipant == nil || ekParticipant?.emailAddress == nil { return nil } - + let attendee = Attendee( name: ekParticipant!.name, emailAddress: ekParticipant!.emailAddress!, @@ -403,10 +403,10 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele attendanceStatus: ekParticipant!.participantStatus.rawValue, isCurrentUser: ekParticipant!.isCurrentUser ) - + return attendee } - + private func convertEkEventAvailability(ekEventAvailability: EKEventAvailability?) -> Availability? { switch ekEventAvailability { case .busy: @@ -454,32 +454,32 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele default: frequency = 0 } - + var totalOccurrences: Int? var endDate: Int64? if(ekRecurrenceRule.recurrenceEnd?.occurrenceCount != nil && ekRecurrenceRule.recurrenceEnd?.occurrenceCount != 0) { totalOccurrences = ekRecurrenceRule.recurrenceEnd?.occurrenceCount } - + let endDateMs = ekRecurrenceRule.recurrenceEnd?.endDate?.millisecondsSinceEpoch if(endDateMs != nil) { endDate = Int64(exactly: endDateMs!) } - + var weekOfMonth = ekRecurrenceRule.setPositions?.first?.intValue - + var daysOfWeek: [Int]? if ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty { daysOfWeek = [] for dayOfWeek in ekRecurrenceRule.daysOfTheWeek! { daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) - + if weekOfMonth == nil { weekOfMonth = dayOfWeek.weekNumber } } } - + // For recurrence of nth day of nth month every year, no calendar parameters are given // So we need to explicitly set them from event start date var dayOfMonth = ekRecurrenceRule.daysOfTheMonth?.first?.intValue @@ -487,16 +487,16 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele if (ekRecurrenceRule.frequency == EKRecurrenceFrequency.yearly && weekOfMonth == nil && dayOfMonth == nil && monthOfYear == nil) { let dateFormatter = DateFormatter() - + // Setting day of the month dateFormatter.dateFormat = "d" dayOfMonth = Int(dateFormatter.string(from: ekEvent.startDate)) - + // Setting month of the year dateFormatter.dateFormat = "M" monthOfYear = Int(dateFormatter.string(from: ekEvent.startDate)) } - + recurrenceRule = RecurrenceRule( recurrenceFrequency: frequency, totalOccurrences: totalOccurrences, @@ -507,37 +507,37 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele monthOfYear: monthOfYear, weekOfMonth: weekOfMonth) } - + return recurrenceRule } - + private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary 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? [Int] var daysOfWeek : [EKRecurrenceDayOfWeek]? - + if daysOfWeekIndices != nil && !daysOfWeekIndices!.isEmpty { daysOfWeek = [] for dayOfWeekIndex in daysOfWeekIndices! { @@ -554,19 +554,19 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } } } - + var dayOfMonthArray : [NSNumber]? if let dayOfMonth = recurrenceRuleArguments![dayOfMonthArgument] as? Int { dayOfMonthArray = [] dayOfMonthArray!.append(NSNumber(value: dayOfMonth)) } - + var monthOfYearArray : [NSNumber]? if let monthOfYear = recurrenceRuleArguments![monthOfYearArgument] as? Int { monthOfYearArray = [] monthOfYearArray!.append(NSNumber(value: monthOfYear)) } - + // Append BYSETPOS only on monthly (but not last), yearly's week number (and last for monthly) appends to BYDAY var weekOfMonthArray : [NSNumber]? if namedFrequency == EKRecurrenceFrequency.monthly { @@ -577,7 +577,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } } } - + return [EKRecurrenceRule( recurrenceWith: namedFrequency, interval: recurrenceInterval, @@ -589,19 +589,19 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele setPositions: weekOfMonthArray, end: recurrenceEnd)] } - + private func setAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { let attendeesArguments = arguments[attendeesArgument] as? [Dictionary] if attendeesArguments == nil { return } - + var attendees = [EKParticipant]() for attendeeArguments in attendeesArguments! { let name = attendeeArguments[nameArgument] as! String let emailAddress = attendeeArguments[emailAddressArgument] as! String let role = attendeeArguments[roleArgument] as! Int - + if (ekEvent!.attendees != nil) { let existingAttendee = ekEvent!.attendees!.first { element in return element.emailAddress == emailAddress @@ -611,40 +611,40 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele continue } } - + let attendee = createParticipant( name: name, emailAddress: emailAddress, role: role) - + 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 setAvailability(_ arguments: [String : AnyObject]) -> EKEventAvailability? { - guard let availabilityValue = arguments[availabilityArgument] as? String else { - return .unavailable + guard let availabilityValue = arguments[availabilityArgument] as? String else { + return .unavailable } switch availabilityValue.uppercased() { @@ -660,7 +660,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele return nil } } - + private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary @@ -681,12 +681,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) return } - + if !(ekCalendar!.allowsContentModifications) { self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) return } - + var ekEvent: EKEvent? if eventId == nil { ekEvent = EKEvent.init(eventStore: self.eventStore) @@ -697,18 +697,18 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele return } } - + ekEvent!.title = title ekEvent!.notes = description ekEvent!.isAllDay = isAllDay ekEvent!.startDate = startDate - if (isAllDay) { ekEvent!.endDate = startDate } - else { - ekEvent!.endDate = endDate - - let timeZone = TimeZone(identifier: startTimeZoneString ?? TimeZone.current.identifier) ?? .current + ekEvent!.endDate = endDate + + if (!isAllDay) { + let timeZone = TimeZone(identifier: startTimeZoneString ?? TimeZone.current.identifier) ?? .current ekEvent!.timeZone = timeZone - } + } + ekEvent!.calendar = ekCalendar! ekEvent!.location = location @@ -720,15 +720,15 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele else { ekEvent!.url = nil } - + ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) setAttendees(arguments, ekEvent) ekEvent!.alarms = createReminders(arguments) - + if let availability = setAvailability(arguments) { ekEvent!.availability = availability } - + do { try self.eventStore.save(ekEvent!, span: .futureEvents) result(ekEvent!.eventIdentifier) @@ -738,7 +738,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } }, result: result) } - + private func createParticipant(name: String, emailAddress: String, role: Int) -> EKParticipant? { let ekAttendeeClass: AnyClass? = NSClassFromString("EKAttendee") if let type = ekAttendeeClass as? NSObject.Type { @@ -750,7 +750,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } return nil } - + private func deleteEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { checkPermissionsThenExecute(permissionsGrantedAction: { let arguments = call.arguments as! Dictionary @@ -759,25 +759,25 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let startDateNumber = arguments[eventStartDateArgument] as? NSNumber let endDateNumber = arguments[eventEndDateArgument] as? NSNumber let followingInstances = arguments[followingInstancesArgument] as? Bool - + let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) if ekCalendar == nil { self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) return } - + if !(ekCalendar!.allowsContentModifications) { self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) return } - + if (startDateNumber == nil && endDateNumber == nil && followingInstances == nil) { let ekEvent = self.eventStore.event(withIdentifier: eventId) if ekEvent == nil { self.finishWithEventNotFoundError(result: result, eventId: eventId) return } - + do { try self.eventStore.remove(ekEvent!, span: .futureEvents) result(true) @@ -789,17 +789,17 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele else { let startDate = Date (timeIntervalSince1970: startDateNumber!.doubleValue / 1000.0) let endDate = Date (timeIntervalSince1970: endDateNumber!.doubleValue / 1000.0) - + let predicate = self.eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil) let foundEkEvents = self.eventStore.events(matching: predicate) as [EKEvent]? - + if foundEkEvents == nil || foundEkEvents?.count == 0 { self.finishWithEventNotFoundError(result: result, eventId: eventId) return } - + let ekEvent = foundEkEvents!.first(where: {$0.eventIdentifier == eventId}) - + do { if (!followingInstances!) { try self.eventStore.remove(ekEvent!, span: .thisEvent, commit: true) @@ -807,7 +807,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele else { try self.eventStore.remove(ekEvent!, span: .futureEvents, commit: true) } - + result(true) } catch { self.eventStore.reset() @@ -822,33 +822,33 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let arguments = call.arguments as! Dictionary let eventId = arguments[eventIdArgument] as! String let event = self.eventStore.event(withIdentifier: eventId) - + if event != nil { let eventController = EKEventViewController() eventController.event = event! eventController.delegate = self eventController.allowsEditing = true eventController.allowsCalendarPreview = true - + let flutterViewController = getTopMostViewController() let navigationController = UINavigationController(rootViewController: eventController) - + navigationController.toolbar.isTranslucent = false navigationController.toolbar.tintColor = .blue navigationController.toolbar.backgroundColor = .white flutterViewController.present(navigationController, animated: true, completion: nil) - - + + } else { result(FlutterError(code: self.genericError, message: self.eventNotFoundErrorMessageFormat, details: nil)) } }, result: result) } - + public func eventViewController(_ controller: EKEventViewController, didCompleteWith action: EKEventViewAction) { controller.dismiss(animated: true, completion: nil) - + if flutterResult != nil { switch action { case .done: @@ -862,35 +862,35 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } } } - + private func getTopMostViewController() -> UIViewController { var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController while ((topController?.presentedViewController) != nil) { topController = topController?.presentedViewController } - + return topController! } - + private func finishWithUnauthorizedError(result: @escaping FlutterResult) { result(FlutterError(code:self.unauthorizedErrorCode, message: self.unauthorizedErrorMessage, details: nil)) } - + private func finishWithCalendarNotFoundError(result: @escaping FlutterResult, calendarId: String) { let errorMessage = String(format: self.calendarNotFoundErrorMessageFormat, calendarId) result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) } - + private func finishWithCalendarReadOnlyError(result: @escaping FlutterResult, calendarId: String) { let errorMessage = String(format: self.calendarReadOnlyErrorMessageFormat, calendarId) result(FlutterError(code:self.notAllowed, message: errorMessage, details: nil)) } - + private func finishWithEventNotFoundError(result: @escaping FlutterResult, eventId: String) { 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() @@ -901,7 +901,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele result(FlutterError(code: genericError, message: error.localizedDescription, details: nil)) } } - + private func checkPermissionsThenExecute(permissionsGrantedAction: () -> Void, result: @escaping FlutterResult) { if hasEventPermissions() { permissionsGrantedAction() @@ -909,7 +909,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele } self.finishWithUnauthorizedError(result: result) } - + private func requestPermissions(completion: @escaping (Bool) -> Void) { if hasEventPermissions() { completion(true) @@ -920,12 +920,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele completion(accessGranted) }) } - + private func hasEventPermissions() -> Bool { let status = EKEventStore.authorizationStatus(for: .event) return status == EKAuthorizationStatus.authorized } - + private func requestPermissions(_ result: @escaping FlutterResult) { if hasEventPermissions() { result(true) @@ -964,7 +964,7 @@ extension UIColor { return nil } } - + public convenience init?(hex: String) { let r, g, b, a: CGFloat @@ -981,7 +981,7 @@ extension UIColor { r = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 g = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 b = CGFloat((hexNumber & 0x000000ff)) / 255 - + self.init(red: r, green: g, blue: b, alpha: a) return } @@ -991,4 +991,3 @@ extension UIColor { return nil } } - From 384890cfe36e6b34cb437daa876e10bb15646b64 Mon Sep 17 00:00:00 2001 From: "goldensoju@gmailcom" Date: Tue, 1 Nov 2022 18:28:07 +0900 Subject: [PATCH 09/15] iOS: Fix error when adding attendees due to UUID is nil. --- example/ios/Podfile.lock | 6 +++--- example/lib/presentation/pages/calendar_event.dart | 7 +++++-- example/lib/presentation/pages/event_attendee.dart | 8 ++++---- ios/Classes/SwiftDeviceCalendarPlugin.swift | 1 + test/device_calendar_test.dart | 1 - 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 09b26adb..faea259c 100755 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -25,10 +25,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 - integration_test: 7db6d89f336f671dcbc7563ee27a5b08f6f8aee1 + integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 PODFILE CHECKSUM: d3740c426905916d1f2ada0ddfce28cc99f7b7af -COCOAPODS: 1.10.1 +COCOAPODS: 1.11.3 diff --git a/example/lib/presentation/pages/calendar_event.dart b/example/lib/presentation/pages/calendar_event.dart index ff2a65ee..ee9040e9 100644 --- a/example/lib/presentation/pages/calendar_event.dart +++ b/example/lib/presentation/pages/calendar_event.dart @@ -416,8 +416,11 @@ class _CalendarEventPageState extends State { itemCount: _attendees.length, itemBuilder: (context, index) { return Container( - color: _attendees[index].isOrganiser - ? Colors.greenAccent[100] + color: (_attendees?[index].isOrganiser ?? false) + ? MediaQuery.of(context).platformBrightness == + Brightness.dark + ? Colors.black26 + : Colors.greenAccent[100] : Colors.transparent, child: ListTile( onTap: () async { diff --git a/example/lib/presentation/pages/event_attendee.dart b/example/lib/presentation/pages/event_attendee.dart index a2f17aba..d262d64f 100644 --- a/example/lib/presentation/pages/event_attendee.dart +++ b/example/lib/presentation/pages/event_attendee.dart @@ -64,7 +64,7 @@ class _EventAttendeePageState extends State { child: TextFormField( controller: _nameController, validator: (value) { - if (_attendee!.isCurrentUser == false && + if (_attendee?.isCurrentUser == false && (value == null || value.isEmpty)) { return 'Please enter a name'; } @@ -153,9 +153,9 @@ class _EventAttendeePageState extends State { name: _nameController.text, emailAddress: _emailAddressController.text, role: _role, - isOrganiser: _attendee!.isOrganiser, - isCurrentUser: _attendee!.isCurrentUser, - iosAttendeeDetails: _attendee!.iosAttendeeDetails, + isOrganiser: _attendee?.isOrganiser ?? false, + isCurrentUser: _attendee?.isCurrentUser ?? false, + iosAttendeeDetails: _attendee?.iosAttendeeDetails, androidAttendeeDetails: AndroidAttendeeDetails.fromJson( {'attendanceStatus': _status.index})); diff --git a/ios/Classes/SwiftDeviceCalendarPlugin.swift b/ios/Classes/SwiftDeviceCalendarPlugin.swift index 940a86b4..41863133 100644 --- a/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -743,6 +743,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDele let ekAttendeeClass: AnyClass? = NSClassFromString("EKAttendee") if let type = ekAttendeeClass as? NSObject.Type { let participant = type.init() + participant.setValue(UUID().uuidString, forKey: "UUID") participant.setValue(name, forKey: "displayName") participant.setValue(emailAddress, forKey: "emailAddress") participant.setValue(role, forKey: "participantRole") diff --git a/test/device_calendar_test.dart b/test/device_calendar_test.dart index 3889c44b..97c963bb 100644 --- a/test/device_calendar_test.dart +++ b/test/device_calendar_test.dart @@ -2,7 +2,6 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:device_calendar/src/common/error_codes.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:timezone/timezone.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); From 2e1fa43096d108fff718eef22f43bfcf40e870e4 Mon Sep 17 00:00:00 2001 From: Julius Bredemeyer <48645716+IVLIVS-III@users.noreply.github.com> Date: Sat, 3 Dec 2022 00:27:30 +0100 Subject: [PATCH 10/15] Updated version and changelog. --- CHANGELOG.md | 5 ++++- pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb5d1ed..6285b62b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ -## [4.2.1](https://github.com/builttoroam/device_calendar/releases/tag/4.2.1) +## [4.3.0](https://github.com/builttoroam/device_calendar/releases/tag/4.3.0) - Updated multiple underlying dependencies - *Note:* `timezone 0.9.0` [removed named database files](https://pub.dev/packages/timezone/changelog#090). If you are only using `device_calendar`, you can ignore this note. +- Added support for all-day multi-day events on iOS +- Fixed iOS issue of adding attendees to events +- Fixed Android issue of the `ownerAccount` being null ## [4.2.0](https://github.com/builttoroam/device_calendar/releases/tag/4.2.0) diff --git a/pubspec.yaml b/pubspec.yaml index 19b57aeb..ec0732c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,13 @@ name: device_calendar description: A cross platform plugin for modifying calendars on the user's device. -version: 4.2.1 +version: 4.3.0 homepage: https://github.com/builttoroam/device_calendar/tree/master dependencies: flutter: sdk: flutter collection: ^1.16.0 - sprintf: ^6.0.2 + sprintf: ^7.0.0 timezone: ^0.9.0 flutter_native_timezone: ^2.0.0 From 0c85ab0d158763ca0f6c93f4a8d88b5c57b87c7f Mon Sep 17 00:00:00 2001 From: Shreyas S Date: Wed, 15 Feb 2023 11:45:23 +0530 Subject: [PATCH 11/15] parseRecurrenceRuleString() when else fix. --- .../kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 744f636e..7141da99 100644 --- a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -787,6 +787,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { DayOfWeek.values().find { dayOfWeek -> dayOfWeek.ordinal == it.weekday.ordinal } }?.toMutableList() } + else -> recurrenceRule.daysOfWeek = null } val rfcRecurrenceRuleString = rfcRecurrenceRule.toString() From e3799b1da6679734c46bdb41218272b3a3acaf3c Mon Sep 17 00:00:00 2001 From: Julius Bredemeyer <48645716+IVLIVS-III@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:16:22 +0100 Subject: [PATCH 12/15] Fixed failing test (dart analyze). --- example/lib/presentation/pages/calendar_event.dart | 2 +- lib/src/models/event.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example/lib/presentation/pages/calendar_event.dart b/example/lib/presentation/pages/calendar_event.dart index ee9040e9..cb258367 100644 --- a/example/lib/presentation/pages/calendar_event.dart +++ b/example/lib/presentation/pages/calendar_event.dart @@ -416,7 +416,7 @@ class _CalendarEventPageState extends State { itemCount: _attendees.length, itemBuilder: (context, index) { return Container( - color: (_attendees?[index].isOrganiser ?? false) + color: (_attendees[index].isOrganiser) ? MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.black26 diff --git a/lib/src/models/event.dart b/lib/src/models/event.dart index c7478005..70fc9e45 100644 --- a/lib/src/models/event.dart +++ b/lib/src/models/event.dart @@ -250,6 +250,7 @@ class Event { case 'NONE': return EventStatus.None; } + return null; } bool updateStartLocation(String? newStartLocation) { From 47c851cd98ac29aeb914330fa83f86833608dc7b Mon Sep 17 00:00:00 2001 From: Julius Bredemeyer <48645716+IVLIVS-III@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:16:40 +0100 Subject: [PATCH 13/15] Bumped version and updated changelog. --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6285b62b..2fa175fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## [4.3.1](https://github.com/builttoroam/device_calendar/releases/tag/4.3.1) + +- Fixed an [issue](https://github.com/builttoroam/device_calendar/issues/470) that prevented the plugin from being used with Kotlin 1.7.10 + ## [4.3.0](https://github.com/builttoroam/device_calendar/releases/tag/4.3.0) - Updated multiple underlying dependencies diff --git a/pubspec.yaml b/pubspec.yaml index ec0732c1..45018475 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: device_calendar description: A cross platform plugin for modifying calendars on the user's device. -version: 4.3.0 +version: 4.3.1 homepage: https://github.com/builttoroam/device_calendar/tree/master dependencies: From 82acd9b5712ddba21d6cfe7bc94ae1930089630a Mon Sep 17 00:00:00 2001 From: Julius Bredemeyer <48645716+IVLIVS-III@users.noreply.github.com> Date: Sat, 20 May 2023 15:04:53 +0200 Subject: [PATCH 14/15] Updated example project for iOS. --- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 4 ++-- example/ios/Runner.xcodeproj/project.pbxproj | 11 +++++++---- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/Info.plist | 4 ++++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 3a9c234f..9b41e7d8 100755 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 9.0 + 11.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 8ab33cfb..997d1cb3 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '9.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index faea259c..cb8f159b 100755 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -27,8 +27,8 @@ SPEC CHECKSUMS: device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 - integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 + integration_test: 13825b8a9334a850581300559b8839134b124670 -PODFILE CHECKSUM: d3740c426905916d1f2ada0ddfce28cc99f7b7af +PODFILE CHECKSUM: 10625bdc9b9ef8574174815aabd5b048e6e29bff COCOAPODS: 1.11.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 7d88e2f9..160e1d14 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -161,7 +161,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -229,10 +229,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -243,6 +245,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -355,7 +358,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -405,7 +408,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 0254012e..14d255fd 100755 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + From 1e7ed588e2afc19d2dad069eba8a689688450598 Mon Sep 17 00:00:00 2001 From: Julius Bredemeyer <48645716+IVLIVS-III@users.noreply.github.com> Date: Sat, 20 May 2023 15:24:17 +0200 Subject: [PATCH 15/15] Fixed deprecation issues. --- test/device_calendar_test.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/device_calendar_test.dart b/test/device_calendar_test.dart index ada9d769..132aad61 100644 --- a/test/device_calendar_test.dart +++ b/test/device_calendar_test.dart @@ -12,7 +12,8 @@ void main() { final log = []; setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { print('Calling channel method ${methodCall.method}'); log.add(methodCall); @@ -23,7 +24,8 @@ void main() { }); test('HasPermissions_Returns_Successfully', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return true; }); @@ -34,7 +36,8 @@ void main() { }); test('RequestPermissions_Returns_Successfully', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return true; }); @@ -46,7 +49,8 @@ void main() { test('RetrieveCalendars_Returns_Successfully', () async { const fakeCalendarName = 'fakeCalendarName'; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return '[{"id":"1","isReadOnly":false,"name":"$fakeCalendarName"}]'; }); @@ -114,7 +118,8 @@ void main() { test('CreateEvent_Returns_Successfully', () async { const fakeNewEventId = 'fakeNewEventId'; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fakeNewEventId; }); @@ -133,7 +138,8 @@ void main() { test('UpdateEvent_Returns_Successfully', () async { const fakeNewEventId = 'fakeNewEventId'; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { final arguments = methodCall.arguments as Map; if (!arguments.containsKey('eventId') || arguments['eventId'] == null) { return null;