diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt index b99649ee2..7277c01b7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt @@ -1129,8 +1129,8 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { } } } else { - handlePermission(PERMISSION_WRITE_STORAGE) { - if (it) { + handlePermission(PERMISSION_WRITE_STORAGE) { granted -> + if (granted) { ExportEventsDialog(this, config.lastExportPath, false) { file, eventTypes -> getFileOutputStream(file.toFileDirItem(this), true) { exportEventsTo(eventTypes, it) @@ -1147,9 +1147,9 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { if (events.isEmpty()) { toast(R.string.no_entries_for_exporting) } else { - IcsExporter().exportEvents(this, outputStream, events, true) { + IcsExporter(this).exportEvents(outputStream, events, true) { result -> toast( - when (it) { + when (result) { ExportResult.EXPORT_OK -> R.string.exporting_successful ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed else -> R.string.exporting_failed diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt index a0af0d22c..7d149ad77 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt @@ -17,13 +17,22 @@ class ExportEventsDialog( val activity: SimpleActivity, val path: String, val hidePath: Boolean, val callback: (file: File, eventTypes: ArrayList) -> Unit ) { - private var realPath = if (path.isEmpty()) activity.internalStoragePath else path + private var realPath = path.ifEmpty { activity.internalStoragePath } private val config = activity.config init { val view = (activity.layoutInflater.inflate(R.layout.dialog_export_events, null) as ViewGroup).apply { export_events_folder.setText(activity.humanizePath(realPath)) export_events_filename.setText("${activity.getString(R.string.events)}_${activity.getCurrentFormattedDateTime()}") + + export_events_checkbox.isChecked = config.exportEvents + export_events_checkbox_holder.setOnClickListener { + export_events_checkbox.toggle() + } + export_tasks_checkbox.isChecked = config.exportTasks + export_tasks_checkbox_holder.setOnClickListener { + export_tasks_checkbox.toggle() + } export_past_events_checkbox.isChecked = config.exportPastEvents export_past_events_checkbox_holder.setOnClickListener { export_past_events_checkbox.toggle() @@ -70,8 +79,12 @@ class ExportEventsDialog( } ensureBackgroundThread { - config.lastExportPath = file.absolutePath.getParentPath() - config.exportPastEvents = view.export_past_events_checkbox.isChecked + config.apply { + lastExportPath = file.absolutePath.getParentPath() + exportEvents = view.export_events_checkbox.isChecked + exportTasks = view.export_tasks_checkbox.isChecked + exportPastEvents = view.export_past_events_checkbox.isChecked + } val eventTypes = (view.export_events_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList() callback(file, eventTypes) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt index 407ffaaa5..098a83d08 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt @@ -13,7 +13,6 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.models.RadioItem import java.io.File import java.util.* -import kotlin.collections.ArrayList fun BaseSimpleActivity.shareEvents(ids: List) { ensureBackgroundThread { @@ -29,8 +28,8 @@ fun BaseSimpleActivity.shareEvents(ids: List) { } getFileOutputStream(file.toFileDirItem(this), true) { - IcsExporter().exportEvents(this, it, events, false) { - if (it == IcsExporter.ExportResult.EXPORT_OK) { + IcsExporter(this).exportEvents(it, events, false) { result -> + if (result == IcsExporter.ExportResult.EXPORT_OK) { sharePathIntent(file.absolutePath, BuildConfig.APPLICATION_ID) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt index fcb435ff4..5217954f1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -223,6 +223,14 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getString(LAST_EXPORT_PATH, "")!! set(lastExportPath) = prefs.edit().putString(LAST_EXPORT_PATH, lastExportPath).apply() + var exportEvents: Boolean + get() = prefs.getBoolean(EXPORT_EVENTS, true) + set(exportEvents) = prefs.edit().putBoolean(EXPORT_EVENTS, exportEvents).apply() + + var exportTasks: Boolean + get() = prefs.getBoolean(EXPORT_TASKS, true) + set(exportTasks) = prefs.edit().putBoolean(EXPORT_TASKS, exportTasks).apply() + var exportPastEvents: Boolean get() = prefs.getBoolean(EXPORT_PAST_EVENTS, false) set(exportPastEvents) = prefs.edit().putBoolean(EXPORT_PAST_EVENTS, exportPastEvents).apply() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index 5bbd77ca9..d589c79c3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -116,6 +116,8 @@ const val ADD_ANNIVERSARIES_AUTOMATICALLY = "add_anniversaries_automatically" const val BIRTHDAY_REMINDERS = "birthday_reminders" const val ANNIVERSARY_REMINDERS = "anniversary_reminders" const val LAST_EXPORT_PATH = "last_export_path" +const val EXPORT_EVENTS = "export_events" +const val EXPORT_TASKS = "export_tasks" const val EXPORT_PAST_EVENTS = "export_past_events" const val WEEKLY_VIEW_ITEM_HEIGHT_MULTIPLIER = "weekly_view_item_height_multiplier" const val WEEKLY_VIEW_DAYS = "weekly_view_days" @@ -144,6 +146,8 @@ const val CALENDAR_PRODID = "PRODID:-//Simple Mobile Tools//NONSGML Event Calend const val CALENDAR_VERSION = "VERSION:2.0" const val BEGIN_EVENT = "BEGIN:VEVENT" const val END_EVENT = "END:VEVENT" +const val BEGIN_TODO = "BEGIN:VTODO" +const val END_TODO = "END:VTODO" const val BEGIN_ALARM = "BEGIN:VALARM" const val END_ALARM = "END:VALARM" const val DTSTART = "DTSTART" @@ -182,6 +186,7 @@ const val UNTIL = "UNTIL" const val COUNT = "COUNT" const val INTERVAL = "INTERVAL" const val CONFIRMED = "CONFIRMED" +const val COMPLETED = "COMPLETED" const val VALUE = "VALUE" const val DATE = "DATE" diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt index 3bc102b78..47e5e37c2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt @@ -522,13 +522,24 @@ class EventsHelper(val context: Context) { fun getEventsToExport(eventTypes: ArrayList): ArrayList { val currTS = getNowSeconds() var events = ArrayList() + val tasks = ArrayList() if (config.exportPastEvents) { events.addAll(eventsDB.getAllEventsWithTypes(eventTypes)) + if (config.exportTasks) { + tasks.addAll(eventsDB.getAllTasksWithTypes(eventTypes)) + } } else { - events.addAll(eventsDB.getOneTimeFutureEventsWithTypes(currTS, eventTypes)) - events.addAll(eventsDB.getRepeatableFutureEventsWithTypes(currTS, eventTypes)) + events.addAll(eventsDB.getAllFutureEventsWithTypes(currTS, eventTypes)) + if (config.exportTasks) { + tasks.addAll(eventsDB.getAllFutureTasksWithTypes(currTS, eventTypes)) + } } + tasks.forEach { + updateIsTaskCompleted(it) + } + events.addAll(tasks) + events = events.distinctBy { it.id } as ArrayList return events } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt index f6d290fe0..037fddfa3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt @@ -17,7 +17,7 @@ import java.io.BufferedWriter import java.io.OutputStream import java.io.OutputStreamWriter -class IcsExporter { +class IcsExporter(private val activity: BaseSimpleActivity) { enum class ExportResult { EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL } @@ -26,9 +26,10 @@ class IcsExporter { private var eventsExported = 0 private var eventsFailed = 0 private var calendars = ArrayList() + private val reminderLabel = activity.getString(R.string.reminder) + private val exportTime = Formatter.getExportedTime(System.currentTimeMillis()) fun exportEvents( - activity: BaseSimpleActivity, outputStream: OutputStream?, events: ArrayList, showExportingToast: Boolean, @@ -40,15 +41,11 @@ class IcsExporter { } ensureBackgroundThread { - val reminderLabel = activity.getString(R.string.reminder) - val exportTime = Formatter.getExportedTime(System.currentTimeMillis()) - calendars = activity.calDAVHelper.getCalDAVCalendars("", false) if (showExportingToast) { activity.toast(R.string.exporting) } - object : BufferedWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)) { val lineSeparator = "\r\n" @@ -66,34 +63,11 @@ class IcsExporter { out.writeLn(CALENDAR_PRODID) out.writeLn(CALENDAR_VERSION) for (event in events) { - out.writeLn(BEGIN_EVENT) - event.title.replace("\n", "\\n").let { if (it.isNotEmpty()) out.writeLn("$SUMMARY:$it") } - event.importId.let { if (it.isNotEmpty()) out.writeLn("$UID$it") } - event.eventType.let { out.writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(it)?.color}") } - event.eventType.let { out.writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(it)?.title}") } - event.lastUpdated.let { out.writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(it)}") } - event.location.let { if (it.isNotEmpty()) out.writeLn("$LOCATION:$it") } - event.availability.let { out.writeLn("$TRANSP${if (it == Events.AVAILABILITY_FREE) TRANSPARENT else OPAQUE}") } - - if (event.getIsAllDay()) { - out.writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.startTS)}") - out.writeLn("$DTEND;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.endTS + TWELVE_HOURS)}") + if (event.isTask()) { + writeTodo(out, event) } else { - event.startTS.let { out.writeLn("$DTSTART:${Formatter.getExportedTime(it * 1000L)}") } - event.endTS.let { out.writeLn("$DTEND:${Formatter.getExportedTime(it * 1000L)}") } + writeEvent(out, event) } - event.hasMissingYear().let { out.writeLn("$MISSING_YEAR${if (it) 1 else 0}") } - - out.writeLn("$DTSTAMP$exportTime") - out.writeLn("$STATUS$CONFIRMED") - Parser().getRepeatCode(event).let { if (it.isNotEmpty()) out.writeLn("$RRULE$it") } - - fillDescription(event.description.replace("\n", "\\n"), out) - fillReminders(event, out, reminderLabel) - fillIgnoredOccurrences(event, out) - - eventsExported++ - out.writeLn(END_EVENT) } out.writeLn(END_CALENDAR) } @@ -153,4 +127,67 @@ class IcsExporter { index += MAX_LINE_LENGTH } } + + private fun writeEvent(writer: BufferedWriter, event: Event) { + with(writer) { + writeLn(BEGIN_EVENT) + event.title.replace("\n", "\\n").let { if (it.isNotEmpty()) writeLn("$SUMMARY:$it") } + event.importId.let { if (it.isNotEmpty()) writeLn("$UID$it") } + writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(event.eventType)?.color}") + writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(event.eventType)?.title}") + writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(event.lastUpdated)}") + writeLn("$TRANSP${if (event.availability == Events.AVAILABILITY_FREE) TRANSPARENT else OPAQUE}") + + if (event.getIsAllDay()) { + writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.startTS)}") + writeLn("$DTEND;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.endTS + TWELVE_HOURS)}") + } else { + writeLn("$DTSTART:${Formatter.getExportedTime(event.startTS * 1000L)}") + writeLn("$DTEND:${Formatter.getExportedTime(event.endTS * 1000L)}") + } + writeLn("$MISSING_YEAR${if (event.hasMissingYear()) 1 else 0}") + + writeLn("$DTSTAMP$exportTime") + writeLn("$STATUS$CONFIRMED") + Parser().getRepeatCode(event).let { if (it.isNotEmpty()) writeLn("$RRULE$it") } + + fillDescription(event.description.replace("\n", "\\n"), writer) + fillReminders(event, writer, reminderLabel) + fillIgnoredOccurrences(event, writer) + + eventsExported++ + writeLn(END_EVENT) + } + } + + private fun writeTodo(writer: BufferedWriter, task: Event) { + with(writer) { + writeLn(BEGIN_TODO) + task.title.replace("\n", "\\n").let { if (it.isNotEmpty()) writeLn("$SUMMARY:$it") } + task.importId.let { if (it.isNotEmpty()) writeLn("$UID$it") } + writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(task.eventType)?.color}") + writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(task.eventType)?.title}") + writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(task.lastUpdated)}") + task.location.let { if (it.isNotEmpty()) writeLn("$LOCATION:$it") } + + if (task.getIsAllDay()) { + writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(task.startTS)}") + } else { + writeLn("$DTSTART:${Formatter.getExportedTime(task.startTS * 1000L)}") + } + + writeLn("$DTSTAMP$exportTime") + if (task.isTaskCompleted()) { + writeLn("$STATUS$COMPLETED") + } + Parser().getRepeatCode(task).let { if (it.isNotEmpty()) writeLn("$RRULE$it") } + + fillDescription(task.description.replace("\n", "\\n"), writer) + fillReminders(task, writer, reminderLabel) + fillIgnoredOccurrences(task, writer) + + eventsExported++ + writeLn(END_TODO) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt index 83128aa27..8bfdb6330 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt @@ -15,6 +15,15 @@ interface EventsDao { @Query("SELECT * FROM events WHERE event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") fun getAllEventsWithTypes(eventTypeIds: List): List + @Query("SELECT * FROM events WHERE event_type IN (:eventTypeIds) AND type = $TYPE_TASK") + fun getAllTasksWithTypes(eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE end_ts > :currTS AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") + fun getAllFutureEventsWithTypes(currTS: Long, eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE end_ts > :currTS AND event_type IN (:eventTypeIds) AND type = $TYPE_TASK") + fun getAllFutureTasksWithTypes(currTS: Long, eventTypeIds: List): List + @Query("SELECT * FROM events WHERE id = :id AND type = $TYPE_EVENT") fun getEventWithId(id: Long): Event? @@ -42,9 +51,6 @@ interface EventsDao { @Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND start_ts != 0 AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND (title LIKE :searchQuery OR location LIKE :searchQuery OR description LIKE :searchQuery)") fun getOneTimeEventsFromToWithTypesForSearch(toTS: Long, fromTS: Long, eventTypeIds: List, searchQuery: String): List - @Query("SELECT * FROM events WHERE end_ts > :toTS AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") - fun getOneTimeFutureEventsWithTypes(toTS: Long, eventTypeIds: List): List - @Query("SELECT * FROM events WHERE start_ts <= :toTS AND repeat_interval != 0") fun getRepeatableEventsOrTasksWithTypes(toTS: Long): List @@ -57,9 +63,6 @@ interface EventsDao { @Query("SELECT * FROM events WHERE start_ts <= :toTS AND start_ts != 0 AND repeat_interval != 0 AND event_type IN (:eventTypeIds) AND (title LIKE :searchQuery OR location LIKE :searchQuery OR description LIKE :searchQuery)") fun getRepeatableEventsOrTasksWithTypesForSearch(toTS: Long, eventTypeIds: List, searchQuery: String): List - @Query("SELECT * FROM events WHERE repeat_interval != 0 AND (repeat_limit == 0 OR repeat_limit > :currTS) AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") - fun getRepeatableFutureEventsWithTypes(currTS: Long, eventTypeIds: List): List - @Query("SELECT * FROM events WHERE id IN (:ids) AND import_id != \"\" AND type = $TYPE_EVENT") fun getEventsByIdsWithImportIds(ids: List): List diff --git a/app/src/main/res/layout/dialog_export_events.xml b/app/src/main/res/layout/dialog_export_events.xml index 304e945f9..0eaf010b5 100644 --- a/app/src/main/res/layout/dialog_export_events.xml +++ b/app/src/main/res/layout/dialog_export_events.xml @@ -48,15 +48,66 @@ + + + + + + + + + + + + + + + android:paddingEnd="@dimen/activity_margin"> + android:text="@string/export_past_entries" />