mirror of
https://github.com/SimpleMobileTools/Simple-Calendar.git
synced 2025-06-05 21:59:17 +02:00
Merge pull request #2027 from Naveen3Singh/feature_automatic_backups
Implement automatic backups
This commit is contained in:
@@ -283,9 +283,14 @@
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".receivers.AutomaticBackupReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
@@ -22,8 +22,8 @@ import com.simplemobiletools.calendar.pro.adapters.EventListAdapter
|
||||
import com.simplemobiletools.calendar.pro.adapters.QuickFilterEventTypeAdapter
|
||||
import com.simplemobiletools.calendar.pro.databases.EventsDatabase
|
||||
import com.simplemobiletools.calendar.pro.dialogs.ExportEventsDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.FilterEventTypesDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.ImportEventsDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypesDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SetRemindersDialog
|
||||
import com.simplemobiletools.calendar.pro.extensions.*
|
||||
import com.simplemobiletools.calendar.pro.fragments.*
|
||||
@@ -526,10 +526,14 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
|
||||
}
|
||||
|
||||
private fun showFilterDialog() {
|
||||
FilterEventTypesDialog(this) {
|
||||
refreshViewPager()
|
||||
setupQuickFilter()
|
||||
updateWidgets()
|
||||
SelectEventTypesDialog(this, config.displayEventTypes) {
|
||||
if (config.displayEventTypes != it) {
|
||||
config.displayEventTypes = it
|
||||
|
||||
refreshViewPager()
|
||||
setupQuickFilter()
|
||||
updateWidgets()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1149,7 +1153,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
|
||||
|
||||
private fun exportEventsTo(eventTypes: ArrayList<Long>, outputStream: OutputStream?) {
|
||||
ensureBackgroundThread {
|
||||
val events = eventsHelper.getEventsToExport(eventTypes)
|
||||
val events = eventsHelper.getEventsToExport(eventTypes, config.exportEvents, config.exportTasks, config.exportPastEntries)
|
||||
if (events.isEmpty()) {
|
||||
toast(R.string.no_entries_for_exporting)
|
||||
} else {
|
||||
|
@@ -11,9 +11,10 @@ import android.widget.Toast
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.dialogs.ManageAutomaticBackupsDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SelectCalendarsDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypeDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SelectQuickFilterEventTypesDialog
|
||||
import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypesDialog
|
||||
import com.simplemobiletools.calendar.pro.extensions.*
|
||||
import com.simplemobiletools.calendar.pro.helpers.*
|
||||
import com.simplemobiletools.calendar.pro.helpers.Formatter
|
||||
@@ -99,6 +100,8 @@ class SettingsActivity : SimpleActivity() {
|
||||
setupAllowChangingTimeZones()
|
||||
updateTextColors(settings_holder)
|
||||
checkPrimaryColor()
|
||||
setupEnableAutomaticBackups()
|
||||
setupManageAutomaticBackups()
|
||||
setupExportSettings()
|
||||
setupImportSettings()
|
||||
|
||||
@@ -114,6 +117,7 @@ class SettingsActivity : SimpleActivity() {
|
||||
settings_widgets_label,
|
||||
settings_events_label,
|
||||
settings_tasks_label,
|
||||
settings_backups_label,
|
||||
settings_migrating_label
|
||||
).forEach {
|
||||
it.setTextColor(getProperPrimaryColor())
|
||||
@@ -334,7 +338,9 @@ class SettingsActivity : SimpleActivity() {
|
||||
}
|
||||
|
||||
private fun showQuickFilterPicker() {
|
||||
SelectQuickFilterEventTypesDialog(this)
|
||||
SelectEventTypesDialog(this, config.quickFilterEventTypes) {
|
||||
config.quickFilterEventTypes = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSundayFirst() {
|
||||
@@ -830,6 +836,46 @@ class SettingsActivity : SimpleActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupEnableAutomaticBackups() {
|
||||
settings_backups_label.beVisibleIf(isRPlus())
|
||||
settings_backups_divider.beVisibleIf(isRPlus())
|
||||
settings_enable_automatic_backups_holder.beVisibleIf(isRPlus())
|
||||
settings_enable_automatic_backups.isChecked = config.autoBackup
|
||||
settings_enable_automatic_backups_holder.setOnClickListener {
|
||||
val wasBackupDisabled = !config.autoBackup
|
||||
if (wasBackupDisabled) {
|
||||
ManageAutomaticBackupsDialog(
|
||||
activity = this,
|
||||
onSuccess = {
|
||||
enableOrDisableAutomaticBackups(true)
|
||||
scheduleNextAutomaticBackup()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
cancelScheduledAutomaticBackup()
|
||||
enableOrDisableAutomaticBackups(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupManageAutomaticBackups() {
|
||||
settings_manage_automatic_backups_holder.beVisibleIf(isRPlus() && config.autoBackup)
|
||||
settings_manage_automatic_backups_holder.setOnClickListener {
|
||||
ManageAutomaticBackupsDialog(
|
||||
activity = this,
|
||||
onSuccess = {
|
||||
scheduleNextAutomaticBackup()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableOrDisableAutomaticBackups(enable: Boolean) {
|
||||
config.autoBackup = enable
|
||||
settings_enable_automatic_backups.isChecked = enable
|
||||
settings_manage_automatic_backups_holder.beVisibleIf(enable)
|
||||
}
|
||||
|
||||
private fun setupExportSettings() {
|
||||
settings_export_holder.setOnClickListener {
|
||||
val configItems = LinkedHashMap<String, Any>().apply {
|
||||
|
@@ -0,0 +1,18 @@
|
||||
package com.simplemobiletools.calendar.pro.dialogs
|
||||
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||
|
||||
class DateTimePatternInfoDialog(activity: BaseSimpleActivity) {
|
||||
|
||||
init {
|
||||
val view = activity.layoutInflater.inflate(R.layout.datetime_pattern_info_layout, null)
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(R.string.ok) { _, _ -> { } }
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
package com.simplemobiletools.calendar.pro.dialogs
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.calendar.pro.extensions.config
|
||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.*
|
||||
import java.io.File
|
||||
|
||||
class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSuccess: () -> Unit) {
|
||||
private val view = (activity.layoutInflater.inflate(R.layout.dialog_manage_automatic_backups, null) as ViewGroup)
|
||||
private val config = activity.config
|
||||
private var backupFolder = config.autoBackupFolder
|
||||
private var selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes }
|
||||
|
||||
init {
|
||||
view.apply {
|
||||
backup_events_folder.setText(activity.humanizePath(backupFolder))
|
||||
val filename = config.autoBackupFilename.ifEmpty {
|
||||
"${activity.getString(R.string.events)}_%Y%M%D_%h%m%s"
|
||||
}
|
||||
|
||||
backup_events_filename.setText(filename)
|
||||
backup_events_filename_hint.setEndIconOnClickListener {
|
||||
DateTimePatternInfoDialog(activity)
|
||||
}
|
||||
|
||||
backup_events_filename_hint.setEndIconOnLongClickListener {
|
||||
DateTimePatternInfoDialog(activity)
|
||||
true
|
||||
}
|
||||
|
||||
backup_events_checkbox.isChecked = config.autoBackupEvents
|
||||
backup_events_checkbox_holder.setOnClickListener {
|
||||
backup_events_checkbox.toggle()
|
||||
}
|
||||
|
||||
backup_tasks_checkbox.isChecked = config.autoBackupTasks
|
||||
backup_tasks_checkbox_holder.setOnClickListener {
|
||||
backup_tasks_checkbox.toggle()
|
||||
}
|
||||
|
||||
backup_past_events_checkbox.isChecked = config.autoBackupPastEntries
|
||||
backup_past_events_checkbox_holder.setOnClickListener {
|
||||
backup_past_events_checkbox.toggle()
|
||||
}
|
||||
|
||||
backup_events_folder.setOnClickListener {
|
||||
selectBackupFolder()
|
||||
}
|
||||
|
||||
manage_event_types_holder.setOnClickListener {
|
||||
SelectEventTypesDialog(activity, selectedEventTypes) {
|
||||
selectedEventTypes = it
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { dialog ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val filename = view.backup_events_filename.value
|
||||
when {
|
||||
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
||||
filename.isAValidFilename() -> {
|
||||
val file = File(backupFolder, "$filename.ics")
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
activity.toast(R.string.name_taken)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val backupEventsChecked = view.backup_events_checkbox.isChecked
|
||||
val backupTasksChecked = view.backup_tasks_checkbox.isChecked
|
||||
if (!backupEventsChecked && !backupTasksChecked || selectedEventTypes.isEmpty()) {
|
||||
activity.toast(R.string.no_entries_for_exporting)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
config.apply {
|
||||
autoBackupFolder = backupFolder
|
||||
autoBackupFilename = filename
|
||||
autoBackupEvents = backupEventsChecked
|
||||
autoBackupTasks = backupTasksChecked
|
||||
autoBackupPastEntries = view.backup_past_events_checkbox.isChecked
|
||||
if (autoBackupEventTypes != selectedEventTypes) {
|
||||
autoBackupEventTypes = selectedEventTypes
|
||||
}
|
||||
}
|
||||
|
||||
activity.runOnUiThread {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
else -> activity.toast(R.string.invalid_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectBackupFolder() {
|
||||
activity.hideKeyboard(view.backup_events_filename)
|
||||
FilePickerDialog(activity, backupFolder, false, showFAB = true) { path ->
|
||||
activity.handleSAFDialog(path) { grantedSAF ->
|
||||
if (!grantedSAF) {
|
||||
return@handleSAFDialog
|
||||
}
|
||||
|
||||
activity.handleSAFDialogSdk30(path) { grantedSAF30 ->
|
||||
if (!grantedSAF30) {
|
||||
return@handleSAFDialogSdk30
|
||||
}
|
||||
|
||||
backupFolder = path
|
||||
view.backup_events_folder.setText(activity.humanizePath(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,23 +4,21 @@ import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter
|
||||
import com.simplemobiletools.calendar.pro.extensions.config
|
||||
import com.simplemobiletools.calendar.pro.extensions.eventsHelper
|
||||
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||
import kotlinx.android.synthetic.main.dialog_filter_event_types.view.*
|
||||
|
||||
class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> Unit) {
|
||||
class SelectEventTypesDialog(val activity: SimpleActivity, selectedEventTypes: Set<String>, val callback: (HashSet<String>) -> Unit) {
|
||||
private var dialog: AlertDialog? = null
|
||||
private val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null)
|
||||
|
||||
init {
|
||||
activity.eventsHelper.getEventTypes(activity, false) {
|
||||
val displayEventTypes = activity.config.displayEventTypes
|
||||
view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, displayEventTypes)
|
||||
view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, selectedEventTypes)
|
||||
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(R.string.ok) { dialogInterface, i -> confirmEventTypes() }
|
||||
.setPositiveButton(R.string.ok) { _, _ -> confirmEventTypes() }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||
@@ -31,11 +29,11 @@ class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> U
|
||||
}
|
||||
|
||||
private fun confirmEventTypes() {
|
||||
val selectedItems = (view.filter_event_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList().map { it.toString() }.toHashSet()
|
||||
if (activity.config.displayEventTypes != selectedItems) {
|
||||
activity.config.displayEventTypes = selectedItems
|
||||
callback()
|
||||
}
|
||||
val adapter = view.filter_event_types_list.adapter as FilterEventTypeAdapter
|
||||
val selectedItems = adapter.getSelectedItemsList()
|
||||
.map { it.toString() }
|
||||
.toHashSet()
|
||||
callback(selectedItems)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package com.simplemobiletools.calendar.pro.dialogs
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter
|
||||
import com.simplemobiletools.calendar.pro.extensions.config
|
||||
import com.simplemobiletools.calendar.pro.extensions.eventsHelper
|
||||
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||
import kotlinx.android.synthetic.main.dialog_filter_event_types.view.*
|
||||
|
||||
class SelectQuickFilterEventTypesDialog(val activity: SimpleActivity) {
|
||||
private var dialog: AlertDialog? = null
|
||||
private val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null)
|
||||
|
||||
init {
|
||||
activity.eventsHelper.getEventTypes(activity, false) {
|
||||
val quickFilterEventTypes = activity.config.quickFilterEventTypes
|
||||
view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, quickFilterEventTypes)
|
||||
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(R.string.ok) { dialogInterface, i -> confirmEventTypes() }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||
dialog = alertDialog
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmEventTypes() {
|
||||
val selectedItems = (view.filter_event_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList().map {
|
||||
it.toString()
|
||||
}.toHashSet()
|
||||
|
||||
if (activity.config.quickFilterEventTypes != selectedItems) {
|
||||
activity.config.quickFilterEventTypes = selectedItems
|
||||
}
|
||||
dialog?.dismiss()
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ import android.content.res.Resources
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.CalendarContract
|
||||
@@ -37,6 +38,7 @@ import com.simplemobiletools.calendar.pro.interfaces.EventsDao
|
||||
import com.simplemobiletools.calendar.pro.interfaces.TasksDao
|
||||
import com.simplemobiletools.calendar.pro.interfaces.WidgetsDao
|
||||
import com.simplemobiletools.calendar.pro.models.*
|
||||
import com.simplemobiletools.calendar.pro.receivers.AutomaticBackupReceiver
|
||||
import com.simplemobiletools.calendar.pro.receivers.CalDAVSyncReceiver
|
||||
import com.simplemobiletools.calendar.pro.receivers.NotificationReceiver
|
||||
import com.simplemobiletools.calendar.pro.services.MarkCompletedService
|
||||
@@ -47,6 +49,7 @@ import kotlinx.android.synthetic.main.day_monthly_event_view.view.*
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.LocalDate
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
val Context.config: Config get() = Config.newInstance(applicationContext)
|
||||
@@ -183,6 +186,103 @@ fun Context.cancelPendingIntent(id: Long) {
|
||||
PendingIntent.getBroadcast(this, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE).cancel()
|
||||
}
|
||||
|
||||
fun Context.getAutomaticBackupIntent(): PendingIntent {
|
||||
val intent = Intent(this, AutomaticBackupReceiver::class.java)
|
||||
return PendingIntent.getBroadcast(this, AUTOMATIC_BACKUP_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
fun Context.scheduleNextAutomaticBackup() {
|
||||
if (config.autoBackup) {
|
||||
val backupAtMillis = getNextAutoBackupTime().millis
|
||||
val pendingIntent = getAutomaticBackupIntent()
|
||||
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
try {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, backupAtMillis, pendingIntent)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.cancelScheduledAutomaticBackup() {
|
||||
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
alarmManager.cancel(getAutomaticBackupIntent())
|
||||
}
|
||||
|
||||
fun Context.checkAndBackupEventsOnBoot() {
|
||||
if (config.autoBackup) {
|
||||
val previousRealBackupTime = config.lastAutoBackupTime
|
||||
val previousScheduledBackupTime = getPreviousAutoBackupTime().seconds()
|
||||
val missedPreviousBackup = previousRealBackupTime < previousScheduledBackupTime
|
||||
if (missedPreviousBackup) {
|
||||
// device was probably off at the scheduled time so backup now
|
||||
backupEventsAndTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.backupEventsAndTasks() {
|
||||
ensureBackgroundThread {
|
||||
val config = config
|
||||
val events = eventsHelper.getEventsToExport(
|
||||
eventTypes = config.autoBackupEventTypes.map { it.toLong() } as ArrayList<Long>,
|
||||
exportEvents = config.autoBackupEvents,
|
||||
exportTasks = config.autoBackupTasks,
|
||||
exportPastEntries = config.autoBackupPastEntries
|
||||
)
|
||||
if (events.isEmpty()) {
|
||||
toast(R.string.no_entries_for_exporting)
|
||||
config.lastAutoBackupTime = getNowSeconds()
|
||||
scheduleNextAutomaticBackup()
|
||||
return@ensureBackgroundThread
|
||||
}
|
||||
|
||||
val now = DateTime.now()
|
||||
val year = now.year.toString()
|
||||
val month = now.monthOfYear.ensureTwoDigits()
|
||||
val day = now.dayOfMonth.ensureTwoDigits()
|
||||
val hours = now.hourOfDay.ensureTwoDigits()
|
||||
val minutes = now.minuteOfHour.ensureTwoDigits()
|
||||
val seconds = now.secondOfMinute.ensureTwoDigits()
|
||||
|
||||
val filename = config.autoBackupFilename
|
||||
.replace("%Y", year, false)
|
||||
.replace("%M", month, false)
|
||||
.replace("%D", day, false)
|
||||
.replace("%h", hours, false)
|
||||
.replace("%m", minutes, false)
|
||||
.replace("%s", seconds, false)
|
||||
|
||||
val outputFolder = File(config.autoBackupFolder).apply {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
val exportFile = File(outputFolder, "$filename.ics")
|
||||
val outputStream = try {
|
||||
exportFile.outputStream()
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
null
|
||||
}
|
||||
|
||||
IcsExporter(this).exportEvents(outputStream, events, showExportingToast = false) { result ->
|
||||
when (result) {
|
||||
IcsExporter.ExportResult.EXPORT_PARTIAL -> toast(R.string.exporting_some_entries_failed)
|
||||
IcsExporter.ExportResult.EXPORT_FAIL -> toast(R.string.exporting_failed)
|
||||
else -> {}
|
||||
}
|
||||
MediaScannerConnection.scanFile(
|
||||
this,
|
||||
arrayOf(exportFile.absolutePath),
|
||||
arrayOf(exportFile.getMimeType())
|
||||
) { _, _ -> }
|
||||
|
||||
config.lastAutoBackupTime = getNowSeconds()
|
||||
}
|
||||
scheduleNextAutomaticBackup()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getRepetitionText(seconds: Int) = when (seconds) {
|
||||
0 -> getString(R.string.no_repetition)
|
||||
DAY -> getString(R.string.daily)
|
||||
|
@@ -3,6 +3,8 @@ package com.simplemobiletools.calendar.pro.helpers
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Environment
|
||||
import android.os.Environment.DIRECTORY_DOWNLOADS
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.extensions.config
|
||||
import com.simplemobiletools.calendar.pro.extensions.scheduleCalDAVSync
|
||||
@@ -258,4 +260,36 @@ class Config(context: Context) : BaseConfig(context) {
|
||||
var wasFilteredOutWarningShown: Boolean
|
||||
get() = prefs.getBoolean(WAS_FILTERED_OUT_WARNING_SHOWN, false)
|
||||
set(wasFilteredOutWarningShown) = prefs.edit().putBoolean(WAS_FILTERED_OUT_WARNING_SHOWN, wasFilteredOutWarningShown).apply()
|
||||
|
||||
var autoBackup: Boolean
|
||||
get() = prefs.getBoolean(AUTO_BACKUP, false)
|
||||
set(enableAutomaticBackups) = prefs.edit().putBoolean(AUTO_BACKUP, enableAutomaticBackups).apply()
|
||||
|
||||
var autoBackupFolder: String
|
||||
get() = prefs.getString(AUTO_BACKUP_FOLDER, Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).absolutePath)!!
|
||||
set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_FOLDER, autoBackupPath).apply()
|
||||
|
||||
var autoBackupFilename: String
|
||||
get() = prefs.getString(AUTO_BACKUP_FILENAME, "")!!
|
||||
set(autoBackupFilename) = prefs.edit().putString(AUTO_BACKUP_FILENAME, autoBackupFilename).apply()
|
||||
|
||||
var autoBackupEventTypes: Set<String>
|
||||
get() = prefs.getStringSet(AUTO_BACKUP_EVENT_TYPES, HashSet())!!
|
||||
set(autoBackupEventTypes) = prefs.edit().remove(AUTO_BACKUP_EVENT_TYPES).putStringSet(AUTO_BACKUP_EVENT_TYPES, autoBackupEventTypes).apply()
|
||||
|
||||
var autoBackupEvents: Boolean
|
||||
get() = prefs.getBoolean(AUTO_BACKUP_EVENTS, true)
|
||||
set(autoBackupEvents) = prefs.edit().putBoolean(AUTO_BACKUP_EVENTS, autoBackupEvents).apply()
|
||||
|
||||
var autoBackupTasks: Boolean
|
||||
get() = prefs.getBoolean(AUTO_BACKUP_TASKS, true)
|
||||
set(autoBackupTasks) = prefs.edit().putBoolean(AUTO_BACKUP_TASKS, autoBackupTasks).apply()
|
||||
|
||||
var autoBackupPastEntries: Boolean
|
||||
get() = prefs.getBoolean(AUTO_BACKUP_PAST_ENTRIES, true)
|
||||
set(autoBackupPastEntries) = prefs.edit().putBoolean(AUTO_BACKUP_PAST_ENTRIES, autoBackupPastEntries).apply()
|
||||
|
||||
var lastAutoBackupTime: Long
|
||||
get() = prefs.getLong(LAST_AUTO_BACKUP_TIME, 0L)
|
||||
set(lastAutoBackupTime) = prefs.edit().putLong(LAST_AUTO_BACKUP_TIME, lastAutoBackupTime).apply()
|
||||
}
|
||||
|
@@ -3,12 +3,14 @@ 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 org.joda.time.DateTime
|
||||
import java.util.*
|
||||
|
||||
const val STORED_LOCALLY_ONLY = 0
|
||||
const val ROW_COUNT = 6
|
||||
const val COLUMN_COUNT = 7
|
||||
const val SCHEDULE_CALDAV_REQUEST_CODE = 10000
|
||||
const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001
|
||||
const val FETCH_INTERVAL = 3 * MONTH_SECONDS
|
||||
const val MAX_SEARCH_YEAR = 2051218800L // 2035, limit search results for events repeating indefinitely
|
||||
|
||||
@@ -72,6 +74,8 @@ const val YEAR = 31536000
|
||||
const val EVENT_PERIOD_TODAY = -1
|
||||
const val EVENT_PERIOD_CUSTOM = -2
|
||||
|
||||
const val AUTO_BACKUP_INTERVAL_IN_DAYS = 1
|
||||
|
||||
const val EVENT_LIST_PERIOD = "event_list_period"
|
||||
|
||||
// Shared Preferences
|
||||
@@ -129,6 +133,14 @@ const val HIGHLIGHT_WEEKENDS_COLOR = "highlight_weekends_color"
|
||||
const val LAST_USED_EVENT_SPAN = "last_used_event_span"
|
||||
const val ALLOW_CREATING_TASKS = "allow_creating_tasks"
|
||||
const val WAS_FILTERED_OUT_WARNING_SHOWN = "was_filtered_out_warning_shown"
|
||||
const val AUTO_BACKUP = "auto_backup"
|
||||
const val AUTO_BACKUP_FOLDER = "auto_backup_folder"
|
||||
const val AUTO_BACKUP_FILENAME = "auto_backup_filename"
|
||||
const val AUTO_BACKUP_EVENT_TYPES = "auto_backup_event_types"
|
||||
const val AUTO_BACKUP_EVENTS = "auto_backup_events"
|
||||
const val AUTO_BACKUP_TASKS = "auto_backup_tasks"
|
||||
const val AUTO_BACKUP_PAST_ENTRIES = "auto_backup_past_entries"
|
||||
const val LAST_AUTO_BACKUP_TIME = "last_auto_backup_time"
|
||||
|
||||
// repeat_rule for monthly and yearly repetition
|
||||
const val REPEAT_SAME_DAY = 1 // i.e. 25th every month, or 3rd june (if yearly repetition)
|
||||
@@ -264,3 +276,19 @@ fun getActivityToOpen(isTask: Boolean) = if (isTask) {
|
||||
fun generateImportId(): String {
|
||||
return UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
|
||||
}
|
||||
|
||||
// 6 am is the hardcoded automatic backup time, intervals shorter than 1 day are not yet supported.
|
||||
fun getNextAutoBackupTime(): DateTime {
|
||||
val now = DateTime.now()
|
||||
val sixHour = now.withHourOfDay(6)
|
||||
return if (now.millis < sixHour.millis) {
|
||||
sixHour
|
||||
} else {
|
||||
sixHour.plusDays(AUTO_BACKUP_INTERVAL_IN_DAYS)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPreviousAutoBackupTime(): DateTime {
|
||||
val nextBackupTime = getNextAutoBackupTime()
|
||||
return nextBackupTime.minusDays(AUTO_BACKUP_INTERVAL_IN_DAYS)
|
||||
}
|
||||
|
@@ -519,22 +519,22 @@ class EventsHelper(val context: Context) {
|
||||
return events
|
||||
}
|
||||
|
||||
fun getEventsToExport(eventTypes: ArrayList<Long>): ArrayList<Event> {
|
||||
fun getEventsToExport(eventTypes: ArrayList<Long>, exportEvents: Boolean, exportTasks: Boolean, exportPastEntries: Boolean): ArrayList<Event> {
|
||||
val currTS = getNowSeconds()
|
||||
var events = ArrayList<Event>()
|
||||
val tasks = ArrayList<Event>()
|
||||
if (config.exportPastEntries) {
|
||||
if (config.exportEvents) {
|
||||
if (exportPastEntries) {
|
||||
if (exportEvents) {
|
||||
events.addAll(eventsDB.getAllEventsWithTypes(eventTypes))
|
||||
}
|
||||
if (config.exportTasks) {
|
||||
if (exportTasks) {
|
||||
tasks.addAll(eventsDB.getAllTasksWithTypes(eventTypes))
|
||||
}
|
||||
} else {
|
||||
if (config.exportEvents) {
|
||||
if (exportEvents) {
|
||||
events.addAll(eventsDB.getAllFutureEventsWithTypes(currTS, eventTypes))
|
||||
}
|
||||
if (config.exportTasks) {
|
||||
if (exportTasks) {
|
||||
tasks.addAll(eventsDB.getAllFutureTasksWithTypes(currTS, eventTypes))
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.simplemobiletools.calendar.pro.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.CalendarContract.Events
|
||||
import com.simplemobiletools.calendar.pro.R
|
||||
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper
|
||||
@@ -9,7 +10,6 @@ import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPOR
|
||||
import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPORT_PARTIAL
|
||||
import com.simplemobiletools.calendar.pro.models.CalDAVCalendar
|
||||
import com.simplemobiletools.calendar.pro.models.Event
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.toast
|
||||
import com.simplemobiletools.commons.extensions.writeLn
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
@@ -17,7 +17,7 @@ import java.io.BufferedWriter
|
||||
import java.io.OutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
|
||||
class IcsExporter(private val activity: BaseSimpleActivity) {
|
||||
class IcsExporter(private val context: Context) {
|
||||
enum class ExportResult {
|
||||
EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
|
||||
private var eventsExported = 0
|
||||
private var eventsFailed = 0
|
||||
private var calendars = ArrayList<CalDAVCalendar>()
|
||||
private val reminderLabel = activity.getString(R.string.reminder)
|
||||
private val reminderLabel = context.getString(R.string.reminder)
|
||||
private val exportTime = Formatter.getExportedTime(System.currentTimeMillis())
|
||||
|
||||
fun exportEvents(
|
||||
@@ -41,9 +41,9 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
|
||||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
calendars = activity.calDAVHelper.getCalDAVCalendars("", false)
|
||||
calendars = context.calDAVHelper.getCalDAVCalendars("", false)
|
||||
if (showExportingToast) {
|
||||
activity.toast(R.string.exporting)
|
||||
context.toast(R.string.exporting)
|
||||
}
|
||||
|
||||
object : BufferedWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)) {
|
||||
@@ -133,8 +133,8 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
|
||||
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("$CATEGORY_COLOR${context.eventTypesDB.getEventTypeWithId(event.eventType)?.color}")
|
||||
writeLn("$CATEGORIES${context.eventTypesDB.getEventTypeWithId(event.eventType)?.title}")
|
||||
writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(event.lastUpdated)}")
|
||||
writeLn("$TRANSP${if (event.availability == Events.AVAILABILITY_FREE) TRANSPARENT else OPAQUE}")
|
||||
event.location.let { if (it.isNotEmpty()) writeLn("$LOCATION:$it") }
|
||||
@@ -166,8 +166,8 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
|
||||
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("$CATEGORY_COLOR${context.eventTypesDB.getEventTypeWithId(task.eventType)?.color}")
|
||||
writeLn("$CATEGORIES${context.eventTypesDB.getEventTypeWithId(task.eventType)?.title}")
|
||||
writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(task.lastUpdated)}")
|
||||
task.location.let { if (it.isNotEmpty()) writeLn("$LOCATION:$it") }
|
||||
|
||||
|
@@ -0,0 +1,17 @@
|
||||
package com.simplemobiletools.calendar.pro.receivers
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.PowerManager
|
||||
import com.simplemobiletools.calendar.pro.extensions.backupEventsAndTasks
|
||||
|
||||
class AutomaticBackupReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simplecalendar:automaticbackupreceiver")
|
||||
wakelock.acquire(3000)
|
||||
context.backupEventsAndTasks()
|
||||
}
|
||||
}
|
@@ -3,9 +3,7 @@ package com.simplemobiletools.calendar.pro.receivers
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.simplemobiletools.calendar.pro.extensions.notifyRunningEvents
|
||||
import com.simplemobiletools.calendar.pro.extensions.recheckCalDAVCalendars
|
||||
import com.simplemobiletools.calendar.pro.extensions.scheduleAllEvents
|
||||
import com.simplemobiletools.calendar.pro.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
|
||||
class BootCompletedReceiver : BroadcastReceiver() {
|
||||
@@ -16,6 +14,8 @@ class BootCompletedReceiver : BroadcastReceiver() {
|
||||
scheduleAllEvents()
|
||||
notifyRunningEvents()
|
||||
recheckCalDAVCalendars(true) {}
|
||||
scheduleNextAutomaticBackup()
|
||||
checkAndBackupEventsOnBoot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -921,6 +921,47 @@
|
||||
android:id="@+id/settings_tasks_divider"
|
||||
layout="@layout/divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_backups_label"
|
||||
style="@style/SettingsSectionLabelStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/backups" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_enable_automatic_backups_holder"
|
||||
style="@style/SettingsHolderCheckboxStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
|
||||
android:id="@+id/settings_enable_automatic_backups"
|
||||
style="@style/SettingsCheckboxStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_automatic_backups" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_manage_automatic_backups_holder"
|
||||
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/settings_manage_automatic_backups"
|
||||
style="@style/SettingsTextLabelStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/manage_automatic_backups" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_backups_divider"
|
||||
layout="@layout/divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_migrating_label"
|
||||
style="@style/SettingsSectionLabelStyle"
|
||||
|
9
app/src/main/res/layout/datetime_pattern_info_layout.xml
Normal file
9
app/src/main/res/layout/datetime_pattern_info_layout.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.simplemobiletools.commons.views.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/date_time_pattern_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="@dimen/big_margin"
|
||||
android:paddingTop="@dimen/big_margin"
|
||||
android:text="@string/date_time_pattern_info"
|
||||
android:textIsSelectable="true" />
|
158
app/src/main/res/layout/dialog_manage_automatic_backups.xml
Normal file
158
app/src/main/res/layout/dialog_manage_automatic_backups.xml
Normal file
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/export_events_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/backup_events_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/activity_margin">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||
android:id="@+id/backup_events_folder_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:hint="@string/folder">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/backup_events_folder"
|
||||
style="@style/UnclickableEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.simplemobiletools.commons.views.MyTextInputLayout>
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||
android:id="@+id/backup_events_filename_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:hint="@string/filename_without_ics"
|
||||
app:endIconDrawable="@drawable/ic_info_vector"
|
||||
app:endIconMode="custom">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/backup_events_filename"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapWords"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/bigger_text_size" />
|
||||
|
||||
</com.simplemobiletools.commons.views.MyTextInputLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/backup_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/backup_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/backup_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/backup_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/backup_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/backup_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:paddingEnd="@dimen/activity_margin">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
|
||||
android:id="@+id/backup_past_events_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:layoutDirection="rtl"
|
||||
android:paddingHorizontal="@dimen/medium_margin"
|
||||
android:text="@string/export_past_entries" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/select_event_types_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/manage_event_types_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.MyTextView
|
||||
android:id="@+id/manage_event_types"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:paddingHorizontal="@dimen/medium_margin"
|
||||
android:paddingVertical="@dimen/normal_margin"
|
||||
android:text="@string/manage_event_types"
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
Reference in New Issue
Block a user