mirror of
https://github.com/SimpleMobileTools/Simple-Notes.git
synced 2025-06-05 17:00:23 +02:00
Merge branch 'master' into feature/641-open-note-ui
This commit is contained in:
@ -13,10 +13,8 @@ import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.RadioItem
|
||||
import com.simplemobiletools.notes.pro.R
|
||||
import com.simplemobiletools.notes.pro.dialogs.ExportNotesDialog
|
||||
import com.simplemobiletools.notes.pro.extensions.config
|
||||
import com.simplemobiletools.notes.pro.extensions.requestUnlockNotes
|
||||
import com.simplemobiletools.notes.pro.extensions.updateWidgets
|
||||
import com.simplemobiletools.notes.pro.extensions.widgetsDB
|
||||
import com.simplemobiletools.notes.pro.dialogs.ManageAutoBackupsDialog
|
||||
import com.simplemobiletools.notes.pro.extensions.*
|
||||
import com.simplemobiletools.notes.pro.helpers.*
|
||||
import com.simplemobiletools.notes.pro.models.Note
|
||||
import com.simplemobiletools.notes.pro.models.Widget
|
||||
@ -62,6 +60,8 @@ class SettingsActivity : SimpleActivity() {
|
||||
setupCustomizeWidgetColors()
|
||||
setupNotesExport()
|
||||
setupNotesImport()
|
||||
setupEnableAutomaticBackups()
|
||||
setupManageAutomaticBackups()
|
||||
updateTextColors(settings_nested_scrollview)
|
||||
|
||||
arrayOf(
|
||||
@ -71,6 +71,7 @@ class SettingsActivity : SimpleActivity() {
|
||||
settings_startup_label,
|
||||
settings_saving_label,
|
||||
settings_migrating_label,
|
||||
settings_backups_label,
|
||||
).forEach {
|
||||
it.setTextColor(getProperPrimaryColor())
|
||||
}
|
||||
@ -355,4 +356,43 @@ class SettingsActivity : SimpleActivity() {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupEnableAutomaticBackups() {
|
||||
settings_backups_label.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) {
|
||||
ManageAutoBackupsDialog(
|
||||
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 {
|
||||
ManageAutoBackupsDialog(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.simplemobiletools.notes.pro.dialogs
|
||||
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||
import com.simplemobiletools.notes.pro.R
|
||||
|
||||
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,99 @@
|
||||
package com.simplemobiletools.notes.pro.dialogs
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.notes.pro.R
|
||||
import com.simplemobiletools.notes.pro.activities.SimpleActivity
|
||||
import com.simplemobiletools.notes.pro.extensions.config
|
||||
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename
|
||||
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename_hint
|
||||
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_folder
|
||||
import java.io.File
|
||||
|
||||
class ManageAutoBackupsDialog(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
|
||||
|
||||
init {
|
||||
view.apply {
|
||||
backup_notes_folder.setText(activity.humanizePath(backupFolder))
|
||||
val filename = config.autoBackupFilename.ifEmpty {
|
||||
"${activity.getString(R.string.notes)}_%Y%M%D_%h%m%s"
|
||||
}
|
||||
|
||||
backup_notes_filename.setText(filename)
|
||||
backup_notes_filename_hint.setEndIconOnClickListener {
|
||||
DateTimePatternInfoDialog(activity)
|
||||
}
|
||||
|
||||
backup_notes_filename_hint.setEndIconOnLongClickListener {
|
||||
DateTimePatternInfoDialog(activity)
|
||||
true
|
||||
}
|
||||
|
||||
backup_notes_folder.setOnClickListener {
|
||||
selectBackupFolder()
|
||||
}
|
||||
}
|
||||
|
||||
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_notes_filename.value
|
||||
when {
|
||||
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
||||
filename.isAValidFilename() -> {
|
||||
val file = File(backupFolder, "$filename.json")
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
activity.toast(R.string.name_taken)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
config.apply {
|
||||
autoBackupFolder = backupFolder
|
||||
autoBackupFilename = filename
|
||||
}
|
||||
|
||||
activity.runOnUiThread {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
else -> activity.toast(R.string.invalid_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectBackupFolder() {
|
||||
activity.hideKeyboard(view.backup_notes_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_notes_folder.setText(activity.humanizePath(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,28 @@
|
||||
package com.simplemobiletools.notes.pro.extensions
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ExportResult
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.notes.pro.R
|
||||
import com.simplemobiletools.notes.pro.databases.NotesDatabase
|
||||
import com.simplemobiletools.notes.pro.dialogs.UnlockNotesDialog
|
||||
import com.simplemobiletools.notes.pro.helpers.Config
|
||||
import com.simplemobiletools.notes.pro.helpers.MyWidgetProvider
|
||||
import com.simplemobiletools.notes.pro.helpers.*
|
||||
import com.simplemobiletools.notes.pro.interfaces.NotesDao
|
||||
import com.simplemobiletools.notes.pro.interfaces.WidgetsDao
|
||||
import com.simplemobiletools.notes.pro.models.Note
|
||||
import com.simplemobiletools.notes.pro.receivers.AutomaticBackupReceiver
|
||||
import org.joda.time.DateTime
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
val Context.config: Config get() = Config.newInstance(applicationContext)
|
||||
|
||||
@ -43,3 +53,111 @@ fun BaseSimpleActivity.requestUnlockNotes(notes: List<Note>, callback: (unlocked
|
||||
callback(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
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.checkAndBackupNotesOnBoot() {
|
||||
if (config.autoBackup) {
|
||||
val previousRealBackupTime = config.lastAutoBackupTime
|
||||
val previousScheduledBackupTime = getPreviousAutoBackupTime().millis
|
||||
val missedPreviousBackup = previousRealBackupTime < previousScheduledBackupTime
|
||||
if (missedPreviousBackup) {
|
||||
// device was probably off at the scheduled time so backup now
|
||||
backupNotes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.backupNotes() {
|
||||
require(isRPlus())
|
||||
ensureBackgroundThread {
|
||||
val config = config
|
||||
NotesHelper(this).getNotes { notesToBackup ->
|
||||
if (notesToBackup.isEmpty()) {
|
||||
toast(R.string.no_entries_for_exporting)
|
||||
config.lastAutoBackupTime = DateTime.now().millis
|
||||
scheduleNextAutomaticBackup()
|
||||
return@getNotes
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
var exportFile = File(outputFolder, "$filename.json")
|
||||
var exportFilePath = exportFile.absolutePath
|
||||
val outputStream = try {
|
||||
if (hasProperStoredFirstParentUri(exportFilePath)) {
|
||||
val exportFileUri = createDocumentUriUsingFirstParentTreeUri(exportFilePath)
|
||||
if (!getDoesFilePathExist(exportFilePath)) {
|
||||
createSAFFileSdk30(exportFilePath)
|
||||
}
|
||||
applicationContext.contentResolver.openOutputStream(exportFileUri, "wt") ?: FileOutputStream(exportFile)
|
||||
} else {
|
||||
var num = 0
|
||||
while (getDoesFilePathExist(exportFilePath) && !exportFile.canWrite()) {
|
||||
num++
|
||||
exportFile = File(outputFolder, "${filename}_${num}.json")
|
||||
exportFilePath = exportFile.absolutePath
|
||||
}
|
||||
FileOutputStream(exportFile)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
scheduleNextAutomaticBackup()
|
||||
return@getNotes
|
||||
}
|
||||
|
||||
val exportResult = try {
|
||||
NotesHelper(this).exportNotes(notesToBackup, outputStream)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
|
||||
if (exportResult == ExportResult.EXPORT_FAIL) {
|
||||
toast(R.string.exporting_failed)
|
||||
}
|
||||
|
||||
config.lastAutoBackupTime = DateTime.now().millis
|
||||
scheduleNextAutomaticBackup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.simplemobiletools.notes.pro.helpers
|
||||
|
||||
import android.graphics.Color
|
||||
import org.joda.time.DateTime
|
||||
|
||||
const val NOTE_ID = "note_id"
|
||||
const val OPEN_NOTE_ID = "open_note_id"
|
||||
@ -39,6 +40,26 @@ const val FONT_SIZE_PERCENTAGE = "font_size_percentage"
|
||||
const val EXPORT_MIME_TYPE = "text/plain"
|
||||
const val ADD_NEW_CHECKLIST_ITEMS_TOP = "add_new_checklist_items_top"
|
||||
|
||||
// auto backups
|
||||
const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001
|
||||
const val AUTO_BACKUP_INTERVAL_IN_DAYS = 1
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// gravity
|
||||
const val GRAVITY_START = 0
|
||||
const val GRAVITY_CENTER = 1
|
||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.helpers.ExportResult
|
||||
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.notes.pro.R
|
||||
@ -11,7 +12,10 @@ import com.simplemobiletools.notes.pro.extensions.config
|
||||
import com.simplemobiletools.notes.pro.extensions.notesDB
|
||||
import com.simplemobiletools.notes.pro.models.Note
|
||||
import com.simplemobiletools.notes.pro.models.NoteType
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
|
||||
class NotesHelper(val context: Context) {
|
||||
fun getNotes(callback: (notes: List<Note>) -> Unit) {
|
||||
@ -124,6 +128,18 @@ class NotesHelper(val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun exportNotes(notesToBackup: List<Note>, outputStream: OutputStream): ExportResult {
|
||||
return try {
|
||||
val jsonString = Json.encodeToString(notesToBackup)
|
||||
outputStream.use {
|
||||
it.write(jsonString.toByteArray())
|
||||
}
|
||||
ExportResult.EXPORT_OK
|
||||
} catch (_: Error) {
|
||||
ExportResult.EXPORT_FAIL
|
||||
}
|
||||
}
|
||||
|
||||
enum class ImportResult {
|
||||
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.simplemobiletools.notes.pro.receivers
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.PowerManager
|
||||
import com.simplemobiletools.notes.pro.extensions.backupNotes
|
||||
|
||||
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, "simplenotes:automaticbackupreceiver")
|
||||
wakelock.acquire(3000)
|
||||
context.backupNotes()
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.simplemobiletools.notes.pro.receivers
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.notes.pro.extensions.checkAndBackupNotesOnBoot
|
||||
|
||||
class BootCompletedReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
ensureBackgroundThread {
|
||||
context.apply {
|
||||
checkAndBackupNotesOnBoot()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user