Added exporting and importing all notes for Android 10+

This commit is contained in:
Agnieszka C
2022-02-20 11:07:13 +01:00
parent 3ddc03f442
commit 2e872db837
39 changed files with 307 additions and 54 deletions

View File

@ -44,6 +44,8 @@ import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.nio.charset.Charset
import java.util.*
import kotlin.collections.ArrayList
@ -58,6 +60,9 @@ class MainActivity : SimpleActivity() {
private val PICK_OPEN_FILE_INTENT = 1
private val PICK_EXPORT_FILE_INTENT = 2
private val PICK_IMPORT_NOTES_INTENT = 3
private val PICK_EXPORT_NOTES_INTENT = 4
private lateinit var mCurrentNote: Note
private var mNotes = ArrayList<Note>()
private var mAdapter: NotesPagerAdapter? = null
@ -72,6 +77,7 @@ class MainActivity : SimpleActivity() {
private var searchIndex = 0
private var searchMatches = emptyList<Int>()
private var isSearchActive = false
private val notesExporter by lazy { NotesExporter(this) }
private lateinit var searchQueryET: MyEditText
private lateinit var searchPrevBtn: ImageView
@ -171,11 +177,13 @@ class MainActivity : SimpleActivity() {
findItem(R.id.rename_note).isVisible = multipleNotesExist
findItem(R.id.open_note).isVisible = multipleNotesExist
findItem(R.id.delete_note).isVisible = multipleNotesExist
findItem(R.id.export_all_notes).isVisible = multipleNotesExist && hasPermission(PERMISSION_WRITE_STORAGE)
findItem(R.id.export_all_notes).isVisible = multipleNotesExist && !isQPlus()
findItem(R.id.export_notes).isVisible = multipleNotesExist && isQPlus()
findItem(R.id.open_search).isVisible = !isCurrentItemChecklist
findItem(R.id.remove_done_items).isVisible = isCurrentItemChecklist
findItem(R.id.sort_checklist).isVisible = isCurrentItemChecklist
findItem(R.id.import_folder).isVisible = hasPermission(PERMISSION_READ_STORAGE)
findItem(R.id.import_folder).isVisible = !isQPlus()
findItem(R.id.import_notes).isVisible = isQPlus()
findItem(R.id.lock_note).isVisible = mNotes.isNotEmpty() && !mCurrentNote.isLocked()
findItem(R.id.unlock_note).isVisible = mNotes.isNotEmpty() && mCurrentNote.isLocked()
@ -208,6 +216,8 @@ class MainActivity : SimpleActivity() {
R.id.import_folder -> openFolder()
R.id.export_as_file -> fragment?.handleUnlocking { tryExportAsFile() }
R.id.export_all_notes -> tryExportAllNotes()
R.id.export_notes -> tryExportNotes()
R.id.import_notes -> tryImportNotes()
R.id.print -> fragment?.handleUnlocking { printText() }
R.id.delete_note -> fragment?.handleUnlocking { displayDeleteNotePrompt() }
R.id.settings -> launchSettings()
@ -269,6 +279,11 @@ class MainActivity : SimpleActivity() {
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
applicationContext.contentResolver.takePersistableUriPermission(resultData.data!!, takeFlags)
showExportFilePickUpdateDialog(resultData.dataString!!, getCurrentNoteValue())
} else if (requestCode == PICK_EXPORT_NOTES_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
val outputStream = contentResolver.openOutputStream(resultData.data!!)
exportNotesTo(outputStream)
} else if (requestCode == PICK_IMPORT_NOTES_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
importNotesFrom(resultData.data!!)
}
}
@ -785,15 +800,21 @@ class MainActivity : SimpleActivity() {
}
private fun openFolder() {
FilePickerDialog(this, pickFile = false, canAddShowHiddenButton = true) {
openFolder(it) {
ImportFolderDialog(this, it.path) {
NotesHelper(this).getNotes {
mNotes = it
showSaveButton = false
initViewPager()
handlePermission(PERMISSION_READ_STORAGE) { hasPermission ->
if (hasPermission) {
FilePickerDialog(this, pickFile = false, canAddShowHiddenButton = true) {
openFolder(it) {
ImportFolderDialog(this, it.path) {
NotesHelper(this).getNotes {
mNotes = it
showSaveButton = false
initViewPager()
}
}
}
}
} else {
toast(R.string.no_storage_permissions)
}
}
}
@ -842,6 +863,77 @@ class MainActivity : SimpleActivity() {
}
}
private fun tryExportNotes() {
val fileName = "${getString(R.string.notes)}_${getCurrentFormattedDateTime()}"
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = EXPORT_MIME_TYPE
putExtra(Intent.EXTRA_TITLE, fileName)
addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(this, PICK_EXPORT_NOTES_INTENT)
}
}
private fun exportNotesTo(outputStream: OutputStream?) {
toast(R.string.exporting)
ensureBackgroundThread {
notesExporter.exportNotes(outputStream) {
val toastId = when (it) {
NotesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
else -> R.string.exporting_failed
}
toast(toastId)
}
}
}
private fun tryImportNotes() {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = EXPORT_MIME_TYPE
startActivityForResult(this, PICK_IMPORT_NOTES_INTENT)
}
}
private fun importNotes(path: String) {
toast(R.string.importing)
ensureBackgroundThread {
NotesImporter(this).importNotes(path) {
toast(
when (it) {
NotesImporter.ImportResult.IMPORT_OK -> R.string.importing_successful
NotesImporter.ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
else -> R.string.no_items_found
}
)
initViewPager()
}
}
}
private fun importNotesFrom(uri: Uri) {
when (uri.scheme) {
"file" -> importNotes(uri.path!!)
"content" -> {
val tempFile = getTempFile("messages", "backup.json")
if (tempFile == null) {
toast(R.string.unknown_error_occurred)
return
}
try {
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
importNotes(tempFile.absolutePath)
} catch (e: Exception) {
showErrorToast(e)
}
}
else -> toast(R.string.invalid_file_format)
}
}
private fun showExportFilePickUpdateDialog(exportPath: String, textToExport: String) {
val items = arrayListOf(
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
@ -871,6 +963,8 @@ class MainActivity : SimpleActivity() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
exportAllNotes()
} else {
toast(R.string.no_storage_permissions)
}
}
}

View File

@ -36,6 +36,7 @@ const val USE_INCOGNITO_MODE = "use_incognito_mode"
const val LAST_CREATED_NOTE_TYPE = "last_created_note_type"
const val MOVE_DONE_CHECKLIST_ITEMS = "move_undone_checklist_items" // it has been replaced from moving undone items at the top to moving done to bottom
const val FONT_SIZE_PERCENTAGE = "font_size_percentage"
const val EXPORT_MIME_TYPE = "application/json"
// gravity
const val GRAVITY_LEFT = 0

View File

@ -0,0 +1,52 @@
package com.simplemobiletools.notes.pro.helpers
import android.content.Context
import com.google.gson.Gson
import com.google.gson.stream.JsonWriter
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.notes.pro.extensions.notesDB
import com.simplemobiletools.notes.pro.models.Note
import java.io.OutputStream
class NotesExporter(private val context: Context) {
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK
}
private val gson = Gson()
fun exportNotes(outputStream: OutputStream?, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ExportResult) -> Unit) {
ensureBackgroundThread {
if (outputStream == null) {
callback.invoke(ExportResult.EXPORT_FAIL)
return@ensureBackgroundThread
}
val writer = JsonWriter(outputStream.bufferedWriter())
writer.use {
try {
var written = 0
writer.beginArray()
val notes = context.notesDB.getNotes() as ArrayList<Note>
val totalNotes = notes.size
for (note in notes) {
if (note.protectionType === PROTECTION_NONE) {
val noteToSave = getNoteToExport(note)
writer.jsonValue(gson.toJson(noteToSave))
written++
onProgress.invoke(totalNotes, written)
}
}
writer.endArray()
callback.invoke(ExportResult.EXPORT_OK)
} catch (e: Exception) {
callback.invoke(ExportResult.EXPORT_FAIL)
}
}
}
}
private fun getNoteToExport(note: Note): Note {
return Note(null, note.title, note.getNoteStoredValue(context) ?: "", note.type, "", PROTECTION_NONE, "")
}
}

View File

@ -0,0 +1,64 @@
package com.simplemobiletools.notes.pro.helpers
import android.content.Context
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.notes.pro.extensions.notesDB
import com.simplemobiletools.notes.pro.models.Note
import java.io.File
class NotesImporter(private val context: Context) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}
private val gson = Gson()
private var notesImported = 0
private var notesFailed = 0
fun importNotes(path: String, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ImportResult) -> Unit) {
ensureBackgroundThread {
try {
val inputStream = if (path.contains("/")) {
File(path).inputStream()
} else {
context.assets.open(path)
}
inputStream.bufferedReader().use { reader ->
val json = reader.readText()
val type = object : TypeToken<List<Note>>() {}.type
val notes = gson.fromJson<List<Note>>(json, type)
val totalNotes = notes.size
if (totalNotes <= 0) {
callback.invoke(ImportResult.IMPORT_NOTHING_NEW)
return@ensureBackgroundThread
}
onProgress.invoke(totalNotes, notesImported)
for (note in notes) {
val exists = context.notesDB.getNoteIdWithTitle(note.title) != null
if (!exists) {
context.notesDB.insertOrUpdate(note)
notesImported++
onProgress.invoke(totalNotes, notesImported)
}
}
}
} catch (e: Exception) {
context.showErrorToast(e)
notesFailed++
}
callback.invoke(
when {
notesImported == 0 -> ImportResult.IMPORT_FAIL
notesFailed > 0 -> ImportResult.IMPORT_PARTIAL
else -> ImportResult.IMPORT_OK
}
)
}
}
}