Skip to content
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ On iOS, you need to manually add the following permissions to your `Info.plist`:
<string>This app requires access to the calendar and reminders</string>
```

On Android, you need to manually add the following to your `AndroidManifest.xml`

```xml
<uses-permission android:name="android.permission.READ_CALENDAR" />
```


## Example
```javascript
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.nearform.calendar;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CalendarContract;

import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;

import java.lang.RuntimeException;
import java.lang.StringBuilder;

import com.nearform.calendar.CalendarEvent;

public class CalendarActivityHandler extends BaseActivityEventListener {
public static final int ADD_EVENT_REQUEST_CODE = 1;
private Promise promise;
private CalendarEvent calendarEvent;

public void init(final CalendarEvent calendarEvent, final Promise promise) {
this.calendarEvent = calendarEvent;
this.promise = promise;
}

@Override
public void onActivityResult(final Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (requestCode == ADD_EVENT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK || (resultCode == Activity.RESULT_CANCELED && this.isEventAdded(activity))) {
this.promise.resolve("EVENT_SAVED");
} else if (resultCode == Activity.RESULT_CANCELED) {
this.promise.resolve("EVENT_CANCELLED");
} else {
this.promise.reject("ERR_UNKNOWN", new RuntimeException(Integer.toString(resultCode)));
}
}
}

// Android Calendar API does not support milliseconds precision
private String correctTime(final long time) {
return String.valueOf((time / 1000L) * 1000L);
}

private boolean isEventAdded(final Activity activity) {
final ContentResolver cr = activity.getContentResolver();
final Uri uri = CalendarContract.Events.CONTENT_URI;

final String selection = new StringBuilder("(").append(CalendarContract.Events.TITLE).append(" = ? AND ")
.append(CalendarContract.Events.DTSTART).append(" >= ? AND ")
.append(CalendarContract.Events.DTSTART).append(" <= ? AND ")
.append(CalendarContract.Events.EVENT_TIMEZONE).append(" = ?)")
.toString();

final String[] selectionArgs = new String[]{this.calendarEvent.name, this.correctTime(this.calendarEvent.startTime), this.correctTime(this.calendarEvent.endTime), String.valueOf(this.calendarEvent.timeZone.getID())};

final Cursor cursor = cr.query(uri, null, selection, selectionArgs, null);
final boolean eventsFound = cursor.getCount() > 0;

cursor.close();

return eventsFound;
}
}
38 changes: 38 additions & 0 deletions android/src/main/java/com/nearform/calendar/CalendarEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.nearform.calendar;

import android.icu.util.TimeZone;

import com.facebook.react.bridge.ReadableMap;

public class CalendarEvent {
public String name;
public String location;
public long startTime;
public long endTime;
public TimeZone timeZone;

private CalendarEvent(final String name, final String location, final long startTime, final long endTime, final TimeZone timeZone) {
this.name = name;
this.location = location;
this.startTime = startTime;
this.endTime = endTime;
this.timeZone = timeZone;
}

public static CalendarEvent fromReadableMap(final ReadableMap eventDetails) {
final Double startTime = eventDetails.getDouble("startTime");
final Double endTime = eventDetails.getDouble("endTime");

return new CalendarEvent(
eventDetails.getString("name"),
eventDetails.getString("location"),
startTime.longValue(),
endTime.longValue(),
getTimeZone(eventDetails)
);
}

private static TimeZone getTimeZone(final ReadableMap eventDetails) {
return eventDetails.hasKey("timeZone") ? TimeZone.getTimeZone(eventDetails.getString("timeZone")) : TimeZone.getDefault();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.provider.CalendarContract;
import android.icu.util.TimeZone;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
Expand All @@ -13,36 +12,41 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;

import com.nearform.calendar.CalendarActivityHandler;
import com.nearform.calendar.CalendarEvent;

public class CalendarManagerModule extends ReactContextBaseJavaModule {
public CalendarManagerModule(ReactApplicationContext reactContext) {
private static final int ADD_EVENT_REQUEST_CODE = 1;
private final CalendarActivityHandler activityEventListener = new CalendarActivityHandler();

public CalendarManagerModule(final ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(this.activityEventListener);
}

@Override
public String getName() {
return "CalendarManager";
}

private TimeZone getTimeZone(ReadableMap eventDetails) {
return eventDetails.hasKey("timeZone") ? TimeZone.getTimeZone(eventDetails.getString("timeZone")) : TimeZone.getDefault();
private Intent createCalendarIntent(final CalendarEvent calendarEvent) {
return new Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, calendarEvent.startTime)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, calendarEvent.endTime)
.putExtra(CalendarContract.Events.TITLE, calendarEvent.name)
.putExtra(CalendarContract.Events.EVENT_LOCATION, calendarEvent.location)
.putExtra(CalendarContract.Events.EVENT_TIMEZONE, calendarEvent.timeZone.getID());
}

@ReactMethod
public void addEvent(ReadableMap eventDetails, Promise promise) {
final Double startTime = eventDetails.getDouble("startTime");
final Double endTime = eventDetails.getDouble("endTime");

final Intent intent = new Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startTime.longValue())
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.longValue())
.putExtra(CalendarContract.Events.TITLE, eventDetails.getString("name"))
.putExtra(CalendarContract.Events.EVENT_LOCATION, eventDetails.getString("location"))
.putExtra(CalendarContract.Events.EVENT_TIMEZONE, this.getTimeZone(eventDetails).getID());
public void addEvent(final ReadableMap eventDetails, final Promise promise) {
final CalendarEvent calendarEvent = CalendarEvent.fromReadableMap(eventDetails);
final Intent calendarIntent = this.createCalendarIntent(calendarEvent);
this.activityEventListener.init(calendarEvent, promise);

try {
getCurrentActivity().startActivity(intent);
promise.resolve(null);
getCurrentActivity().startActivityForResult(calendarIntent, ADD_EVENT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
promise.reject("ERR_NO_CALENDAR", e);
}
Expand Down
16 changes: 15 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { NativeModules } from 'react-native';
import { NativeModules, PermissionsAndroid, Platform } from 'react-native';

export const ERRORS = {
NO_CALENDAR: 'ERR_NO_CALENDAR',
NO_PERMISSION: 'ERR_NO_PERMISSION',
UNKNOWN: 'ERR_UNKNOWN',
};

export const RESULTS = {
Expand All @@ -12,5 +13,18 @@ export const RESULTS = {
}

const CalendarManager = NativeModules.CalendarManager;
const oldAddEvent = CalendarManager.addEvent.bind();

CalendarManager.addEvent = async (eventDetails) => {
if (Platform.OS === 'android') {
const permissionResult = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CALENDAR,
);
if (permissionResult !== PermissionsAndroid.RESULTS.GRANTED) {
throw new Error(ERRORS.NO_PERMISSION)
}
}
return oldAddEvent(eventDetails);
}

export default CalendarManager;