Merge pull request #1937 from Naveen3Singh/feature_export_tasks
Add option to export/import tasks
This commit is contained in:
commit
bf3cf47285
13 changed files with 281 additions and 79 deletions
|
@ -1115,7 +1115,7 @@ class EventActivity : SimpleActivity() {
|
|||
val newImportId = if (mEvent.id != null) {
|
||||
mEvent.importId
|
||||
} else {
|
||||
UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
|
||||
generateImportId()
|
||||
}
|
||||
|
||||
val newEventType = if (!config.caldavSync || config.lastUsedCaldavCalendarId == 0 || mEventCalendarId == STORED_LOCALLY_ONLY) {
|
||||
|
|
|
@ -159,6 +159,10 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
|
|||
ConfirmationDialog(this, "", R.string.upgraded_to_pro_calendar, R.string.ok, 0, false) {}
|
||||
config.wasUpgradedFromFreeShown = true
|
||||
}
|
||||
|
||||
addImportIdsToTasks {
|
||||
refreshViewPager()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -1129,8 +1133,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 +1151,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
|
||||
|
|
|
@ -351,6 +351,11 @@ class TaskActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
val wasRepeatable = mTask.repeatInterval > 0
|
||||
val newImportId = if (mTask.id != null) {
|
||||
mTask.importId
|
||||
} else {
|
||||
generateImportId()
|
||||
}
|
||||
|
||||
val reminders = getReminders()
|
||||
if (!task_all_day.isChecked) {
|
||||
|
@ -393,6 +398,7 @@ class TaskActivity : SimpleActivity() {
|
|||
updateTaskCompletion(copy(startTS = mOriginalStartTS), true)
|
||||
}
|
||||
}
|
||||
importId = newImportId
|
||||
flags = mTask.flags.addBitIf(task_all_day.isChecked, FLAG_ALL_DAY)
|
||||
lastUpdated = System.currentTimeMillis()
|
||||
eventType = mEventTypeId
|
||||
|
@ -458,7 +464,7 @@ class TaskActivity : SimpleActivity() {
|
|||
when (it) {
|
||||
0 -> {
|
||||
ensureBackgroundThread {
|
||||
eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, true)
|
||||
eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, addToCalDAV = false)
|
||||
mTask.apply {
|
||||
parentId = id!!.toLong()
|
||||
id = null
|
||||
|
@ -610,7 +616,6 @@ class TaskActivity : SimpleActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun updateDateText() {
|
||||
task_date.text = Formatter.getDate(this, mTaskDateTime)
|
||||
}
|
||||
|
@ -766,7 +771,6 @@ class TaskActivity : SimpleActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun showRepeatIntervalDialog() {
|
||||
showEventRepeatIntervalDialog(mRepeatInterval) {
|
||||
setRepeatInterval(it)
|
||||
|
|
|
@ -17,14 +17,23 @@ 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_past_events_checkbox.isChecked = config.exportPastEvents
|
||||
|
||||
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.exportPastEntries
|
||||
export_past_events_checkbox_holder.setOnClickListener {
|
||||
export_past_events_checkbox.toggle()
|
||||
}
|
||||
|
@ -69,9 +78,20 @@ class ExportEventsDialog(
|
|||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val exportEventsChecked = view.export_events_checkbox.isChecked
|
||||
val exportTasksChecked = view.export_tasks_checkbox.isChecked
|
||||
if (!exportEventsChecked && !exportTasksChecked) {
|
||||
activity.toast(R.string.no_entries_for_exporting)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
config.lastExportPath = file.absolutePath.getParentPath()
|
||||
config.exportPastEvents = view.export_past_events_checkbox.isChecked
|
||||
config.apply {
|
||||
lastExportPath = file.absolutePath.getParentPath()
|
||||
exportEvents = exportEventsChecked
|
||||
exportTasks = exportTasksChecked
|
||||
exportPastEntries = view.export_past_events_checkbox.isChecked
|
||||
}
|
||||
|
||||
val eventTypes = (view.export_events_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList()
|
||||
callback(file, eventTypes)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -708,3 +708,23 @@ inline fun Context.queryCursorInlined(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.addImportIdsToTasks(callback: () -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
var count = 0
|
||||
|
||||
eventsDB.getAllTasks().forEach { task ->
|
||||
if (task.importId.isEmpty()) {
|
||||
eventsDB.updateTaskImportId(
|
||||
importId = generateImportId(),
|
||||
id = task.id!!
|
||||
)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,7 +223,15 @@ class Config(context: Context) : BaseConfig(context) {
|
|||
get() = prefs.getString(LAST_EXPORT_PATH, "")!!
|
||||
set(lastExportPath) = prefs.edit().putString(LAST_EXPORT_PATH, lastExportPath).apply()
|
||||
|
||||
var exportPastEvents: Boolean
|
||||
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 exportPastEntries: Boolean
|
||||
get() = prefs.getBoolean(EXPORT_PAST_EVENTS, false)
|
||||
set(exportPastEvents) = prefs.edit().putBoolean(EXPORT_PAST_EVENTS, exportPastEvents).apply()
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.simplemobiletools.calendar.pro.helpers
|
|||
import com.simplemobiletools.calendar.pro.activities.EventActivity
|
||||
import com.simplemobiletools.calendar.pro.activities.TaskActivity
|
||||
import com.simplemobiletools.commons.helpers.MONTH_SECONDS
|
||||
import java.util.*
|
||||
|
||||
const val STORED_LOCALLY_ONLY = 0
|
||||
const val ROW_COUNT = 6
|
||||
|
@ -116,6 +117,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 +147,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_TASK = "BEGIN:VTODO"
|
||||
const val END_TASK = "END:VTODO"
|
||||
const val BEGIN_ALARM = "BEGIN:VALARM"
|
||||
const val END_ALARM = "END:VALARM"
|
||||
const val DTSTART = "DTSTART"
|
||||
|
@ -182,6 +187,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"
|
||||
|
||||
|
@ -253,3 +259,7 @@ fun getActivityToOpen(isTask: Boolean) = if (isTask) {
|
|||
} else {
|
||||
EventActivity::class.java
|
||||
}
|
||||
|
||||
fun generateImportId(): String {
|
||||
return UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
|
||||
}
|
||||
|
|
|
@ -522,13 +522,28 @@ class EventsHelper(val context: Context) {
|
|||
fun getEventsToExport(eventTypes: ArrayList<Long>): ArrayList<Event> {
|
||||
val currTS = getNowSeconds()
|
||||
var events = ArrayList<Event>()
|
||||
if (config.exportPastEvents) {
|
||||
events.addAll(eventsDB.getAllEventsWithTypes(eventTypes))
|
||||
val tasks = ArrayList<Event>()
|
||||
if (config.exportPastEntries) {
|
||||
if (config.exportEvents) {
|
||||
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))
|
||||
if (config.exportEvents) {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
writeTask(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 writeTask(writer: BufferedWriter, task: Event) {
|
||||
with(writer) {
|
||||
writeLn(BEGIN_TASK)
|
||||
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_TASK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.simplemobiletools.calendar.pro.R
|
|||
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.calendar.pro.extensions.eventsDB
|
||||
import com.simplemobiletools.calendar.pro.extensions.eventsHelper
|
||||
import com.simplemobiletools.calendar.pro.extensions.updateTaskCompletion
|
||||
import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.IMPORT_FAIL
|
||||
import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.IMPORT_NOTHING_NEW
|
||||
import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.IMPORT_OK
|
||||
|
@ -17,6 +18,7 @@ import com.simplemobiletools.commons.extensions.showErrorToast
|
|||
import com.simplemobiletools.commons.helpers.HOUR_SECONDS
|
||||
import org.joda.time.DateTimeZone
|
||||
import java.io.File
|
||||
import kotlin.math.min
|
||||
|
||||
class IcsImporter(val activity: SimpleActivity) {
|
||||
enum class ImportResult {
|
||||
|
@ -45,7 +47,9 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
private var isNotificationDescription = false
|
||||
private var isProperReminderAction = false
|
||||
private var isSequence = false
|
||||
private var curType = TYPE_EVENT
|
||||
private var isParsingEvent = false
|
||||
private var isParsingTask = false
|
||||
private var curReminderTriggerMinutes = REMINDER_OFF
|
||||
private var curReminderTriggerAction = REMINDER_NOTIFICATION
|
||||
private val eventsHelper = activity.eventsHelper
|
||||
|
@ -63,7 +67,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
): ImportResult {
|
||||
try {
|
||||
val eventTypes = eventsHelper.getEventTypesSync()
|
||||
val existingEvents = activity.eventsDB.getEventsWithImportIds().toMutableList() as ArrayList<Event>
|
||||
val existingEvents = activity.eventsDB.getEventsOrTasksWithImportIds().toMutableList() as ArrayList<Event>
|
||||
val eventsToInsert = ArrayList<Event>()
|
||||
var line = ""
|
||||
|
||||
|
@ -89,8 +93,13 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
resetValues()
|
||||
curEventTypeId = defaultEventTypeId
|
||||
isParsingEvent = true
|
||||
} else if (line.trim() == BEGIN_TASK) {
|
||||
resetValues()
|
||||
curEventTypeId = defaultEventTypeId
|
||||
isParsingTask = true
|
||||
curType = TYPE_TASK
|
||||
} else if (line.startsWith(DTSTART)) {
|
||||
if (isParsingEvent) {
|
||||
if (isParsingEvent || isParsingTask) {
|
||||
curStart = getTimestamp(line.substring(DTSTART.length))
|
||||
|
||||
if (curRrule != "") {
|
||||
|
@ -144,6 +153,14 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
if (line.substring(MISSING_YEAR.length) == "1") {
|
||||
curFlags = curFlags or FLAG_MISSING_YEAR
|
||||
}
|
||||
} else if (line.startsWith(STATUS)) {
|
||||
if (isParsingTask && line.substring(STATUS.length) == COMPLETED) {
|
||||
curFlags = curFlags or FLAG_TASK_COMPLETED
|
||||
}
|
||||
} else if (line.startsWith(COMPLETED)) {
|
||||
if (isParsingTask && line.substring(COMPLETED.length).trim().isNotEmpty()) {
|
||||
curFlags = curFlags or FLAG_TASK_COMPLETED
|
||||
}
|
||||
} else if (line.startsWith(CATEGORIES) && !overrideFileEventTypes) {
|
||||
val categories = line.substring(CATEGORIES.length)
|
||||
tryAddCategories(categories)
|
||||
|
@ -182,11 +199,12 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
curReminderActions.add(curReminderTriggerAction)
|
||||
}
|
||||
isNotificationDescription = false
|
||||
} else if (line.trim() == END_EVENT) {
|
||||
isParsingEvent = false
|
||||
if (curStart != -1L && curEnd == -1L) {
|
||||
} else if (line.trim() == END_EVENT || line.trim() == END_TASK) {
|
||||
if (curStart != -1L && (curEnd == -1L || isParsingTask)) {
|
||||
curEnd = curStart
|
||||
}
|
||||
isParsingEvent = false
|
||||
isParsingTask = false
|
||||
|
||||
if (curTitle.isEmpty() || curStart == -1L) {
|
||||
line = curLine
|
||||
|
@ -194,8 +212,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
}
|
||||
|
||||
// repeating event exceptions can have the same import id as their parents, so pick the latest event to update
|
||||
val eventToUpdate =
|
||||
existingEvents.filter { curImportId.isNotEmpty() && curImportId == it.importId }.sortedByDescending { it.lastUpdated }.firstOrNull()
|
||||
val eventToUpdate = existingEvents.filter { curImportId.isNotEmpty() && curImportId == it.importId }.maxByOrNull { it.lastUpdated }
|
||||
if (eventToUpdate != null && eventToUpdate.lastUpdated >= curLastModified) {
|
||||
eventsAlreadyExist++
|
||||
line = curLine
|
||||
|
@ -238,10 +255,11 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
0,
|
||||
curLastModified,
|
||||
source,
|
||||
curAvailability
|
||||
curAvailability,
|
||||
type = curType
|
||||
)
|
||||
|
||||
if (isAllDay && curEnd > curStart) {
|
||||
if (isAllDay && curEnd > curStart && !event.isTask()) {
|
||||
event.endTS -= TWELVE_HOURS
|
||||
// fix some glitches related to daylight saving shifts
|
||||
if (event.startTS - event.endTS == HOUR_SECONDS.toLong()) {
|
||||
|
@ -264,13 +282,13 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
// if an event belongs to a sequence insert it immediately, to avoid some glitches with linked events
|
||||
if (isSequence) {
|
||||
if (curRecurrenceDayCode.isEmpty()) {
|
||||
eventsHelper.insertEvent(event, true, false)
|
||||
eventsHelper.insertEvent(event, addToCalDAV = !event.isTask(), showToasts = false)
|
||||
} else {
|
||||
// if an event contains the RECURRENCE-ID field, it is an exception to a recurring event, so update its parent too
|
||||
val parentEvent = activity.eventsDB.getEventWithImportId(event.importId)
|
||||
if (parentEvent != null && !parentEvent.repetitionExceptions.contains(curRecurrenceDayCode)) {
|
||||
parentEvent.addRepetitionException(curRecurrenceDayCode)
|
||||
eventsHelper.insertEvent(parentEvent, true, false)
|
||||
eventsHelper.insertEvent(parentEvent, !parentEvent.isTask(), showToasts = false)
|
||||
|
||||
event.parentId = parentEvent.id!!
|
||||
eventsToInsert.add(event)
|
||||
|
@ -281,7 +299,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
}
|
||||
} else {
|
||||
event.id = eventToUpdate.id
|
||||
eventsHelper.updateEvent(event, true, false)
|
||||
eventsHelper.updateEvent(event, updateAtCalDAV = !event.isTask(), showToasts = false)
|
||||
}
|
||||
eventsImported++
|
||||
resetValues()
|
||||
|
@ -290,7 +308,12 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
}
|
||||
}
|
||||
|
||||
eventsHelper.insertEvents(eventsToInsert, true)
|
||||
val (tasks, events) = eventsToInsert.partition { it.isTask() }
|
||||
eventsHelper.insertEvents(tasks as ArrayList<Event>, addToCalDAV = false)
|
||||
eventsHelper.insertEvents(events as ArrayList<Event>, addToCalDAV = true)
|
||||
tasks.filter { it.isTaskCompleted() }.forEach {
|
||||
activity.updateTaskCompletion(it, completed = true)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
eventsFailed++
|
||||
|
@ -361,7 +384,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
return if (title.startsWith(";") && title.contains(":")) {
|
||||
title.substring(title.lastIndexOf(':') + 1)
|
||||
} else {
|
||||
title.substring(1, Math.min(title.length, 180))
|
||||
title.substring(1, min(title.length, 180))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,5 +420,6 @@ class IcsImporter(val activity: SimpleActivity) {
|
|||
isParsingEvent = false
|
||||
curReminderTriggerMinutes = REMINDER_OFF
|
||||
curReminderTriggerAction = REMINDER_NOTIFICATION
|
||||
curType = TYPE_EVENT
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,21 @@ interface EventsDao {
|
|||
@Query("SELECT * FROM events")
|
||||
fun getAllEvents(): List<Event>
|
||||
|
||||
@Query("SELECT * FROM events WHERE type = $TYPE_TASK")
|
||||
fun getAllTasks(): List<Event>
|
||||
|
||||
@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 +54,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 +66,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>
|
||||
|
||||
|
@ -69,8 +75,8 @@ interface EventsDao {
|
|||
@Query("SELECT * FROM events WHERE source = \'$SOURCE_CONTACT_ANNIVERSARY\' AND type = $TYPE_EVENT")
|
||||
fun getAnniversaries(): List<Event>
|
||||
|
||||
@Query("SELECT * FROM events WHERE import_id != \"\" AND type = $TYPE_EVENT")
|
||||
fun getEventsWithImportIds(): List<Event>
|
||||
@Query("SELECT * FROM events WHERE import_id != \"\"")
|
||||
fun getEventsOrTasksWithImportIds(): List<Event>
|
||||
|
||||
@Query("SELECT * FROM events WHERE source = :source AND type = $TYPE_EVENT")
|
||||
fun getEventsFromCalDAVCalendar(source: String): List<Event>
|
||||
|
@ -119,6 +125,9 @@ interface EventsDao {
|
|||
@Query("UPDATE events SET flags = :newFlags WHERE id = :id")
|
||||
fun updateTaskCompletion(id: Long, newFlags: Int)
|
||||
|
||||
@Query("UPDATE events SET import_id = :importId WHERE id = :id AND type = $TYPE_TASK")
|
||||
fun updateTaskImportId(importId: String, id: Long)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertOrUpdate(event: Event): Long
|
||||
|
||||
|
|
|
@ -48,15 +48,67 @@
|
|||
|
||||
</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/smaller_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:paddingHorizontal="@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/smaller_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:paddingHorizontal="@dimen/medium_margin"
|
||||
android:text="@string/export_tasks" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/export_past_entries_divider"
|
||||
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/smaller_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"
|
||||
|
@ -65,8 +117,8 @@
|
|||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:layoutDirection="rtl"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:text="@string/export_past_events_too" />
|
||||
android:paddingHorizontal="@dimen/medium_margin"
|
||||
android:text="@string/export_past_entries" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
Loading…
Reference in a new issue