diff --git a/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.java b/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.java deleted file mode 100644 index b75d77a35..000000000 --- a/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ - -package com.todoroo.astrid.gcal; - -import static org.tasks.Strings.isNullOrEmpty; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.provider.CalendarContract; -import android.text.format.Time; -import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.astrid.dao.TaskDaoBlocking; -import com.todoroo.astrid.data.Task; -import dagger.hilt.android.qualifiers.ApplicationContext; -import java.util.TimeZone; -import javax.inject.Inject; -import org.tasks.R; -import org.tasks.calendars.AndroidCalendarEvent; -import org.tasks.calendars.CalendarEventProvider; -import org.tasks.preferences.PermissionChecker; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -public class GCalHelper { - - /** If task has no estimated time, how early to set a task in calendar (seconds) */ - private static final long DEFAULT_CAL_TIME = DateUtilities.ONE_HOUR; - - private final TaskDaoBlocking taskDao; - private final Preferences preferences; - private final PermissionChecker permissionChecker; - private final CalendarEventProvider calendarEventProvider; - private final ContentResolver cr; - - @Inject - public GCalHelper( - @ApplicationContext Context context, - TaskDaoBlocking taskDao, - Preferences preferences, - PermissionChecker permissionChecker, - CalendarEventProvider calendarEventProvider) { - this.taskDao = taskDao; - this.preferences = preferences; - this.permissionChecker = permissionChecker; - this.calendarEventProvider = calendarEventProvider; - cr = context.getContentResolver(); - } - - private String getTaskEventUri(Task task) { - String uri; - if (!isNullOrEmpty(task.getCalendarURI())) { - uri = task.getCalendarURI(); - } else { - task = taskDao.fetchBlocking(task.getId()); - if (task == null) { - return null; - } - uri = task.getCalendarURI(); - } - - return uri; - } - - public void createTaskEventIfEnabled(Task t) { - if (!t.hasDueDate()) { - return; - } - createTaskEventIfEnabled(t, true); - } - - private void createTaskEventIfEnabled(Task t, boolean deleteEventIfExists) { - if (preferences.isDefaultCalendarSet()) { - Uri calendarUri = createTaskEvent(t, new ContentValues(), deleteEventIfExists); - if (calendarUri != null) { - t.setCalendarURI(calendarUri.toString()); - } - } - } - - public Uri createTaskEvent(Task task, String calendarId) { - ContentValues values = new ContentValues(); - values.put(CalendarContract.Events.CALENDAR_ID, calendarId); - return createTaskEvent(task, values, true); - } - - private Uri createTaskEvent(Task task, ContentValues values, boolean deleteEventIfExists) { - if (!permissionChecker.canAccessCalendars()) { - return null; - } - - String eventuri = getTaskEventUri(task); - - if (!isNullOrEmpty(eventuri) && deleteEventIfExists) { - calendarEventProvider.deleteEvent(task); - } - - try { - values.put(CalendarContract.Events.TITLE, task.getTitle()); - values.put(CalendarContract.Events.DESCRIPTION, task.getNotes()); - values.put(CalendarContract.Events.HAS_ALARM, 0); - boolean valuesContainCalendarId = - (values.containsKey(CalendarContract.Events.CALENDAR_ID) - && !isNullOrEmpty(values.getAsString(CalendarContract.Events.CALENDAR_ID))); - if (!valuesContainCalendarId) { - String calendarId = preferences.getDefaultCalendar(); - if (!isNullOrEmpty(calendarId)) { - values.put(CalendarContract.Events.CALENDAR_ID, calendarId); - } - } - - createStartAndEndDate(task, values); - - //noinspection MissingPermission - Uri eventUri = cr.insert(CalendarContract.Events.CONTENT_URI, values); - cr.notifyChange(eventUri, null); - - return eventUri; - } catch (Exception e) { - // won't work on emulator - Timber.e(e); - } - - return null; - } - - public void updateEvent(String uri, Task task) { - try { - ContentValues updateValues = new ContentValues(); - // check if we need to update the item - updateValues.put(CalendarContract.Events.TITLE, task.getTitle()); - updateValues.put(CalendarContract.Events.DESCRIPTION, task.getNotes()); - createStartAndEndDate(task, updateValues); - cr.update(Uri.parse(uri), updateValues, null, null); - } catch (Exception e) { - Timber.e(e, "Failed to update calendar: %s [%s]", uri, task); - } - } - - public void rescheduleRepeatingTask(Task task) { - String taskUri = getTaskEventUri(task); - if (isNullOrEmpty(taskUri)) { - return; - } - - Uri eventUri = Uri.parse(taskUri); - - AndroidCalendarEvent event = calendarEventProvider.getEvent(eventUri); - if (event == null) { - task.setCalendarURI(""); - return; - } - ContentValues cv = new ContentValues(); - cv.put(CalendarContract.Events.CALENDAR_ID, event.getCalendarId()); - - Uri uri = createTaskEvent(task, cv, false); - task.setCalendarURI(uri.toString()); - } - - public void createStartAndEndDate(Task task, ContentValues values) { - long dueDate = task.getDueDate(); - long tzCorrectedDueDate = dueDate + TimeZone.getDefault().getOffset(dueDate); - long tzCorrectedDueDateNow = - DateUtilities.now() + TimeZone.getDefault().getOffset(DateUtilities.now()); - // FIXME: doesnt respect timezones, see story 17443653 - if (task.hasDueDate()) { - if (task.hasDueTime()) { - long estimatedTime = task.getEstimatedSeconds() * 1000; - if (estimatedTime <= 0) { - estimatedTime = DEFAULT_CAL_TIME; - } - if (preferences.getBoolean(R.string.p_end_at_deadline, true)) { - values.put(CalendarContract.Events.DTSTART, dueDate); - values.put(CalendarContract.Events.DTEND, dueDate + estimatedTime); - } else { - values.put(CalendarContract.Events.DTSTART, dueDate - estimatedTime); - values.put(CalendarContract.Events.DTEND, dueDate); - } - // setting a duetime to a previously timeless event requires explicitly setting allDay=0 - values.put(CalendarContract.Events.ALL_DAY, "0"); - values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); - } else { - values.put(CalendarContract.Events.DTSTART, tzCorrectedDueDate); - values.put(CalendarContract.Events.DTEND, tzCorrectedDueDate); - values.put(CalendarContract.Events.ALL_DAY, "1"); - } - } else { - values.put(CalendarContract.Events.DTSTART, tzCorrectedDueDateNow); - values.put(CalendarContract.Events.DTEND, tzCorrectedDueDateNow); - values.put(CalendarContract.Events.ALL_DAY, "1"); - } - if ("1".equals(values.get(CalendarContract.Events.ALL_DAY))) { - values.put(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC); - } else { - values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); - } - } -} diff --git a/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.kt b/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.kt new file mode 100644 index 000000000..13ac9fac6 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/gcal/GCalHelper.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2012 Todoroo Inc + * + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.gcal + +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.provider.CalendarContract +import android.text.format.Time +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.data.Task +import dagger.hilt.android.qualifiers.ApplicationContext +import org.tasks.R +import org.tasks.Strings.isNullOrEmpty +import org.tasks.calendars.CalendarEventProvider +import org.tasks.preferences.PermissionChecker +import org.tasks.preferences.Preferences +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +class GCalHelper @Inject constructor( + @ApplicationContext context: Context, + private val taskDao: TaskDao, + private val preferences: Preferences, + private val permissionChecker: PermissionChecker, + private val calendarEventProvider: CalendarEventProvider) { + + private val cr: ContentResolver = context.contentResolver + + private suspend fun getTaskEventUri(task: Task) = + if (!task.calendarURI.isNullOrBlank()) { + task.calendarURI + } else { + taskDao.fetch(task.id) + ?.calendarURI + } + + suspend fun createTaskEventIfEnabled(t: Task) { + if (!t.hasDueDate()) { + return + } + createTaskEventIfEnabled(t, true) + } + + private suspend fun createTaskEventIfEnabled(t: Task, deleteEventIfExists: Boolean) { + if (preferences.isDefaultCalendarSet) { + val calendarUri = createTaskEvent(t, ContentValues(), deleteEventIfExists) + if (calendarUri != null) { + t.calendarURI = calendarUri.toString() + } + } + } + + suspend fun createTaskEvent(task: Task, calendarId: String?): Uri? { + val values = ContentValues() + values.put(CalendarContract.Events.CALENDAR_ID, calendarId) + return createTaskEvent(task, values, true) + } + + private suspend fun createTaskEvent(task: Task, values: ContentValues, deleteEventIfExists: Boolean): Uri? { + if (!permissionChecker.canAccessCalendars()) { + return null + } + val eventuri = getTaskEventUri(task) + if (!isNullOrEmpty(eventuri) && deleteEventIfExists) { + calendarEventProvider.deleteEvent(task) + } + try { + values.put(CalendarContract.Events.TITLE, task.title) + values.put(CalendarContract.Events.DESCRIPTION, task.notes) + values.put(CalendarContract.Events.HAS_ALARM, 0) + val valuesContainCalendarId = (values.containsKey(CalendarContract.Events.CALENDAR_ID) + && !isNullOrEmpty(values.getAsString(CalendarContract.Events.CALENDAR_ID))) + if (!valuesContainCalendarId) { + val calendarId = preferences.defaultCalendar + if (!isNullOrEmpty(calendarId)) { + values.put(CalendarContract.Events.CALENDAR_ID, calendarId) + } + } + createStartAndEndDate(task, values) + val eventUri = cr.insert(CalendarContract.Events.CONTENT_URI, values) + cr.notifyChange(eventUri!!, null) + return eventUri + } catch (e: Exception) { + // won't work on emulator + Timber.e(e) + } + return null + } + + fun updateEvent(uri: String?, task: Task) { + try { + val updateValues = ContentValues() + // check if we need to update the item + updateValues.put(CalendarContract.Events.TITLE, task.title) + updateValues.put(CalendarContract.Events.DESCRIPTION, task.notes) + createStartAndEndDate(task, updateValues) + cr.update(Uri.parse(uri), updateValues, null, null) + } catch (e: Exception) { + Timber.e(e, "Failed to update calendar: %s [%s]", uri, task) + } + } + + suspend fun rescheduleRepeatingTask(task: Task) { + val taskUri = getTaskEventUri(task) + if (isNullOrEmpty(taskUri)) { + return + } + val eventUri = Uri.parse(taskUri) + val event = calendarEventProvider.getEvent(eventUri) + if (event == null) { + task.calendarURI = "" + return + } + val cv = ContentValues() + cv.put(CalendarContract.Events.CALENDAR_ID, event.calendarId) + val uri = createTaskEvent(task, cv, false) + task.calendarURI = uri.toString() + } + + private fun createStartAndEndDate(task: Task, values: ContentValues) { + val dueDate = task.dueDate + val tzCorrectedDueDate = dueDate + TimeZone.getDefault().getOffset(dueDate) + val tzCorrectedDueDateNow = DateUtilities.now() + TimeZone.getDefault().getOffset(DateUtilities.now()) + // FIXME: doesnt respect timezones, see story 17443653 + if (task.hasDueDate()) { + if (task.hasDueTime()) { + var estimatedTime = task.estimatedSeconds * 1000.toLong() + if (estimatedTime <= 0) { + estimatedTime = DEFAULT_CAL_TIME + } + if (preferences.getBoolean(R.string.p_end_at_deadline, true)) { + values.put(CalendarContract.Events.DTSTART, dueDate) + values.put(CalendarContract.Events.DTEND, dueDate + estimatedTime) + } else { + values.put(CalendarContract.Events.DTSTART, dueDate - estimatedTime) + values.put(CalendarContract.Events.DTEND, dueDate) + } + // setting a duetime to a previously timeless event requires explicitly setting allDay=0 + values.put(CalendarContract.Events.ALL_DAY, "0") + values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id) + } else { + values.put(CalendarContract.Events.DTSTART, tzCorrectedDueDate) + values.put(CalendarContract.Events.DTEND, tzCorrectedDueDate) + values.put(CalendarContract.Events.ALL_DAY, "1") + } + } else { + values.put(CalendarContract.Events.DTSTART, tzCorrectedDueDateNow) + values.put(CalendarContract.Events.DTEND, tzCorrectedDueDateNow) + values.put(CalendarContract.Events.ALL_DAY, "1") + } + if ("1" == values[CalendarContract.Events.ALL_DAY]) { + values.put(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC) + } else { + values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id) + } + } + + companion object { + /** If task has no estimated time, how early to set a task in calendar (seconds) */ + private const val DEFAULT_CAL_TIME = DateUtilities.ONE_HOUR + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt index 95a269ab7..d6e14760f 100644 --- a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt +++ b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt @@ -382,7 +382,7 @@ class TaskEditViewModel @ViewModelInject constructor( true } ?: false - private fun applyCalendarChanges() { + private suspend fun applyCalendarChanges() { if (!permissionChecker.canAccessCalendars()) { return } @@ -398,7 +398,7 @@ class TaskEditViewModel @ViewModelInject constructor( } selectedCalendar?.let { try { - task?.calendarURI = gCalHelper.createTaskEvent(task, it)?.toString() + task?.calendarURI = gCalHelper.createTaskEvent(task!!, it)?.toString() } catch (e: Exception) { Timber.e(e) }