Merge pull request #644 from Merkost/migration_settings

Notes import/export moved to settings
This commit is contained in:
Tibor Kaputa 2023-07-10 16:01:44 +02:00 committed by GitHub
commit 6c7d3aa0cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 450 additions and 466 deletions

View File

@ -1,7 +1,10 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
}
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
@ -10,7 +13,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 33
compileSdk 33
defaultConfig {
applicationId "com.simplemobiletools.notes.pro"
@ -70,4 +73,6 @@ dependencies {
kapt 'androidx.room:room-compiler:2.5.1'
implementation 'androidx.room:room-runtime:2.5.1'
annotationProcessor 'androidx.room:room-compiler:2.5.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
}

View File

@ -1,3 +1,27 @@
-keep class com.simplemobiletools.notes.pro.models.* {
<fields>;
}
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}

View File

@ -43,13 +43,11 @@ import com.simplemobiletools.notes.pro.dialogs.*
import com.simplemobiletools.notes.pro.extensions.*
import com.simplemobiletools.notes.pro.fragments.TextFragment
import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.helpers.NotesImporter.ImportResult
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.item_checklist.*
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.nio.charset.Charset
import java.util.*
@ -63,11 +61,8 @@ 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 mNotes = listOf<Note>()
private var mAdapter: NotesPagerAdapter? = null
private var noteViewWithTextSelected: MyEditText? = null
private var saveNoteButton: MenuItem? = null
@ -132,6 +127,12 @@ class MainActivity : SimpleActivity() {
initViewPager()
}
NotesHelper(this).getNotes { lastestNotes ->
if (mNotes.size != lastestNotes.size) {
initViewPager()
}
}
refreshMenuItems()
pager_tab_strip.apply {
setTextSize(TypedValue.COMPLEX_UNIT_PX, getPercentageFontSize())
@ -171,31 +172,29 @@ class MainActivity : SimpleActivity() {
main_toolbar.menu.apply {
findItem(R.id.undo).apply {
isVisible = showUndoButton && mCurrentNote.type == NoteType.TYPE_TEXT.value
isVisible = showUndoButton && mCurrentNote.type == NoteType.TYPE_TEXT
icon?.alpha = if (isEnabled) 255 else 127
}
findItem(R.id.redo).apply {
isVisible = showRedoButton && mCurrentNote.type == NoteType.TYPE_TEXT.value
isVisible = showRedoButton && mCurrentNote.type == NoteType.TYPE_TEXT
icon?.alpha = if (isEnabled) 255 else 127
}
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
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 = !isQPlus()
findItem(R.id.import_notes).isVisible = isQPlus()
findItem(R.id.lock_note).isVisible = mNotes.isNotEmpty() && (::mCurrentNote.isInitialized && !mCurrentNote.isLocked())
findItem(R.id.unlock_note).isVisible = mNotes.isNotEmpty() && (::mCurrentNote.isInitialized && mCurrentNote.isLocked())
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(R.bool.hide_google_relations)
saveNoteButton = findItem(R.id.save_note)
saveNoteButton!!.isVisible =
!config.autosaveNotes && showSaveButton && (::mCurrentNote.isInitialized && mCurrentNote.type == NoteType.TYPE_TEXT.value)
!config.autosaveNotes && showSaveButton && (::mCurrentNote.isInitialized && mCurrentNote.type == NoteType.TYPE_TEXT)
}
pager_tab_strip.beVisibleIf(multipleNotesExist)
@ -223,8 +222,6 @@ class MainActivity : SimpleActivity() {
R.id.open_file -> tryOpenFile()
R.id.import_folder -> openFolder()
R.id.export_as_file -> fragment?.handleUnlocking { tryExportAsFile() }
R.id.export_all_notes -> tryExportNotes()
R.id.import_notes -> tryImportNotes()
R.id.print -> fragment?.handleUnlocking { printText() }
R.id.delete_note -> fragment?.handleUnlocking { displayDeleteNotePrompt() }
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
@ -288,15 +285,10 @@ 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) {
tryImportingAsJson(resultData.data!!)
}
}
private fun isCurrentItemChecklist() = if (::mCurrentNote.isInitialized) mCurrentNote.type == NoteType.TYPE_CHECKLIST.value else false
private fun isCurrentItemChecklist() = if (::mCurrentNote.isInitialized) mCurrentNote.type == NoteType.TYPE_CHECKLIST else false
@SuppressLint("NewApi")
private fun checkShortcuts() {
@ -368,10 +360,10 @@ class MainActivity : SimpleActivity() {
val file = File(realPath)
handleUri(Uri.fromFile(file))
} else if (intent.getBooleanExtra(NEW_TEXT_NOTE, false)) {
val newTextNote = Note(null, getCurrentFormattedDateTime(), "", NoteType.TYPE_TEXT.value, "", PROTECTION_NONE, "")
val newTextNote = Note(null, getCurrentFormattedDateTime(), "", NoteType.TYPE_TEXT, "", PROTECTION_NONE, "")
addNewNote(newTextNote)
} else if (intent.getBooleanExtra(NEW_CHECKLIST, false)) {
val newChecklist = Note(null, getCurrentFormattedDateTime(), "", NoteType.TYPE_CHECKLIST.value, "", PROTECTION_NONE, "")
val newChecklist = Note(null, getCurrentFormattedDateTime(), "", NoteType.TYPE_CHECKLIST, "", PROTECTION_NONE, "")
addNewNote(newChecklist)
} else {
handleUri(data!!)
@ -446,7 +438,7 @@ class MainActivity : SimpleActivity() {
}
}
if (!config.showKeyboard || mCurrentNote.type == NoteType.TYPE_CHECKLIST.value) {
if (!config.showKeyboard || mCurrentNote.type == NoteType.TYPE_CHECKLIST) {
hideKeyboard()
}
refreshMenuItems()
@ -678,7 +670,7 @@ class MainActivity : SimpleActivity() {
val checklistItems = fileText.parseChecklistItems()
if (checklistItems != null) {
val title = it.absolutePath.getFilenameFromPath().substringBeforeLast('.')
val note = Note(null, title, fileText, NoteType.TYPE_CHECKLIST.value, "", PROTECTION_NONE, "")
val note = Note(null, title, fileText, NoteType.TYPE_CHECKLIST, "", PROTECTION_NONE, "")
runOnUiThread {
OpenFileDialog(this, it.path) {
displayNewNoteDialog(note.value, title = it.title, it.path, setChecklistAsDefault = true)
@ -732,29 +724,23 @@ class MainActivity : SimpleActivity() {
}
private fun importUri(uri: Uri) {
tryImportingAsJson(uri, force = true, showToasts = false) { success ->
if (success) {
return@tryImportingAsJson
}
when (uri.scheme) {
"file" -> openPath(uri.path!!)
"content" -> {
val realPath = getRealPathFromURI(uri)
if (hasPermission(PERMISSION_READ_STORAGE)) {
if (realPath != null) {
openPath(realPath)
} else {
R.string.unknown_error_occurred
}
} else if (realPath != null && realPath != "") {
checkFile(realPath, false) {
addNoteFromUri(uri, realPath.getFilenameFromPath())
}
when (uri.scheme) {
"file" -> openPath(uri.path!!)
"content" -> {
val realPath = getRealPathFromURI(uri)
if (hasPermission(PERMISSION_READ_STORAGE)) {
if (realPath != null) {
openPath(realPath)
} else {
checkUri(uri) {
addNoteFromUri(uri)
}
R.string.unknown_error_occurred
}
} else if (realPath != null && realPath != "") {
checkFile(realPath, false) {
addNoteFromUri(uri, realPath.getFilenameFromPath())
}
} else {
checkUri(uri) {
addNoteFromUri(uri)
}
}
}
@ -786,7 +772,7 @@ class MainActivity : SimpleActivity() {
}
}
val noteType = if (checklistItems != null) NoteType.TYPE_CHECKLIST.value else NoteType.TYPE_TEXT.value
val noteType = if (checklistItems != null) NoteType.TYPE_CHECKLIST else NoteType.TYPE_TEXT
if (!canSyncNoteWithFile) {
val note = Note(null, noteTitle, content, noteType, "", PROTECTION_NONE, "")
displayNewNoteDialog(note.value, title = noteTitle, "")
@ -812,9 +798,9 @@ class MainActivity : SimpleActivity() {
val fileText = it.readText().trim()
val checklistItems = fileText.parseChecklistItems()
val note = if (checklistItems != null) {
Note(null, title.substringBeforeLast('.'), fileText, NoteType.TYPE_CHECKLIST.value, "", PROTECTION_NONE, "")
Note(null, title.substringBeforeLast('.'), fileText, NoteType.TYPE_CHECKLIST, "", PROTECTION_NONE, "")
} else {
Note(null, title, "", NoteType.TYPE_TEXT.value, path, PROTECTION_NONE, "")
Note(null, title, "", NoteType.TYPE_TEXT, path, PROTECTION_NONE, "")
}
if (mNotes.any { it.title.equals(note.title, true) }) {
@ -885,10 +871,10 @@ class MainActivity : SimpleActivity() {
private fun exportAsFile() {
ExportFileDialog(this, mCurrentNote) {
val textToExport = if (mCurrentNote.type == NoteType.TYPE_TEXT.value) getCurrentNoteText() else mCurrentNote.value
val textToExport = if (mCurrentNote.type == NoteType.TYPE_TEXT) getCurrentNoteText() else mCurrentNote.value
if (textToExport == null || textToExport.isEmpty()) {
toast(R.string.unknown_error_occurred)
} else if (mCurrentNote.type == NoteType.TYPE_TEXT.value) {
} else if (mCurrentNote.type == NoteType.TYPE_TEXT) {
showExportFilePickUpdateDialog(it, textToExport)
} else {
tryExportNoteValueToFile(it, mCurrentNote.title, textToExport, true)
@ -896,138 +882,6 @@ class MainActivity : SimpleActivity() {
}
}
private fun tryExportNotes() {
if (isQPlus()) {
hideKeyboard()
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)
try {
startActivityForResult(this, PICK_EXPORT_NOTES_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
} else {
tryExportAllNotesBelowQ()
}
}
private fun requestUnlockNotes(callback: (unlockedNoteIds: List<Long>) -> Unit) {
val lockedNotes = mNotes.filter { it.isLocked() }
if (lockedNotes.isNotEmpty()) {
runOnUiThread {
UnlockNotesDialog(this, lockedNotes, callback)
}
} else {
callback(emptyList())
}
}
private fun exportNotesTo(outputStream: OutputStream?) {
ensureBackgroundThread {
NotesHelper(this).getNotes {
mNotes = it
requestUnlockNotes { unlockedNoteIds ->
toast(R.string.exporting)
val notesExporter = NotesExporter(this)
notesExporter.exportNotes(mNotes, unlockedNoteIds, outputStream) { result ->
val toastId = when (result) {
NotesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
else -> R.string.exporting_failed
}
toast(toastId)
}
}
}
}
}
private fun tryImportNotes() {
hideKeyboard()
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = EXPORT_MIME_TYPE
try {
startActivityForResult(this, PICK_IMPORT_NOTES_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
private fun tryImportingAsJson(uri: Uri, force: Boolean = false, showToasts: Boolean = true, callback: ((success: Boolean) -> Unit)? = null) {
val path: String
val filename: String
when (uri.scheme) {
"file" -> {
path = uri.path!!
filename = path.getFilenameFromPath()
}
"content" -> {
val tempFile = getTempFile("messages", "backup.txt")
if (tempFile == null) {
maybeToast(R.string.unknown_error_occurred, showToasts)
callback?.invoke(false)
return
}
try {
filename = getFilenameFromUri(uri)
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
path = tempFile.absolutePath
} catch (e: Exception) {
showErrorToast(e)
callback?.invoke(false)
return
}
}
else -> {
maybeToast(R.string.invalid_file_format, showToasts)
callback?.invoke(false)
return
}
}
maybeToast(R.string.importing, showToasts)
ensureBackgroundThread {
NotesImporter(this).importNotes(path, filename, force) { importResult ->
if (importResult == ImportResult.IMPORT_FAIL) {
maybeToast(R.string.no_new_items, showToasts)
runOnUiThread { callback?.invoke(false) }
return@importNotes
}
toast(
when (importResult) {
ImportResult.IMPORT_OK -> R.string.importing_successful
ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
else -> R.string.no_new_items
}
)
initViewPager()
runOnUiThread { callback?.invoke(true) }
}
}
}
private fun maybeToast(id: Int, show: Boolean) {
if (show) {
toast(id)
}
}
private fun showExportFilePickUpdateDialog(exportPath: String, textToExport: String) {
val items = arrayListOf(
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
@ -1053,73 +907,6 @@ class MainActivity : SimpleActivity() {
}
}
private fun tryExportAllNotesBelowQ() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
exportAllNotesBelowQ()
} else {
toast(R.string.no_storage_permissions)
}
}
}
private fun exportAllNotesBelowQ() {
ensureBackgroundThread {
NotesHelper(this).getNotes { notes ->
mNotes = notes
requestUnlockNotes { unlockedNoteIds ->
ExportFilesDialog(this, mNotes) { parent, extension ->
val items = arrayListOf(
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
RadioItem(EXPORT_FILE_NO_SYNC, getString(R.string.only_export_file_content))
)
RadioGroupDialog(this, items) { any ->
val syncFile = any as Int == EXPORT_FILE_SYNC
var failCount = 0
mNotes.filter { !it.isLocked() || it.id in unlockedNoteIds }.forEachIndexed { index, note ->
val filename = if (extension.isEmpty()) note.title else "${note.title}.$extension"
val file = File(parent, filename)
if (!filename.isAValidFilename()) {
toast(String.format(getString(R.string.filename_invalid_characters_placeholder, filename)))
} else {
val noteStoredValue = note.getNoteStoredValue(this) ?: ""
tryExportNoteValueToFile(file.absolutePath, mCurrentNote.title, note.value, false) { exportedSuccessfully ->
if (exportedSuccessfully) {
if (syncFile) {
note.path = file.absolutePath
note.value = ""
} else {
note.path = ""
note.value = noteStoredValue
}
NotesHelper(this).insertOrUpdateNote(note)
}
if (mCurrentNote.id == note.id) {
mCurrentNote.value = note.value
mCurrentNote.path = note.path
getPagerAdapter().updateCurrentNoteData(view_pager.currentItem, mCurrentNote.path, mCurrentNote.value)
}
if (!exportedSuccessfully) {
failCount++
}
if (index == mNotes.size - 1) {
toast(if (failCount == 0) R.string.exporting_successful else R.string.exporting_some_entries_failed)
}
}
}
}
}
}
}
}
}
}
fun tryExportNoteValueToFile(path: String, title: String, content: String, showSuccessToasts: Boolean, callback: ((success: Boolean) -> Unit)? = null) {
if (path.startsWith("content://")) {
exportNoteValueToUri(Uri.parse(path), title, content, showSuccessToasts, callback)
@ -1238,7 +1025,7 @@ class MainActivity : SimpleActivity() {
private fun getCurrentNoteText() = getPagerAdapter().getCurrentNoteViewText(view_pager.currentItem)
private fun getCurrentNoteValue(): String {
return if (mCurrentNote.type == NoteType.TYPE_TEXT.value) {
return if (mCurrentNote.type == NoteType.TYPE_TEXT) {
getCurrentNoteText() ?: ""
} else {
getPagerAdapter().getNoteChecklistItems(view_pager.currentItem) ?: ""
@ -1246,7 +1033,7 @@ class MainActivity : SimpleActivity() {
}
private fun getPrintableText(): String {
return if (mCurrentNote.type == NoteType.TYPE_TEXT.value) {
return if (mCurrentNote.type == NoteType.TYPE_TEXT) {
getCurrentNoteText() ?: ""
} else {
var printableText = ""
@ -1261,7 +1048,7 @@ class MainActivity : SimpleActivity() {
private fun saveCurrentNote(force: Boolean) {
getPagerAdapter().saveCurrentNote(view_pager.currentItem, force)
if (mCurrentNote.type == NoteType.TYPE_CHECKLIST.value) {
if (mCurrentNote.type == NoteType.TYPE_CHECKLIST) {
mCurrentNote.value = getPagerAdapter().getNoteChecklistItems(view_pager.currentItem) ?: ""
}
}
@ -1359,8 +1146,8 @@ class MainActivity : SimpleActivity() {
}
private fun shareText() {
val text = if (mCurrentNote.type == NoteType.TYPE_TEXT.value) getCurrentNoteText() else mCurrentNote.value
if (text == null || text.isEmpty()) {
val text = if (mCurrentNote.type == NoteType.TYPE_TEXT) getCurrentNoteText() else mCurrentNote.value
if (text.isNullOrEmpty()) {
toast(R.string.cannot_share_empty_text)
return
}

View File

@ -1,25 +1,33 @@
package com.simplemobiletools.notes.pro.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import androidx.activity.result.contract.ActivityResultContracts
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.commons.extensions.*
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.helpers.*
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.Widget
import kotlinx.android.synthetic.main.activity_settings.*
import java.util.*
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.Locale
import kotlin.system.exitProcess
class SettingsActivity : SimpleActivity() {
private val notesFileType = "application/json"
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
@ -50,6 +58,8 @@ class SettingsActivity : SimpleActivity() {
setupCursorPlacement()
setupIncognitoMode()
setupCustomizeWidgetColors()
setupNotesExport()
setupNotesImport()
updateTextColors(settings_nested_scrollview)
arrayOf(
@ -57,7 +67,8 @@ class SettingsActivity : SimpleActivity() {
settings_general_settings_label,
settings_text_label,
settings_startup_label,
settings_saving_label
settings_saving_label,
settings_migrating_label,
).forEach {
it.setTextColor(getProperPrimaryColor())
}
@ -68,6 +79,26 @@ class SettingsActivity : SimpleActivity() {
return super.onCreateOptionsMenu(menu)
}
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null) {
toast(R.string.importing)
importNotes(uri)
}
}
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(notesFileType)) { uri ->
if (uri != null) {
toast(R.string.exporting)
NotesHelper(this).getNotes { notes ->
requestUnlockNotes(notes) { unlockedNotes ->
val notLockedNotes = notes.filterNot { it.isLocked() }
val notesToExport = unlockedNotes + notLockedNotes
exportNotes(notesToExport, uri)
}
}
}
}
private fun setupCustomizeColors() {
settings_color_customization_holder.setOnClickListener {
startCustomizationActivity()
@ -257,4 +288,63 @@ class SettingsActivity : SimpleActivity() {
config.useIncognitoMode = settings_use_incognito_mode.isChecked
}
}
private fun setupNotesExport() {
settings_export_notes_holder.setOnClickListener {
ExportNotesDialog(this) { filename ->
saveDocument.launch(filename)
}
}
}
private fun setupNotesImport() {
settings_import_notes_holder.setOnClickListener {
getContent.launch(notesFileType)
}
}
private fun exportNotes(notes: List<Note>, uri: Uri) {
if (notes.isEmpty()) {
toast(R.string.no_entries_for_exporting)
} else {
try {
val outputStream = contentResolver.openOutputStream(uri)!!
val jsonString = Json.encodeToString(notes)
outputStream.use {
it.write(jsonString.toByteArray())
}
toast(R.string.exporting_successful)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
private fun importNotes(uri: Uri) {
try {
val jsonString = contentResolver.openInputStream(uri)!!.use { inputStream ->
inputStream.bufferedReader().readText()
}
val objects = Json.decodeFromString<List<Note>>(jsonString)
if (objects.isEmpty()) {
toast(R.string.no_entries_for_importing)
return
}
NotesHelper(this).importNotes(this, objects) { importResult ->
when (importResult) {
NotesHelper.ImportResult.IMPORT_OK -> toast(R.string.importing_successful)
NotesHelper.ImportResult.IMPORT_PARTIAL -> toast(R.string.importing_some_entries_failed)
NotesHelper.ImportResult.IMPORT_NOTHING_NEW -> toast(R.string.no_new_items)
else -> toast(R.string.importing_failed)
}
}
} catch (_: SerializationException) {
toast(R.string.invalid_file_format)
} catch (_: IllegalArgumentException) {
toast(R.string.invalid_file_format)
} catch (e: Exception) {
showErrorToast(e)
}
}
}

View File

@ -27,8 +27,11 @@ import com.simplemobiletools.notes.pro.extensions.widgetsDB
import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.models.ChecklistItem
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
import com.simplemobiletools.notes.pro.models.Widget
import kotlinx.android.synthetic.main.widget_config.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
class WidgetConfigureActivity : SimpleActivity() {
private var mBgAlpha = 0f
@ -39,7 +42,7 @@ class WidgetConfigureActivity : SimpleActivity() {
private var mCurrentNoteId = 0L
private var mIsCustomizingColors = false
private var mShowTitle = false
private var mNotes = ArrayList<Note>()
private var mNotes = listOf<Note>()
public override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
@ -156,7 +159,7 @@ class WidgetConfigureActivity : SimpleActivity() {
mCurrentNoteId = note.id!!
notes_picker_value.text = note.title
text_note_view_title.text = note.title
if (note.type == NoteType.TYPE_CHECKLIST.value) {
if (note.type == NoteType.TYPE_CHECKLIST) {
val checklistItemType = object : TypeToken<List<ChecklistItem>>() {}.type
val items = Gson().fromJson<ArrayList<ChecklistItem>>(note.value, checklistItemType) ?: ArrayList(1)
items.apply {

View File

@ -31,7 +31,7 @@ import kotlinx.android.synthetic.main.item_checklist.view.*
import java.util.*
class ChecklistAdapter(
activity: BaseSimpleActivity, var items: ArrayList<ChecklistItem>, val listener: ChecklistItemsListener?,
activity: BaseSimpleActivity, var items: MutableList<ChecklistItem>, val listener: ChecklistItemsListener?,
recyclerView: MyRecyclerView, val showIcons: Boolean, itemClick: (Any) -> Unit
) :
MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract {

View File

@ -10,8 +10,8 @@ import com.simplemobiletools.notes.pro.fragments.ChecklistFragment
import com.simplemobiletools.notes.pro.fragments.NoteFragment
import com.simplemobiletools.notes.pro.fragments.TextFragment
import com.simplemobiletools.notes.pro.helpers.NOTE_ID
import com.simplemobiletools.notes.pro.helpers.NoteType
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity: Activity) : FragmentStatePagerAdapter(fm) {
private var fragments: HashMap<Int, NoteFragment> = LinkedHashMap()
@ -30,7 +30,7 @@ class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity
return fragments[position]!!
}
val fragment = if (note.type == NoteType.TYPE_TEXT.value) TextFragment() else ChecklistFragment()
val fragment = if (note.type == NoteType.TYPE_TEXT) TextFragment() else ChecklistFragment()
fragment.arguments = bundle
fragments[position] = fragment
return fragment

View File

@ -20,6 +20,9 @@ import com.simplemobiletools.notes.pro.extensions.notesDB
import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.models.ChecklistItem
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory {
private val textIds = arrayOf(
@ -32,7 +35,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
)
private var widgetTextColor = DEFAULT_WIDGET_TEXT_COLOR
private var note: Note? = null
private var checklistItems = ArrayList<ChecklistItem>()
private var checklistItems = mutableListOf<ChecklistItem>()
override fun getViewAt(position: Int): RemoteViews {
val noteId = intent.getLongExtra(NOTE_ID, 0L)
@ -43,7 +46,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
}
val textSize = context.getPercentageFontSize() / context.resources.displayMetrics.density
if (note!!.type == NoteType.TYPE_CHECKLIST.value) {
if (note!!.type == NoteType.TYPE_CHECKLIST) {
remoteView = RemoteViews(context.packageName, R.layout.item_checklist_widget).apply {
val checklistItem = checklistItems.getOrNull(position) ?: return@apply
val widgetNewTextColor = if (checklistItem.isDone) widgetTextColor.adjustAlpha(DONE_CHECKLIST_ITEM_ALPHA) else widgetTextColor
@ -123,9 +126,8 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
widgetTextColor = intent.getIntExtra(WIDGET_TEXT_COLOR, DEFAULT_WIDGET_TEXT_COLOR)
val noteId = intent.getLongExtra(NOTE_ID, 0L)
note = context.notesDB.getNoteWithId(noteId)
if (note?.type == NoteType.TYPE_CHECKLIST.value) {
val checklistItemType = object : TypeToken<List<ChecklistItem>>() {}.type
checklistItems = Gson().fromJson<ArrayList<ChecklistItem>>(note!!.getNoteStoredValue(context), checklistItemType) ?: ArrayList(1)
if (note?.type == NoteType.TYPE_CHECKLIST) {
checklistItems = note!!.getNoteStoredValue(context)?.let { Json.decodeFromString(it) } ?: mutableListOf()
// checklist title can be null only because of the glitch in upgrade to 6.6.0, remove this check in the future
checklistItems = checklistItems.filter { it.title != null }.toMutableList() as ArrayList<ChecklistItem>
@ -135,7 +137,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
override fun hasStableIds() = true
override fun getCount(): Int {
return if (note?.type == NoteType.TYPE_CHECKLIST.value) {
return if (note?.type == NoteType.TYPE_CHECKLIST) {
checklistItems.size
} else {
1

View File

@ -9,10 +9,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.helpers.DEFAULT_WIDGET_TEXT_COLOR
import com.simplemobiletools.notes.pro.helpers.NoteType
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.models.NoteType
import com.simplemobiletools.notes.pro.models.Widget
import java.util.concurrent.Executors
@ -57,7 +57,7 @@ abstract class NotesDatabase : RoomDatabase() {
private fun insertFirstNote(context: Context) {
Executors.newSingleThreadScheduledExecutor().execute {
val generalNote = context.resources.getString(R.string.general_note)
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT.value, "", PROTECTION_NONE, "")
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT, "", PROTECTION_NONE, "")
db!!.NotesDao().insertOrUpdate(note)
}
}

View File

@ -0,0 +1,42 @@
package com.simplemobiletools.notes.pro.dialogs
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import kotlinx.android.synthetic.main.dialog_export_notes.view.export_notes_filename
class ExportNotesDialog(val activity: SimpleActivity, callback: (filename: String) -> Unit) {
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_notes, null) as ViewGroup).apply {
export_notes_filename.setText(
buildString {
append(context.getString(R.string.notes))
append("_")
append(context.getCurrentFormattedDateTime())
}
)
}
activity.getAlertDialogBuilder().setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null).apply {
activity.setupDialogStuff(view, this, R.string.export_notes) { alertDialog ->
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.export_notes_filename.value
when {
filename.isEmpty() -> activity.toast(R.string.empty_name)
filename.isAValidFilename() -> {
callback(filename)
alertDialog.dismiss()
}
else -> activity.toast(R.string.invalid_name)
}
}
}
}
}
}

View File

@ -9,10 +9,11 @@ import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.extensions.notesDB
import com.simplemobiletools.notes.pro.extensions.parseChecklistItems
import com.simplemobiletools.notes.pro.helpers.NoteType
import com.simplemobiletools.notes.pro.helpers.NotesHelper
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_import_folder.view.*
import com.simplemobiletools.notes.pro.models.NoteType
import kotlinx.android.synthetic.main.dialog_import_folder.view.open_file_filename
import kotlinx.android.synthetic.main.dialog_import_folder.view.open_file_type
import java.io.File
class ImportFolderDialog(val activity: SimpleActivity, val path: String, val callback: () -> Unit) : AlertDialog.Builder(activity) {
@ -50,21 +51,21 @@ class ImportFolderDialog(val activity: SimpleActivity, val path: String, val cal
activity.notesDB.getNoteIdWithTitle(filename) != null -> false
else -> true
}
}.forEach {
}?.forEach {
val storePath = if (updateFilesOnEdit) it.absolutePath else ""
val title = it.absolutePath.getFilenameFromPath()
val value = if (updateFilesOnEdit) "" else it.readText()
val fileText = it.readText().trim()
val checklistItems = fileText.parseChecklistItems()
if (checklistItems != null) {
saveNote(title.substringBeforeLast('.'), fileText, NoteType.TYPE_CHECKLIST.value, "")
saveNote(title.substringBeforeLast('.'), fileText, NoteType.TYPE_CHECKLIST, "")
} else {
if (updateFilesOnEdit) {
activity.handleSAFDialog(path) {
saveNote(title, value, NoteType.TYPE_TEXT.value, storePath)
saveNote(title, value, NoteType.TYPE_TEXT, storePath)
}
} else {
saveNote(title, value, NoteType.TYPE_TEXT.value, storePath)
saveNote(title, value, NoteType.TYPE_TEXT, storePath)
}
}
}
@ -75,7 +76,7 @@ class ImportFolderDialog(val activity: SimpleActivity, val path: String, val cal
}
}
private fun saveNote(title: String, value: String, type: Int, path: String) {
private fun saveNote(title: String, value: String, type: NoteType, path: String) {
val note = Note(null, title, value, type, path, PROTECTION_NONE, "")
NotesHelper(activity).insertOrUpdateNote(note)
}

View File

@ -8,8 +8,8 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.notesDB
import com.simplemobiletools.notes.pro.helpers.NoteType
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
import kotlinx.android.synthetic.main.dialog_new_note.view.*
class NewNoteDialog(val activity: Activity, title: String? = null, val setChecklistAsDefault: Boolean, callback: (note: Note) -> Unit) {
@ -40,12 +40,12 @@ class NewNoteDialog(val activity: Activity, title: String? = null, val setCheckl
activity.notesDB.getNoteIdWithTitle(newTitle) != null -> activity.toast(R.string.title_taken)
else -> {
val type = if (view.new_note_type.checkedRadioButtonId == view.type_checklist.id) {
NoteType.TYPE_CHECKLIST.value
NoteType.TYPE_CHECKLIST
} else {
NoteType.TYPE_TEXT.value
NoteType.TYPE_TEXT
}
activity.config.lastCreatedNoteType = type
activity.config.lastCreatedNoteType = type.value
val newNote = Note(null, newTitle, "", type, "", PROTECTION_NONE, "")
callback(newNote)
alertDialog.dismiss()

View File

@ -9,8 +9,8 @@ import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.helpers.NoteType
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.NoteType
import kotlinx.android.synthetic.main.dialog_open_file.view.*
import java.io.File
@ -47,7 +47,7 @@ class OpenFileDialog(val activity: SimpleActivity, val path: String, val callbac
private fun saveNote(storeContent: String, storePath: String) {
val filename = path.getFilenameFromPath()
val note = Note(null, filename, storeContent, NoteType.TYPE_TEXT.value, storePath, PROTECTION_NONE, "")
val note = Note(null, filename, storeContent, NoteType.TYPE_TEXT, storePath, PROTECTION_NONE, "")
callback(note)
dialog?.dismiss()
}

View File

@ -31,7 +31,7 @@ class OpenNoteDialog(val activity: Activity, val callback: (checkedId: Long, new
}
}
private fun initDialog(notes: ArrayList<Note>, view: View) {
private fun initDialog(notes: List<Note>, view: View) {
val textColor = activity.getProperTextColor()
notes.forEach {
activity.layoutInflater.inflate(R.layout.open_note_item, null).apply {

View File

@ -10,7 +10,7 @@ import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_unlock_notes.view.*
import kotlinx.android.synthetic.main.item_locked_note.view.*
class UnlockNotesDialog(val activity: BaseSimpleActivity, val notes: List<Note>, callback: (unlockedNoteIds: List<Long>) -> Unit) {
class UnlockNotesDialog(val activity: BaseSimpleActivity, val notes: List<Note>, callback: (unlockedNotes: List<Note>) -> Unit) {
private var dialog: AlertDialog? = null
private val view = activity.layoutInflater.inflate(R.layout.dialog_unlock_notes, null) as ViewGroup
private val redColor = activity.getColor(R.color.md_red)
@ -29,7 +29,7 @@ class UnlockNotesDialog(val activity: BaseSimpleActivity, val notes: List<Note>,
activity.setupDialogStuff(view, this, R.string.unlock_notes, cancelOnTouchOutside = false) { alertDialog ->
dialog = alertDialog
alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
callback(unlockedNoteIds)
callback(unlockedNoteIds.mapNotNull { id -> notes.firstOrNull { it.id == id } })
alertDialog.dismiss()
}
}

View File

@ -4,12 +4,15 @@ import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import com.simplemobiletools.commons.activities.BaseSimpleActivity
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.interfaces.NotesDao
import com.simplemobiletools.notes.pro.interfaces.WidgetsDao
import com.simplemobiletools.notes.pro.models.Note
val Context.config: Config get() = Config.newInstance(applicationContext)
@ -29,3 +32,14 @@ fun Context.updateWidgets() {
}
fun Context.getPercentageFontSize() = resources.getDimension(R.dimen.middle_text_size) * (config.fontSizePercentage / 100f)
fun BaseSimpleActivity.requestUnlockNotes(notes: List<Note>, callback: (unlockedNotes: List<Note>) -> Unit) {
val lockedNotes = notes.filter { it.isLocked() }
if (lockedNotes.isNotEmpty()) {
runOnUiThread {
UnlockNotesDialog(this, lockedNotes, callback)
}
} else {
callback(emptyList())
}
}

View File

@ -29,7 +29,7 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener {
lateinit var view: ViewGroup
var items = ArrayList<ChecklistItem>()
var items = mutableListOf<ChecklistItem>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
view = inflater.inflate(R.layout.fragment_checklist, container, false) as ViewGroup

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.os.Environment
import android.view.Gravity
import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.notes.pro.models.NoteType
class Config(context: Context) : BaseConfig(context) {
companion object {

View File

@ -44,9 +44,6 @@ const val GRAVITY_LEFT = 0
const val GRAVITY_CENTER = 1
const val GRAVITY_RIGHT = 2
// note types
enum class NoteType(val value: Int) { TYPE_TEXT(0), TYPE_CHECKLIST(1) }
// mime types
const val MIME_TEXT_PLAIN = "text/plain"

View File

@ -1,48 +0,0 @@
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.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(notes: List<Note>, unlockedNoteIds: List<Long>, outputStream: OutputStream?, 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()
for (note in notes) {
if (!note.isLocked() || note.id in unlockedNoteIds) {
val noteToSave = getNoteToExport(note)
writer.jsonValue(gson.toJson(noteToSave))
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

@ -1,19 +1,20 @@
package com.simplemobiletools.notes.pro.helpers
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.notes.pro.R
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 java.io.File
class NotesHelper(val context: Context) {
fun getNotes(callback: (notes: ArrayList<Note>) -> Unit) {
fun getNotes(callback: (notes: List<Note>) -> Unit) {
ensureBackgroundThread {
// make sure the initial note has enough time to be precreated
if (context.config.appRunCount <= 1) {
@ -21,8 +22,8 @@ class NotesHelper(val context: Context) {
Thread.sleep(200)
}
val notes = context.notesDB.getNotes() as ArrayList<Note>
val notesToDelete = ArrayList<Note>(notes.size)
val notes = context.notesDB.getNotes().toMutableList()
val notesToDelete = mutableListOf<Note>()
notes.forEach {
if (it.path.isNotEmpty()) {
if (!it.path.startsWith("content://") && !File(it.path).exists()) {
@ -36,7 +37,7 @@ class NotesHelper(val context: Context) {
if (notes.isEmpty()) {
val generalNote = context.resources.getString(R.string.general_note)
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT.value, "", PROTECTION_NONE, "")
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT, "", PROTECTION_NONE, "")
context.notesDB.insertOrUpdate(note)
notes.add(note)
}
@ -73,4 +74,57 @@ class NotesHelper(val context: Context) {
}
}
}
fun insertOrUpdateNotes(notes: List<Note>, callback: ((newNoteIds: List<Long>) -> Unit)? = null) {
ensureBackgroundThread {
val noteIds = context.notesDB.insertOrUpdate(notes)
Handler(Looper.getMainLooper()).post {
callback?.invoke(noteIds)
}
}
}
fun importNotes(activity: BaseSimpleActivity, notes: List<Note>, callback: (ImportResult) -> Unit) {
ensureBackgroundThread {
val currentNotes = activity.notesDB.getNotes()
if (currentNotes.isEmpty()) {
insertOrUpdateNotes(notes) { savedNotes ->
val newCurrentNotes = activity.notesDB.getNotes()
val result = when {
currentNotes.size == newCurrentNotes.size -> ImportResult.IMPORT_NOTHING_NEW
notes.size == savedNotes.size -> ImportResult.IMPORT_OK
savedNotes.isEmpty() -> ImportResult.IMPORT_FAIL
else -> ImportResult.IMPORT_PARTIAL
}
callback(result)
}
} else {
var imported = 0
var skipped = 0
notes.forEach { note ->
val exists = context.notesDB.getNoteIdWithTitle(note.title) != null
if (!exists) {
context.notesDB.insertOrUpdate(note)
imported++
} else {
skipped++
}
}
val result = when {
skipped == notes.size || imported == 0 -> ImportResult.IMPORT_NOTHING_NEW
imported == notes.size -> ImportResult.IMPORT_OK
else -> ImportResult.IMPORT_PARTIAL
}
callback(result)
}
}
}
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}
}

View File

@ -1,101 +0,0 @@
package com.simplemobiletools.notes.pro.helpers
import android.content.Context
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.showErrorToast
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.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
private var notesSkipped = 0
fun importNotes(path: String, filename: String, force: Boolean = false, 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 ?: 0
if (totalNotes <= 0) {
callback.invoke(ImportResult.IMPORT_FAIL)
return@ensureBackgroundThread
}
for (note in notes) {
val exists = context.notesDB.getNoteIdWithTitle(note.title) != null
if (!exists) {
context.notesDB.insertOrUpdate(note)
notesImported++
} else {
notesSkipped++
}
}
}
} catch (e: JsonSyntaxException) {
if (force) {
callback(ImportResult.IMPORT_FAIL)
return@ensureBackgroundThread
}
// Import notes expects a json with note name, content etc, but lets be more flexible and accept the basic files with note content only too
val inputStream = if (path.contains("/")) {
File(path).inputStream()
} else {
context.assets.open(path)
}
inputStream.bufferedReader().use { reader ->
val text = reader.readText()
val note = Note(null, filename, text, NoteType.TYPE_TEXT.value, "", PROTECTION_NONE, "")
var i = 1
if (context.notesDB.getNoteIdWithTitle(note.title) != null) {
while (true) {
val tryTitle = "$filename ($i)"
if (context.notesDB.getNoteIdWithTitle(tryTitle) == null) {
break
}
i++
}
note.title = "$filename ($i)"
}
context.notesDB.insertOrUpdate(note)
notesImported++
}
} catch (e: Exception) {
context.showErrorToast(e)
notesFailed++
}
callback.invoke(
when {
notesSkipped > 0 && notesImported == 0 -> ImportResult.IMPORT_NOTHING_NEW
notesImported == 0 -> ImportResult.IMPORT_FAIL
notesFailed > 0 -> ImportResult.IMPORT_PARTIAL
else -> ImportResult.IMPORT_OK
}
)
}
}
}

View File

@ -23,6 +23,9 @@ interface NotesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(note: Note): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(notes: List<Note>): List<Long>
@Delete
fun deleteNote(note: Note)
}

View File

@ -3,8 +3,16 @@ package com.simplemobiletools.notes.pro.models
import com.simplemobiletools.commons.helpers.AlphanumericComparator
import com.simplemobiletools.commons.helpers.SORT_BY_TITLE
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import kotlinx.serialization.Serializable
@Serializable
data class ChecklistItem(
val id: Int,
val dateCreated: Long = 0L,
var title: String,
var isDone: Boolean
) : Comparable<ChecklistItem> {
data class ChecklistItem(val id: Int, val dateCreated: Long = 0L, var title: String, var isDone: Boolean) : Comparable<ChecklistItem> {
companion object {
var sorting = 0
}

View File

@ -2,21 +2,27 @@ package com.simplemobiletools.notes.pro.models
import android.content.Context
import android.net.Uri
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.*
import com.simplemobiletools.commons.extensions.isBiometricIdAvailable
import com.simplemobiletools.commons.helpers.PROTECTION_FINGERPRINT
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
import kotlinx.serialization.Serializable
import java.io.File
/**
* Represents a note.
*
* @property value The content of the note. Could be plain text or [ChecklistItem]
* @property type The type of the note. Should be one of the [NoteType] enum entries.
*/
@Serializable
@Entity(tableName = "notes", indices = [(Index(value = ["id"], unique = true))])
@TypeConverters(NoteTypeConverter::class)
data class Note(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "title") var title: String,
@ColumnInfo(name = "value") var value: String,
@ColumnInfo(name = "type") var type: Int,
@ColumnInfo(name = "type") var type: NoteType,
@ColumnInfo(name = "path") var path: String,
@ColumnInfo(name = "protection_type") var protectionType: Int,
@ColumnInfo(name = "protection_hash") var protectionHash: String

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.notes.pro.models
import kotlinx.serialization.Serializable
@Serializable
enum class NoteType(val value: Int) {
TYPE_TEXT(0),
TYPE_CHECKLIST(1);
companion object {
fun fromValue(value: Int): NoteType {
return values().find { it.value == value } ?: TYPE_TEXT
}
}
}

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.notes.pro.models
import androidx.room.TypeConverter
class NoteTypeConverter {
@TypeConverter
fun fromNoteType(noteType: NoteType): Int {
return noteType.value
}
@TypeConverter
fun toNoteType(value: Int): NoteType {
return NoteType.fromValue(value)
}
}

View File

@ -346,6 +346,46 @@
android:text="@string/display_success_message" />
</RelativeLayout>
<include
android:id="@+id/settings_general_settings_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_migrating_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/migrating" />
<RelativeLayout
android:id="@+id/settings_export_notes_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_export_notes_label"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/export_notes" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_import_notes_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_import_notes_label"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/import_notes" />
</RelativeLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/export_notes_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/export_notes_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/export_notes_filename_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/medium_margin"
android:hint="@string/filename_without_json"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/export_notes_filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/bigger_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
</LinearLayout>
</ScrollView>

View File

@ -70,18 +70,10 @@
android:id="@+id/import_folder"
android:title="@string/import_folder"
app:showAsAction="never" />
<item
android:id="@+id/import_notes"
android:title="@string/import_notes"
app:showAsAction="never" />
<item
android:id="@+id/export_as_file"
android:title="@string/export_as_file"
app:showAsAction="never" />
<item
android:id="@+id/export_all_notes"
android:title="@string/export_notes"
app:showAsAction="never" />
<item
android:id="@+id/print"
android:title="@string/print"