Skip to content

Commit 54fb0fe

Browse files
authored
Merge pull request #602 from Naveen3Singh/export_locked_notes
Allow exporting locked notes
2 parents 3cc15e9 + 6cc5e07 commit 54fb0fe

File tree

54 files changed

+351
-99
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+351
-99
lines changed

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

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -904,17 +904,33 @@ class MainActivity : SimpleActivity() {
904904
}
905905
}
906906

907+
private fun requestUnlockNotes(callback: (unlockedNoteIds: List<Long>) -> Unit) {
908+
val lockedNotes = mNotes.filter { it.isLocked() }
909+
if (lockedNotes.isNotEmpty()) {
910+
runOnUiThread {
911+
UnlockNotesDialog(this, lockedNotes, callback)
912+
}
913+
} else {
914+
callback(emptyList())
915+
}
916+
}
917+
907918
private fun exportNotesTo(outputStream: OutputStream?) {
908-
toast(R.string.exporting)
909919
ensureBackgroundThread {
910-
val notesExporter = NotesExporter(this)
911-
notesExporter.exportNotes(outputStream) {
912-
val toastId = when (it) {
913-
NotesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
914-
else -> R.string.exporting_failed
915-
}
920+
NotesHelper(this).getNotes {
921+
mNotes = it
922+
requestUnlockNotes { unlockedNoteIds ->
923+
toast(R.string.exporting)
924+
val notesExporter = NotesExporter(this)
925+
notesExporter.exportNotes(mNotes, unlockedNoteIds, outputStream) { result ->
926+
val toastId = when (result) {
927+
NotesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
928+
else -> R.string.exporting_failed
929+
}
916930

917-
toast(toastId)
931+
toast(toastId)
932+
}
933+
}
918934
}
919935
}
920936
}
@@ -1011,49 +1027,53 @@ class MainActivity : SimpleActivity() {
10111027
}
10121028

10131029
private fun exportAllNotesBelowQ() {
1014-
ExportFilesDialog(this, mNotes) { parent, extension ->
1015-
val items = arrayListOf(
1016-
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
1017-
RadioItem(EXPORT_FILE_NO_SYNC, getString(R.string.only_export_file_content))
1018-
)
1019-
1020-
RadioGroupDialog(this, items) {
1021-
val syncFile = it as Int == EXPORT_FILE_SYNC
1022-
var failCount = 0
1023-
NotesHelper(this).getNotes {
1024-
mNotes = it
1025-
mNotes.filter { !it.isLocked() }.forEachIndexed { index, note ->
1026-
val filename = if (extension.isEmpty()) note.title else "${note.title}.$extension"
1027-
val file = File(parent, filename)
1028-
if (!filename.isAValidFilename()) {
1029-
toast(String.format(getString(R.string.filename_invalid_characters_placeholder, filename)))
1030-
} else {
1031-
val noteStoredValue = note.getNoteStoredValue(this) ?: ""
1032-
tryExportNoteValueToFile(file.absolutePath, mCurrentNote.title, note.value, false) { exportedSuccessfully ->
1033-
if (exportedSuccessfully) {
1034-
if (syncFile) {
1035-
note.path = file.absolutePath
1036-
note.value = ""
1037-
} else {
1038-
note.path = ""
1039-
note.value = noteStoredValue
1030+
ensureBackgroundThread {
1031+
NotesHelper(this).getNotes { notes ->
1032+
mNotes = notes
1033+
requestUnlockNotes { unlockedNoteIds ->
1034+
ExportFilesDialog(this, mNotes) { parent, extension ->
1035+
val items = arrayListOf(
1036+
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
1037+
RadioItem(EXPORT_FILE_NO_SYNC, getString(R.string.only_export_file_content))
1038+
)
1039+
1040+
RadioGroupDialog(this, items) { any ->
1041+
val syncFile = any as Int == EXPORT_FILE_SYNC
1042+
var failCount = 0
1043+
mNotes.filter { !it.isLocked() || it.id in unlockedNoteIds }.forEachIndexed { index, note ->
1044+
val filename = if (extension.isEmpty()) note.title else "${note.title}.$extension"
1045+
val file = File(parent, filename)
1046+
if (!filename.isAValidFilename()) {
1047+
toast(String.format(getString(R.string.filename_invalid_characters_placeholder, filename)))
1048+
} else {
1049+
val noteStoredValue = note.getNoteStoredValue(this) ?: ""
1050+
tryExportNoteValueToFile(file.absolutePath, mCurrentNote.title, note.value, false) { exportedSuccessfully ->
1051+
if (exportedSuccessfully) {
1052+
if (syncFile) {
1053+
note.path = file.absolutePath
1054+
note.value = ""
1055+
} else {
1056+
note.path = ""
1057+
note.value = noteStoredValue
1058+
}
1059+
1060+
NotesHelper(this).insertOrUpdateNote(note)
1061+
}
1062+
1063+
if (mCurrentNote.id == note.id) {
1064+
mCurrentNote.value = note.value
1065+
mCurrentNote.path = note.path
1066+
getPagerAdapter().updateCurrentNoteData(view_pager.currentItem, mCurrentNote.path, mCurrentNote.value)
1067+
}
1068+
1069+
if (!exportedSuccessfully) {
1070+
failCount++
1071+
}
1072+
1073+
if (index == mNotes.size - 1) {
1074+
toast(if (failCount == 0) R.string.exporting_successful else R.string.exporting_some_entries_failed)
1075+
}
10401076
}
1041-
1042-
NotesHelper(this).insertOrUpdateNote(note)
1043-
}
1044-
1045-
if (mCurrentNote.id == note.id) {
1046-
mCurrentNote.value = note.value
1047-
mCurrentNote.path = note.path
1048-
getPagerAdapter().updateCurrentNoteData(view_pager.currentItem, mCurrentNote.path, mCurrentNote.value)
1049-
}
1050-
1051-
if (!exportedSuccessfully) {
1052-
failCount++
1053-
}
1054-
1055-
if (index == mNotes.size - 1) {
1056-
toast(if (failCount == 0) R.string.exporting_successful else R.string.exporting_some_entries_failed)
10571077
}
10581078
}
10591079
}

app/src/main/kotlin/com/simplemobiletools/notes/pro/dialogs/NewNoteDialog.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ class NewNoteDialog(val activity: Activity, title: String? = null, val setCheckl
2424
new_note_type.check(defaultType)
2525
}
2626

27-
view.note_title.setText(title)
27+
view.locked_note_title.setText(title)
2828

2929
activity.getAlertDialogBuilder()
3030
.setPositiveButton(R.string.ok, null)
3131
.setNegativeButton(R.string.cancel, null)
3232
.apply {
3333
activity.setupDialogStuff(view, this, R.string.new_note) { alertDialog ->
34-
alertDialog.showKeyboard(view.note_title)
34+
alertDialog.showKeyboard(view.locked_note_title)
3535
alertDialog.getButton(BUTTON_POSITIVE).setOnClickListener {
36-
val newTitle = view.note_title.value
36+
val newTitle = view.locked_note_title.value
3737
ensureBackgroundThread {
3838
when {
3939
newTitle.isEmpty() -> activity.toast(R.string.no_title)

app/src/main/kotlin/com/simplemobiletools/notes/pro/dialogs/RenameNoteDialog.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ class RenameNoteDialog(val activity: SimpleActivity, val note: Note, val current
1818

1919
init {
2020
val view = activity.layoutInflater.inflate(R.layout.dialog_rename_note, null)
21-
view.note_title.setText(note.title)
21+
view.locked_note_title.setText(note.title)
2222

2323
activity.getAlertDialogBuilder()
2424
.setPositiveButton(R.string.ok, null)
2525
.setNegativeButton(R.string.cancel, null)
2626
.apply {
2727
activity.setupDialogStuff(view, this, R.string.rename_note) { alertDialog ->
28-
alertDialog.showKeyboard(view.note_title)
28+
alertDialog.showKeyboard(view.locked_note_title)
2929
alertDialog.getButton(BUTTON_POSITIVE).setOnClickListener {
30-
val title = view.note_title.value
30+
val title = view.locked_note_title.value
3131
ensureBackgroundThread {
3232
newTitleConfirmed(title, alertDialog)
3333
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.simplemobiletools.notes.pro.dialogs
2+
3+
import android.content.DialogInterface
4+
import android.view.ViewGroup
5+
import androidx.appcompat.app.AlertDialog
6+
import com.simplemobiletools.commons.activities.BaseSimpleActivity
7+
import com.simplemobiletools.commons.extensions.*
8+
import com.simplemobiletools.notes.pro.R
9+
import com.simplemobiletools.notes.pro.models.Note
10+
import kotlinx.android.synthetic.main.dialog_unlock_notes.view.*
11+
import kotlinx.android.synthetic.main.item_locked_note.view.*
12+
13+
class UnlockNotesDialog(val activity: BaseSimpleActivity, val notes: List<Note>, callback: (unlockedNoteIds: List<Long>) -> Unit) {
14+
private var dialog: AlertDialog? = null
15+
private val view = activity.layoutInflater.inflate(R.layout.dialog_unlock_notes, null) as ViewGroup
16+
private val redColor = activity.getColor(R.color.md_red)
17+
private val greenColor = activity.getColor(R.color.md_green)
18+
private val unlockedNoteIds = mutableListOf<Long>()
19+
20+
init {
21+
for (note in notes) {
22+
addLockedNoteView(note)
23+
}
24+
25+
activity.getAlertDialogBuilder()
26+
.setPositiveButton(R.string.skip, null)
27+
.setNegativeButton(R.string.cancel, null)
28+
.apply {
29+
activity.setupDialogStuff(view, this, R.string.unlock_notes, cancelOnTouchOutside = false) { alertDialog ->
30+
dialog = alertDialog
31+
alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
32+
callback(unlockedNoteIds)
33+
alertDialog.dismiss()
34+
}
35+
}
36+
}
37+
}
38+
39+
private fun addLockedNoteView(note: Note) {
40+
activity.layoutInflater.inflate(R.layout.item_locked_note, null).apply {
41+
view.notes_holder.addView(this)
42+
activity.updateTextColors(view.notes_holder)
43+
locked_note_title.text = note.title
44+
locked_unlocked_image.applyColorFilter(redColor)
45+
locked_note_holder.setOnClickListener {
46+
if (note.id !in unlockedNoteIds) {
47+
activity.performSecurityCheck(
48+
protectionType = note.protectionType,
49+
requiredHash = note.protectionHash,
50+
successCallback = { _, _ ->
51+
unlockedNoteIds.add(note.id!!)
52+
locked_unlocked_image.apply {
53+
setImageResource(R.drawable.ic_lock_open_vector)
54+
applyColorFilter(greenColor)
55+
}
56+
updatePositiveButton()
57+
}
58+
)
59+
} else {
60+
unlockedNoteIds.remove(note.id)
61+
locked_unlocked_image.apply {
62+
setImageResource(R.drawable.ic_lock_vector)
63+
applyColorFilter(redColor)
64+
}
65+
updatePositiveButton()
66+
}
67+
}
68+
}
69+
}
70+
71+
private fun updatePositiveButton() {
72+
dialog?.getButton(DialogInterface.BUTTON_POSITIVE)?.text = if (unlockedNoteIds.isNotEmpty()) {
73+
activity.getString(R.string.ok)
74+
} else {
75+
activity.getString(R.string.skip)
76+
}
77+
}
78+
}

app/src/main/kotlin/com/simplemobiletools/notes/pro/helpers/NotesExporter.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.google.gson.Gson
55
import com.google.gson.stream.JsonWriter
66
import com.simplemobiletools.commons.helpers.PROTECTION_NONE
77
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
8-
import com.simplemobiletools.notes.pro.extensions.notesDB
98
import com.simplemobiletools.notes.pro.models.Note
109
import java.io.OutputStream
1110

@@ -16,7 +15,7 @@ class NotesExporter(private val context: Context) {
1615

1716
private val gson = Gson()
1817

19-
fun exportNotes(outputStream: OutputStream?, callback: (result: ExportResult) -> Unit) {
18+
fun exportNotes(notes: List<Note>, unlockedNoteIds: List<Long>, outputStream: OutputStream?, callback: (result: ExportResult) -> Unit) {
2019
ensureBackgroundThread {
2120
if (outputStream == null) {
2221
callback.invoke(ExportResult.EXPORT_FAIL)
@@ -27,9 +26,8 @@ class NotesExporter(private val context: Context) {
2726
try {
2827
var written = 0
2928
writer.beginArray()
30-
val notes = context.notesDB.getNotes() as ArrayList<Note>
3129
for (note in notes) {
32-
if (note.protectionType == PROTECTION_NONE) {
30+
if (!note.isLocked() || note.id in unlockedNoteIds) {
3331
val noteToSave = getNoteToExport(note)
3432
writer.jsonValue(gson.toJson(noteToSave))
3533
written++
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<vector android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
2+
<path android:fillColor="@android:color/white" android:pathData="M12 17c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 0.9-2 2v10c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2V10c0-1.1-0.9-2-2-2zm0 12H6V10h12v10z"/>
3+
</vector>

app/src/main/res/layout/dialog_new_note.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
android:hint="@string/label">
1818

1919
<com.google.android.material.textfield.TextInputEditText
20-
android:id="@+id/note_title"
20+
android:id="@+id/locked_note_title"
2121
android:layout_width="match_parent"
2222
android:layout_height="wrap_content"
2323
android:inputType="textCapSentences"

app/src/main/res/layout/dialog_rename_note.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
android:hint="@string/title">
1515

1616
<com.google.android.material.textfield.TextInputEditText
17-
android:id="@+id/note_title"
17+
android:id="@+id/locked_note_title"
1818
android:layout_width="match_parent"
1919
android:layout_height="wrap_content"
2020
android:inputType="textCapSentences"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@+id/dialog_holder"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:fillViewport="true"
7+
android:paddingTop="@dimen/activity_margin">
8+
9+
<LinearLayout
10+
android:layout_width="match_parent"
11+
android:layout_height="wrap_content"
12+
android:orientation="vertical">
13+
14+
<com.simplemobiletools.commons.views.MyTextView
15+
android:id="@+id/found_locked_notes_info"
16+
android:layout_width="wrap_content"
17+
android:layout_height="wrap_content"
18+
android:layout_marginHorizontal="@dimen/big_margin"
19+
android:layout_marginTop="@dimen/small_margin"
20+
android:layout_marginBottom="@dimen/activity_margin"
21+
android:text="@string/found_locked_notes_info" />
22+
23+
<LinearLayout
24+
android:id="@+id/notes_holder"
25+
android:layout_width="match_parent"
26+
android:layout_height="wrap_content"
27+
android:orientation="vertical" />
28+
29+
</LinearLayout>
30+
</ScrollView>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/locked_note_holder"
6+
android:layout_width="match_parent"
7+
android:layout_height="wrap_content"
8+
android:background="@drawable/ripple_background"
9+
android:clickable="true"
10+
android:focusable="true"
11+
android:paddingHorizontal="@dimen/normal_margin">
12+
13+
<com.simplemobiletools.commons.views.MyTextView
14+
android:id="@+id/locked_note_title"
15+
android:layout_width="0dp"
16+
android:layout_height="wrap_content"
17+
android:paddingStart="@dimen/activity_margin"
18+
android:textSize="@dimen/bigger_text_size"
19+
app:layout_constraintBottom_toBottomOf="parent"
20+
app:layout_constraintEnd_toStartOf="@+id/locked_unlocked_image"
21+
app:layout_constraintStart_toStartOf="parent"
22+
app:layout_constraintTop_toTopOf="parent"
23+
tools:text="Passwords" />
24+
25+
<ImageView
26+
android:id="@+id/locked_unlocked_image"
27+
android:layout_width="@dimen/fab_size"
28+
android:layout_height="@dimen/fab_size"
29+
android:paddingVertical="@dimen/activity_margin"
30+
android:src="@drawable/ic_lock_vector"
31+
app:layout_constraintBottom_toBottomOf="parent"
32+
app:layout_constraintEnd_toEndOf="parent"
33+
app:layout_constraintTop_toTopOf="parent" />
34+
35+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)