Skip to content

Commit 0e44ad3

Browse files
committed
Merge branch 'master' into feature/641-open-note-ui
2 parents 5f2cc81 + bdf9448 commit 0e44ad3

File tree

15 files changed

+481
-10
lines changed

15 files changed

+481
-10
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ android {
6666
}
6767

6868
dependencies {
69-
implementation 'com.github.esensar:Simple-Commons:2a2c17151e'
69+
implementation 'com.github.SimpleMobileTools:Simple-Commons:91763fe00f'
7070
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
7171
implementation 'androidx.documentfile:documentfile:1.0.1'
7272

app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<uses-permission
77
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
88
android:maxSdkVersion="28" />
9+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
10+
<uses-permission android:name="android.permission.WAKE_LOCK" />
11+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
12+
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
913

1014
<uses-feature
1115
android:name="android.hardware.faketouch"
@@ -107,6 +111,22 @@
107111
android:resource="@xml/widget_info" />
108112
</receiver>
109113

114+
<receiver
115+
android:name=".receivers.BootCompletedReceiver"
116+
android:exported="true">
117+
118+
<intent-filter>
119+
<action android:name="android.intent.action.BOOT_COMPLETED" />
120+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
121+
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
122+
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
123+
</intent-filter>
124+
</receiver>
125+
126+
<receiver
127+
android:name=".receivers.AutomaticBackupReceiver"
128+
android:exported="false" />
129+
110130
<activity-alias
111131
android:name=".activities.SplashActivity.Red"
112132
android:enabled="false"

app/src/main/kotlin/com/simplemobiletools/notes/pro/activities/SettingsActivity.kt

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ import com.simplemobiletools.commons.helpers.*
1313
import com.simplemobiletools.commons.models.RadioItem
1414
import com.simplemobiletools.notes.pro.R
1515
import com.simplemobiletools.notes.pro.dialogs.ExportNotesDialog
16-
import com.simplemobiletools.notes.pro.extensions.config
17-
import com.simplemobiletools.notes.pro.extensions.requestUnlockNotes
18-
import com.simplemobiletools.notes.pro.extensions.updateWidgets
19-
import com.simplemobiletools.notes.pro.extensions.widgetsDB
16+
import com.simplemobiletools.notes.pro.dialogs.ManageAutoBackupsDialog
17+
import com.simplemobiletools.notes.pro.extensions.*
2018
import com.simplemobiletools.notes.pro.helpers.*
2119
import com.simplemobiletools.notes.pro.models.Note
2220
import com.simplemobiletools.notes.pro.models.Widget
@@ -62,6 +60,8 @@ class SettingsActivity : SimpleActivity() {
6260
setupCustomizeWidgetColors()
6361
setupNotesExport()
6462
setupNotesImport()
63+
setupEnableAutomaticBackups()
64+
setupManageAutomaticBackups()
6565
updateTextColors(settings_nested_scrollview)
6666

6767
arrayOf(
@@ -71,6 +71,7 @@ class SettingsActivity : SimpleActivity() {
7171
settings_startup_label,
7272
settings_saving_label,
7373
settings_migrating_label,
74+
settings_backups_label,
7475
).forEach {
7576
it.setTextColor(getProperPrimaryColor())
7677
}
@@ -355,4 +356,43 @@ class SettingsActivity : SimpleActivity() {
355356
showErrorToast(e)
356357
}
357358
}
359+
360+
private fun setupEnableAutomaticBackups() {
361+
settings_backups_label.beVisibleIf(isRPlus())
362+
settings_enable_automatic_backups_holder.beVisibleIf(isRPlus())
363+
settings_enable_automatic_backups.isChecked = config.autoBackup
364+
settings_enable_automatic_backups_holder.setOnClickListener {
365+
val wasBackupDisabled = !config.autoBackup
366+
if (wasBackupDisabled) {
367+
ManageAutoBackupsDialog(
368+
activity = this,
369+
onSuccess = {
370+
enableOrDisableAutomaticBackups(true)
371+
scheduleNextAutomaticBackup()
372+
}
373+
)
374+
} else {
375+
cancelScheduledAutomaticBackup()
376+
enableOrDisableAutomaticBackups(false)
377+
}
378+
}
379+
}
380+
381+
private fun setupManageAutomaticBackups() {
382+
settings_manage_automatic_backups_holder.beVisibleIf(isRPlus() && config.autoBackup)
383+
settings_manage_automatic_backups_holder.setOnClickListener {
384+
ManageAutoBackupsDialog(
385+
activity = this,
386+
onSuccess = {
387+
scheduleNextAutomaticBackup()
388+
}
389+
)
390+
}
391+
}
392+
393+
private fun enableOrDisableAutomaticBackups(enable: Boolean) {
394+
config.autoBackup = enable
395+
settings_enable_automatic_backups.isChecked = enable
396+
settings_manage_automatic_backups_holder.beVisibleIf(enable)
397+
}
358398
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.simplemobiletools.notes.pro.dialogs
2+
3+
import com.simplemobiletools.commons.activities.BaseSimpleActivity
4+
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
5+
import com.simplemobiletools.commons.extensions.setupDialogStuff
6+
import com.simplemobiletools.notes.pro.R
7+
8+
class DateTimePatternInfoDialog(activity: BaseSimpleActivity) {
9+
10+
init {
11+
val view = activity.layoutInflater.inflate(R.layout.datetime_pattern_info_layout, null)
12+
activity.getAlertDialogBuilder()
13+
.setPositiveButton(R.string.ok) { _, _ -> { } }
14+
.apply {
15+
activity.setupDialogStuff(view, this)
16+
}
17+
}
18+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.simplemobiletools.notes.pro.dialogs
2+
3+
import android.view.ViewGroup
4+
import androidx.appcompat.app.AlertDialog
5+
import com.simplemobiletools.commons.dialogs.FilePickerDialog
6+
import com.simplemobiletools.commons.extensions.*
7+
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
8+
import com.simplemobiletools.notes.pro.R
9+
import com.simplemobiletools.notes.pro.activities.SimpleActivity
10+
import com.simplemobiletools.notes.pro.extensions.config
11+
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename
12+
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename_hint
13+
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_folder
14+
import java.io.File
15+
16+
class ManageAutoBackupsDialog(private val activity: SimpleActivity, onSuccess: () -> Unit) {
17+
private val view = (activity.layoutInflater.inflate(R.layout.dialog_manage_automatic_backups, null) as ViewGroup)
18+
private val config = activity.config
19+
private var backupFolder = config.autoBackupFolder
20+
21+
init {
22+
view.apply {
23+
backup_notes_folder.setText(activity.humanizePath(backupFolder))
24+
val filename = config.autoBackupFilename.ifEmpty {
25+
"${activity.getString(R.string.notes)}_%Y%M%D_%h%m%s"
26+
}
27+
28+
backup_notes_filename.setText(filename)
29+
backup_notes_filename_hint.setEndIconOnClickListener {
30+
DateTimePatternInfoDialog(activity)
31+
}
32+
33+
backup_notes_filename_hint.setEndIconOnLongClickListener {
34+
DateTimePatternInfoDialog(activity)
35+
true
36+
}
37+
38+
backup_notes_folder.setOnClickListener {
39+
selectBackupFolder()
40+
}
41+
}
42+
43+
activity.getAlertDialogBuilder()
44+
.setPositiveButton(R.string.ok, null)
45+
.setNegativeButton(R.string.cancel, null)
46+
.apply {
47+
activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { dialog ->
48+
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
49+
val filename = view.backup_notes_filename.value
50+
when {
51+
filename.isEmpty() -> activity.toast(R.string.empty_name)
52+
filename.isAValidFilename() -> {
53+
val file = File(backupFolder, "$filename.json")
54+
if (file.exists() && !file.canWrite()) {
55+
activity.toast(R.string.name_taken)
56+
return@setOnClickListener
57+
}
58+
59+
ensureBackgroundThread {
60+
config.apply {
61+
autoBackupFolder = backupFolder
62+
autoBackupFilename = filename
63+
}
64+
65+
activity.runOnUiThread {
66+
onSuccess()
67+
}
68+
69+
dialog.dismiss()
70+
}
71+
}
72+
73+
else -> activity.toast(R.string.invalid_name)
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
private fun selectBackupFolder() {
81+
activity.hideKeyboard(view.backup_notes_filename)
82+
FilePickerDialog(activity, backupFolder, false, showFAB = true) { path ->
83+
activity.handleSAFDialog(path) { grantedSAF ->
84+
if (!grantedSAF) {
85+
return@handleSAFDialog
86+
}
87+
88+
activity.handleSAFDialogSdk30(path) { grantedSAF30 ->
89+
if (!grantedSAF30) {
90+
return@handleSAFDialogSdk30
91+
}
92+
93+
backupFolder = path
94+
view.backup_notes_folder.setText(activity.humanizePath(path))
95+
}
96+
}
97+
}
98+
}
99+
}

app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Context.kt

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
package com.simplemobiletools.notes.pro.extensions
22

3+
import android.app.AlarmManager
4+
import android.app.PendingIntent
35
import android.appwidget.AppWidgetManager
46
import android.content.ComponentName
57
import android.content.Context
68
import android.content.Intent
9+
import androidx.core.app.AlarmManagerCompat
710
import com.simplemobiletools.commons.activities.BaseSimpleActivity
11+
import com.simplemobiletools.commons.extensions.*
12+
import com.simplemobiletools.commons.helpers.ExportResult
13+
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
14+
import com.simplemobiletools.commons.helpers.isRPlus
815
import com.simplemobiletools.notes.pro.R
916
import com.simplemobiletools.notes.pro.databases.NotesDatabase
1017
import com.simplemobiletools.notes.pro.dialogs.UnlockNotesDialog
11-
import com.simplemobiletools.notes.pro.helpers.Config
12-
import com.simplemobiletools.notes.pro.helpers.MyWidgetProvider
18+
import com.simplemobiletools.notes.pro.helpers.*
1319
import com.simplemobiletools.notes.pro.interfaces.NotesDao
1420
import com.simplemobiletools.notes.pro.interfaces.WidgetsDao
1521
import com.simplemobiletools.notes.pro.models.Note
22+
import com.simplemobiletools.notes.pro.receivers.AutomaticBackupReceiver
23+
import org.joda.time.DateTime
24+
import java.io.File
25+
import java.io.FileOutputStream
1626

1727
val Context.config: Config get() = Config.newInstance(applicationContext)
1828

@@ -43,3 +53,111 @@ fun BaseSimpleActivity.requestUnlockNotes(notes: List<Note>, callback: (unlocked
4353
callback(emptyList())
4454
}
4555
}
56+
57+
fun Context.getAutomaticBackupIntent(): PendingIntent {
58+
val intent = Intent(this, AutomaticBackupReceiver::class.java)
59+
return PendingIntent.getBroadcast(this, AUTOMATIC_BACKUP_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
60+
}
61+
62+
fun Context.scheduleNextAutomaticBackup() {
63+
if (config.autoBackup) {
64+
val backupAtMillis = getNextAutoBackupTime().millis
65+
val pendingIntent = getAutomaticBackupIntent()
66+
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
67+
try {
68+
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, backupAtMillis, pendingIntent)
69+
} catch (e: Exception) {
70+
showErrorToast(e)
71+
}
72+
}
73+
}
74+
75+
fun Context.cancelScheduledAutomaticBackup() {
76+
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
77+
alarmManager.cancel(getAutomaticBackupIntent())
78+
}
79+
80+
fun Context.checkAndBackupNotesOnBoot() {
81+
if (config.autoBackup) {
82+
val previousRealBackupTime = config.lastAutoBackupTime
83+
val previousScheduledBackupTime = getPreviousAutoBackupTime().millis
84+
val missedPreviousBackup = previousRealBackupTime < previousScheduledBackupTime
85+
if (missedPreviousBackup) {
86+
// device was probably off at the scheduled time so backup now
87+
backupNotes()
88+
}
89+
}
90+
}
91+
92+
fun Context.backupNotes() {
93+
require(isRPlus())
94+
ensureBackgroundThread {
95+
val config = config
96+
NotesHelper(this).getNotes { notesToBackup ->
97+
if (notesToBackup.isEmpty()) {
98+
toast(R.string.no_entries_for_exporting)
99+
config.lastAutoBackupTime = DateTime.now().millis
100+
scheduleNextAutomaticBackup()
101+
return@getNotes
102+
}
103+
104+
105+
val now = DateTime.now()
106+
val year = now.year.toString()
107+
val month = now.monthOfYear.ensureTwoDigits()
108+
val day = now.dayOfMonth.ensureTwoDigits()
109+
val hours = now.hourOfDay.ensureTwoDigits()
110+
val minutes = now.minuteOfHour.ensureTwoDigits()
111+
val seconds = now.secondOfMinute.ensureTwoDigits()
112+
113+
val filename = config.autoBackupFilename
114+
.replace("%Y", year, false)
115+
.replace("%M", month, false)
116+
.replace("%D", day, false)
117+
.replace("%h", hours, false)
118+
.replace("%m", minutes, false)
119+
.replace("%s", seconds, false)
120+
121+
val outputFolder = File(config.autoBackupFolder).apply {
122+
mkdirs()
123+
}
124+
125+
var exportFile = File(outputFolder, "$filename.json")
126+
var exportFilePath = exportFile.absolutePath
127+
val outputStream = try {
128+
if (hasProperStoredFirstParentUri(exportFilePath)) {
129+
val exportFileUri = createDocumentUriUsingFirstParentTreeUri(exportFilePath)
130+
if (!getDoesFilePathExist(exportFilePath)) {
131+
createSAFFileSdk30(exportFilePath)
132+
}
133+
applicationContext.contentResolver.openOutputStream(exportFileUri, "wt") ?: FileOutputStream(exportFile)
134+
} else {
135+
var num = 0
136+
while (getDoesFilePathExist(exportFilePath) && !exportFile.canWrite()) {
137+
num++
138+
exportFile = File(outputFolder, "${filename}_${num}.json")
139+
exportFilePath = exportFile.absolutePath
140+
}
141+
FileOutputStream(exportFile)
142+
}
143+
} catch (e: Exception) {
144+
showErrorToast(e)
145+
scheduleNextAutomaticBackup()
146+
return@getNotes
147+
}
148+
149+
val exportResult = try {
150+
NotesHelper(this).exportNotes(notesToBackup, outputStream)
151+
} catch (e: Exception) {
152+
showErrorToast(e)
153+
}
154+
155+
if (exportResult == ExportResult.EXPORT_FAIL) {
156+
toast(R.string.exporting_failed)
157+
}
158+
159+
config.lastAutoBackupTime = DateTime.now().millis
160+
scheduleNextAutomaticBackup()
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)