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

Timezone support #209

Merged
merged 6 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions device_calendar/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.2.0

* Added time zone support

## 3.1.0 6th March 2020 - Bug fixes and new features

* Boolean variable `isDefault` added for issue [145](https://github.com/builttoroam/flutter_plugins/issues/145) (**NOTE**: This is not supported Android API 16 or lower, `isDefault` will always be false)
Expand Down
4 changes: 4 additions & 0 deletions device_calendar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ A cross platform plugin for modifying calendars on the user's device.
* **NOTE**: Deleting multiple instances in **Android** takes time to update, you'll see the changes after a few seconds
* Ability to add, modify or remove attendees and receive if an attendee is an organiser for an event
* Ability to setup reminders for an event
* Ability to specify a time zone for event start and end date
* **NOTE**: Due to a limitation of iOS API, single time zone property is used for iOS (`event.startTimeZone`)
* **NOTE**: For the time zone list, please refer to the `TZ database name` column on [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
* **NOTE**: If the time zone values are null or invalid, it will be defaulted to the device's current time zone.

## Android Integration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.builttoroam.devicecalendar

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
Expand All @@ -14,7 +13,6 @@ 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
import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_EMAIL_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_NAME_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_PROJECTION
Expand All @@ -23,29 +21,31 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_STATUS
import com.builttoroam.devicecalendar.common.Constants.Companion.ATTENDEE_TYPE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ACCESS_LEVEL_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_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_COLOR_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
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_OLDER_API
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_BEGIN_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_END_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_LAST_DATE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_RRULE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_ALL_DAY_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_BEGIN_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_CUSTOM_APP_URI_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_DESCRIPTION_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_END_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_END_TIMEZONE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_EVENT_LOCATION_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_CUSTOM_APP_URI_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_RECURRING_RULE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_START_TIMEZONE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_TITLE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_RRULE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_LAST_DATE_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_BEGIN_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_END_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_MINUTES_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_PROJECTION
import com.builttoroam.devicecalendar.common.DayOfWeek
Expand All @@ -65,12 +65,12 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.Registrar
import org.dmfs.rfc5545.DateTime
import org.dmfs.rfc5545.Weekday
import org.dmfs.rfc5545.recur.Freq
import java.text.SimpleDateFormat
import java.util.*
import com.builttoroam.devicecalendar.models.CalendarMethodsParametersCacheModel

class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
private val RETRIEVE_CALENDARS_REQUEST_CODE = 0
Expand Down Expand Up @@ -426,11 +426,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
}
else {
values.put(Events.DTSTART, event.start!!)
values.put(Events.DTEND, event.end!!)
values.put(Events.EVENT_TIMEZONE, getTimeZone(event.startTimeZone).id)

// MK using current device time zone
val currentTimeZone: TimeZone = java.util.Calendar.getInstance().timeZone
values.put(Events.EVENT_TIMEZONE, currentTimeZone.id)
values.put(Events.DTEND, event.end!!)
values.put(Events.EVENT_END_TIMEZONE, getTimeZone(event.endTimeZone).id)
}
values.put(Events.TITLE, event.title)
values.put(Events.DESCRIPTION, event.description)
Expand All @@ -446,6 +445,18 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
return values
}

private fun getTimeZone(timeZoneString: String?): TimeZone {
val deviceTimeZone: TimeZone = java.util.Calendar.getInstance().timeZone
var timeZone = TimeZone.getTimeZone(timeZoneString ?: deviceTimeZone.id)

// Invalid time zone names defaults to GMT so update that to be device's time zone
if (timeZone.id == "GMT" && timeZoneString != "GMT") {
timeZone = TimeZone.getTimeZone(deviceTimeZone.id)
}

return timeZone
}

@SuppressLint("MissingPermission")
private fun insertAttendees(attendees: List<Attendee>, eventId: Long?, contentResolver: ContentResolver?) {
if (attendees.isEmpty()) {
Expand Down Expand Up @@ -637,7 +648,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
val recurringRule = cursor.getString(EVENT_PROJECTION_RECURRING_RULE_INDEX)
val allDay = cursor.getInt(EVENT_PROJECTION_ALL_DAY_INDEX) > 0
val location = cursor.getString(EVENT_PROJECTION_EVENT_LOCATION_INDEX)
var url = cursor.getString(EVENT_PROJECTION_CUSTOM_APP_URI_INDEX)
val url = cursor.getString(EVENT_PROJECTION_CUSTOM_APP_URI_INDEX)
val startTimeZone = cursor.getString(EVENT_PROJECTION_START_TIMEZONE_INDEX)
val endTimeZone = cursor.getString(EVENT_PROJECTION_END_TIMEZONE_INDEX)

val event = Event()
event.title = title ?: "New Event"
Expand All @@ -650,6 +663,8 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
event.location = location
event.url = url
event.recurrenceRule = parseRecurrenceRuleString(recurringRule)
event.startTimeZone = startTimeZone
event.endTimeZone = endTimeZone
return event
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class DeviceCalendarPlugin() : MethodCallHandler {
private val EVENT_ALL_DAY_ARGUMENT = "eventAllDay"
private val EVENT_START_DATE_ARGUMENT = "eventStartDate"
private val EVENT_END_DATE_ARGUMENT = "eventEndDate"
private val EVENT_START_TIMEZONE_ARGUMENT = "eventStartTimeZone"
private val EVENT_END_TIMEZONE_ARGUMENT = "eventEndTimeZone"
private val RECURRENCE_RULE_ARGUMENT = "recurrenceRule"
private val RECURRENCE_FREQUENCY_ARGUMENT = "recurrenceFrequency"
private val TOTAL_OCCURRENCES_ARGUMENT = "totalOccurrences"
Expand Down Expand Up @@ -145,6 +147,8 @@ class DeviceCalendarPlugin() : MethodCallHandler {
event.allDay = call.argument<Boolean>(EVENT_ALL_DAY_ARGUMENT) ?: false
event.start = call.argument<Long>(EVENT_START_DATE_ARGUMENT)!!
event.end = call.argument<Long>(EVENT_END_DATE_ARGUMENT)!!
event.startTimeZone = call.argument<String>(EVENT_START_TIMEZONE_ARGUMENT)
event.endTimeZone = call.argument<String>(EVENT_END_TIMEZONE_ARGUMENT)
event.location = call.argument<String>(EVENT_LOCATION_ARGUMENT)
event.url = call.argument<String>(EVENT_URL_ARGUMENT)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.builttoroam.devicecalendar.common

import android.provider.CalendarContract
import java.util.*

class Constants {
companion object {
Expand Down Expand Up @@ -46,6 +47,8 @@ class Constants {
const val EVENT_PROJECTION_ALL_DAY_INDEX: Int = 8
const val EVENT_PROJECTION_EVENT_LOCATION_INDEX: Int = 9
const val EVENT_PROJECTION_CUSTOM_APP_URI_INDEX: Int = 10
const val EVENT_PROJECTION_START_TIMEZONE_INDEX: Int = 11
const val EVENT_PROJECTION_END_TIMEZONE_INDEX: Int = 12

val EVENT_PROJECTION: Array<String> = arrayOf(
CalendarContract.Instances.EVENT_ID,
Expand All @@ -58,7 +61,9 @@ class Constants {
CalendarContract.Events.RRULE,
CalendarContract.Events.ALL_DAY,
CalendarContract.Events.EVENT_LOCATION,
CalendarContract.Events.CUSTOM_APP_URI
CalendarContract.Events.CUSTOM_APP_URI,
CalendarContract.Events.EVENT_TIMEZONE,
CalendarContract.Events.EVENT_END_TIMEZONE
)

const val EVENT_INSTANCE_DELETION_ID_INDEX: Int = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class Event {
var description: String? = null
var start: Long? = null
var end: Long? = null
var startTimeZone: String? = null
var endTimeZone: String? = null
var allDay: Boolean = false
var location: String? = null
var url: String? = null
Expand Down
27 changes: 27 additions & 0 deletions device_calendar/example/lib/presentation/pages/calendar_event.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/services.dart';
import 'event_attendee.dart';
Expand Down Expand Up @@ -230,6 +232,19 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
),
),
if (!_event.allDay) ... [
if (Platform.isAndroid)
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
initialValue: _event.startTimeZone,
decoration: const InputDecoration(
labelText: 'Start date time zone',
hintText: 'Australia/Sydney'),
onSaved: (String value) {
_event.startTimeZone = value;
},
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: DateTimePicker(
Expand All @@ -256,6 +271,18 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
},
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
initialValue: Platform.isAndroid ? _event.endTimeZone : _event.startTimeZone,
decoration: InputDecoration(
labelText: Platform.isAndroid ? 'End date time zone' : 'Start and end time zone',
hintText: 'Australia/Sydney'),
onSaved: (String value) => Platform.isAndroid
? _event.endTimeZone = value
: _event.startTimeZone = value,
),
),
],
GestureDetector(
onTap: () async {
Expand Down
19 changes: 17 additions & 2 deletions device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let description: String?
let start: Int64
let end: Int64
let startTimeZone: String?
let allDay: Bool
let attendees: [Attendee]
let location: String?
Expand Down Expand Up @@ -90,6 +91,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let eventAllDayArgument = "eventAllDay"
let eventStartDateArgument = "eventStartDate"
let eventEndDateArgument = "eventEndDate"
let eventStartTimeZoneArgument = "eventStartTimeZone"
let eventLocationArgument = "eventLocation"
let eventURLArgument = "eventURL"
let attendeesArgument = "attendees"
Expand Down Expand Up @@ -294,6 +296,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
description: ekEvent.notes,
start: Int64(ekEvent.startDate.millisecondsSinceEpoch),
end: Int64(ekEvent.endDate.millisecondsSinceEpoch),
startTimeZone: ekEvent.timeZone?.identifier,
allDay: ekEvent.isAllDay,
attendees: attendees,
location: ekEvent.location,
Expand Down Expand Up @@ -531,6 +534,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let endDateDateMillisecondsSinceEpoch = arguments[eventEndDateArgument] as! NSNumber
let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch.doubleValue / 1000.0)
let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch.doubleValue / 1000.0)
let startTimeZoneString = arguments[eventStartTimeZoneArgument] as? String
let title = arguments[self.eventTitleArgument] as! String
let description = arguments[self.eventDescriptionArgument] as? String
let location = arguments[self.eventLocationArgument] as? String
Expand Down Expand Up @@ -562,7 +566,12 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
ekEvent!.isAllDay = isAllDay
ekEvent!.startDate = startDate
if (isAllDay) { ekEvent!.endDate = startDate }
else { ekEvent!.endDate = endDate }
else {
ekEvent!.endDate = endDate

let timeZone = TimeZone(identifier: startTimeZoneString ?? TimeZone.current.identifier) ?? .current
ekEvent!.timeZone = timeZone
}
ekEvent!.calendar = ekCalendar!
ekEvent!.location = location

Expand Down Expand Up @@ -732,8 +741,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
}
}

extension UIColor {
extension Date {
func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
let delta = TimeInterval(initTimeZone.secondsFromGMT() - targetTimeZone.secondsFromGMT())
return addingTimeInterval(delta)
}
}

extension UIColor {
func rgb() -> Int? {
var fRed : CGFloat = 0
var fGreen : CGFloat = 0
Expand Down
17 changes: 1 addition & 16 deletions device_calendar/lib/src/device_calendar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,22 +218,7 @@ class DeviceCalendarPlugin {
}

try {
result.data =
await channel.invokeMethod('createOrUpdateEvent', <String, Object>{
'calendarId': event.calendarId,
'eventId': event.eventId,
'eventTitle': event.title,
'eventDescription': event.description,
'eventLocation': event.location,
'eventAllDay': event.allDay,
'eventStartDate': event.start.millisecondsSinceEpoch,
'eventEndDate': event.end.millisecondsSinceEpoch,
'eventLocation': event.location,
'eventURL': event.url?.data?.contentText,
'recurrenceRule': event.recurrenceRule?.toJson(),
'attendees': event.attendees?.map((a) => a.toJson())?.toList(),
'reminders': event.reminders?.map((r) => r.toJson())?.toList()
});
result.data = await channel.invokeMethod('createOrUpdateEvent', event.toJson());
} catch (e) {
_parsePlatformExceptionAndUpdateResult<String>(e, result);
}
Expand Down
Loading