Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8c99986
Added migration section in settings layout
Merkost Jul 7, 2023
66546df
Refactored ArrayList to List
Merkost Jul 7, 2023
437adef
Added serialization plugin
Merkost Jul 7, 2023
6b44179
Extracted requestUnlockNotes to an extension fun
Merkost Jul 7, 2023
7b9c05f
Added serializable to the Note entity
Merkost Jul 7, 2023
a305a2b
Created insertOrUpdate fun for many notes
Merkost Jul 7, 2023
323ca2c
Added importNotes fun in NotesHelper
Merkost Jul 7, 2023
9503b1e
Cleared MainActivity
Merkost Jul 7, 2023
f79b1fa
Improved UnlockNotesDialog to return unlocked notes instead of ids
Merkost Jul 7, 2023
4d2a756
ArrayList to List refactoring
Merkost Jul 7, 2023
9c2d0e5
Implemented import/export for notes in SettingsActivity
Merkost Jul 7, 2023
7a15eef
Got rid of NoteExporter and NoteImporter
Merkost Jul 7, 2023
881506f
Removed log
Merkost Jul 8, 2023
f626389
Added migration label coloring in onResume
Merkost Jul 8, 2023
487350b
NoteType refactored
Merkost Jul 10, 2023
932699a
Added Serializable to ChecklistItem
Merkost Jul 10, 2023
e8b8d2b
Changed NoteType to the enum class in Note.kt
Merkost Jul 10, 2023
0112f39
Created NoteTypeConverter
Merkost Jul 10, 2023
26cd68b
Checklists refactoring
Merkost Jul 10, 2023
29e4509
Moved ActivityResult properties
Merkost Jul 10, 2023
5e0d4a5
Added pager update if new notes appeared
Merkost Jul 10, 2023
6614539
Added ExportNotesDialog
Merkost Jul 10, 2023
cd89984
Changed result if nothing has been imported
Merkost Jul 10, 2023
d1e5262
removing an extra space
tibbi Jul 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -10,7 +13,7 @@ if (keystorePropertiesFile.exists()) {
}

android {
compileSdkVersion 33
compileSdk 33

defaultConfig {
applicationId "com.simplemobiletools.notes.pro"
Expand Down Expand Up @@ -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"
}
24 changes: 24 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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(...);
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -50,14 +58,17 @@ class SettingsActivity : SimpleActivity() {
setupCursorPlacement()
setupIncognitoMode()
setupCustomizeWidgetColors()
setupNotesExport()
setupNotesImport()
updateTextColors(settings_nested_scrollview)

arrayOf(
settings_color_customization_section_label,
settings_general_settings_label,
settings_text_label,
settings_startup_label,
settings_saving_label
settings_saving_label,
settings_migrating_label,
).forEach {
it.setTextColor(getProperPrimaryColor())
}
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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>
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
}

Loading