Skip to content

Commit 6c7d3aa

Browse files
authored
Merge pull request #644 from Merkost/migration_settings
Notes import/export moved to settings
2 parents ca6529e + d1e5262 commit 6c7d3aa

30 files changed

+450
-466
lines changed

app/build.gradle

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
apply plugin: 'com.android.application'
2-
apply plugin: 'kotlin-android'
3-
apply plugin: 'kotlin-android-extensions'
4-
apply plugin: 'kotlin-kapt'
1+
plugins {
2+
id 'com.android.application'
3+
id 'kotlin-android'
4+
id 'kotlin-android-extensions'
5+
id 'kotlin-kapt'
6+
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
7+
}
58

69
def keystorePropertiesFile = rootProject.file("keystore.properties")
710
def keystoreProperties = new Properties()
@@ -10,7 +13,7 @@ if (keystorePropertiesFile.exists()) {
1013
}
1114

1215
android {
13-
compileSdkVersion 33
16+
compileSdk 33
1417

1518
defaultConfig {
1619
applicationId "com.simplemobiletools.notes.pro"
@@ -70,4 +73,6 @@ dependencies {
7073
kapt 'androidx.room:room-compiler:2.5.1'
7174
implementation 'androidx.room:room-runtime:2.5.1'
7275
annotationProcessor 'androidx.room:room-compiler:2.5.1'
76+
77+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
7378
}

app/proguard-rules.pro

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
11
-keep class com.simplemobiletools.notes.pro.models.* {
22
<fields>;
33
}
4+
5+
# Keep `Companion` object fields of serializable classes.
6+
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
7+
-if @kotlinx.serialization.Serializable class **
8+
-keepclassmembers class <1> {
9+
static <1>$Companion Companion;
10+
}
11+
12+
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
13+
-if @kotlinx.serialization.Serializable class ** {
14+
static **$* *;
15+
}
16+
-keepclassmembers class <2>$<3> {
17+
kotlinx.serialization.KSerializer serializer(...);
18+
}
19+
20+
# Keep `INSTANCE.serializer()` of serializable objects.
21+
-if @kotlinx.serialization.Serializable class ** {
22+
public static ** INSTANCE;
23+
}
24+
-keepclassmembers class <1> {
25+
public static <1> INSTANCE;
26+
kotlinx.serialization.KSerializer serializer(...);
27+
}

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

Lines changed: 42 additions & 255 deletions
Large diffs are not rendered by default.

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

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
package com.simplemobiletools.notes.pro.activities
22

33
import android.content.Intent
4+
import android.net.Uri
45
import android.os.Bundle
56
import android.view.Menu
7+
import androidx.activity.result.contract.ActivityResultContracts
68
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
7-
import com.simplemobiletools.commons.extensions.beVisibleIf
8-
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
9-
import com.simplemobiletools.commons.extensions.updateTextColors
9+
import com.simplemobiletools.commons.extensions.*
1010
import com.simplemobiletools.commons.helpers.*
1111
import com.simplemobiletools.commons.models.RadioItem
1212
import com.simplemobiletools.notes.pro.R
13+
import com.simplemobiletools.notes.pro.dialogs.ExportNotesDialog
1314
import com.simplemobiletools.notes.pro.extensions.config
15+
import com.simplemobiletools.notes.pro.extensions.requestUnlockNotes
1416
import com.simplemobiletools.notes.pro.extensions.updateWidgets
1517
import com.simplemobiletools.notes.pro.extensions.widgetsDB
1618
import com.simplemobiletools.notes.pro.helpers.*
19+
import com.simplemobiletools.notes.pro.models.Note
1720
import com.simplemobiletools.notes.pro.models.Widget
1821
import kotlinx.android.synthetic.main.activity_settings.*
19-
import java.util.*
22+
import kotlinx.serialization.SerializationException
23+
import kotlinx.serialization.decodeFromString
24+
import kotlinx.serialization.encodeToString
25+
import kotlinx.serialization.json.Json
26+
import java.util.Locale
2027
import kotlin.system.exitProcess
2128

2229
class SettingsActivity : SimpleActivity() {
30+
private val notesFileType = "application/json"
2331

2432
override fun onCreate(savedInstanceState: Bundle?) {
2533
isMaterialActivity = true
@@ -50,14 +58,17 @@ class SettingsActivity : SimpleActivity() {
5058
setupCursorPlacement()
5159
setupIncognitoMode()
5260
setupCustomizeWidgetColors()
61+
setupNotesExport()
62+
setupNotesImport()
5363
updateTextColors(settings_nested_scrollview)
5464

5565
arrayOf(
5666
settings_color_customization_section_label,
5767
settings_general_settings_label,
5868
settings_text_label,
5969
settings_startup_label,
60-
settings_saving_label
70+
settings_saving_label,
71+
settings_migrating_label,
6172
).forEach {
6273
it.setTextColor(getProperPrimaryColor())
6374
}
@@ -68,6 +79,26 @@ class SettingsActivity : SimpleActivity() {
6879
return super.onCreateOptionsMenu(menu)
6980
}
7081

82+
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
83+
if (uri != null) {
84+
toast(R.string.importing)
85+
importNotes(uri)
86+
}
87+
}
88+
89+
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(notesFileType)) { uri ->
90+
if (uri != null) {
91+
toast(R.string.exporting)
92+
NotesHelper(this).getNotes { notes ->
93+
requestUnlockNotes(notes) { unlockedNotes ->
94+
val notLockedNotes = notes.filterNot { it.isLocked() }
95+
val notesToExport = unlockedNotes + notLockedNotes
96+
exportNotes(notesToExport, uri)
97+
}
98+
}
99+
}
100+
}
101+
71102
private fun setupCustomizeColors() {
72103
settings_color_customization_holder.setOnClickListener {
73104
startCustomizationActivity()
@@ -257,4 +288,63 @@ class SettingsActivity : SimpleActivity() {
257288
config.useIncognitoMode = settings_use_incognito_mode.isChecked
258289
}
259290
}
291+
292+
private fun setupNotesExport() {
293+
settings_export_notes_holder.setOnClickListener {
294+
ExportNotesDialog(this) { filename ->
295+
saveDocument.launch(filename)
296+
}
297+
}
298+
}
299+
300+
private fun setupNotesImport() {
301+
settings_import_notes_holder.setOnClickListener {
302+
getContent.launch(notesFileType)
303+
}
304+
}
305+
306+
private fun exportNotes(notes: List<Note>, uri: Uri) {
307+
if (notes.isEmpty()) {
308+
toast(R.string.no_entries_for_exporting)
309+
} else {
310+
try {
311+
val outputStream = contentResolver.openOutputStream(uri)!!
312+
313+
val jsonString = Json.encodeToString(notes)
314+
outputStream.use {
315+
it.write(jsonString.toByteArray())
316+
}
317+
toast(R.string.exporting_successful)
318+
} catch (e: Exception) {
319+
showErrorToast(e)
320+
}
321+
}
322+
}
323+
324+
private fun importNotes(uri: Uri) {
325+
try {
326+
val jsonString = contentResolver.openInputStream(uri)!!.use { inputStream ->
327+
inputStream.bufferedReader().readText()
328+
}
329+
val objects = Json.decodeFromString<List<Note>>(jsonString)
330+
if (objects.isEmpty()) {
331+
toast(R.string.no_entries_for_importing)
332+
return
333+
}
334+
NotesHelper(this).importNotes(this, objects) { importResult ->
335+
when (importResult) {
336+
NotesHelper.ImportResult.IMPORT_OK -> toast(R.string.importing_successful)
337+
NotesHelper.ImportResult.IMPORT_PARTIAL -> toast(R.string.importing_some_entries_failed)
338+
NotesHelper.ImportResult.IMPORT_NOTHING_NEW -> toast(R.string.no_new_items)
339+
else -> toast(R.string.importing_failed)
340+
}
341+
}
342+
} catch (_: SerializationException) {
343+
toast(R.string.invalid_file_format)
344+
} catch (_: IllegalArgumentException) {
345+
toast(R.string.invalid_file_format)
346+
} catch (e: Exception) {
347+
showErrorToast(e)
348+
}
349+
}
260350
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ import com.simplemobiletools.notes.pro.extensions.widgetsDB
2727
import com.simplemobiletools.notes.pro.helpers.*
2828
import com.simplemobiletools.notes.pro.models.ChecklistItem
2929
import com.simplemobiletools.notes.pro.models.Note
30+
import com.simplemobiletools.notes.pro.models.NoteType
3031
import com.simplemobiletools.notes.pro.models.Widget
3132
import kotlinx.android.synthetic.main.widget_config.*
33+
import kotlinx.serialization.decodeFromString
34+
import kotlinx.serialization.json.Json
3235

3336
class WidgetConfigureActivity : SimpleActivity() {
3437
private var mBgAlpha = 0f
@@ -39,7 +42,7 @@ class WidgetConfigureActivity : SimpleActivity() {
3942
private var mCurrentNoteId = 0L
4043
private var mIsCustomizingColors = false
4144
private var mShowTitle = false
42-
private var mNotes = ArrayList<Note>()
45+
private var mNotes = listOf<Note>()
4346

4447
public override fun onCreate(savedInstanceState: Bundle?) {
4548
useDynamicTheme = false
@@ -156,7 +159,7 @@ class WidgetConfigureActivity : SimpleActivity() {
156159
mCurrentNoteId = note.id!!
157160
notes_picker_value.text = note.title
158161
text_note_view_title.text = note.title
159-
if (note.type == NoteType.TYPE_CHECKLIST.value) {
162+
if (note.type == NoteType.TYPE_CHECKLIST) {
160163
val checklistItemType = object : TypeToken<List<ChecklistItem>>() {}.type
161164
val items = Gson().fromJson<ArrayList<ChecklistItem>>(note.value, checklistItemType) ?: ArrayList(1)
162165
items.apply {

app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/ChecklistAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import kotlinx.android.synthetic.main.item_checklist.view.*
3131
import java.util.*
3232

3333
class ChecklistAdapter(
34-
activity: BaseSimpleActivity, var items: ArrayList<ChecklistItem>, val listener: ChecklistItemsListener?,
34+
activity: BaseSimpleActivity, var items: MutableList<ChecklistItem>, val listener: ChecklistItemsListener?,
3535
recyclerView: MyRecyclerView, val showIcons: Boolean, itemClick: (Any) -> Unit
3636
) :
3737
MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract {

app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/NotesPagerAdapter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import com.simplemobiletools.notes.pro.fragments.ChecklistFragment
1010
import com.simplemobiletools.notes.pro.fragments.NoteFragment
1111
import com.simplemobiletools.notes.pro.fragments.TextFragment
1212
import com.simplemobiletools.notes.pro.helpers.NOTE_ID
13-
import com.simplemobiletools.notes.pro.helpers.NoteType
1413
import com.simplemobiletools.notes.pro.models.Note
14+
import com.simplemobiletools.notes.pro.models.NoteType
1515

1616
class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity: Activity) : FragmentStatePagerAdapter(fm) {
1717
private var fragments: HashMap<Int, NoteFragment> = LinkedHashMap()
@@ -30,7 +30,7 @@ class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity
3030
return fragments[position]!!
3131
}
3232

33-
val fragment = if (note.type == NoteType.TYPE_TEXT.value) TextFragment() else ChecklistFragment()
33+
val fragment = if (note.type == NoteType.TYPE_TEXT) TextFragment() else ChecklistFragment()
3434
fragment.arguments = bundle
3535
fragments[position] = fragment
3636
return fragment

app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/WidgetAdapter.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import com.simplemobiletools.notes.pro.extensions.notesDB
2020
import com.simplemobiletools.notes.pro.helpers.*
2121
import com.simplemobiletools.notes.pro.models.ChecklistItem
2222
import com.simplemobiletools.notes.pro.models.Note
23+
import com.simplemobiletools.notes.pro.models.NoteType
24+
import kotlinx.serialization.decodeFromString
25+
import kotlinx.serialization.json.Json
2326

2427
class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory {
2528
private val textIds = arrayOf(
@@ -32,7 +35,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
3235
)
3336
private var widgetTextColor = DEFAULT_WIDGET_TEXT_COLOR
3437
private var note: Note? = null
35-
private var checklistItems = ArrayList<ChecklistItem>()
38+
private var checklistItems = mutableListOf<ChecklistItem>()
3639

3740
override fun getViewAt(position: Int): RemoteViews {
3841
val noteId = intent.getLongExtra(NOTE_ID, 0L)
@@ -43,7 +46,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
4346
}
4447

4548
val textSize = context.getPercentageFontSize() / context.resources.displayMetrics.density
46-
if (note!!.type == NoteType.TYPE_CHECKLIST.value) {
49+
if (note!!.type == NoteType.TYPE_CHECKLIST) {
4750
remoteView = RemoteViews(context.packageName, R.layout.item_checklist_widget).apply {
4851
val checklistItem = checklistItems.getOrNull(position) ?: return@apply
4952
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
123126
widgetTextColor = intent.getIntExtra(WIDGET_TEXT_COLOR, DEFAULT_WIDGET_TEXT_COLOR)
124127
val noteId = intent.getLongExtra(NOTE_ID, 0L)
125128
note = context.notesDB.getNoteWithId(noteId)
126-
if (note?.type == NoteType.TYPE_CHECKLIST.value) {
127-
val checklistItemType = object : TypeToken<List<ChecklistItem>>() {}.type
128-
checklistItems = Gson().fromJson<ArrayList<ChecklistItem>>(note!!.getNoteStoredValue(context), checklistItemType) ?: ArrayList(1)
129+
if (note?.type == NoteType.TYPE_CHECKLIST) {
130+
checklistItems = note!!.getNoteStoredValue(context)?.let { Json.decodeFromString(it) } ?: mutableListOf()
129131

130132
// checklist title can be null only because of the glitch in upgrade to 6.6.0, remove this check in the future
131133
checklistItems = checklistItems.filter { it.title != null }.toMutableList() as ArrayList<ChecklistItem>
@@ -135,7 +137,7 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi
135137
override fun hasStableIds() = true
136138

137139
override fun getCount(): Int {
138-
return if (note?.type == NoteType.TYPE_CHECKLIST.value) {
140+
return if (note?.type == NoteType.TYPE_CHECKLIST) {
139141
checklistItems.size
140142
} else {
141143
1

app/src/main/kotlin/com/simplemobiletools/notes/pro/databases/NotesDatabase.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase
99
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
1010
import com.simplemobiletools.notes.pro.R
1111
import com.simplemobiletools.notes.pro.helpers.DEFAULT_WIDGET_TEXT_COLOR
12-
import com.simplemobiletools.notes.pro.helpers.NoteType
1312
import com.simplemobiletools.notes.pro.interfaces.NotesDao
1413
import com.simplemobiletools.notes.pro.interfaces.WidgetsDao
1514
import com.simplemobiletools.notes.pro.models.Note
15+
import com.simplemobiletools.notes.pro.models.NoteType
1616
import com.simplemobiletools.notes.pro.models.Widget
1717
import java.util.concurrent.Executors
1818

@@ -57,7 +57,7 @@ abstract class NotesDatabase : RoomDatabase() {
5757
private fun insertFirstNote(context: Context) {
5858
Executors.newSingleThreadScheduledExecutor().execute {
5959
val generalNote = context.resources.getString(R.string.general_note)
60-
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT.value, "", PROTECTION_NONE, "")
60+
val note = Note(null, generalNote, "", NoteType.TYPE_TEXT, "", PROTECTION_NONE, "")
6161
db!!.NotesDao().insertOrUpdate(note)
6262
}
6363
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.simplemobiletools.notes.pro.dialogs
2+
3+
import android.view.ViewGroup
4+
import androidx.appcompat.app.AlertDialog
5+
import com.simplemobiletools.commons.extensions.*
6+
import com.simplemobiletools.notes.pro.R
7+
import com.simplemobiletools.notes.pro.activities.SimpleActivity
8+
import kotlinx.android.synthetic.main.dialog_export_notes.view.export_notes_filename
9+
10+
class ExportNotesDialog(val activity: SimpleActivity, callback: (filename: String) -> Unit) {
11+
12+
init {
13+
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_notes, null) as ViewGroup).apply {
14+
export_notes_filename.setText(
15+
buildString {
16+
append(context.getString(R.string.notes))
17+
append("_")
18+
append(context.getCurrentFormattedDateTime())
19+
}
20+
)
21+
}
22+
23+
activity.getAlertDialogBuilder().setPositiveButton(R.string.ok, null).setNegativeButton(R.string.cancel, null).apply {
24+
activity.setupDialogStuff(view, this, R.string.export_notes) { alertDialog ->
25+
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
26+
27+
val filename = view.export_notes_filename.value
28+
when {
29+
filename.isEmpty() -> activity.toast(R.string.empty_name)
30+
filename.isAValidFilename() -> {
31+
callback(filename)
32+
alertDialog.dismiss()
33+
}
34+
35+
else -> activity.toast(R.string.invalid_name)
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+

0 commit comments

Comments
 (0)