Skip to content

Commit

Permalink
feat(android): added new methods in Ti.Calendar.Calendar module for b…
Browse files Browse the repository at this point in the history
…ulk operations (#14149)

* feat(android): added new methods in CalendarProxy for bulk operations

* chore: use constant properties

* fix: add missing properties of `scrolling` event

* chore(android): add docs to new methods for Ti.Calendar.Calendar module

* fix(android): set exitOnClose defaults to true on root window if not set already

* Revert "fix(android): set exitOnClose defaults to true on root window if not set already"

This reverts commit e2c4bb9.

* fix: fix docs formatting

* Update android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarProxy.java

Co-authored-by: Michael Gangolf <[email protected]>

* Update android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarProxy.java

Co-authored-by: Michael Gangolf <[email protected]>

* Update android/modules/calendar/src/java/ti/modules/titanium/calendar/CalendarProxy.java

Co-authored-by: Michael Gangolf <[email protected]>

* fix: fix docs

---------

Co-authored-by: Michael Gangolf <[email protected]>
Co-authored-by: Hans Knöchel <[email protected]>
  • Loading branch information
3 people authored Dec 12, 2024
1 parent 5457ee8 commit dfb6a82
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@

package ti.modules.titanium.calendar;

import static ti.modules.titanium.calendar.EventProxy.getEventsUri;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollProxy;
Expand All @@ -19,11 +23,17 @@

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.CalendarContract;
import android.text.format.DateUtils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.OperationApplicationException;
import android.os.RemoteException;

@Kroll.proxy(parentModule = CalendarModule.class)
public class CalendarProxy extends KrollProxy
Expand Down Expand Up @@ -188,12 +198,147 @@ public EventProxy getEventById(int id)
return null;
}

@Kroll.method
public EventProxy[] getEventsById(Object args)
{
ArrayList<EventProxy> events = new ArrayList<>();

if (args instanceof Object[] eventIds && eventIds.length > 0) {
String query = CalendarUtils.prepareQuerySelection("_id", eventIds.length);
String[] queryArgs = CalendarUtils.prepareQueryArguments(eventIds);

events.addAll(EventProxy.queryEvents(query, queryArgs));
}

return events.toArray(new EventProxy[0]);
}

@Kroll.method
public EventProxy createEvent(KrollDict data)
{
return EventProxy.createEvent(this, data);
}

@Kroll.method
public EventProxy[] createEvents(Object data)
{
// Validate arguments to be an array.
if (!(data instanceof Object[] dataList && dataList.length > 0)) {
Log.e(TAG, "Argument expected to be an array.");
return null;
}

// Check for permissions.
ContentResolver contentResolver = TiApplication.getInstance().getContentResolver();
if (!hasCalendarPermissions()) {
Log.e(TAG, "Calendar permissions are missing.");
return null;
}

ArrayList<ContentProviderOperation> operations = new ArrayList<>();
ArrayList<EventProxy> eventProxies = new ArrayList<>();
Map<Integer, Integer> proxyResultIndexMapping = new HashMap<>();

for (int i = 0, firstIndex = 0; i < dataList.length; i++) {
KrollDict krollDict = new KrollDict((HashMap) dataList[i]);

EventProxy eventProxy = new EventProxy();
ContentValues contentValues = CalendarUtils.createContentValues(this, krollDict, eventProxy);

// We cannot pass null data to ContentProviderOperation.
// Necessary to keep track of non-null items later.
if (contentValues == null) {
eventProxies.add(null);
Log.e(TAG, "Event was not created, no title found for event");
continue;
}

ContentProviderOperation.Builder builder = ContentProviderOperation
.newInsert(CalendarContract.Events.CONTENT_URI)
.withValues(contentValues);

operations.add(builder.build());
eventProxies.add(eventProxy);

proxyResultIndexMapping.put(i, firstIndex);
firstIndex++;
}

try {
// Execute the batch operation
ContentProviderResult[] results = contentResolver.applyBatch(
getEventsUri().getAuthority(),
operations
);

// Find non-null proxies and map their IDs
for (int proxyIndex : proxyResultIndexMapping.keySet()) {
int proxyIndexInResults = proxyResultIndexMapping.get(proxyIndex);
Uri eventUri = results[proxyIndexInResults].uri;

if (eventUri != null) {
// Set event id to proxy.
eventProxies.get(proxyIndex).id = eventUri.getLastPathSegment();
} else {
// Event failed to get a proper URI path, should set to null in this case too.
eventProxies.set(proxyIndex, null);
}
}

return eventProxies.toArray(new EventProxy[0]);

} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Batch insert operation failed: " + e.getMessage());
return null;
}
}

@Kroll.method
public int deleteEvents(Object args)
{
int deletedCount = 0;

// Validate arguments to be an array.
if (!(args instanceof Object[] eventIds && eventIds.length > 0)) {
Log.e(TAG, "Argument expected to be an array.");
return deletedCount;
}

// Check for permissions.
ContentResolver contentResolver = TiApplication.getInstance().getContentResolver();
if (!hasCalendarPermissions()) {
Log.e(TAG, "Calendar permissions are missing.");
return deletedCount;
}

String query = CalendarUtils.prepareQuerySelection("_id", eventIds.length);
String[] queryArgs = CalendarUtils.prepareQueryArguments(eventIds);

ArrayList<ContentProviderOperation> operations = new ArrayList<>();
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(CalendarContract.Events.CONTENT_URI)
.withSelection(query, queryArgs);

operations.add(builder.build());

try {
// Execute the batch operation
ContentProviderResult[] results = contentResolver.applyBatch(
getEventsUri().getAuthority(),
operations
);

if (results.length > 0 && results[0].count != null) {
deletedCount = results[0].count;
}

} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Batch deletion failed: " + e.getMessage());
}

return deletedCount;
}

@Kroll.getProperty
public String getName()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package ti.modules.titanium.calendar;

import android.content.ContentValues;
import android.provider.CalendarContract;
import android.text.TextUtils;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.util.TiConvert;

import java.util.Collections;
import java.util.Date;

public class CalendarUtils
{
public static final String TAG = "CalendarUtils";

// Build the selection string for IN clause.
public static String prepareQuerySelection(String columnName, int limit)
{
return columnName + " IN (" + TextUtils.join(", ", Collections.nCopies(limit, "?")) + ")";
}

// Creates String[] for selectionArgs.
public static String[] prepareQueryArguments(Object[] data)
{
String[] queryArgs = new String[data.length];
for (int i = 0; i < data.length; i++) {
queryArgs[i] = String.valueOf(data[i]);
}
return queryArgs;
}

public static ContentValues createContentValues(CalendarProxy calendar, KrollDict data, EventProxy event)
{
if (!data.containsKey(TiC.PROPERTY_TITLE)) {
return null;
}

ContentValues contentValues = new ContentValues();
contentValues.put("hasAlarm", 1);
contentValues.put("hasExtendedProperties", 1);

event.title = TiConvert.toString(data, TiC.PROPERTY_TITLE);
contentValues.put(TiC.PROPERTY_TITLE, event.title);
contentValues.put("calendar_id", calendar.getId());
contentValues.put(CalendarContract.Events.EVENT_TIMEZONE, new Date().toString());

if (data.containsKey(TiC.PROPERTY_LOCATION)) {
event.location = TiConvert.toString(data, TiC.PROPERTY_LOCATION);
contentValues.put(CalendarModule.EVENT_LOCATION, event.location);
}

if (data.containsKey(TiC.PROPERTY_DESCRIPTION)) {
event.description = TiConvert.toString(data, TiC.PROPERTY_DESCRIPTION);
contentValues.put(TiC.PROPERTY_DESCRIPTION, event.description);
}

if (data.containsKey("begin")) {
event.begin = TiConvert.toDate(data, "begin");
if (event.begin != null) {
contentValues.put("dtstart", event.begin.getTime());
}
}

if (data.containsKey(TiC.PROPERTY_END)) {
event.end = TiConvert.toDate(data, TiC.PROPERTY_END);
if (event.end != null) {
contentValues.put("dtend", event.end.getTime());
}
}

if (data.containsKey("allDay")) {
event.allDay = TiConvert.toBoolean(data, "allDay");
contentValues.put("allDay", event.allDay ? 1 : 0);
}

if (data.containsKey("hasExtendedProperties")) {
event.hasExtendedProperties = TiConvert.toBoolean(data, "hasExtendedProperties");
contentValues.put("hasExtendedProperties", event.hasExtendedProperties ? 1 : 0);
}

if (data.containsKey("hasAlarm")) {
event.hasAlarm = TiConvert.toBoolean(data, "hasAlarm");
contentValues.put("hasAlarm", event.hasAlarm ? 1 : 0);
}

return contentValues;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public EventProxy()
super();
}

public static String getEventsUri()
public static Uri getEventsUri()
{
return CalendarProxy.getBaseCalendarUri() + "/events";
return Events.CONTENT_URI;
}

public static String getInstancesWhenUri()
Expand All @@ -72,7 +72,7 @@ public static String getExtendedPropertiesUri()

public static ArrayList<EventProxy> queryEvents(String query, String[] queryArgs)
{
return queryEvents(Uri.parse(getEventsUri()), query, queryArgs, "dtstart ASC");
return queryEvents(getEventsUri(), query, queryArgs, "dtstart ASC");
}

public static ArrayList<EventProxy> queryEventsBetweenDates(long date1, long date2, String query,
Expand Down Expand Up @@ -146,7 +146,7 @@ public void save()
contentValues.put(Events.RRULE, ruleToSave);
ContentResolver contentResolver = TiApplication.getInstance().getContentResolver();
try {
contentResolver.update(Events.CONTENT_URI, contentValues, Events._ID + "=?", new String[] { id });
contentResolver.update(getEventsUri(), contentValues, Events._ID + "=?", new String[] { id });
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid event recurrence rule.");
}
Expand Down Expand Up @@ -203,60 +203,20 @@ public static EventProxy createEvent(CalendarProxy calendar, KrollDict data)
if (!CalendarProxy.hasCalendarPermissions()) {
return null;
}
EventProxy event = new EventProxy();

ContentValues eventValues = new ContentValues();
eventValues.put("hasAlarm", 1);
eventValues.put("hasExtendedProperties", 1);
EventProxy event = new EventProxy();
ContentValues contentValues = CalendarUtils.createContentValues(calendar, data, event);

if (!data.containsKey("title")) {
if (contentValues == null) {
Log.e(TAG, "Title was not created, no title found for event");
return null;
}

event.title = TiConvert.toString(data, "title");
eventValues.put("title", event.title);
eventValues.put("calendar_id", calendar.getId());
eventValues.put(Events.EVENT_TIMEZONE, new Date().toString());

if (data.containsKey(TiC.PROPERTY_LOCATION)) {
event.location = TiConvert.toString(data, TiC.PROPERTY_LOCATION);
eventValues.put(CalendarModule.EVENT_LOCATION, event.location);
}
if (data.containsKey("description")) {
event.description = TiConvert.toString(data, "description");
eventValues.put("description", event.description);
}
if (data.containsKey("begin")) {
event.begin = TiConvert.toDate(data, "begin");
if (event.begin != null) {
eventValues.put("dtstart", event.begin.getTime());
}
}
if (data.containsKey("end")) {
event.end = TiConvert.toDate(data, "end");
if (event.end != null) {
eventValues.put("dtend", event.end.getTime());
}
}
if (data.containsKey("allDay")) {
event.allDay = TiConvert.toBoolean(data, "allDay");
eventValues.put("allDay", event.allDay ? 1 : 0);
}

if (data.containsKey("hasExtendedProperties")) {
event.hasExtendedProperties = TiConvert.toBoolean(data, "hasExtendedProperties");
eventValues.put("hasExtendedProperties", event.hasExtendedProperties ? 1 : 0);
}

if (data.containsKey("hasAlarm")) {
event.hasAlarm = TiConvert.toBoolean(data, "hasAlarm");
eventValues.put("hasAlarm", event.hasAlarm ? 1 : 0);
Uri eventUri = contentResolver.insert(Uri.parse(CalendarProxy.getBaseCalendarUri() + "/events"), contentValues);
if (eventUri == null) {
return null;
}

Uri eventUri = contentResolver.insert(Uri.parse(CalendarProxy.getBaseCalendarUri() + "/events"), eventValues);
Log.d("TiEvents", "created event with uri: " + eventUri, Log.DEBUG_MODE);

String eventId = eventUri.getLastPathSegment();
event.id = eventId;

Expand Down Expand Up @@ -565,13 +525,12 @@ public boolean remove()
ContentResolver contentResolver = TiApplication.getInstance().getContentResolver();

try {
Uri deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, TiConvert.toInt(id));
contentResolver.delete(deleteUri, null, null);
Uri deleteUri = ContentUris.withAppendedId(getEventsUri(), TiConvert.toInt(id));
int deletedCount = contentResolver.delete(deleteUri, null, null);
return deletedCount == 1;
} catch (IllegalArgumentException e) {
return false;
}

return true;
}

@Override
Expand Down
Loading

0 comments on commit dfb6a82

Please sign in to comment.