Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Calendar & Retrieve Account Name/Type #189

Merged
merged 14 commits into from
Mar 20, 2020
2 changes: 2 additions & 0 deletions device_calendar/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* Added compatibility with a new Flutter plugin for Android. Thanks to the PR submitted by [RohitKumarMishra](https://github.com/RohitKumarMishra)
* [Android] Fixed all day timezone issue [164](https://github.com/builttoroam/flutter_plugins/issues/164)
* Added support for deleting individual or multiple instances of a recurring event for issue [108](https://github.com/builttoroam/flutter_plugins/issues/108)
* Ability to add local calendars with a desired colour for issue [115](https://github.com/builttoroam/flutter_plugins/issues/115)
* Returns account name and type for each calendars for issue [179](https://github.com/builttoroam/flutter_plugins/issues/179)

## 3.0.0+3 3rd February 2020

Expand Down
2 changes: 1 addition & 1 deletion device_calendar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A cross platform plugin for modifying calendars on the user's device.

* Ability to request permissions to modify calendars on the user's device
* Ability to check if permissions to modify the calendars on the user's device have been granted
* Retrieve calendars on the user's device
* Ability to add or retrieve calendars on the user's device
* Retrieve events associated with a calendar
* Ability to add, update or delete events from a calendar
* Ability to set up, edit or delete recurring events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.provider.CalendarContract
import android.provider.CalendarContract.CALLER_IS_SYNCADAPTER
import android.provider.CalendarContract.Events
import android.text.format.DateUtils
import io.flutter.plugin.common.PluginRegistry.Registrar
Expand All @@ -23,6 +24,8 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_I
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_COLOR_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCOUNT_TYPE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_DISPLAY_NAME_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_IS_PRIMARY_INDEX
Expand Down Expand Up @@ -160,7 +163,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
if (arePermissionsGranted()) {
val contentResolver: ContentResolver? = _context?.contentResolver
val uri: Uri = CalendarContract.Calendars.CONTENT_URI
val cursor: Cursor? = if (atLeastAPI(17)) {
val cursor: Cursor? = if (atLeastAPI(17)) {
contentResolver?.query(uri, CALENDAR_PROJECTION, null, null, null)
} else {
contentResolver?.query(uri, CALENDAR_PROJECTION_OLDER_API, null, null, null)
Expand Down Expand Up @@ -229,6 +232,32 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
return null
}

fun createCalendar(calendarName: String, calendarColor: String?, localAccountName: String, pendingChannelResult: MethodChannel.Result) {
val contentResolver: ContentResolver? = _context?.contentResolver

var uri = CalendarContract.Calendars.CONTENT_URI
uri = uri.buildUpon()
.appendQueryParameter(CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, localAccountName)
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
.build()
val values = ContentValues()
values.put(CalendarContract.Calendars.NAME, calendarName)
values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, calendarName)
values.put(CalendarContract.Calendars.ACCOUNT_NAME, localAccountName)
values.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER)
values.put(CalendarContract.Calendars.CALENDAR_COLOR, calendarColor ?: "0xFFFF0000") // Red colour as a default
values.put(CalendarContract.Calendars.OWNER_ACCOUNT, localAccountName)
values.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, java.util.Calendar.getInstance().timeZone.id)

val result = contentResolver?.insert(uri, values)
// Get the calendar ID that is the last element in the Uri
val calendarId = java.lang.Long.parseLong(result?.lastPathSegment!!)

finishWithSuccess(calendarId.toString(), pendingChannelResult)
}

@SuppressLint("MissingPermission")
fun retrieveEvents(calendarId: String, startDate: Long?, endDate: Long?, eventIds: List<String>, pendingChannelResult: MethodChannel.Result) {
if (startDate == null && endDate == null && eventIds.isEmpty()) {
Expand Down Expand Up @@ -580,9 +609,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
val displayName = cursor.getString(CALENDAR_PROJECTION_DISPLAY_NAME_INDEX)
val accessLevel = cursor.getInt(CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX)
val calendarColor = cursor.getInt(CALENDAR_PROJECTION_COLOR_INDEX)
val accountName = cursor.getString(CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX)
val accountType = cursor.getString(CALENDAR_PROJECTION_ACCOUNT_TYPE_INDEX)


val calendar = Calendar(calId.toString(), displayName, calendarColor)
val calendar = Calendar(calId.toString(), displayName, calendarColor, accountName, accountType)
calendar.isReadOnly = isCalendarReadOnly(accessLevel)
if (atLeastAPI(17)) {
val isPrimary = cursor.getString(CALENDAR_PROJECTION_IS_PRIMARY_INDEX)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import org.json.JSONArray

const val CHANNEL_NAME = "plugins.builttoroam.com/device_calendar"

Expand All @@ -25,9 +26,11 @@ class DeviceCalendarPlugin() : MethodCallHandler {
private val DELETE_EVENT_METHOD = "deleteEvent"
private val DELETE_EVENT_INSTANCE_METHOD = "deleteEventInstance"
private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent"
private val CREATE_CALENDAR_METHOD = "createCalendar"

// Method arguments
private val CALENDAR_ID_ARGUMENT = "calendarId"
private val CALENDAR_NAME_ARGUMENT = "calendarName"
private val START_DATE_ARGUMENT = "startDate"
private val END_DATE_ARGUMENT = "endDate"
private val EVENT_IDS_ARGUMENT = "eventIds"
Expand All @@ -54,6 +57,8 @@ class DeviceCalendarPlugin() : MethodCallHandler {
private val REMINDERS_ARGUMENT = "reminders"
private val MINUTES_ARGUMENT = "minutes"
private val FOLLOWING_INSTANCES = "followingInstances"
private val CALENDAR_COLOR_ARGUMENT = "calendarColor"
private val LOCAL_ACCOUNT_NAME_ARGUMENT = "localAccountName"

private lateinit var _registrar: Registrar
private lateinit var _calendarDelegate: CalendarDelegate
Expand Down Expand Up @@ -118,6 +123,13 @@ class DeviceCalendarPlugin() : MethodCallHandler {

_calendarDelegate.deleteEvent(calendarId!!, eventId!!, result, startDate, endDate, followingInstances)
}
CREATE_CALENDAR_METHOD -> {
val calendarName = call.argument<String>(CALENDAR_NAME_ARGUMENT)
val calendarColor = call.argument<String>(CALENDAR_COLOR_ARGUMENT)
val localAccountName = call.argument<String>(LOCAL_ACCOUNT_NAME_ARGUMENT)

_calendarDelegate.createCalendar(calendarName!!, calendarColor, localAccountName!!, result)
}
else -> {
result.notImplemented()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,35 @@ class Constants {
companion object {
const val CALENDAR_PROJECTION_ID_INDEX: Int = 0
const val CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX: Int = 1
const val CALENDAR_PROJECTION_DISPLAY_NAME_INDEX: Int = 2
const val CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX: Int = 3
const val CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX: Int = 4
const val CALENDAR_PROJECTION_COLOR_INDEX: Int = 5
const val CALENDAR_PROJECTION_IS_PRIMARY_INDEX: Int = 6
const val CALENDAR_PROJECTION_ACCOUNT_TYPE_INDEX: Int = 2
const val CALENDAR_PROJECTION_DISPLAY_NAME_INDEX: Int = 3
const val CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX: Int = 4
const val CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX: Int = 5
const val CALENDAR_PROJECTION_COLOR_INDEX: Int = 6
const val CALENDAR_PROJECTION_IS_PRIMARY_INDEX: Int = 7

// API 17 or higher
val CALENDAR_PROJECTION: Array<String> = arrayOf(
CalendarContract.Calendars._ID, // 0
CalendarContract.Calendars.ACCOUNT_NAME, // 1
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 2
CalendarContract.Calendars.OWNER_ACCOUNT, // 3
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, // 4
CalendarContract.Calendars.CALENDAR_COLOR, // 5
CalendarContract.Calendars.IS_PRIMARY // 6
CalendarContract.Calendars.ACCOUNT_TYPE, // 2
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 3
CalendarContract.Calendars.OWNER_ACCOUNT, // 4
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, // 5
CalendarContract.Calendars.CALENDAR_COLOR, // 6
CalendarContract.Calendars.IS_PRIMARY // 7

)

// API 16 or lower
val CALENDAR_PROJECTION_OLDER_API: Array<String> = arrayOf(
CalendarContract.Calendars._ID, // 0
CalendarContract.Calendars.ACCOUNT_NAME, // 1
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 2
CalendarContract.Calendars.OWNER_ACCOUNT, // 3
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, // 4
CalendarContract.Calendars.CALENDAR_COLOR // 5
CalendarContract.Calendars.ACCOUNT_TYPE, // 2
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 3
CalendarContract.Calendars.OWNER_ACCOUNT, // 4
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, // 5
CalendarContract.Calendars.CALENDAR_COLOR // 6
)

const val EVENT_PROJECTION_ID_INDEX: Int = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.builttoroam.devicecalendar.models

class Account(val name: String, val type: String) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.builttoroam.devicecalendar.models

import android.graphics.Color

class Calendar(val id: String, val name: String, val color : Int) {
class Calendar(val id: String, val name: String, val color : Int, val accountName: String, val accountType: String) {
var isReadOnly: Boolean = false
var isDefault: Boolean = false
}
149 changes: 149 additions & 0 deletions device_calendar/example/lib/presentation/pages/calendar_add.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'dart:io';

import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/material.dart';

class CalendarAddPage extends StatefulWidget {
@override
_CalendarAddPageState createState() {
return _CalendarAddPageState();
}
}

class _CalendarAddPageState extends State<CalendarAddPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
DeviceCalendarPlugin _deviceCalendarPlugin;

bool _autovalidate = false;
String _calendarName = '';
ColorChoice _colorChoice;
String _localAccountName = '';

_CalendarAddPageState() {
_deviceCalendarPlugin = DeviceCalendarPlugin();
}

@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Create Calendar'),
),
body: Form(
autovalidate: _autovalidate,
key: _formKey,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Calendar Name',
hintText: 'My New Calendar',
),
validator: _validateCalendarName,
onSaved: (String value) => _calendarName = value,
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Calendar Color'),
DropdownButton<ColorChoice>(
onChanged: (selectedColor) {
setState(() => _colorChoice = selectedColor);
},
value: _colorChoice,
items: ColorChoice.values
.map((color) => DropdownMenuItem(
value: color,
child: Text(color.toString().split('.').last),
))
.toList(),
),
],
),
if (Platform.isAndroid) ...[
bhl09 marked this conversation as resolved.
Show resolved Hide resolved
TextFormField(
decoration: const InputDecoration(
labelText: 'Local Account Name',
hintText: 'Device Calendar',
),
onSaved: (String value) => _localAccountName = value,
),
]
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.check),
onPressed: () async {
final FormState form = _formKey.currentState;
if (!form.validate()) {
_autovalidate = true; // Start validating on every change.
showInSnackBar('Please fix the errors in red before submitting.');
} else {
form.save();
var result = await _deviceCalendarPlugin.createCalendar(
_calendarName,
calendarColor: _colorChoice.value,
localAccountName: _localAccountName,
);

if (result.isSuccess) {
Navigator.pop(context, true);
} else {
showInSnackBar(result.errorMessages.join(' | '));
}
}
},
),
);
}

String _validateCalendarName(String value) {
if (value.isEmpty) {
return 'Calendar name is required.';
}

return null;
}

void showInSnackBar(String value) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(value)));
}
}

enum ColorChoice {
Red,
Orange,
Yellow,
Green,
Blue,
Purple,
Brown,
Black,
White
}

extension ColorChoiceExtension on ColorChoice {
static Color _value(ColorChoice val) {
switch (val) {
case ColorChoice.Red: return Colors.red;
case ColorChoice.Orange: return Colors.orange;
case ColorChoice.Yellow: return Colors.yellow;
case ColorChoice.Green: return Colors.green;
case ColorChoice.Blue: return Colors.blue;
case ColorChoice.Purple: return Colors.purple;
case ColorChoice.Brown: return Colors.brown;
case ColorChoice.Black: return Colors.black;
case ColorChoice.White: return Colors.white;
default: return Colors.red;
}
}

Color get value => _value(this);
}
16 changes: 15 additions & 1 deletion device_calendar/example/lib/presentation/pages/calendars.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:device_calendar/device_calendar.dart';
import 'package:device_calendar_example/presentation/pages/calendar_add.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';

Expand Down Expand Up @@ -98,9 +99,22 @@ class _CalendarsPageState extends State<CalendarsPage> {
);
},
),
)
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final createCalendar = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return CalendarAddPage();
}));

if (createCalendar == true) {
_retrieveCalendars();
}
},
child: Icon(Icons.add),
),
);
}

Expand Down
Loading