mirror of
https://github.com/SimpleMobileTools/Simple-Calendar.git
synced 2025-03-03 19:09:07 +01:00
Add option to export tasks
This commit is contained in:
parent
579bc8bb66
commit
635fa436c7
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user