From 6a94468e8f4f20777ed81e5a1486da646cd08172 Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 22 Mar 2023 22:09:02 +0530 Subject: [PATCH 01/20] Add automatic backup feature --- app/src/main/AndroidManifest.xml | 4 + .../calendar/pro/activities/MainActivity.kt | 10 +- .../pro/activities/SettingsActivity.kt | 43 ++++- .../pro/dialogs/DateTimePatternInfoDialog.kt | 18 ++ .../dialogs/ManageAutomaticBackupsDialog.kt | 123 ++++++++++++++ ...pesDialog.kt => SelectEventTypesDialog.kt} | 18 +- .../SelectQuickFilterEventTypesDialog.kt | 43 ----- .../calendar/pro/extensions/Context.kt | 79 +++++++++ .../calendar/pro/helpers/Config.kt | 32 ++++ .../calendar/pro/helpers/Constants.kt | 9 + .../calendar/pro/helpers/EventsHelper.kt | 12 +- .../calendar/pro/helpers/IcsExporter.kt | 18 +- .../pro/receivers/AutomaticBackupReceiver.kt | 17 ++ .../pro/receivers/BootCompletedReceiver.kt | 12 +- app/src/main/res/layout/activity_settings.xml | 41 +++++ .../layout/datetime_pattern_info_layout.xml | 9 + .../dialog_manage_automatic_backups.xml | 158 ++++++++++++++++++ 17 files changed, 570 insertions(+), 76 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt rename app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/{FilterEventTypesDialog.kt => SelectEventTypesDialog.kt} (64%) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt create mode 100644 app/src/main/res/layout/datetime_pattern_info_layout.xml create mode 100644 app/src/main/res/layout/dialog_manage_automatic_backups.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9ca3096ea..088f9228d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -286,6 +286,10 @@ + + , 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 { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index d0fda2ba8..70ed5155f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -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,11 @@ class SettingsActivity : SimpleActivity() { } private fun showQuickFilterPicker() { - SelectQuickFilterEventTypesDialog(this) + SelectEventTypesDialog(this, config.quickFilterEventTypes) { + if (config.quickFilterEventTypes != it) { + config.quickFilterEventTypes = it + } + } } private fun setupSundayFirst() { @@ -830,6 +838,37 @@ class SettingsActivity : SimpleActivity() { } } + private fun setupEnableAutomaticBackups() { + settings_enable_automatic_backups.isChecked = config.autoBackup + settings_enable_automatic_backups_holder.setOnClickListener { + settings_enable_automatic_backups.toggle() + if (!config.autoBackup) { + ManageAutomaticBackupsDialog( + activity = this, + onSuccess = { + scheduleNextAutomaticBackup() + }, + onCancel = { + config.autoBackup = false + settings_enable_automatic_backups.isChecked = false + settings_manage_automatic_backups_holder.beGone() + } + ) + } else { + cancelScheduledAutomaticBackup() + } + config.autoBackup = settings_enable_automatic_backups.isChecked + settings_manage_automatic_backups_holder.beVisibleIf(config.autoBackup) + } + } + + private fun setupManageAutomaticBackups() { + settings_manage_automatic_backups_holder.beVisibleIf(config.autoBackup) + settings_manage_automatic_backups_holder.setOnClickListener { + ManageAutomaticBackupsDialog(this) + } + } + private fun setupExportSettings() { settings_export_holder.setOnClickListener { val configItems = LinkedHashMap().apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt new file mode 100644 index 000000000..a49859a09 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt @@ -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) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt new file mode 100644 index 000000000..a1c9398f5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -0,0 +1,123 @@ +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.* + +class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSuccess: (() -> Unit)? = null, onCancel: (() -> Unit)? = null) { + private val view = (activity.layoutInflater.inflate(R.layout.dialog_manage_automatic_backups, null) as ViewGroup) + private val config = activity.config + private var realPath = config.autoBackupPath + private var selectedEventTypes = config.autoBackupEventTypes + + init { + view.apply { + backup_events_folder.setText( + if (realPath.isEmpty()) { + activity.getString(R.string.select_folder) + } else { + activity.humanizePath(realPath) + } + ) + + 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) { alertDialog -> + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + if (realPath.isEmpty()) { + activity.toast(R.string.select_folder) + selectBackupFolder() + return@setOnClickListener + } + + val filename = view.backup_events_filename.value + when { + filename.isEmpty() -> activity.toast(R.string.empty_name) + filename.isAValidFilename() -> { + 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 { + autoBackupPath = realPath + autoBackupFilename = filename + autoBackupEvents = backupEventsChecked + autoBackupTasks = backupTasksChecked + autoBackupPastEntries = view.backup_past_events_checkbox.isChecked + if (autoBackupEventTypes != selectedEventTypes) { + autoBackupEventTypes = selectedEventTypes + } + } + + onSuccess?.invoke() + alertDialog.dismiss() + } + } + else -> activity.toast(R.string.invalid_name) + } + } + + alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + onCancel?.invoke() + alertDialog.dismiss() + } + } + } + } + + private fun selectBackupFolder() { + activity.hideKeyboard(view.backup_events_filename) + FilePickerDialog(activity, realPath, false, showFAB = true) { + view.backup_events_folder.setText(activity.humanizePath(it)) + realPath = it + } + } +} + diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt similarity index 64% rename from app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt index 8e8f6da7f..e69a32fae 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt @@ -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, val callback: (HashSet) -> 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() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt deleted file mode 100644 index 17c29a8dd..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 36538d89f..1f4fd598a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -37,6 +37,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 +48,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 +185,83 @@ 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 = DateTime.now().plusDays(1).withHourOfDay(6).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.backupEventsAndTasks() { + ensureBackgroundThread { + val config = config + val events = eventsHelper.getEventsToExport( + eventTypes = config.autoBackupEventTypes.map { it.toLong() } as ArrayList, + 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 exportPath = File(config.autoBackupPath, "$filename.ics") + val outputStream = try { + exportPath.outputStream() + } catch (e: Exception) { + showErrorToast(e) + null + } + + IcsExporter(this).exportEvents(outputStream, events, true) { result -> + toast( + when (result) { + IcsExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful + IcsExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed + else -> R.string.exporting_failed + } + ) + config.lastAutoBackupTime = getNowSeconds() + scheduleNextAutomaticBackup() + } + } +} + fun Context.getRepetitionText(seconds: Int) = when (seconds) { 0 -> getString(R.string.no_repetition) DAY -> getString(R.string.daily) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt index 87726d5ea..46189afc8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -258,4 +258,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 autoBackupPath: String + get() = prefs.getString(AUTO_BACKUP_PATH, "")!! + set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_PATH, autoBackupPath).apply() + + var autoBackupFilename: String + get() = prefs.getString(AUTO_BACKUP_FILENAME, "")!! + set(autoBackupFilename) = prefs.edit().putString(AUTO_BACKUP_FILENAME, autoBackupFilename).apply() + + var autoBackupEventTypes: Set + 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, false) + set(autoBackupEvents) = prefs.edit().putBoolean(AUTO_BACKUP_EVENTS, autoBackupEvents).apply() + + var autoBackupTasks: Boolean + get() = prefs.getBoolean(AUTO_BACKUP_TASKS, false) + set(autoBackupTasks) = prefs.edit().putBoolean(AUTO_BACKUP_TASKS, autoBackupTasks).apply() + + var autoBackupPastEntries: Boolean + get() = prefs.getBoolean(AUTO_BACKUP_PAST_ENTRIES, false) + 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() } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index 195a98df0..022a22922 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -9,6 +9,7 @@ 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 @@ -129,6 +130,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_PATH = "auto_backup_path" +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) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt index bcd00625e..31c635149 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt @@ -519,22 +519,22 @@ class EventsHelper(val context: Context) { return events } - fun getEventsToExport(eventTypes: ArrayList): ArrayList { + fun getEventsToExport(eventTypes: ArrayList, exportEvents: Boolean, exportTasks: Boolean, exportPastEntries: Boolean): ArrayList { val currTS = getNowSeconds() var events = ArrayList() val tasks = ArrayList() - 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)) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt index 1c4969b8b..1d2e8c753 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt @@ -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() - 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") } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt new file mode 100644 index 000000000..79d717736 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt @@ -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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt index e6cd7e523..2b4726cf1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt @@ -3,9 +3,9 @@ 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.calendar.pro.helpers.DAY +import com.simplemobiletools.calendar.pro.helpers.getNowSeconds import com.simplemobiletools.commons.helpers.ensureBackgroundThread class BootCompletedReceiver : BroadcastReceiver() { @@ -16,6 +16,12 @@ class BootCompletedReceiver : BroadcastReceiver() { scheduleAllEvents() notifyRunningEvents() recheckCalDAVCalendars(true) {} + scheduleNextAutomaticBackup() + val now = getNowSeconds() + if (config.autoBackup && config.lastAutoBackupTime !in (now - DAY)..now) { + // device was probably off at the scheduled time so backup now + backupEventsAndTasks() + } } } } diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 82e4ea7a5..0fa295391 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -921,6 +921,47 @@ android:id="@+id/settings_tasks_divider" layout="@layout/divider" /> + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_manage_automatic_backups.xml b/app/src/main/res/layout/dialog_manage_automatic_backups.xml new file mode 100644 index 000000000..3f0c97fcd --- /dev/null +++ b/app/src/main/res/layout/dialog_manage_automatic_backups.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d4e5b572918d2cab00b8b20516f95f6e5652e95c Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 24 Mar 2023 17:06:08 +0530 Subject: [PATCH 02/20] Handle storage access framework permissions --- .../dialogs/ManageAutomaticBackupsDialog.kt | 41 ++++++++++++------- .../calendar/pro/extensions/Context.kt | 2 +- .../calendar/pro/helpers/Config.kt | 6 +-- .../calendar/pro/helpers/Constants.kt | 2 +- .../dialog_manage_automatic_backups.xml | 2 +- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index a1c9398f5..740e3cfb0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -13,16 +13,16 @@ import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.* class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSuccess: (() -> Unit)? = null, onCancel: (() -> Unit)? = null) { private val view = (activity.layoutInflater.inflate(R.layout.dialog_manage_automatic_backups, null) as ViewGroup) private val config = activity.config - private var realPath = config.autoBackupPath - private var selectedEventTypes = config.autoBackupEventTypes + private var backupFolder = config.autoBackupFolder + private var selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes } init { view.apply { backup_events_folder.setText( - if (realPath.isEmpty()) { + if (backupFolder.isEmpty()) { activity.getString(R.string.select_folder) } else { - activity.humanizePath(realPath) + activity.humanizePath(backupFolder) } ) @@ -65,9 +65,9 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce .setPositiveButton(R.string.ok, null) .setNegativeButton(R.string.cancel, null) .apply { - activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { alertDialog -> - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - if (realPath.isEmpty()) { + activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { dialog -> + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + if (backupFolder.isEmpty()) { activity.toast(R.string.select_folder) selectBackupFolder() return@setOnClickListener @@ -86,7 +86,7 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce ensureBackgroundThread { config.apply { - autoBackupPath = realPath + autoBackupFolder = backupFolder autoBackupFilename = filename autoBackupEvents = backupEventsChecked autoBackupTasks = backupTasksChecked @@ -97,16 +97,16 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce } onSuccess?.invoke() - alertDialog.dismiss() + dialog.dismiss() } } else -> activity.toast(R.string.invalid_name) } } - alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { onCancel?.invoke() - alertDialog.dismiss() + dialog.dismiss() } } } @@ -114,9 +114,22 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce private fun selectBackupFolder() { activity.hideKeyboard(view.backup_events_filename) - FilePickerDialog(activity, realPath, false, showFAB = true) { - view.backup_events_folder.setText(activity.humanizePath(it)) - realPath = it + FilePickerDialog(activity, backupFolder, false, showFAB = true) { + val path = it + activity.handleSAFDialog(it) { grantedSAF -> + if (!grantedSAF) { + return@handleSAFDialog + } + + activity.handleSAFDialogSdk30(path) { grantedSAF30 -> + if (!grantedSAF30) { + return@handleSAFDialogSdk30 + } + + backupFolder = path + view.backup_events_folder.setText(activity.humanizePath(path)) + } + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 1f4fd598a..e03dc6fae 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -240,7 +240,7 @@ fun Context.backupEventsAndTasks() { .replace("%m", minutes, false) .replace("%s", seconds, false) - val exportPath = File(config.autoBackupPath, "$filename.ics") + val exportPath = File(config.autoBackupFolder, "$filename.ics") val outputStream = try { exportPath.outputStream() } catch (e: Exception) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt index 46189afc8..dc2d87c32 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -263,9 +263,9 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getBoolean(AUTO_BACKUP, false) set(enableAutomaticBackups) = prefs.edit().putBoolean(AUTO_BACKUP, enableAutomaticBackups).apply() - var autoBackupPath: String - get() = prefs.getString(AUTO_BACKUP_PATH, "")!! - set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_PATH, autoBackupPath).apply() + var autoBackupFolder: String + get() = prefs.getString(AUTO_BACKUP_FOLDER, "")!! + set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_FOLDER, autoBackupPath).apply() var autoBackupFilename: String get() = prefs.getString(AUTO_BACKUP_FILENAME, "")!! diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index 022a22922..ab9f09e4b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -131,7 +131,7 @@ 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_PATH = "auto_backup_path" +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" diff --git a/app/src/main/res/layout/dialog_manage_automatic_backups.xml b/app/src/main/res/layout/dialog_manage_automatic_backups.xml index 3f0c97fcd..7cd016e65 100644 --- a/app/src/main/res/layout/dialog_manage_automatic_backups.xml +++ b/app/src/main/res/layout/dialog_manage_automatic_backups.xml @@ -19,7 +19,7 @@ android:layout_marginStart="@dimen/activity_margin" android:layout_marginEnd="@dimen/activity_margin" android:layout_marginBottom="@dimen/activity_margin" - android:hint="@string/folder"> + android:hint="@string/select_folder"> Date: Fri, 24 Mar 2023 17:07:53 +0530 Subject: [PATCH 03/20] Enable automatic backup on Android R+ only --- .../pro/activities/SettingsActivity.kt | 33 +++++++++++-------- .../calendar/pro/extensions/Context.kt | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index 70ed5155f..92e90b976 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -839,36 +839,41 @@ 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 { - settings_enable_automatic_backups.toggle() - if (!config.autoBackup) { + val wasBackupDisabled = !config.autoBackup + if (wasBackupDisabled) { ManageAutomaticBackupsDialog( activity = this, - onSuccess = { - scheduleNextAutomaticBackup() - }, - onCancel = { - config.autoBackup = false - settings_enable_automatic_backups.isChecked = false - settings_manage_automatic_backups_holder.beGone() - } + onSuccess = { scheduleNextAutomaticBackup() }, + onCancel = { enableOrDisableAutomaticBackups(false) } ) + enableOrDisableAutomaticBackups(true) } else { cancelScheduledAutomaticBackup() + enableOrDisableAutomaticBackups(false) } - config.autoBackup = settings_enable_automatic_backups.isChecked - settings_manage_automatic_backups_holder.beVisibleIf(config.autoBackup) } } private fun setupManageAutomaticBackups() { - settings_manage_automatic_backups_holder.beVisibleIf(config.autoBackup) + settings_manage_automatic_backups_holder.beVisibleIf(isRPlus() && config.autoBackup) settings_manage_automatic_backups_holder.setOnClickListener { - ManageAutomaticBackupsDialog(this) + ManageAutomaticBackupsDialog(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().apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index e03dc6fae..7c3ecf4b2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -257,8 +257,8 @@ fun Context.backupEventsAndTasks() { } ) config.lastAutoBackupTime = getNowSeconds() - scheduleNextAutomaticBackup() } + scheduleNextAutomaticBackup() } } From f86dafd52815cf2e9d99b10ed8db3fd225885a69 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 24 Mar 2023 17:12:02 +0530 Subject: [PATCH 04/20] Show toasts only on failure --- .../calendar/pro/extensions/Context.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 7c3ecf4b2..5437ea3f4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -248,14 +248,12 @@ fun Context.backupEventsAndTasks() { null } - IcsExporter(this).exportEvents(outputStream, events, true) { result -> - toast( - when (result) { - IcsExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful - IcsExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed - else -> R.string.exporting_failed - } - ) + 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 -> {} + } config.lastAutoBackupTime = getNowSeconds() } scheduleNextAutomaticBackup() From 631ec37d521e62ca84bc2bcd2ea76d6cdef4adf3 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 24 Mar 2023 17:35:41 +0530 Subject: [PATCH 05/20] Move automatic backup interval to constants --- .../com/simplemobiletools/calendar/pro/extensions/Context.kt | 2 +- .../com/simplemobiletools/calendar/pro/helpers/Constants.kt | 2 ++ .../calendar/pro/receivers/BootCompletedReceiver.kt | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 5437ea3f4..201217797 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -192,7 +192,7 @@ fun Context.getAutomaticBackupIntent(): PendingIntent { fun Context.scheduleNextAutomaticBackup() { if (config.autoBackup) { - val backupAtMillis = DateTime.now().plusDays(1).withHourOfDay(6).millis + val backupAtMillis = DateTime.now().withHourOfDay(6).plusDays(AUTO_BACKUP_INTERVAL_IN_DAYS).millis val pendingIntent = getAutomaticBackupIntent() val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager try { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index ab9f09e4b..eb3694e84 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -73,6 +73,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 diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt index 2b4726cf1..f4a638113 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.simplemobiletools.calendar.pro.extensions.* +import com.simplemobiletools.calendar.pro.helpers.AUTO_BACKUP_INTERVAL_IN_DAYS import com.simplemobiletools.calendar.pro.helpers.DAY import com.simplemobiletools.calendar.pro.helpers.getNowSeconds import com.simplemobiletools.commons.helpers.ensureBackgroundThread @@ -18,7 +19,8 @@ class BootCompletedReceiver : BroadcastReceiver() { recheckCalDAVCalendars(true) {} scheduleNextAutomaticBackup() val now = getNowSeconds() - if (config.autoBackup && config.lastAutoBackupTime !in (now - DAY)..now) { + val intervalInSeconds = AUTO_BACKUP_INTERVAL_IN_DAYS * DAY + if (config.autoBackup && config.lastAutoBackupTime !in (now - intervalInSeconds)..now) { // device was probably off at the scheduled time so backup now backupEventsAndTasks() } From eb1c5157af9f8a7f184cead3d25d85e5e0bdf6ef Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 24 Mar 2023 17:42:40 +0530 Subject: [PATCH 06/20] Move code to extension --- .../calendar/pro/extensions/Context.kt | 9 +++++++++ .../calendar/pro/receivers/BootCompletedReceiver.kt | 10 +--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 201217797..d41df061e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -208,6 +208,15 @@ fun Context.cancelScheduledAutomaticBackup() { alarmManager.cancel(getAutomaticBackupIntent()) } +fun Context.checkAndBackupEventsOnBoot() { + val now = getNowSeconds() + val intervalInSeconds = AUTO_BACKUP_INTERVAL_IN_DAYS * DAY + if (config.autoBackup && config.lastAutoBackupTime !in (now - intervalInSeconds)..now) { + // device was probably off at the scheduled time so backup now + backupEventsAndTasks() + } +} + fun Context.backupEventsAndTasks() { ensureBackgroundThread { val config = config diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt index f4a638113..cc50ec102 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt @@ -4,9 +4,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.simplemobiletools.calendar.pro.extensions.* -import com.simplemobiletools.calendar.pro.helpers.AUTO_BACKUP_INTERVAL_IN_DAYS -import com.simplemobiletools.calendar.pro.helpers.DAY -import com.simplemobiletools.calendar.pro.helpers.getNowSeconds import com.simplemobiletools.commons.helpers.ensureBackgroundThread class BootCompletedReceiver : BroadcastReceiver() { @@ -18,12 +15,7 @@ class BootCompletedReceiver : BroadcastReceiver() { notifyRunningEvents() recheckCalDAVCalendars(true) {} scheduleNextAutomaticBackup() - val now = getNowSeconds() - val intervalInSeconds = AUTO_BACKUP_INTERVAL_IN_DAYS * DAY - if (config.autoBackup && config.lastAutoBackupTime !in (now - intervalInSeconds)..now) { - // device was probably off at the scheduled time so backup now - backupEventsAndTasks() - } + checkAndBackupEventsOnBoot() } } } From 0481b20fab4f65b753bee8fb626cfd4cb5532653 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 12:31:21 +0530 Subject: [PATCH 07/20] Fix backup on boot logic and improve readability --- .../calendar/pro/extensions/Context.kt | 15 +++++++++------ .../calendar/pro/helpers/Constants.kt | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index d41df061e..a05394f56 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -192,7 +192,7 @@ fun Context.getAutomaticBackupIntent(): PendingIntent { fun Context.scheduleNextAutomaticBackup() { if (config.autoBackup) { - val backupAtMillis = DateTime.now().withHourOfDay(6).plusDays(AUTO_BACKUP_INTERVAL_IN_DAYS).millis + val backupAtMillis = getNextAutoBackupTime().millis val pendingIntent = getAutomaticBackupIntent() val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager try { @@ -209,11 +209,14 @@ fun Context.cancelScheduledAutomaticBackup() { } fun Context.checkAndBackupEventsOnBoot() { - val now = getNowSeconds() - val intervalInSeconds = AUTO_BACKUP_INTERVAL_IN_DAYS * DAY - if (config.autoBackup && config.lastAutoBackupTime !in (now - intervalInSeconds)..now) { - // device was probably off at the scheduled time so backup now - backupEventsAndTasks() + 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() + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index eb3694e84..ef5af39d1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -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 org.joda.time.DateTime import java.util.* const val STORED_LOCALLY_ONLY = 0 @@ -275,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) +} From c8bba75c9203e14158f3527c5ed5a2ce29f350fd Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 12:45:54 +0530 Subject: [PATCH 08/20] Use multi-line lambda --- .../calendar/pro/activities/SettingsActivity.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index 92e90b976..464c94035 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -848,8 +848,12 @@ class SettingsActivity : SimpleActivity() { if (wasBackupDisabled) { ManageAutomaticBackupsDialog( activity = this, - onSuccess = { scheduleNextAutomaticBackup() }, - onCancel = { enableOrDisableAutomaticBackups(false) } + onSuccess = { + scheduleNextAutomaticBackup() + }, + onCancel = { + enableOrDisableAutomaticBackups(false) + } ) enableOrDisableAutomaticBackups(true) } else { From a29eab5c184d9be3417b7c62c4f33d88cdcb8300 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 13:00:46 +0530 Subject: [PATCH 09/20] Minor code improvement --- .../calendar/pro/activities/MainActivity.kt | 8 ++++---- .../calendar/pro/activities/SettingsActivity.kt | 4 +--- .../calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt index 7177b6ec1..9d8ffb22c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt @@ -529,11 +529,11 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { SelectEventTypesDialog(this, config.displayEventTypes) { if (config.displayEventTypes != it) { config.displayEventTypes = it - } - refreshViewPager() - setupQuickFilter() - updateWidgets() + refreshViewPager() + setupQuickFilter() + updateWidgets() + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index 464c94035..7a8e73d78 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -339,9 +339,7 @@ class SettingsActivity : SimpleActivity() { private fun showQuickFilterPicker() { SelectEventTypesDialog(this, config.quickFilterEventTypes) { - if (config.quickFilterEventTypes != it) { - config.quickFilterEventTypes = it - } + config.quickFilterEventTypes = it } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 740e3cfb0..71f50d80d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -29,6 +29,7 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce 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) From bd56c1c86ceff89c96d1a6862c1564eb48b5171e Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 13:22:42 +0530 Subject: [PATCH 10/20] Add empty lines after closing brackets --- .../calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 71f50d80d..6642ffb17 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -34,6 +34,7 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce backup_events_filename_hint.setEndIconOnClickListener { DateTimePatternInfoDialog(activity) } + backup_events_filename_hint.setEndIconOnLongClickListener { DateTimePatternInfoDialog(activity) true @@ -43,10 +44,12 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce 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() From ed127ee6d293eea2244343c385756e4c8365d08b Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 15:42:03 +0530 Subject: [PATCH 11/20] Enable automatic backups only after user taps OK --- .../calendar/pro/activities/SettingsActivity.kt | 14 +++++++------- .../pro/dialogs/ManageAutomaticBackupsDialog.kt | 12 +++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index 7a8e73d78..21ef9d51e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -847,13 +847,10 @@ class SettingsActivity : SimpleActivity() { ManageAutomaticBackupsDialog( activity = this, onSuccess = { + enableOrDisableAutomaticBackups(true) scheduleNextAutomaticBackup() - }, - onCancel = { - enableOrDisableAutomaticBackups(false) } ) - enableOrDisableAutomaticBackups(true) } else { cancelScheduledAutomaticBackup() enableOrDisableAutomaticBackups(false) @@ -864,9 +861,12 @@ class SettingsActivity : SimpleActivity() { private fun setupManageAutomaticBackups() { settings_manage_automatic_backups_holder.beVisibleIf(isRPlus() && config.autoBackup) settings_manage_automatic_backups_holder.setOnClickListener { - ManageAutomaticBackupsDialog(this, onSuccess = { - scheduleNextAutomaticBackup() - }) + ManageAutomaticBackupsDialog( + activity = this, + onSuccess = { + scheduleNextAutomaticBackup() + } + ) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 6642ffb17..184ba2cea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -10,7 +10,7 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.ensureBackgroundThread import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.* -class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSuccess: (() -> Unit)? = null, onCancel: (() -> Unit)? = null) { +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 @@ -100,18 +100,16 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce } } - onSuccess?.invoke() + activity.runOnUiThread { + onSuccess() + } + dialog.dismiss() } } else -> activity.toast(R.string.invalid_name) } } - - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { - onCancel?.invoke() - dialog.dismiss() - } } } } From 411a3313ce05b1b28fb32d11047e14b23a62e64e Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 16:43:49 +0530 Subject: [PATCH 12/20] Use Downloads folder by default for automatic backups --- .../dialogs/ManageAutomaticBackupsDialog.kt | 30 ++++++++----------- .../calendar/pro/extensions/Context.kt | 6 +++- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 184ba2cea..2c9f8dd4c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -1,5 +1,6 @@ package com.simplemobiletools.calendar.pro.dialogs +import android.os.Environment import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import com.simplemobiletools.calendar.pro.R @@ -9,23 +10,23 @@ 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 EVENTS_DIR = "Events" + 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 backupFolder = config.autoBackupFolder.ifEmpty { + val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + File(downloadDir, EVENTS_DIR).absolutePath + } + private var selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes } init { view.apply { - backup_events_folder.setText( - if (backupFolder.isEmpty()) { - activity.getString(R.string.select_folder) - } else { - activity.humanizePath(backupFolder) - } - ) - + backup_events_folder.setText(activity.humanizePath(backupFolder)) val filename = config.autoBackupFilename.ifEmpty { "${activity.getString(R.string.events)}_%Y%M%D_%h%m%s" } @@ -71,12 +72,6 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce .apply { activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { dialog -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - if (backupFolder.isEmpty()) { - activity.toast(R.string.select_folder) - selectBackupFolder() - return@setOnClickListener - } - val filename = view.backup_events_filename.value when { filename.isEmpty() -> activity.toast(R.string.empty_name) @@ -116,9 +111,8 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce private fun selectBackupFolder() { activity.hideKeyboard(view.backup_events_filename) - FilePickerDialog(activity, backupFolder, false, showFAB = true) { - val path = it - activity.handleSAFDialog(it) { grantedSAF -> + FilePickerDialog(activity, backupFolder, false, showFAB = true) { path -> + activity.handleSAFDialog(path) { grantedSAF -> if (!grantedSAF) { return@handleSAFDialog } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index a05394f56..83665f89e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -252,7 +252,11 @@ fun Context.backupEventsAndTasks() { .replace("%m", minutes, false) .replace("%s", seconds, false) - val exportPath = File(config.autoBackupFolder, "$filename.ics") + val outputFolder = File(config.autoBackupFolder).apply { + mkdirs() + } + + val exportPath = File(outputFolder, "$filename.ics") val outputStream = try { exportPath.outputStream() } catch (e: Exception) { From 8a9fe2ac61731d11d74c6057f74a60dc6dfb7223 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 16:44:18 +0530 Subject: [PATCH 13/20] Minor UX improvement --- .../com/simplemobiletools/calendar/pro/helpers/Config.kt | 6 +++--- app/src/main/res/layout/dialog_manage_automatic_backups.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt index dc2d87c32..fa92f34bf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -276,15 +276,15 @@ class Config(context: Context) : BaseConfig(context) { 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, false) + 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, false) + 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, false) + get() = prefs.getBoolean(AUTO_BACKUP_PAST_ENTRIES, true) set(autoBackupPastEntries) = prefs.edit().putBoolean(AUTO_BACKUP_PAST_ENTRIES, autoBackupPastEntries).apply() var lastAutoBackupTime: Long diff --git a/app/src/main/res/layout/dialog_manage_automatic_backups.xml b/app/src/main/res/layout/dialog_manage_automatic_backups.xml index 7cd016e65..3f0c97fcd 100644 --- a/app/src/main/res/layout/dialog_manage_automatic_backups.xml +++ b/app/src/main/res/layout/dialog_manage_automatic_backups.xml @@ -19,7 +19,7 @@ android:layout_marginStart="@dimen/activity_margin" android:layout_marginEnd="@dimen/activity_margin" android:layout_marginBottom="@dimen/activity_margin" - android:hint="@string/select_folder"> + android:hint="@string/folder"> Date: Sat, 25 Mar 2023 17:04:18 +0530 Subject: [PATCH 14/20] Remove "Events" subfolder --- .../calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 2c9f8dd4c..053798384 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -10,16 +10,12 @@ 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 EVENTS_DIR = "Events" - 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.ifEmpty { - val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - File(downloadDir, EVENTS_DIR).absolutePath + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath } private var selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes } From 7a70183961416d50b48df77f23eb16f12fdfd2a5 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 17:21:28 +0530 Subject: [PATCH 15/20] Minor code improvement --- .../calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt | 6 +----- .../com/simplemobiletools/calendar/pro/helpers/Config.kt | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 053798384..058937a4a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -1,6 +1,5 @@ package com.simplemobiletools.calendar.pro.dialogs -import android.os.Environment import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import com.simplemobiletools.calendar.pro.R @@ -14,10 +13,7 @@ import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.* 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.ifEmpty { - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath - } - + private var backupFolder = config.autoBackupFolder private var selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes } init { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt index fa92f34bf..09cfdafde 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -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 @@ -264,7 +266,7 @@ class Config(context: Context) : BaseConfig(context) { set(enableAutomaticBackups) = prefs.edit().putBoolean(AUTO_BACKUP, enableAutomaticBackups).apply() var autoBackupFolder: String - get() = prefs.getString(AUTO_BACKUP_FOLDER, "")!! + get() = prefs.getString(AUTO_BACKUP_FOLDER, Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).absolutePath)!! set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_FOLDER, autoBackupPath).apply() var autoBackupFilename: String From 02e3878622ea9addb9c5b343f2e3e28aacdc2af9 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 17:45:17 +0530 Subject: [PATCH 16/20] Scheduled automatic backup on start --- app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt index 2b7e6ab0c..fa2e115e3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt @@ -1,11 +1,13 @@ package com.simplemobiletools.calendar.pro import androidx.multidex.MultiDexApplication +import com.simplemobiletools.calendar.pro.extensions.scheduleNextAutomaticBackup import com.simplemobiletools.commons.extensions.checkUseEnglish class App : MultiDexApplication() { override fun onCreate() { super.onCreate() checkUseEnglish() + scheduleNextAutomaticBackup() } } From 929e995604ff08fc6214897b1e54fac008d2ef27 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 25 Mar 2023 21:37:06 +0530 Subject: [PATCH 17/20] Schedule alarms when package is replaced --- app/src/main/AndroidManifest.xml | 1 + app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 088f9228d..5eca4365f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -283,6 +283,7 @@ + diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt index fa2e115e3..2b7e6ab0c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt @@ -1,13 +1,11 @@ package com.simplemobiletools.calendar.pro import androidx.multidex.MultiDexApplication -import com.simplemobiletools.calendar.pro.extensions.scheduleNextAutomaticBackup import com.simplemobiletools.commons.extensions.checkUseEnglish class App : MultiDexApplication() { override fun onCreate() { super.onCreate() checkUseEnglish() - scheduleNextAutomaticBackup() } } From e63b97f552e9ac77bf7991121d2657a211bcf5b5 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sun, 26 Mar 2023 14:14:54 +0530 Subject: [PATCH 18/20] Add exported files to MediaStore --- .../com/simplemobiletools/calendar/pro/extensions/Context.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 83665f89e..615ce90a7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -256,9 +256,9 @@ fun Context.backupEventsAndTasks() { mkdirs() } - val exportPath = File(outputFolder, "$filename.ics") + val exportFile = File(outputFolder, "$filename.ics") val outputStream = try { - exportPath.outputStream() + exportFile.outputStream() } catch (e: Exception) { showErrorToast(e) null @@ -270,6 +270,7 @@ fun Context.backupEventsAndTasks() { IcsExporter.ExportResult.EXPORT_FAIL -> toast(R.string.exporting_failed) else -> {} } + rescanPath(exportFile.absolutePath) config.lastAutoBackupTime = getNowSeconds() } scheduleNextAutomaticBackup() From 4f3497ed30b9793a550db18477bfc6ea2f256b3e Mon Sep 17 00:00:00 2001 From: Naveen Date: Sun, 26 Mar 2023 15:03:45 +0530 Subject: [PATCH 19/20] Restore file name check Because we won't be able to replace an already existing file created by some other app or an uninstalled version of the app --- .../calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt index 058937a4a..a744f291e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt @@ -9,6 +9,7 @@ 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) @@ -68,6 +69,12 @@ class ManageAutomaticBackupsDialog(private val activity: SimpleActivity, onSucce 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()) { From 98443eca17809a09a942581e91a701b431a1ba61 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 6 Apr 2023 09:45:32 +0530 Subject: [PATCH 20/20] Remove ACTION_MEDIA_SCANNER_SCAN_FILE broadcast --- .../simplemobiletools/calendar/pro/extensions/Context.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index 615ce90a7..de9e5ad25 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -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 @@ -270,7 +271,12 @@ fun Context.backupEventsAndTasks() { IcsExporter.ExportResult.EXPORT_FAIL -> toast(R.string.exporting_failed) else -> {} } - rescanPath(exportFile.absolutePath) + MediaScannerConnection.scanFile( + this, + arrayOf(exportFile.absolutePath), + arrayOf(exportFile.getMimeType()) + ) { _, _ -> } + config.lastAutoBackupTime = getNowSeconds() } scheduleNextAutomaticBackup()