Add option to export tasks

This commit is contained in:
Naveen 2023-01-15 02:37:31 +05:30
parent 579bc8bb66
commit 635fa436c7
9 changed files with 181 additions and 54 deletions

View File

@ -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

View File

@ -17,13 +17,22 @@ class ExportEventsDialog(
val activity: SimpleActivity, val path: String, val hidePath: Boolean,
val callback: (file: File, eventTypes: ArrayList<Long>) -> 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)

View File

@ -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<Long>) {
ensureBackgroundThread {
@ -29,8 +28,8 @@ fun BaseSimpleActivity.shareEvents(ids: List<Long>) {
}
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)
}
}

View File

@ -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()

View File

@ -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"

View File

@ -522,13 +522,24 @@ class EventsHelper(val context: Context) {
fun getEventsToExport(eventTypes: ArrayList<Long>): ArrayList<Event> {
val currTS = getNowSeconds()
var events = ArrayList<Event>()
val tasks = ArrayList<Event>()
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<Event>
return events
}

View File

@ -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<CalDAVCalendar>()
private val reminderLabel = activity.getString(R.string.reminder)
private val exportTime = Formatter.getExportedTime(System.currentTimeMillis())
fun exportEvents(
activity: BaseSimpleActivity,
outputStream: OutputStream?,
events: ArrayList<Event>,
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)
}
}
}

View File

@ -15,6 +15,15 @@ interface EventsDao {
@Query("SELECT * FROM events WHERE event_type IN (:eventTypeIds) AND type = $TYPE_EVENT")
fun getAllEventsWithTypes(eventTypeIds: List<Long>): List<Event>
@Query("SELECT * FROM events WHERE event_type IN (:eventTypeIds) AND type = $TYPE_TASK")
fun getAllTasksWithTypes(eventTypeIds: List<Long>): List<Event>
@Query("SELECT * FROM events WHERE end_ts > :currTS AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT")
fun getAllFutureEventsWithTypes(currTS: Long, eventTypeIds: List<Long>): List<Event>
@Query("SELECT * FROM events WHERE end_ts > :currTS AND event_type IN (:eventTypeIds) AND type = $TYPE_TASK")
fun getAllFutureTasksWithTypes(currTS: Long, eventTypeIds: List<Long>): List<Event>
@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<Long>, searchQuery: String): List<Event>
@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<Long>): List<Event>
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND repeat_interval != 0")
fun getRepeatableEventsOrTasksWithTypes(toTS: Long): List<Event>
@ -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<Long>, searchQuery: String): List<Event>
@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<Long>): List<Event>
@Query("SELECT * FROM events WHERE id IN (:ids) AND import_id != \"\" AND type = $TYPE_EVENT")
fun getEventsByIdsWithImportIds(ids: List<Long>): List<Event>

View File

@ -48,15 +48,66 @@
</com.simplemobiletools.commons.views.MyTextInputLayout>
<RelativeLayout
android:id="@+id/export_events_checkbox_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:background="?attr/selectableItemBackground"
android:paddingVertical="@dimen/medium_margin"
android:paddingStart="@dimen/normal_margin"
android:paddingEnd="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/export_events_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:checked="true"
android:clickable="false"
android:layoutDirection="rtl"
android:padding="@dimen/medium_margin"
android:text="@string/export_events" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/export_tasks_checkbox_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingVertical="@dimen/medium_margin"
android:paddingStart="@dimen/normal_margin"
android:paddingEnd="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/export_tasks_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:checked="true"
android:clickable="false"
android:layoutDirection="rtl"
android:padding="@dimen/medium_margin"
android:text="@string/export_tasks" />
</RelativeLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginStart="@dimen/activity_margin"
android:background="@color/divider_grey"
android:importantForAccessibility="no" />
<RelativeLayout
android:id="@+id/export_past_events_checkbox_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingVertical="@dimen/medium_margin"
android:paddingStart="@dimen/normal_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin">
android:paddingEnd="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/export_past_events_checkbox"
@ -66,7 +117,7 @@
android:clickable="false"
android:layoutDirection="rtl"
android:padding="@dimen/medium_margin"
android:text="@string/export_past_events_too" />
android:text="@string/export_past_entries" />
</RelativeLayout>