From 4ccded41af8f39939c2356639a5c423dd26d3a52 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 20 Feb 2026 11:12:59 +0100 Subject: [PATCH 1/8] [fix] Adds catch-all around note preview to prevent app-start-crashes. The user is then capable of saving the content in the stored format to try to rescue the content. --- .../ui/adapter/NoteAdapter.kt | 37 ++++++++++++++--- .../ui/main/MainActivity.kt | 41 +++++++++++++++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt index 02443ac..903f3ca 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt @@ -16,6 +16,7 @@ package org.secuso.privacyfriendlynotes.ui.adapter import android.app.Activity import android.graphics.Color import android.text.Html +import android.util.Log import android.util.TypedValue import android.view.LayoutInflater import android.view.View @@ -23,6 +24,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources +import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide @@ -48,6 +50,7 @@ class NoteAdapter( var notes: MutableList = ArrayList() private set + var saveContent: ((Note, NoteHolder) -> Unit)? = null private var listener: ((Note, NoteHolder) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteHolder { val itemView = LayoutInflater.from(parent.context) @@ -102,6 +105,7 @@ class NoteAdapter( } } + try { when (currentNote.type) { DbContract.NoteEntry.TYPE_TEXT -> { if (showPreview) { @@ -120,12 +124,23 @@ class NoteAdapter( if (showPreview) { holder.imageViewcategory.setBackgroundColor(run { val value = TypedValue() - holder.itemView.context.theme.resolveAttribute(R.attr.colorSurfaceVariantLight, value, true) + holder.itemView.context.theme.resolveAttribute( + R.attr.colorSurfaceVariantLight, + value, + true + ) value.data }) - holder.imageViewcategory.minimumHeight = 200; holder.imageViewcategory.minimumWidth = 200 - Glide.with(activity).load(File("${activity.application.filesDir.path}/sketches${currentNote.content}")) - .placeholder(AppCompatResources.getDrawable(activity, R.drawable.ic_photo_icon_24dp)) + holder.imageViewcategory.minimumHeight = + 200; holder.imageViewcategory.minimumWidth = 200 + Glide.with(activity) + .load(File("${activity.application.filesDir.path}/sketches${currentNote.content}")) + .placeholder( + AppCompatResources.getDrawable( + activity, + R.drawable.ic_photo_icon_24dp + ) + ) .into(holder.imageViewcategory) } else { holder.imageViewcategory.setImageResource(R.drawable.ic_photo_icon_24dp) @@ -138,14 +153,24 @@ class NoteAdapter( if (showPreview) { val preview = mainActivityViewModel.checklistPreview(currentNote) - holder.textViewExtraText.text = "${preview.filter { it.first }.count()}/${preview.size}" - holder.textViewDescription.text = preview.take(3).joinToString(System.lineSeparator()) { it.second } + holder.textViewExtraText.text = + "${preview.filter { it.first }.count()}/${preview.size}" + holder.textViewDescription.text = + preview.take(3).joinToString(System.lineSeparator()) { it.second } holder.textViewDescription.maxLines = 3 } else { holder.textViewExtraText.text = "-/-" } } } + } catch (error: Exception) { + Log.d("NoteAdapter", "could not preview note.") + error.printStackTrace() + holder.textViewDescription.text = ContextCompat.getString(activity, R.string.preview_note_failed) + holder.itemView.setOnClickListener { + saveContent?.let { it(currentNote, holder) } + } + } // if the Description is empty, don't show it if (holder.textViewDescription.text.toString().isEmpty()) { diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.kt index abf0e6b..49259c1 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.kt @@ -19,6 +19,7 @@ import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.preference.PreferenceManager +import android.text.Html import android.util.Log import android.util.TypedValue import android.view.ContextThemeWrapper @@ -35,6 +36,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.arch.core.util.Function @@ -47,6 +49,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.navigation.NavigationView import kotlinx.coroutines.CoroutineScope @@ -71,6 +74,7 @@ import org.secuso.privacyfriendlynotes.ui.notes.BaseNoteActivity import org.secuso.privacyfriendlynotes.ui.notes.ChecklistNoteActivity import org.secuso.privacyfriendlynotes.ui.notes.SketchActivity import org.secuso.privacyfriendlynotes.ui.notes.TextNoteActivity +import java.io.File import java.io.FileOutputStream import java.io.OutputStream import java.util.Collections @@ -129,6 +133,35 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte } } + private var noteToExport: Note? = null + private val saveSingleNoteToExternalStorageResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + val fileOutputStream = contentResolver.openOutputStream(uri) + if (fileOutputStream == null) { + return@registerForActivityResult + } + CoroutineScope(Dispatchers.IO).launch { + val content = when (noteToExport?.type) { + DbContract.NoteEntry.TYPE_TEXT -> noteToExport!!.content + DbContract.NoteEntry.TYPE_AUDIO -> File(filesDir.path + "/audio_notes" + noteToExport!!.content).readBytes().toString() + DbContract.NoteEntry.TYPE_SKETCH -> File(filesDir.path + "/sketches" + noteToExport!!.content).readBytes().toString() + DbContract.NoteEntry.TYPE_CHECKLIST -> noteToExport!!.content + else -> return@launch + } + fileOutputStream.bufferedWriter().write(content) + runOnUiThread { + Toast.makeText( + applicationContext, + String.format(getString(R.string.toast_file_exported_to), uri.toString()), + Toast.LENGTH_LONG + ).show() + } + } + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = object : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { @@ -176,6 +209,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte PreferenceManager.getDefaultSharedPreferences(this).getBoolean("settings_color_category", true) && mainActivityViewModel.getCategory() == CAT_ALL ) + adapter.saveContent = { note, _ -> + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.putExtra(Intent.EXTRA_TITLE, note.name + ".txt") + intent.type = "text/plain" + noteToExport = note + saveSingleNoteToExternalStorageResultLauncher.launch(intent) + } recyclerView.adapter = adapter lifecycleScope.launch { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0e77a10..2a14fd8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -172,4 +172,5 @@ Dateigrößenlimit für importierte Textnotizen Alle abwählen Sperren + Diese Notiz konnte nicht geladen werden. Wenn Sie versuchen diese Notiz zu öffnen, wird stattdessen versucht den Inhalt der Notiz unformattiert zu speichern. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58042f1..b0c0f90 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,4 +196,5 @@ Text Note Character limit for imported text notes File Size limit for imported text notes + This note could not be loaded. Click to save the stored content. From 13613eddb3dd0629d054fcc2330e5aedcbf539e2 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 10:39:03 +0100 Subject: [PATCH 2/8] [fix] zip-export of notes with same name could lead to crash due to name collision. [feat] adds categories to the zip-exported hierarchy, so {category_name}/{note_type}/{note_name}_{note_id}.{ext} --- .../privacyfriendlynotes/room/dao/CategoryDao.kt | 3 +++ .../ui/main/MainActivityViewModel.kt | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt index f9734fa..ec76334 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt @@ -44,6 +44,9 @@ interface CategoryDao { @get:Query("SELECT * FROM categories GROUP BY name") val allCategories: Flow> + @get:Query("SELECT * FROM categories GROUP BY name") + val allCategoriesSync: List + @Query("SELECT name FROM categories WHERE _id=:thisCategoryId ") fun categoryNameFromId(thisCategoryId: Integer): LiveData diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt index 64fce3c..3f8d115 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt @@ -262,25 +262,28 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica fun zipAllNotes(notes: List, output: OutputStream) { ZipOutputStream(output).use { zipOut -> + val categories = + repository.categoryDao().allCategoriesSync.associate { it._id to it.name }.toMutableMap() + categories[CAT_ALL] = "default" notes.forEach { note -> val name = note.name.replace("/", "_") lateinit var entry: String lateinit var inputStream: InputStream when(note.type) { DbContract.NoteEntry.TYPE_TEXT -> { - entry = "text/" + name + "_" + System.currentTimeMillis() + "_" + TextNoteActivity.getFileExtension() + entry = categories[note.category] + "/text/" + name + "_" + note._id + "_" + TextNoteActivity.getFileExtension() inputStream = ByteArrayInputStream(note.content.toByteArray()) } DbContract.NoteEntry.TYPE_CHECKLIST -> { - entry = "checklist/" + name + "_" + System.currentTimeMillis() + "_" + ChecklistNoteActivity.getFileExtension() + entry = categories[note.category] + "/checklist/" + name + "_" + note._id + "_" + ChecklistNoteActivity.getFileExtension() inputStream = ByteArrayInputStream(note.content.toByteArray()) } DbContract.NoteEntry.TYPE_AUDIO -> { - entry = "audio/" + name + "_" + System.currentTimeMillis() + "_" + AudioNoteActivity.getFileExtension() + entry = categories[note.category] + "/audio/" + name + "_" + note._id + "_" + AudioNoteActivity.getFileExtension() inputStream = FileInputStream(File(filesDir.path + "/audio_notes" + note.content)) } DbContract.NoteEntry.TYPE_SKETCH -> { - entry ="sketch/" + name + "_" + System.currentTimeMillis() + "_" + SketchActivity.getFileExtension() + entry = categories[note.category] + "/sketch/" + name + "_" + note._id + "_" + SketchActivity.getFileExtension() inputStream = FileInputStream(File(filesDir.path + "/sketches" + note.content)) } } From f778a049d688d5779a1c56e6eee6f6b97084a3df Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 11:56:09 +0100 Subject: [PATCH 3/8] [fix] Pressing enter did not add item anymore. --- .../privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt index 50a9b47..dad94e8 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt @@ -19,6 +19,7 @@ import android.os.Bundle import android.text.Html import android.text.SpannedString import android.view.ContextThemeWrapper +import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.View @@ -72,7 +73,7 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI override fun onLoadActivity() { etNewItem.setOnEditorActionListener { _, _, event -> - if (event == null && etNewItem.text.isNotEmpty()) { + if ((event == null || event.keyCode == KeyEvent.KEYCODE_ENTER) && etNewItem.text.isNotEmpty()) { addItem() } return@setOnEditorActionListener true From 9655094448b5dff8ffc7b72f5c593ebbff2b6073 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 12:35:51 +0100 Subject: [PATCH 4/8] [fix] Allow ArrowKey movement as well as clickable links. --- .../helper/ArrowKeyLinkTouchMovementMethod.kt | 59 +++++++++++++++++++ .../ui/notes/TextNoteActivity.kt | 3 +- .../main/res/layout/activity_text_note.xml | 13 ++-- 3 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/ArrowKeyLinkTouchMovementMethod.kt diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/ArrowKeyLinkTouchMovementMethod.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/ArrowKeyLinkTouchMovementMethod.kt new file mode 100644 index 0000000..874b06b --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/ArrowKeyLinkTouchMovementMethod.kt @@ -0,0 +1,59 @@ +package org.secuso.privacyfriendlynotes.ui.helper + +import android.text.Selection +import android.text.Spannable +import android.text.method.ArrowKeyMovementMethod +import android.text.style.ClickableSpan +import android.view.MotionEvent +import android.widget.TextView + +class ArrowKeyLinkTouchMovementMethod : ArrowKeyMovementMethod() { + + override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { + val action = event.action + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { + var x = event.x.toInt() + var y = event.y.toInt() + x -= widget.totalPaddingLeft + y -= widget.totalPaddingTop + + x += widget.scrollX + y += widget.scrollY + + val offset = widget.layout.let { + it.getOffsetForHorizontal( + it.getLineForVertical(y), + x.toFloat() + ) + } + + val link = buffer.getSpans(offset, offset, ClickableSpan::class.java) + + if (link.isNotEmpty()) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget) + } else { + Selection.setSelection( + buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0]) + ) + } + return true + } + } + return super.onTouchEvent(widget, buffer, event) + } + + companion object { + private var instance: ArrowKeyLinkTouchMovementMethod? = null + + fun getInstance(): ArrowKeyLinkTouchMovementMethod { + if (instance == null) { + instance = ArrowKeyLinkTouchMovementMethod() + } + return instance!! + } + } +} diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt index 4875d00..9fd22d0 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.launch import org.secuso.privacyfriendlynotes.R import org.secuso.privacyfriendlynotes.room.DbContract import org.secuso.privacyfriendlynotes.room.model.Note +import org.secuso.privacyfriendlynotes.ui.helper.ArrowKeyLinkTouchMovementMethod import org.secuso.privacyfriendlynotes.ui.util.ChecklistUtil import java.io.File import java.io.InputStreamReader @@ -110,7 +111,7 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { } } - etContent.movementMethod = LinkMovementMethod.getInstance() + etContent.movementMethod = ArrowKeyLinkTouchMovementMethod.getInstance() super.onCreate(savedInstanceState) } diff --git a/app/src/main/res/layout/activity_text_note.xml b/app/src/main/res/layout/activity_text_note.xml index b4442af..5c8fc5d 100644 --- a/app/src/main/res/layout/activity_text_note.xml +++ b/app/src/main/res/layout/activity_text_note.xml @@ -18,23 +18,18 @@ - - + android:layout_marginTop="10dp" + android:layout_marginBottom="10dp"/> From 0e694af097eb734d4ee2d0379576849351957eab Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 12:57:26 +0100 Subject: [PATCH 5/8] [fix] remember cursor location. --- .../privacyfriendlynotes/ui/notes/TextNoteActivity.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt index 9fd22d0..ea7711e 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt @@ -61,6 +61,7 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { private val boldBtn: FloatingActionButton by lazy { findViewById(R.id.btn_bold) } private val italicsBtn: FloatingActionButton by lazy { findViewById(R.id.btn_italics) } private val underlineBtn: FloatingActionButton by lazy { findViewById(R.id.btn_underline) } + private var lastCursorPosition = 0 private val isBold = MutableLiveData(false) private val isItalic = MutableLiveData(false) @@ -117,6 +118,7 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { override fun onNoteLoadedFromDB(note: Note) { etContent.setText(Html.fromHtml(note.content)) + etContent.setSelection(lastCursorPosition.coerceIn(0, etContent.text.length)) oldText = etContent.text.toString() } @@ -226,6 +228,11 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { } } + override fun onPause() { + lastCursorPosition = etContent.selectionStart + super.onPause() + } + override fun onClick(v: View) { val startSelection: Int val endSelection: Int From 2afd887c6600e9ead5524cf9c419d2c6f51cf62f Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 13:21:02 +0100 Subject: [PATCH 6/8] [feat] makes text note FAB draggable --- .../ui/helper/DraggableFAB.kt | 59 +++++++++++++++++++ .../ui/notes/TextNoteActivity.kt | 2 + app/src/main/res/layout/text_note_fab.xml | 17 ++---- 3 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/DraggableFAB.kt diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/DraggableFAB.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/DraggableFAB.kt new file mode 100644 index 0000000..bc524b8 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/DraggableFAB.kt @@ -0,0 +1,59 @@ +package org.secuso.privacyfriendlynotes.ui.helper + +import android.view.MotionEvent +import android.view.View +import com.google.android.material.floatingactionbutton.FloatingActionButton +import kotlin.math.abs + +fun FloatingActionButton.makeDraggable(target: View = this) { + var downX = 0f + var downY = 0f + var dX = 0f + var dY = 0f + + val CLICK_DRAG_TOLERANCE = 10f + + this.setOnTouchListener { _, event -> + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + downX = event.rawX + downY = event.rawY + dX = target.x - downX + dY = target.y - downY + true + } + + MotionEvent.ACTION_MOVE -> { + val viewWidth = target.width + val viewHeight = target.height + + val viewParent = target.parent as View + val parentWidth = viewParent.width + val parentHeight = viewParent.height + + target.animate() + .x((parentWidth - viewWidth).toFloat().coerceAtMost(event.rawX + dX)) + .y((parentHeight - viewHeight).toFloat().coerceAtMost(event.rawY + dY)) + .setDuration(0) + .start() + true + } + + MotionEvent.ACTION_UP -> { + val upRawX = event.rawX + val upRawY = event.rawY + + val distanceX = upRawX - downX + val distanceY = upRawY - downY + + // If the finger didn't move much, trigger a click + if (abs(distanceX) < CLICK_DRAG_TOLERANCE && abs(distanceY) < CLICK_DRAG_TOLERANCE) { + performClick() + } + true + } + + else -> false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt index ea7711e..8d0ea45 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt @@ -46,6 +46,7 @@ import org.secuso.privacyfriendlynotes.R import org.secuso.privacyfriendlynotes.room.DbContract import org.secuso.privacyfriendlynotes.room.model.Note import org.secuso.privacyfriendlynotes.ui.helper.ArrowKeyLinkTouchMovementMethod +import org.secuso.privacyfriendlynotes.ui.helper.makeDraggable import org.secuso.privacyfriendlynotes.ui.util.ChecklistUtil import java.io.File import java.io.InputStreamReader @@ -78,6 +79,7 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { val fabMenuBtn = findViewById(R.id.fab_menu) val fabMenu = findViewById(R.id.fab_menu_wrapper) + fabMenuBtn.makeDraggable(fabMenuBtn.parent as View) var expanded = false fabMenuBtn.setOnClickListener { if (expanded) { diff --git a/app/src/main/res/layout/text_note_fab.xml b/app/src/main/res/layout/text_note_fab.xml index c8d7631..24b6885 100644 --- a/app/src/main/res/layout/text_note_fab.xml +++ b/app/src/main/res/layout/text_note_fab.xml @@ -1,5 +1,5 @@ - - - - - + From 67938b4ff9a2033b2218eb6ef9ca9fc1bea4d334 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 13:31:29 +0100 Subject: [PATCH 7/8] [feat] allows to copy text from locked text note. --- .../ui/notes/TextNoteActivity.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt index 8d0ea45..ccb6d44 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt @@ -21,10 +21,12 @@ import android.graphics.Typeface import android.net.Uri import android.os.Bundle import android.text.Html +import android.text.InputType import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned import android.text.method.LinkMovementMethod +import android.text.method.TextKeyListener import android.text.style.StyleSpan import android.text.style.UnderlineSpan import android.view.ContextThemeWrapper @@ -108,8 +110,15 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - isLocked.collect { - etContent.isEnabled = !it + isLocked.collect { readonly -> + if (readonly) { + etContent.keyListener = null + etContent.showSoftInputOnFocus = false + } else { + etContent.keyListener = TextKeyListener.getInstance() + etContent.showSoftInputOnFocus = true + etContent.movementMethod = ArrowKeyLinkTouchMovementMethod.getInstance() + } } } } From f43339b14d498a1b4bf63ee6c01f65b81ab74710 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 6 Mar 2026 13:53:50 +0100 Subject: [PATCH 8/8] [feat] allows creation of new checklist with all checked/unchecked items. --- .../ui/notes/BaseNoteActivity.kt | 14 ++++++++++++++ .../ui/notes/ChecklistNoteActivity.kt | 18 ++++++++++++++++++ app/src/main/res/menu/activity_checklist.xml | 10 ++++++++++ app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 5 files changed, 46 insertions(+) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/BaseNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/BaseNoteActivity.kt index 78bf968..cc207c8 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/BaseNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/BaseNoteActivity.kt @@ -683,6 +683,20 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli } } + fun newNote(content: String, type: Int, afterUpdate: (Int) -> Unit) { + saveNote(force = true) + shouldSaveOnPause = false + createEditNoteViewModel.getNoteByID(id.toLong()).observe(this) { + if (it != null) { + it.content = content + it.type = type + it._id = 0 + val id = createEditNoteViewModel.insert(it) + afterUpdate(id) + } + } + } + class ActionResult(private val status: Boolean, val ok: O?, val err: E? = null) { fun isOk(): Boolean { return this.status diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt index dad94e8..04ed274 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/ChecklistNoteActivity.kt @@ -141,6 +141,24 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI } R.id.action_select_all -> adapter.selectAll() R.id.action_deselect_all -> adapter.deselectAll() + R.id.action_new_checked -> { + val items = adapter.getItems().filter { it.state }.map { ChecklistItem(false, it.name) } + super.newNote(ChecklistUtil.json(items).toString(), DbContract.NoteEntry.TYPE_CHECKLIST) { + val i = Intent(application, ChecklistNoteActivity::class.java) + i.putExtra(EXTRA_ID, it) + startActivity(i) + finish() + } + } + R.id.action_new_unchecked -> { + val items = adapter.getItems().filter { !it.state } + super.newNote(ChecklistUtil.json(items).toString(), DbContract.NoteEntry.TYPE_CHECKLIST) { + val i = Intent(application, ChecklistNoteActivity::class.java) + i.putExtra(EXTRA_ID, it) + startActivity(i) + finish() + } + } else -> {} } diff --git a/app/src/main/res/menu/activity_checklist.xml b/app/src/main/res/menu/activity_checklist.xml index 930e8b7..67448b7 100644 --- a/app/src/main/res/menu/activity_checklist.xml +++ b/app/src/main/res/menu/activity_checklist.xml @@ -7,6 +7,16 @@ android:title="@string/action_convert_to_text" android:icon="@drawable/ic_short_text_icon_24dp" app:showAsAction="ifRoom"/> + + Alphabetisch sortieren Erinnerung setzen Alle auswählen + Neue Checkliste mit Offenen Speichern Privacy Friendly Notizen Entsperren @@ -173,4 +174,5 @@ Alle abwählen Sperren Diese Notiz konnte nicht geladen werden. Wenn Sie versuchen diese Notiz zu öffnen, wird stattdessen versucht den Inhalt der Notiz unformattiert zu speichern. + Neue Checkliste mit Fertigen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b0c0f90..b195e49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,8 @@ Delete all Deselect all Select all + New checklist from checked + New checklist from unchecked Save Lock Unlock