Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4ccded4
[fix] Adds catch-all around note preview to prevent app-start-crashes.
coderPaddyS Feb 20, 2026
13613ed
[fix] zip-export of notes with same name could lead to crash due to n…
coderPaddyS Mar 6, 2026
f778a04
[fix] Pressing enter did not add item anymore.
coderPaddyS Mar 6, 2026
9655094
[fix] Allow ArrowKey movement as well as clickable links.
coderPaddyS Mar 6, 2026
0e694af
[fix] remember cursor location.
coderPaddyS Mar 6, 2026
2afd887
[feat] makes text note FAB draggable
coderPaddyS Mar 6, 2026
67938b4
[feat] allows to copy text from locked text note.
coderPaddyS Mar 6, 2026
f43339b
[feat] allows creation of new checklist with all checked/unchecked it…
coderPaddyS Mar 6, 2026
bad1b62
[feat] allows to export sketch as png.
coderPaddyS Mar 30, 2026
b636a15
[feat] allows toggling of checklist item strike-through.
coderPaddyS Mar 30, 2026
5ae0c65
[feat] Adds option to insert picture or take photo into text note.
coderPaddyS Mar 31, 2026
e92b932
[feat] Automatically delete unused files after closing text note.
coderPaddyS Mar 31, 2026
e61a670
[feat] Allows images on new notes.
coderPaddyS Mar 31, 2026
0851769
[feat] Correctly exports text notes with images.
coderPaddyS Mar 31, 2026
3f2fad4
[feat] Correctly save & restore text notes with images.
coderPaddyS Mar 31, 2026
0aaaeca
fixes lint.
coderPaddyS Apr 1, 2026
128114e
[feat] Automatically deletes trashed notes older than 7 days.
coderPaddyS Apr 13, 2026
f9566e5
[feat] Adds settings to disable and adjust auto delete.
coderPaddyS Apr 13, 2026
ba1107e
[feat] Adds pinned notes.
coderPaddyS Apr 13, 2026
712a74a
[fix] fixed pinned notes may not take up full screen height.
coderPaddyS Apr 13, 2026
acf940e
[feat] Allows marking notes as done.
coderPaddyS Apr 14, 2026
ded29f0
[fix] #236 trailing new lines in text note.
coderPaddyS Apr 14, 2026
d07fdef
[fix] could not change to default category in note.
coderPaddyS Apr 14, 2026
f08acf0
[feat] Adds option to change category of many notes at once.
coderPaddyS Apr 14, 2026
53441c0
[feat] Adds option to sort checklist items.
coderPaddyS Apr 14, 2026
cba119c
[fix] lint.
coderPaddyS Apr 14, 2026
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
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />

<application
android:name=".PFNotesApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.List;

public class BackupCreator implements IBackupCreator {

Expand Down Expand Up @@ -68,6 +69,20 @@ public boolean writeBackup(@NonNull Context context, @NonNull OutputStream outpu
writer.name(path);
FileUtil.writePath(writer, new File(context.getFilesDir().getPath(), path), false);
}
File text_notes = new File(context.getFilesDir().getPath(), "text_notes");
if (text_notes.exists() && text_notes.isDirectory()) {
File[] files = text_notes.listFiles();
if (files != null) {
writer.name("text_notes");
writer.beginObject();
for (File path : files) {
Log.d("PFA BackupCreator", "Writing images of text note " + path);
writer.name(path.getName());
FileUtil.writePath(writer, path, true);
}
writer.endObject();
}
}
Log.d("PFA BackupCreator", "finished writing files");
writer.endObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ private void readFiles(@NonNull JsonReader reader, @NonNull Context context) thr

switch (name) {
case "sketches":
case "text_notes":
case "audio_notes":
File f = new File(context.getFilesDir(), name);
FileUtil.readPath(reader, f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,35 @@
)
public abstract class NoteDatabase extends RoomDatabase {

public static final int VERSION = 7;
public static final int VERSION = 9;
public static final String DATABASE_NAME = "allthenotes";

static final Migration MIGRATION_8_9 = new Migration(8, 9) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE notes DROP COLUMN is_done;");

database.execSQL("ALTER TABLE notes ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0;");
}
};

static final Migration MIGRATION_7_8 = new Migration(7, 8) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE notes ADD COLUMN in_trash_time INTEGER NOT NULL DEFAULT 0;");
database.execSQL("ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;");
database.execSQL("ALTER TABLE notes ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0;");

database.execSQL(
"CREATE TRIGGER [UpdateTrashTime] AFTER UPDATE ON notes FOR EACH ROW " +
"WHEN NEW.in_trash != OLD.in_trash " +
"BEGIN " +
"UPDATE notes SET in_trash_time = (CASE NEW.in_trash WHEN 0 THEN 0 ELSE unixepoch('subsec') * 1000 END) WHERE _id=NEW._id; " +
"END;"
);
}
};

static final Migration MIGRATION_6_7 = new Migration(6, 7) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Expand Down Expand Up @@ -332,6 +358,8 @@ public void migrate(SupportSQLiteDatabase database) {
MIGRATION_4_5,
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9,
};
private static final RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import org.secuso.privacyfriendlynotes.room.model.Category
import org.secuso.privacyfriendlynotes.room.model.CategoryWithCompleteInformation

/**
* Data Access Object for categories that define the interactions with the database
Expand All @@ -43,6 +44,14 @@ interface CategoryDao {

@get:Query("SELECT * FROM categories GROUP BY name")
val allCategories: Flow<List<Category>>
@get:Query("SELECT '' as name, -1 as _id, COUNT(CASE WHEN is_done > 0 THEN 1 END) AS done, COUNT(*) AS _all FROM notes " +
"UNION " +
"SELECT categories.name as name, categories._id, COUNT(CASE WHEN notes.is_done > 0 THEN 1 END) AS done, COUNT(*) AS _all FROM categories INNER JOIN notes ON categories._id == notes.category GROUP BY categories.name"
)
val allCategoriesWithDoneInformation: Flow<List<CategoryWithCompleteInformation>>

@get:Query("SELECT * FROM categories GROUP BY name")
val allCategoriesSync: List<Category>

@Query("SELECT name FROM categories WHERE _id=:thisCategoryId ")
fun categoryNameFromId(thisCategoryId: Integer): LiveData<String?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ interface NoteDao {

@Query("SELECT * FROM notes WHERE _id = :id")
fun getNoteByID(id: Long): Note?

@Query("SELECT seq + 1 FROM sqlite_sequence WHERE name = :tableName")
fun getNextId(tableName: String = "notes"): Int

@Query("SELECT * FROM notes WHERE in_trash = 1 AND in_trash_time <= :timestamp AND in_trash_time > 0")
fun getAllTrashedNotesOlderThan(timestamp: Long): List<Note>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
package org.secuso.privacyfriendlynotes.room.model

import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey

Expand Down Expand Up @@ -40,4 +42,11 @@ data class Category(
color = color
)

}
}

data class CategoryWithCompleteInformation(
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "_id") val _id: Int,
@ColumnInfo(name = "done") val done: Int,
@ColumnInfo(name = "_all") val all: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ data class Note(
var in_trash: Int = 0,
var last_modified: Long,
var custom_order: Int,
var readonly: Int
var readonly: Int,
var in_trash_time: Long = 0,
var pinned: Int = 0,
var is_done: Int = 0,
) {

constructor(name: String, content: String, type: Int, category: Int) : this(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import android.annotation.SuppressLint
import android.graphics.Paint
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -38,6 +39,19 @@ class ChecklistAdapter(
private val startDrag: (ItemHolder) -> Unit,
) : RecyclerView.Adapter<ChecklistAdapter.ItemHolder>() {

var sortingOption: SortingOption = SortingOption.NONE
set(value) {
items.sortWith { a, b ->
when (value) {
SortingOption.ASCENDING -> a.name.compareTo(b.name)
SortingOption.DESCENDING -> b.name.compareTo(a.name)
SortingOption.NONE -> 1
}
}
Log.d("Sorting", "$value")
notifyDataSetChanged()
field = value
}
private var items: MutableList<ChecklistItem> = mutableListOf()
var hasChanged = false
private set
Expand Down Expand Up @@ -77,16 +91,19 @@ class ChecklistAdapter(

override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val (checked, item) = items[position]
val strikeThrough = PreferenceManager.getDefaultSharedPreferences(holder.itemView.context).getBoolean("settings_checklist_strike_items", true)
holder.textView.text = item
holder.checkbox.isChecked = checked
holder.dragHandle.setOnTouchListener { v, _ ->
startDrag(holder)
v.performClick()
if (sortingOption == SortingOption.NONE) {
holder.dragHandle.setOnTouchListener { v, _ ->
startDrag(holder)
v.performClick()
}
}
holder.checkbox.setOnClickListener { _ ->
items[holder.bindingAdapterPosition].state = holder.checkbox.isChecked
holder.textView.apply {
paintFlags = if (holder.checkbox.isChecked) {
paintFlags = if (holder.checkbox.isChecked && strikeThrough) {
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
Expand All @@ -111,7 +128,7 @@ class ChecklistAdapter(
})

holder.textView.apply {
paintFlags = if (checked) {
paintFlags = if (checked && strikeThrough) {
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
Expand All @@ -129,8 +146,17 @@ class ChecklistAdapter(
}

fun addItem(item: String) {
this.items.add(ChecklistItem(false, item))
notifyItemInserted(items.size - 1)
var index = when (sortingOption) {
SortingOption.NONE -> items.size
SortingOption.ASCENDING -> items.indexOfFirst { it.name >= item }
SortingOption.DESCENDING -> items.indexOfFirst { it.name <= item }
}
// item not found -> index = -1 -> should be at end
if (index < 0) {
index = items.size
}
this.items.add(index, ChecklistItem(false, item))
notifyItemInserted(index)
hasChanged = true
}

Expand All @@ -149,4 +175,10 @@ class ChecklistAdapter(
val checkbox: MaterialCheckBox = itemView.findViewById(R.id.item_checkbox)
val dragHandle: View = itemView.findViewById(R.id.drag_handle)
}

enum class SortingOption {
ASCENDING,
DESCENDING,
NONE
}
}
Loading
Loading