diff --git a/.gitignore b/.gitignore
index da1f061a..9ef5f066 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@ local.properties
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
+.idea/*
*.iml
# OS-specific files
diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
deleted file mode 100644
index 376667eb..00000000
--- a/.idea/assetWizardSettings.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 0d52f12f..185b8a90 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'androidx.room'
def pfaFile = rootProject.file('pfa.properties')
@@ -23,14 +24,8 @@ android {
minSdkVersion 21
compileSdk 34
targetSdkVersion 34
- versionCode 18
- versionName "1.4.5"
-
- javaCompileOptions {
- annotationProcessorOptions {
- arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
- }
- }
+ versionCode 19
+ versionName "2.0.0"
}
applicationVariants.configureEach { variant ->
@@ -63,6 +58,10 @@ android {
jvmTarget = JavaVersion.VERSION_17.toString()
}
+ room {
+ schemaDirectory "$projectDir/schemas"
+ }
+
namespace 'org.secuso.privacyfriendlynotes'
}
@@ -71,12 +70,15 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.room:room-testing:2.6.0'
+ implementation 'androidx.preference:preference-ktx:1.2.1'
+ implementation 'com.google.android.material:material:1.11.0'
testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'junit:junit:4.13.2'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
- implementation 'com.getbase:floatingactionbutton:1.10.1'
+ implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.simplify:ink:1.0.0'
- implementation 'petrov.kristiyan:colorpicker-library:1.1.10'
+ implementation 'io.github.eltos:simpledialogfragments:3.6.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
@@ -88,8 +90,8 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
-
- def room_version = "2.5.2"
+ androidTestImplementation('androidx.test:runner:1.5.2')
+ androidTestImplementation('androidx.test:core:1.5.0')
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
@@ -97,6 +99,7 @@ dependencies {
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
diff --git a/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/4.json b/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/4.json
new file mode 100644
index 00000000..2426ca8f
--- /dev/null
+++ b/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/4.json
@@ -0,0 +1,116 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "3833bf55517c3164a564eea2186d4cb9",
+ "entities": [
+ {
+ "tableName": "notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `content` TEXT NOT NULL, `type` INTEGER NOT NULL, `category` INTEGER NOT NULL, `in_trash` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "_id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "in_trash",
+ "columnName": "in_trash",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "categories",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "_id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_noteId` INTEGER NOT NULL, `time` INTEGER NOT NULL, PRIMARY KEY(`_noteId`))",
+ "fields": [
+ {
+ "fieldPath": "_noteId",
+ "columnName": "_noteId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_noteId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3833bf55517c3164a564eea2186d4cb9')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/5.json b/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/5.json
new file mode 100644
index 00000000..fed4cecc
--- /dev/null
+++ b/app/schemas/org.secuso.privacyfriendlynotes.room.NoteDatabase/5.json
@@ -0,0 +1,134 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "11488d17adaf947e6a6c0deb72b97ea7",
+ "entities": [
+ {
+ "tableName": "notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `content` TEXT NOT NULL, `type` INTEGER NOT NULL, `category` INTEGER NOT NULL, `in_trash` INTEGER NOT NULL, `last_modified` TEXT NOT NULL, `custom_order` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "_id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "in_trash",
+ "columnName": "in_trash",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "last_modified",
+ "columnName": "last_modified",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "custom_order",
+ "columnName": "custom_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "categories",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "_id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_noteId` INTEGER NOT NULL, `time` INTEGER NOT NULL, PRIMARY KEY(`_noteId`))",
+ "fields": [
+ {
+ "fieldPath": "_noteId",
+ "columnName": "_noteId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_noteId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '11488d17adaf947e6a6c0deb72b97ea7')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/secuso/privacyfriendlynotes/ApplicationTest.java b/app/src/androidTest/java/org/secuso/privacyfriendlynotes/ApplicationTest.java
index 8dd79ed0..7bbc146e 100644
--- a/app/src/androidTest/java/org/secuso/privacyfriendlynotes/ApplicationTest.java
+++ b/app/src/androidTest/java/org/secuso/privacyfriendlynotes/ApplicationTest.java
@@ -1,13 +1,20 @@
package org.secuso.privacyfriendlynotes;
-import android.app.Application;
-import android.test.ApplicationTestCase;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Testing Fundamentals
*/
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
+@RunWith(AndroidJUnit4.class)
+public class ApplicationTest {
+ @Test
+ public void instrumentationTest() throws Exception {
+ assertEquals("org.secuso.privacyfriendlynotes", InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName());
}
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ca1c1e59..d626dddf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,7 +9,7 @@
+ android:parentActivityName=".ui.main.MainActivity"
+ android:exported="true">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
.
- */
-package org.secuso.privacyfriendlynotes;
-
-import android.app.Activity;
-import android.app.Application;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.work.Configuration;
-
-import org.secuso.privacyfriendlybackup.api.pfa.BackupManager;
-import org.secuso.privacyfriendlynotes.backup.BackupCreator;
-import org.secuso.privacyfriendlynotes.backup.BackupRestorer;
-
-import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class NotesApplication extends Application implements Configuration.Provider {
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- BackupManager.setBackupCreator(new BackupCreator());
- BackupManager.setBackupRestorer(new BackupRestorer());
- }
-
- @Override
- public @NonNull Configuration getWorkManagerConfiguration() {
- return new Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build();
- }
-
- private AtomicBoolean lock = new AtomicBoolean(false);
-
- public void lock() {
- lock.set(true);
- showAlertDialog(shownView.get());
- }
-
- public void release() {
- lock.set(false);
- }
-
- private @NonNull WeakReference shownView = new WeakReference<>(null);
- public void register(@NonNull Activity obs) {
- shownView = new WeakReference<>(obs);
- if(lock.get()) {
- showAlertDialog(obs);
- }
- }
- public void unregister() {
- shownView = new WeakReference<>(null);
- }
-
- private void showAlertDialog(Context context) {
- //AlertDialog.Builder builder = new AlertDialog.Builder(context);
-
- }
-
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/PFNotesApplication.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/PFNotesApplication.kt
new file mode 100644
index 00000000..f068f9f9
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/PFNotesApplication.kt
@@ -0,0 +1,40 @@
+/*
+This file is part of the application Privacy Friendly Notes.
+Privacy Friendly Notes is free software:
+you can redistribute it and/or modify it under the terms of the
+GNU General Public License as published by the Free Software Foundation,
+either version 3 of the License, or any later version.
+Privacy Friendly Notes is distributed in the hope
+that it will be useful, but WITHOUT ANY WARRANTY; without even
+the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+You should have received a copy of the GNU General Public License
+along with Privacy Friendly Notes. If not, see .
+*/
+package org.secuso.privacyfriendlynotes
+
+import android.app.Application
+import android.util.Log
+import androidx.work.Configuration
+import org.secuso.privacyfriendlybackup.api.pfa.BackupManager.backupCreator
+import org.secuso.privacyfriendlybackup.api.pfa.BackupManager.backupRestorer
+import org.secuso.privacyfriendlynotes.backup.BackupCreator
+import org.secuso.privacyfriendlynotes.backup.BackupRestorer
+
+/**
+ * The main application.
+ * Configures backup.
+ *
+ * @author Patrick Schneider
+ */
+class PFNotesApplication : Application(), Configuration.Provider {
+ override fun onCreate() {
+ super.onCreate()
+ backupCreator = BackupCreator()
+ backupRestorer = BackupRestorer()
+ }
+
+ override fun getWorkManagerConfiguration(): Configuration {
+ return Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build()
+ }
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupCreator.java b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupCreator.java
index bf1e394e..60166c67 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupCreator.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupCreator.java
@@ -13,6 +13,9 @@
*/
package org.secuso.privacyfriendlynotes.backup;
+import static org.secuso.privacyfriendlynotes.room.NoteDatabase.DATABASE_NAME;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
@@ -23,28 +26,20 @@
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
-import org.secuso.privacyfriendlybackup.api.backup.FileUtil;
-import org.secuso.privacyfriendlybackup.api.pfa.IBackupCreator;
import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil;
+import org.secuso.privacyfriendlybackup.api.backup.FileUtil;
import org.secuso.privacyfriendlybackup.api.backup.PreferenceUtil;
-import org.secuso.privacyfriendlynotes.NotesApplication;
+import org.secuso.privacyfriendlybackup.api.pfa.IBackupCreator;
import java.io.File;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.secuso.privacyfriendlynotes.room.NoteDatabase.DATABASE_NAME;
-
public class BackupCreator implements IBackupCreator {
@Override
public boolean writeBackup(@NonNull Context context, @NonNull OutputStream outputStream) {
- // lock application, so no changes can be made as long as this backup is created
- // depending on the size of the application - this could take a bit
- ((NotesApplication) context.getApplicationContext()).lock();
-
Log.d("PFA BackupCreator", "createBackup() started");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, UTF_8);
JsonWriter writer = new JsonWriter(outputStreamWriter);
@@ -69,7 +64,7 @@ public boolean writeBackup(@NonNull Context context, @NonNull OutputStream outpu
Log.d("PFA BackupCreator", "Writing files");
writer.name("files");
writer.beginObject();
- for(String path : Arrays.asList("sketches", "audio_notes")) {
+ for (String path : Arrays.asList("sketches", "audio_notes")) {
writer.name(path);
FileUtil.writePath(writer, new File(context.getFilesDir().getPath(), path), false);
}
@@ -87,7 +82,6 @@ public boolean writeBackup(@NonNull Context context, @NonNull OutputStream outpu
Log.d("PFA BackupCreator", "Backup created successfully");
- ((NotesApplication) context.getApplicationContext()).release();
return true;
}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupRestorer.java b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupRestorer.java
index 3b2960c6..d94a3206 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupRestorer.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/BackupRestorer.java
@@ -13,6 +13,8 @@
*/
package org.secuso.privacyfriendlynotes.backup;
+import static org.secuso.privacyfriendlynotes.room.NoteDatabase.DATABASE_NAME;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
@@ -34,8 +36,6 @@
import java.io.InputStream;
import java.io.InputStreamReader;
-import static org.secuso.privacyfriendlynotes.room.NoteDatabase.DATABASE_NAME;
-
public class BackupRestorer implements IBackupRestorer {
private void readFiles(@NonNull JsonReader reader, @NonNull Context context) throws IOException {
@@ -121,16 +121,15 @@ private void readPreferences(@NonNull JsonReader reader, @NonNull Context contex
String name = reader.nextName();
switch (name) {
- case "settings_use_custom_font_size":
- case "settings_del_notes":
- case "settings_show_preview":
- editor.putBoolean(name, reader.nextBoolean());
- break;
- case "settings_font_size":
- editor.putString(name, reader.nextString());
- break;
- default:
- throw new RuntimeException("Unknown preference " + name);
+ case "settings_use_custom_font_size",
+ "settings_del_notes",
+ "settings_show_preview",
+ "settings_dialog_on_trashing",
+ "settings_color_category",
+ "notes_reversed_ordering",
+ "settings_sketch_undo_redo" -> editor.putBoolean(name, reader.nextBoolean());
+ case "settings_font_size", "settings_day_night_theme", "notes_ordering" -> editor.putString(name, reader.nextString());
+ default -> throw new RuntimeException("Unknown preference " + name);
}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/PFABackupService.java b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/PFABackupService.java
index 924301c0..095a77c7 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/backup/PFABackupService.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/backup/PFABackupService.java
@@ -15,4 +15,5 @@
import org.secuso.privacyfriendlybackup.api.pfa.PFAAuthService;
-public class PFABackupService extends PFAAuthService { }
+public class PFABackupService extends PFAAuthService {
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListItem.java b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt
similarity index 54%
rename from app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListItem.java
rename to app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt
index 3aad79be..c53e36e5 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListItem.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt
@@ -11,33 +11,16 @@
You should have received a copy of the GNU General Public License
along with Privacy Friendly Notes. If not, see .
*/
-package org.secuso.privacyfriendlynotes.ui.util;
+package org.secuso.privacyfriendlynotes.model
/**
- * Created by Robin on 12.09.2016.
+ * This enum represents all supported sorting orders to sort notes by.
+ * @author Patrick Schneider
*/
-public class CheckListItem {
- private boolean isChecked = false;
- private String name = "";
-
- public CheckListItem(boolean isChecked, String name) {
- this.isChecked = isChecked;
- this.name = name;
- }
-
- public boolean isChecked() {
- return isChecked;
- }
-
- public void setChecked(boolean checked) {
- isChecked = checked;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-}
+enum class SortingOrder {
+ AlphabeticalAscending,
+ TypeAscending,
+ Creation,
+ LastModified,
+ Custom
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java b/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java
index a26dbbf7..94b7b0cb 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java
@@ -22,4 +22,7 @@ public class PreferenceKeys {
public static final String SP_DATA_DISPLAY_TRASH_MESSAGE = "sp_data_display_trash_message";
public static final String SP_VALUES = "values";
public static final String SP_VALUES_NAMECOUNTER = "sp_values_namecounter";
+
+ public static final String SP_NOTES_ORDERING = "notes_ordering";
+ public static final String SP_NOTES_REVERSED = "notes_reversed_ordering";
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/receiver/NotificationReceiver.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/receiver/NotificationReceiver.kt
new file mode 100644
index 00000000..a74af2a0
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/receiver/NotificationReceiver.kt
@@ -0,0 +1,91 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.receiver
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.DbContract
+import org.secuso.privacyfriendlynotes.ui.notes.AudioNoteActivity
+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
+
+/**
+ * This receiver is responsible to create and show notifications to the user.
+ * As a receiver, this will run even if the app is closed if scheduled with AlarmManager.
+ * Originally created by Robin on 26.06.2016 as NotificationService.java.
+ * Migrated by Patrick on 27.01.2024.
+ *
+ * @author Robin
+ * @author Patrick Schneider
+ */
+class NotificationReceiver : BroadcastReceiver() {
+ companion object {
+ const val NOTIFICATION_ID = "notification_id"
+ const val NOTIFICATION_TYPE = "notification_type"
+ const val NOTIFICATION_TITLE = "notification_title"
+ const val NOTIFICATION_CHANNEL = "Notes_Notifications"
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val notification = intent.getIntExtra(NOTIFICATION_ID, -1)
+ val type = intent.getIntExtra(NOTIFICATION_TYPE, -1)
+ val name = intent.getStringExtra(NOTIFICATION_TITLE)
+ Log.d(
+ javaClass.simpleName,
+ "onHandleIntent($NOTIFICATION_ID:$notification;$NOTIFICATION_TYPE:$type;$NOTIFICATION_TITLE:$name)"
+ )
+ if (notification != -1 && type != -1) {
+ Log.d(javaClass.simpleName, "Creating intent for $context with type $type and intent $intent and id $notification and name $name")
+ val pendingIntent = Intent(
+ context, when (type) {
+ DbContract.NoteEntry.TYPE_TEXT -> TextNoteActivity::class.java
+ DbContract.NoteEntry.TYPE_AUDIO -> AudioNoteActivity::class.java
+ DbContract.NoteEntry.TYPE_SKETCH -> SketchActivity::class.java
+ DbContract.NoteEntry.TYPE_CHECKLIST -> ChecklistNoteActivity::class.java
+ else -> throw IllegalStateException("Note with type $type does not exist!")
+ }
+ ).apply {
+ putExtra(BaseNoteActivity.EXTRA_ID, notification)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }.let {
+ PendingIntent.getActivity(context, notification, it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+ val mNotifyMgr = ContextCompat.getSystemService(context, NotificationManager::class.java) as NotificationManager
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationChannel = NotificationChannel(NOTIFICATION_CHANNEL, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH)
+ notificationChannel.description = context.getString(R.string.app_name)
+ mNotifyMgr.createNotificationChannel(notificationChannel)
+ }
+ val mBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
+ mBuilder.setSmallIcon(R.mipmap.ic_notification)
+ .setColor(context.resources.getColor(R.color.colorPrimary))
+ .setContentTitle(name)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+ mNotifyMgr.notify(notification, mBuilder.build())
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/DbContract.java b/app/src/main/java/org/secuso/privacyfriendlynotes/room/DbContract.java
index e15bce71..a9f3f239 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/DbContract.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/DbContract.java
@@ -20,7 +20,8 @@
* Created by Robin on 11.06.2016.
*/
public class DbContract {
- public DbContract(){}
+ public DbContract() {
+ }
public static abstract class NoteEntry implements BaseColumns {
public static final String TABLE_NAME = "notes";
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java b/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java
index 94ec1f90..c98dd252 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java
@@ -18,7 +18,6 @@
import android.database.Cursor;
import android.text.Html;
import android.text.SpannedString;
-import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
@@ -47,80 +46,53 @@
)
public abstract class NoteDatabase extends RoomDatabase {
- public static final int VERSION = 4;
+ public static final int VERSION = 5;
public static final String DATABASE_NAME = "allthenotes";
- private static NoteDatabase instance;
-
- public abstract NoteDao noteDao();
-
- public abstract CategoryDao categoryDao();
+ static final Migration MIGRATION_4_5 = new Migration(4, 5) {
- public abstract NotificationDao notificationDao();
-
- public static synchronized NoteDatabase getInstance(Context context) {
- return getInstance(context, DATABASE_NAME);
- }
-
- public static synchronized NoteDatabase getInstance(Context context, String databaseName) {
- if (instance == null || !DATABASE_NAME.equals(databaseName)) {
- instance = Room.databaseBuilder(context.getApplicationContext(),
- NoteDatabase.class, databaseName)
- .allowMainThreadQueries()
- .addMigrations(MIGRATIONS)
- .addCallback(roomCallback)
- .build();
- }
- return instance;
- }
-
- public static synchronized NoteDatabase getInstance(Context context, String databaseName, File file) {
- if (instance == null) {
- instance = Room.databaseBuilder(context.getApplicationContext(),
- NoteDatabase.class, databaseName)
- .createFromFile(file)
- .allowMainThreadQueries()
- .addMigrations(MIGRATIONS)
- .addCallback(roomCallback)
- .build();
- }
- return instance;
- }
-
- private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
@Override
- public void onCreate(@NonNull SupportSQLiteDatabase db) {
- super.onCreate(db);
- }
- };
-
- /**
- * Provides data migration from database version 3 to 4 which checks for an error in the previous
- * migration when a backup was imported
- */
- static final Migration MIGRATION_3_4 = new Migration(3, 4) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // get current schema and check if it needs to be fixed
- String result = "";
- Cursor c = database.query("SELECT sql FROM sqlite_master WHERE type='table' AND name='notes';");
- if (c != null) {
- if (c.moveToFirst()) {
- while (!c.isAfterLast()) {
- result = c.getString(c.getColumnIndexOrThrow("sql"));
- c.moveToNext();
- }
- }
- c.close();
- }
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
- String categorySQL = result.split("category")[1].split(",")[0];
+ // Adds new color field
+ database.execSQL("ALTER TABLE categories ADD COLUMN color TEXT");
- if (categorySQL != null && categorySQL.toUpperCase().contains("INTEGER") && !categorySQL.toUpperCase().contains("NOT NULL")) {
- MIGRATION_1_2.migrate(database);
- }
+ // Adds new fields to sort by
+ database.execSQL(
+ "CREATE TABLE notes_new (_id INTEGER NOT NULL DEFAULT 0,"
+ + "in_trash INTEGER NOT NULL DEFAULT 0,"
+ + "name TEXT NOT NULL DEFAULT 'TEXT',"
+ + "type INTEGER NOT NULL DEFAULT 0,"
+ + "category INTEGER NOT NULL DEFAULT 0,"
+ + "content TEXT NOT NULL DEFAULT 'TEXT',"
+ + "last_modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ + "custom_order INTEGER NOT NULL DEFAULT 0,"
+ + "PRIMARY KEY(_id));");
+ database.execSQL("INSERT INTO notes_new(_id, in_trash,name,type,category,content,custom_order) SELECT _id, in_trash,name,type,category,content,_id as custom_order FROM notes ORDER BY _id ASC;");
+ database.execSQL("DROP TABLE notes;");
+ database.execSQL("ALTER TABLE notes_new RENAME TO notes");
+ database.execSQL(
+ "CREATE TRIGGER [UpdateLastModified] AFTER UPDATE ON notes FOR EACH ROW " +
+ "WHEN NEW.last_modified = OLD.last_modified AND NEW.custom_order = OLD.custom_order AND NEW.in_trash = OLD.in_trash " +
+ "BEGIN " +
+ "UPDATE notes SET last_modified = DateTime('now') WHERE _id=NEW._id; " +
+ "END;"
+ );
+ database.execSQL(
+ "CREATE TRIGGER [InsertCustomOrder] AFTER INSERT ON notes FOR EACH ROW " +
+ "BEGIN " +
+ "UPDATE notes SET custom_order = _id WHERE _id=NEW._id; " +
+ "END;"
+ );
+ // This trigger ensures that a custom_order cannot be updated to an invalid value <= 0 and defers to the old value or the id to ensure valid custom_orders.
+ database.execSQL(
+ "CREATE TRIGGER [UpdateCustomOrder] AFTER UPDATE OF custom_order ON notes FOR EACH ROW " +
+ "WHEN NEW.custom_order <= 0 " +
+ "BEGIN " +
+ "UPDATE notes SET custom_order = (CASE WHEN OLD.custom_order <= 0 THEN OLD._id ELSE OLD.custom_order END) WHERE _id=NEW._id; " +
+ "END;"
+ );
}
};
-
/**
* Provides data migration from database version 1 (SQLite) to 2 (Room)
*/
@@ -184,6 +156,33 @@ public void migrate(SupportSQLiteDatabase database) {
}
}
};
+ /**
+ * Provides data migration from database version 3 to 4 which checks for an error in the previous
+ * migration when a backup was imported
+ */
+ static final Migration MIGRATION_3_4 = new Migration(3, 4) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // get current schema and check if it needs to be fixed
+ String result = "";
+ Cursor c = database.query("SELECT sql FROM sqlite_master WHERE type='table' AND name='notes';");
+ if (c != null) {
+ if (c.moveToFirst()) {
+ while (!c.isAfterLast()) {
+ result = c.getString(c.getColumnIndexOrThrow("sql"));
+ c.moveToNext();
+ }
+ }
+ c.close();
+ }
+
+ String categorySQL = result.split("category")[1].split(",")[0];
+
+ if (categorySQL != null && categorySQL.toUpperCase().contains("INTEGER") && !categorySQL.toUpperCase().contains("NOT NULL")) {
+ MIGRATION_1_2.migrate(database);
+ }
+ }
+ };
static final Migration MIGRATION_1_3 = new Migration(1, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
@@ -228,11 +227,61 @@ public void migrate(SupportSQLiteDatabase database) {
}
}
};
-
public static final Migration[] MIGRATIONS = {
MIGRATION_1_2,
MIGRATION_1_3,
MIGRATION_2_3,
- MIGRATION_3_4
+ MIGRATION_3_4,
+ MIGRATION_4_5
+ };
+ private static final RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
+ @Override
+ public void onCreate(@NonNull SupportSQLiteDatabase db) {
+ // Adds a trigger to auto-set custom_order to _id
+ // Room currently supports no DEFAULT = COLUMN or @Trigger Annotation
+ db.execSQL(
+ "CREATE TRIGGER [InsertCustomOrder] AFTER INSERT ON notes FOR EACH ROW " +
+ "BEGIN " +
+ "UPDATE notes SET custom_order = _id WHERE _id=NEW._id; " +
+ "END;"
+ );
+ super.onCreate(db);
+ }
};
+ private static NoteDatabase instance;
+
+ public static synchronized NoteDatabase getInstance(Context context) {
+ return getInstance(context, DATABASE_NAME);
+ }
+
+ public static synchronized NoteDatabase getInstance(Context context, String databaseName) {
+ if (instance == null || !DATABASE_NAME.equals(databaseName)) {
+ instance = Room.databaseBuilder(context.getApplicationContext(),
+ NoteDatabase.class, databaseName)
+ .allowMainThreadQueries()
+ .addMigrations(MIGRATIONS)
+ .addCallback(roomCallback)
+ .build();
+ }
+ return instance;
+ }
+
+ public static synchronized NoteDatabase getInstance(Context context, String databaseName, File file) {
+ if (instance == null) {
+ instance = Room.databaseBuilder(context.getApplicationContext(),
+ NoteDatabase.class, databaseName)
+ .createFromFile(file)
+ .allowMainThreadQueries()
+ .addMigrations(MIGRATIONS)
+ .addCallback(roomCallback)
+ .build();
+ }
+ return instance;
+ }
+
+ public abstract NoteDao noteDao();
+
+ public abstract CategoryDao categoryDao();
+
+ public abstract NotificationDao notificationDao();
}
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 4da253a0..f9734fa0 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
@@ -14,8 +14,13 @@
package org.secuso.privacyfriendlynotes.room.dao
import androidx.lifecycle.LiveData
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
import androidx.room.OnConflictStrategy.Companion.REPLACE
+import androidx.room.Query
+import androidx.room.Update
+import kotlinx.coroutines.flow.Flow
import org.secuso.privacyfriendlynotes.room.model.Category
/**
@@ -30,15 +35,18 @@ interface CategoryDao {
@Update(onConflict = REPLACE)
fun update(category: Category)
+ @Query("UPDATE categories SET color = :color WHERE _id = :id")
+ fun update(id: Int, color: String?)
+
@Delete
fun delete(category: Category)
@get:Query("SELECT * FROM categories GROUP BY name")
- val allCategoriesLive: LiveData>
-
- @Query("SELECT * FROM categories GROUP BY name")
- suspend fun getAllCategories(): List
+ val allCategories: Flow>
@Query("SELECT name FROM categories WHERE _id=:thisCategoryId ")
fun categoryNameFromId(thisCategoryId: Integer): LiveData
+
+ @Query("SELECT color FROM categories WHERE _id=:category ")
+ fun getCategoryColor(category: Int): String?
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt
index 5874d48f..14b25ce7 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt
@@ -13,8 +13,11 @@
*/
package org.secuso.privacyfriendlynotes.room.dao
-import androidx.lifecycle.LiveData
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import org.secuso.privacyfriendlynotes.room.model.Note
@@ -30,38 +33,20 @@ interface NoteDao {
@Update
fun update(note: Note)
+ @Update
+ fun updateAll(note: List)
+
@Delete
fun delete(note: Note)
- @get:Query("SELECT * FROM notes ORDER BY category DESC")
- val allNotes: LiveData>
-
- @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY name ASC")
- val allNotesAlphabetical: LiveData>
-
- @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY name DESC")
- val allActiveNotes: LiveData>
-
- @get:Query("SELECT * FROM notes WHERE in_trash = 1 ORDER BY name DESC")
- val allTrashedNotes: LiveData>
-
- @Query("SELECT * FROM notes WHERE category=:thisCategory AND in_trash='0'")
- fun notesFromCategory(thisCategory: Integer): LiveData?>
-
- @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY name DESC")
- fun activeNotesFiltered(thisFilterText: String): Flow?>
-
- @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY name ASC")
- fun activeNotesFilteredAlphabetical(thisFilterText: String): Flow?>
-
- @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='1' ORDER BY name DESC")
- fun trashedNotesFiltered(thisFilterText: String): Flow?>
+ @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY category DESC")
+ val allActiveNotes: Flow>
- @Query("SELECT * FROM notes WHERE (category=:thisCategory) AND (in_trash='0') AND ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) ORDER BY name DESC")
- fun activeNotesFilteredFromCategory(thisFilterText: String, thisCategory: Integer): Flow?>
+ @get:Query("SELECT * FROM notes WHERE in_trash = 1 ORDER BY category DESC")
+ val allTrashedNotes: Flow>
@Query("SELECT * FROM notes")
- fun getNotesDebug() : List
+ fun getNotesDebug(): List
@Query("SELECT * FROM notes WHERE _id = :id")
fun getNoteByID(id: Long): Note?
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NotificationDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NotificationDao.kt
index 5c076536..723cbb35 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NotificationDao.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NotificationDao.kt
@@ -14,8 +14,12 @@
package org.secuso.privacyfriendlynotes.room.dao
import androidx.lifecycle.LiveData
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
import androidx.room.OnConflictStrategy.Companion.REPLACE
+import androidx.room.Query
+import androidx.room.Update
import org.secuso.privacyfriendlynotes.room.model.Notification
/**
@@ -40,5 +44,5 @@ interface NotificationDao {
val allNotificationsLiveData: LiveData>
@Query("SELECT * FROM notifications")
- fun getAllNotifications() : List
+ fun getAllNotifications(): List
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt
index 0065267d..137807e0 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt
@@ -22,13 +22,22 @@ import androidx.room.PrimaryKey
@Entity(tableName = "categories")
data class Category(
- @PrimaryKey(autoGenerate = true)
- val _id: Int,
- val name: String) {
+ @PrimaryKey(autoGenerate = true)
+ val _id: Int,
+ val name: String,
+ var color: String?
+) {
- constructor(name: String) : this(
- name = name,
- _id = 0
- )
+ constructor(name: String) : this(
+ name = name,
+ _id = 0,
+ color = null
+ )
+
+ constructor(name: String, color: String?) : this(
+ name = name,
+ _id = 0,
+ color = color
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt
index 491d1652..02b28d31 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt
@@ -13,8 +13,10 @@
*/
package org.secuso.privacyfriendlynotes.room.model
+import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
+import java.util.Calendar
/**
* Provides note class with variables and constructor.
@@ -22,20 +24,36 @@ import androidx.room.PrimaryKey
@Entity(tableName = "notes")
data class Note(
- @PrimaryKey(autoGenerate = true)
- var _id: Int = 0,
- var name: String,
- var content: String,
- var type: Int,
- var category: Int,
- var in_trash: Int = 0) {
+ @PrimaryKey(autoGenerate = true)
+ var _id: Int = 0,
+ var name: String,
+ var content: String,
+ var type: Int,
+ var category: Int,
+ var in_trash: Int = 0,
+ var last_modified: String,
+ var custom_order: Int
+) {
- constructor(name: String, content: String, type: Int, category: Int) : this(
- name = name,
- content = content,
- type = type,
- category = category,
- in_trash = 0,
- _id = 0
- )
+ constructor(name: String, content: String, type: Int, category: Int) : this(
+ name = name,
+ content = content,
+ type = type,
+ category = category,
+ in_trash = 0,
+ _id = 0,
+ last_modified = Calendar.getInstance().time.toString(),
+ custom_order = 0
+ )
+
+ constructor(name: String, content: String, type: Int, category: Int, custom_order: Int) : this(
+ name = name,
+ content = content,
+ type = type,
+ category = category,
+ in_trash = 0,
+ _id = 0,
+ last_modified = Calendar.getInstance().time.toString(),
+ custom_order = custom_order
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Notification.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Notification.kt
index e41b5743..60c852a5 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Notification.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Notification.kt
@@ -22,7 +22,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "notifications")
data class Notification(
- @PrimaryKey(autoGenerate = false)
- var _noteId: Int,
- var time: Int) {
-}
\ No newline at end of file
+ @PrimaryKey(autoGenerate = false)
+ var _noteId: Int,
+ var time: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/service/NotificationService.java b/app/src/main/java/org/secuso/privacyfriendlynotes/service/NotificationService.java
index e5b803be..e7ea4914 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/service/NotificationService.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/service/NotificationService.java
@@ -31,10 +31,13 @@
import org.secuso.privacyfriendlynotes.ui.notes.SketchActivity;
import org.secuso.privacyfriendlynotes.ui.notes.TextNoteActivity;
-/**
+/***
* Service does handle the activated notifications with a PendingIntent
* Created by Robin on 26.06.2016.
+ *
+ * @deprecated THIS CLASS IS ONLY FOR LEGACY REASONS. USE NotificationReceiver instead.
*/
+@Deprecated(forRemoval = true)
public class NotificationService extends IntentService {
public static final String NOTIFICATION_ID = "notification_id";
public static final String NOTIFICATION_TYPE = "notification_type";
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/AboutActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/AboutActivity.java
index c5118384..af27ec3d 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/AboutActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/AboutActivity.java
@@ -14,10 +14,11 @@
package org.secuso.privacyfriendlynotes.ui;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+
import org.secuso.privacyfriendlynotes.BuildConfig;
import org.secuso.privacyfriendlynotes.R;
@@ -31,8 +32,8 @@ public class AboutActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
- ((TextView)findViewById(R.id.about_secuso_website)).setMovementMethod(LinkMovementMethod.getInstance());
- ((TextView)findViewById(R.id.about_github_url)).setMovementMethod(LinkMovementMethod.getInstance());
+ ((TextView) findViewById(R.id.about_secuso_website)).setMovementMethod(LinkMovementMethod.getInstance());
+ ((TextView) findViewById(R.id.about_github_url)).setMovementMethod(LinkMovementMethod.getInstance());
((TextView) findViewById(R.id.textFieldVersionName)).setText(BuildConfig.VERSION_NAME);
}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/HelpActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/HelpActivity.java
index 58514cf6..4e640ec4 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/HelpActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/HelpActivity.java
@@ -14,6 +14,7 @@
package org.secuso.privacyfriendlynotes.ui;
import android.os.Bundle;
+
import androidx.appcompat.app.AppCompatActivity;
import org.secuso.privacyfriendlynotes.R;
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.java
deleted file mode 100644
index bc503c19..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui;
-
-import android.content.DialogInterface;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import android.preference.PreferenceManager;
-import android.widget.SearchView;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.secuso.privacyfriendlynotes.room.DbContract;
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.room.model.Note;
-import org.secuso.privacyfriendlynotes.ui.adapter.NoteAdapter;
-import org.secuso.privacyfriendlynotes.ui.main.MainActivityViewModel;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Activity that allows to interact with trashed notes.
- */
-
-public class RecycleActivity extends AppCompatActivity {
-
- MainActivityViewModel mainActivityViewModel;
- SearchView searchView;
- NoteAdapter adapter;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_recycle);
-
-
- RecyclerView recyclerView = findViewById(R.id.recyclerViewRecycle);
- recyclerView.setLayoutManager(new LinearLayoutManager(this));
- recyclerView.setHasFixedSize(true);
- adapter = new NoteAdapter();
- recyclerView.setAdapter(adapter);
- searchView = findViewById(R.id.searchViewFilterRecycle);
-
- mainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
- mainActivityViewModel.getTrashedNotes().observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List notes) {
- adapter.setNotes(notes);
- }
- });
-
- searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextChange(String newText) {
- applyFilterTrashed(newText);
- return true;
- }
-
- @Override
- public boolean onQueryTextSubmit(String query) {
- applyFilterTrashed(query);
- return true;
- }
- });
-
- adapter.setOnItemClickListener(new NoteAdapter.OnItemClickListener() {
- @Override
- public void onItemClick(Note note) {
- new AlertDialog.Builder(RecycleActivity.this)
- .setTitle(String.format(getString(R.string.dialog_restore_title), note.getName()))
- .setMessage(String.format(getString(R.string.dialog_restore_message), note.getName()))
- .setNegativeButton(R.string.dialog_option_delete, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mainActivityViewModel.delete(note);
- if (note.getType() == DbContract.NoteEntry.TYPE_AUDIO) {
- new File(getFilesDir().getPath()+"/audio_notes"+note.getContent() ).delete();
- } else if (note.getType() == DbContract.NoteEntry.TYPE_SKETCH) {
- new File(getFilesDir().getPath()+"/sketches"+note.getContent() ).delete();
- new File(getFilesDir().getPath()+"/sketches"+ note.getContent().substring(0, note.getContent().length()-3) + "jpg").delete();
- }
- }
- })
- .setNeutralButton(android.R.string.no, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- //do nothing
- }
- })
- .setPositiveButton(R.string.dialog_option_restore, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- note.setIn_trash(0);
- mainActivityViewModel.update(note);
- }
- })
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show();
-
- }
- });
- PreferenceManager.setDefaultValues(this, R.xml.pref_settings, false);
- }
-
- private void applyFilterTrashed(String filter){
- mainActivityViewModel.getTrashedNotesFiltered(filter).observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List notes) {
- // Filter checklist notes
- List filteredNotes = new ArrayList<>();
- for(Note note: notes){
- Boolean add = false;
- if(note.getType() == 3){
- try {
- JSONArray content = new JSONArray(note.getContent());
- for (int i=0; i < content.length(); i++) {
- JSONObject o = content.getJSONObject(i);
- if (o.getString("name").contains(filter) || note.getName().contains(filter)){
- add = true;
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else{
- add = true;
- }
- if(add){
- filteredNotes.add(note);
- }
- }
- adapter.setNotes(filteredNotes);
- }
- });
- }
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.kt
new file mode 100644
index 00000000..823ff7b2
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/RecycleActivity.kt
@@ -0,0 +1,128 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui
+
+import android.os.Bundle
+import androidx.preference.PreferenceManager
+import android.view.ContextThemeWrapper
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.SearchView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.model.Note
+import org.secuso.privacyfriendlynotes.ui.adapter.NoteAdapter
+import org.secuso.privacyfriendlynotes.ui.main.MainActivityViewModel
+
+/**
+ * Activity that allows to interact with trashed notes.
+ */
+class RecycleActivity : AppCompatActivity() {
+ private val mainActivityViewModel: MainActivityViewModel by lazy { ViewModelProvider(this)[MainActivityViewModel::class.java] }
+ private val searchView: SearchView by lazy { findViewById(R.id.searchViewFilterRecycle) }
+ private val adapter: NoteAdapter by lazy { NoteAdapter(mainActivityViewModel, true) }
+ private val trashedNotes by lazy {
+ mainActivityViewModel.trashedNotes.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).stateIn(lifecycleScope, SharingStarted.Lazily, listOf())
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_recycle)
+ val recyclerView = findViewById(R.id.recyclerViewRecycle)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.setHasFixedSize(true)
+ recyclerView.adapter = adapter
+
+ lifecycleScope.launch {
+ trashedNotes.collect { adapter.setNotes(it) }
+ }
+
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextChange(newText: String): Boolean {
+ mainActivityViewModel.setFilter(newText)
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String): Boolean {
+ mainActivityViewModel.setFilter(query)
+ return true
+ }
+ })
+
+ val showDeleteDialog: (Note, ViewHolder) -> Unit = { note, viewHolder ->
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@RecycleActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(String.format(getString(R.string.dialog_restore_title), note.name))
+ .setMessage(String.format(getString(R.string.dialog_restore_message), note.name))
+ .setPositiveButton(R.string.dialog_option_delete) { _, _ ->
+ mainActivityViewModel.delete(note)
+ adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
+ }
+ .setNegativeButton(R.string.dialog_option_restore) { _, _ ->
+ note.in_trash = 0
+ mainActivityViewModel.update(note)
+ adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
+ }
+ .setOnDismissListener { adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show()
+ }
+
+ ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
+ override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false
+
+ override fun onSwiped(viewHolder: ViewHolder, direction: Int) = showDeleteDialog(adapter.getNoteAt(viewHolder.bindingAdapterPosition), viewHolder)
+ }).attachToRecyclerView(recyclerView)
+
+ adapter.setOnItemClickListener(showDeleteDialog)
+
+ PreferenceManager.setDefaultValues(this, R.xml.pref_settings, false)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.recycle, menu)
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_delete_all -> {
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(getString(R.string.dialog_delete_all_recycle_bin_title))
+ .setMessage(getString(R.string.dialog_delete_all_recycle_bin_message))
+ .setPositiveButton(R.string.dialog_option_delete) { _, _ ->
+ lifecycleScope.launch { trashedNotes.value.forEach { mainActivityViewModel.delete(it) } }
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun showDeleteDialog(note: Note, position: Int) {
+
+ }
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/SettingsActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/SettingsActivity.java
index 68912813..b72c4cca 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/SettingsActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/SettingsActivity.java
@@ -14,6 +14,7 @@
package org.secuso.privacyfriendlynotes.ui;
import android.os.Bundle;
+
import androidx.appcompat.app.AppCompatActivity;
import org.secuso.privacyfriendlynotes.R;
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/TutorialActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/TutorialActivity.java
index 054f57ef..ca0e4b75 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/TutorialActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/TutorialActivity.java
@@ -18,9 +18,6 @@
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
-import androidx.viewpager.widget.PagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-import androidx.appcompat.app.AppCompatActivity;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +28,10 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
import org.secuso.privacyfriendlynotes.R;
import org.secuso.privacyfriendlynotes.ui.helper.FirstLaunchManager;
import org.secuso.privacyfriendlynotes.ui.main.MainActivity;
@@ -39,6 +40,7 @@
* Activity that explains the app briefly.
* It is shown at the first start of the app or when it is selected from the NavigationDrawer.
* Class structure taken from tutorial at http://www.androidhive.info/2016/05/android-build-intro-slider-app/
+ *
* @author Karola Marky, Christopher Beckmann
*/
@@ -50,6 +52,35 @@ public class TutorialActivity extends AppCompatActivity {
private TextView[] dots;
private int[] layouts;
private Button btnSkip, btnNext;
+ // viewpager change listener
+ ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {
+
+ @Override
+ public void onPageSelected(int position) {
+ addBottomDots(position);
+
+ // changing the next button text 'NEXT' / 'GOT IT'
+ if (position == layouts.length - 1) {
+ // last page. make button text to GOT IT
+ btnNext.setText(getString(R.string.okay));
+ btnSkip.setVisibility(View.GONE);
+ } else {
+ // still pages are left
+ btnNext.setText(getString(R.string.next));
+ btnSkip.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int arg0, float arg1, int arg2) {
+
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int arg0) {
+
+ }
+ };
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -130,36 +161,6 @@ private void launchHomeScreen() {
finish();
}
- // viewpager change listener
- ViewPager.OnPageChangeListener viewPagerPageChangeListener = new ViewPager.OnPageChangeListener() {
-
- @Override
- public void onPageSelected(int position) {
- addBottomDots(position);
-
- // changing the next button text 'NEXT' / 'GOT IT'
- if (position == layouts.length - 1) {
- // last page. make button text to GOT IT
- btnNext.setText(getString(R.string.okay));
- btnSkip.setVisibility(View.GONE);
- } else {
- // still pages are left
- btnNext.setText(getString(R.string.next));
- btnSkip.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public void onPageScrolled(int arg0, float arg1, int arg2) {
-
- }
-
- @Override
- public void onPageScrollStateChanged(int arg0) {
-
- }
- };
-
/**
* Making notification bar transparent
*/
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.java
deleted file mode 100644
index b22c6a50..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui.adapter;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.room.model.Category;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Adapter that provides a binding for categories
- * @see org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActivity
- */
-
-public class CategoryAdapter extends RecyclerView.Adapter{
-
- private List categories = new ArrayList<>();
- private CategoryAdapter.OnItemClickListener listener;
-
- @NonNull
- @Override
- public CategoryAdapter.CategoryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View itemView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_category, parent, false);
- return new CategoryAdapter.CategoryHolder(itemView);
- }
-
- @Override
- public void onBindViewHolder(@NonNull CategoryHolder holder, int position) {
- Category currentCategory = categories.get(position);
- holder.textViewCategoryName.setText(currentCategory.getName());
- }
-
- @Override
- public int getItemCount() {
- return categories.size();
- }
-
- public void setCategories(List categories) {
- this.categories = categories;
- notifyDataSetChanged();
- }
-
-
-
- class CategoryHolder extends RecyclerView.ViewHolder {
- private TextView textViewCategoryName;
-
- public CategoryHolder(@NonNull View itemView) {
- super(itemView);
- textViewCategoryName = itemView.findViewById(R.id.item_name);
-
- itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- int position = getAdapterPosition();
- if (listener != null && position != RecyclerView.NO_POSITION) {
- listener.onItemClick(categories.get(position));
- }
- }
- });
- }
- }
-
- public interface OnItemClickListener {
- void onItemClick(Category category);
- }
-
- public void setOnItemClickListener(CategoryAdapter.OnItemClickListener listener) {
- this.listener = listener;
- }
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.kt
new file mode 100644
index 00000000..ebb6dbb2
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/CategoryAdapter.kt
@@ -0,0 +1,87 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.adapter
+
+import android.graphics.Color
+import android.preference.PreferenceManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.cardview.widget.CardView
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.button.MaterialButton
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.model.Category
+
+/**
+ * Adapter that provides a binding for categories
+ * @see org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActivity
+ */
+class CategoryAdapter : RecyclerView.Adapter() {
+
+ var displayColorDialog: ((Category, CategoryHolder) -> Unit)? = null
+ var updateCategory: ((Category) -> Unit)? = null
+
+ var categories: List = ArrayList()
+ private set
+
+ fun setCategories(categories: List) {
+ this.categories = categories
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_category, parent, false)
+ return CategoryHolder(itemView)
+ }
+
+ override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
+ val (_, name, color) = categories[position]
+ holder.textViewCategoryName.text = name
+
+ if (PreferenceManager.getDefaultSharedPreferences(holder.itemView.context).getBoolean("settings_color_category", true)) {
+ if (color == null) {
+ holder.btnColorSelector.setIconResource(R.drawable.transparent_checker)
+ holder.btnColorSelector.setBackgroundColor(holder.btnColorSelector.resources.getColor(R.color.transparent))
+ } else {
+ holder.btnColorSelector.icon = null
+ holder.btnColorSelector.setBackgroundColor(Color.parseColor(color))
+ }
+ } else {
+ holder.colorSelectorWrapper.visibility = View.GONE
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return categories.size
+ }
+
+ fun setCategoryColor(color: Int, position: Int) {
+ categories[position].color = "#${Integer.toHexString(color)}"
+ updateCategory?.let { it(categories[position]) }
+ }
+
+ inner class CategoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val textViewCategoryName: TextView = itemView.findViewById(R.id.item_name)
+ val colorSelectorWrapper: CardView = itemView.findViewById(R.id.btn_color_selector_wrapper)
+ val btnColorSelector: MaterialButton by lazy { itemView.findViewById(R.id.category_item_color_selector) }
+
+ init {
+ btnColorSelector.setOnClickListener { displayColorDialog?.let { it(categories[bindingAdapterPosition], this) } }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/ChecklistAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/ChecklistAdapter.kt
new file mode 100644
index 00000000..1b4196d0
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/ChecklistAdapter.kt
@@ -0,0 +1,136 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.adapter
+
+import android.graphics.Paint
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.checkbox.MaterialCheckBox
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.ui.SettingsActivity
+import java.util.Collections
+
+/**
+ * Provides bindings to show a Checklist-Item in a RecyclerView.
+ * @author Patrick Schneider
+ */
+class ChecklistAdapter(
+ private var items: MutableList>,
+ private val startDrag: (ItemHolder) -> Unit,
+) : RecyclerView.Adapter() {
+
+ var hasChanged = false
+ private set
+
+ fun getItems(): List> {
+ return items
+ }
+
+ fun swap(from: Int, to: Int) {
+ Collections.swap(items, from, to)
+ hasChanged = true
+ }
+
+ fun setAll(items: Collection>) {
+ if (this.items.isNotEmpty()) {
+ this.items.clear()
+ } else {
+ hasChanged = false
+ }
+ this.items.addAll(items)
+ notifyItemRangeChanged(0, this.items.size)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_checklist, parent, false)
+ return ItemHolder(itemView)
+ }
+
+ override fun onBindViewHolder(holder: ItemHolder, position: Int) {
+ val (checked, item) = items[position]
+ holder.textView.text = item
+ holder.checkbox.isChecked = checked
+ holder.dragHandle.setOnTouchListener { v, _ ->
+ startDrag(holder)
+ v.performClick()
+ }
+ holder.checkbox.setOnClickListener { _ ->
+ items[holder.bindingAdapterPosition] = Pair(holder.checkbox.isChecked, holder.textView.text.toString())
+ holder.textView.apply {
+ paintFlags = if (holder.checkbox.isChecked) {
+ paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
+ } else {
+ paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
+ }
+ }
+ hasChanged = true
+ }
+ holder.textView.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+
+ }
+
+ override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+
+ }
+
+ override fun afterTextChanged(text: Editable?) {
+ items[holder.bindingAdapterPosition] = Pair(holder.checkbox.isChecked, (text ?: "").toString())
+ hasChanged = true
+ }
+
+ })
+
+ holder.textView.apply {
+ paintFlags = if (checked) {
+ paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
+ } else {
+ paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
+ }
+ textSize = PreferenceManager.getDefaultSharedPreferences(context).getString(SettingsActivity.PREF_CUSTOM_FONT_SIZE, "15")!!.toFloat()
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ fun addItem(item: String) {
+ this.items.add(Pair(false, item))
+ notifyItemInserted(items.size - 1)
+ hasChanged = true
+ }
+
+ fun removeItem(position: Int) {
+ this.items.removeAt(position)
+ notifyItemRemoved(position)
+ }
+
+ /**
+ * The view holder presenting a checklist item.
+ * @author Patrick Schneider
+ */
+ inner class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val textView: TextView = itemView.findViewById(R.id.item_name)
+ val checkbox: MaterialCheckBox = itemView.findViewById(R.id.item_checkbox)
+ val dragHandle: View = itemView.findViewById(R.id.drag_handle)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java
deleted file mode 100644
index 7488fcc0..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui.adapter;
-
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.text.Html;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.room.DbContract;
-import org.secuso.privacyfriendlynotes.room.model.Note;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Adapter that provides a binding for notes
- * @see org.secuso.privacyfriendlynotes.ui.main.MainActivity
- * @see org.secuso.privacyfriendlynotes.ui.RecycleActivity
- */
-
-public class NoteAdapter extends RecyclerView.Adapter {
- private List notes = new ArrayList<>();
- private List notesFilteredList = new ArrayList<>();
- private OnItemClickListener listener;
-
- @NonNull
- @Override
- public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View itemView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.note_item, parent, false);
- return new NoteHolder(itemView);
- }
-
-
- /**
- * Defines how notes are presented in the RecyclerView.
- * @see org.secuso.privacyfriendlynotes.ui.main.MainActivity
- * @param holder
- * @param position
- */
- @Override
- public void onBindViewHolder(@NonNull NoteHolder holder, int position) {
- Note currentNote = notes.get(position);
- holder.textViewTitle.setText(currentNote.getName());
- holder.textViewDescription.setText("");
-
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(holder.itemView.getContext());
- holder.textViewDescription.setVisibility(pref.getBoolean("settings_show_preview", true) ? View.VISIBLE : View.GONE);
-
- switch (currentNote.getType()) {
- case DbContract.NoteEntry.TYPE_TEXT:
- holder.imageViewcategory.setImageResource(R.drawable.ic_short_text_black_24dp);
- holder.textViewDescription.setText(Html.fromHtml(currentNote.getContent()));
- holder.textViewDescription.setMaxLines(3);
- break;
- case DbContract.NoteEntry.TYPE_AUDIO:
- holder.imageViewcategory.setImageResource(R.drawable.ic_mic_black_24dp);
- break;
- case DbContract.NoteEntry.TYPE_SKETCH:
- holder.imageViewcategory.setImageResource(R.drawable.ic_photo_black_24dp);
- break;
- case DbContract.NoteEntry.TYPE_CHECKLIST:
- holder.imageViewcategory.setImageResource(R.drawable.ic_format_list_bulleted_black_24dp);
- String preview = "";
- try {
- JSONArray content = new JSONArray(currentNote.getContent());
- for (int i=0; i < content.length(); i++) {
- JSONObject o = content.getJSONObject(i);
- if(o.getBoolean("checked")){
- preview = preview + "(\u2713)";
- } else {
- preview = preview + "(X)";
- }
- preview = preview + " " + o.getString("name");
- if(i != content.length()-1){
- preview = preview + "\n";
- }
-
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- holder.textViewDescription.setText(preview);
- holder.textViewDescription.setMaxLines(3);
- }
-
- // if the Description is empty, don't show it
- if (holder.textViewDescription.getText().toString().isEmpty()) {
- holder.textViewDescription.setVisibility(View.GONE);
- }
- }
-
- @Override
- public int getItemCount() {
- return notes.size();
- }
-
- public void setNotes(List notes) {
- this.notes = notes;
- notifyDataSetChanged();
- }
-
- class NoteHolder extends RecyclerView.ViewHolder {
- private TextView textViewTitle;
- private TextView textViewDescription;
- private ImageView imageViewcategory;
-
-
- public NoteHolder(@NonNull View itemView) {
- super(itemView);
- textViewTitle = itemView.findViewById(R.id.text_view_title);
- textViewDescription = itemView.findViewById(R.id.text_view_description);
- imageViewcategory = itemView.findViewById(R.id.imageView_category);
-
- itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- int position = getAdapterPosition();
- if (listener != null && position != RecyclerView.NO_POSITION) {
- listener.onItemClick(notes.get(position));
- }
- }
- });
- }
- }
-
- public interface OnItemClickListener {
- void onItemClick(Note note);
- }
-
- public void setOnItemClickListener(OnItemClickListener listener) {
- this.listener = listener;
- }
-
- public Note getNoteAt(int pos){
- return notes.get(pos);
- }
-}
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
new file mode 100644
index 00000000..de0a76f6
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt
@@ -0,0 +1,186 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.adapter
+
+import android.graphics.Color
+import android.preference.PreferenceManager
+import android.text.Html
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.DbContract
+import org.secuso.privacyfriendlynotes.room.model.Note
+import org.secuso.privacyfriendlynotes.ui.main.MainActivityViewModel
+import org.secuso.privacyfriendlynotes.ui.util.DarkModeUtil
+
+/**
+ * Adapter that provides a binding for notes
+ * @see org.secuso.privacyfriendlynotes.ui.main.MainActivity
+ *
+ * @see org.secuso.privacyfriendlynotes.ui.RecycleActivity
+ */
+class NoteAdapter(
+ private val mainActivityViewModel: MainActivityViewModel,
+ var colorCategory: Boolean,
+) : RecyclerView.Adapter() {
+ var startDrag: ((NoteAdapter.NoteHolder) -> Unit)? = null
+ var notes: MutableList = ArrayList()
+ private set
+
+ private var listener: ((Note, NoteHolder) -> Unit)? = null
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteHolder {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.note_item, parent, false)
+ return NoteHolder(itemView)
+ }
+
+ /**
+ * Defines how notes are presented in the RecyclerView.
+ * @see org.secuso.privacyfriendlynotes.ui.main.MainActivity
+ *
+ * @param holder
+ * @param position
+ */
+ override fun onBindViewHolder(holder: NoteHolder, position: Int) {
+ val currentNote = notes[position]
+ holder.textViewTitle.text = currentNote.name
+ holder.textViewDescription.text = ""
+ val pref = PreferenceManager.getDefaultSharedPreferences(holder.itemView.context)
+ holder.textViewDescription.visibility = if (pref.getBoolean("settings_show_preview", true)) View.VISIBLE else View.GONE
+ holder.textViewExtraText.visibility = View.GONE
+ holder.textViewExtraText.text = null
+ holder.imageViewcategory.visibility = View.GONE
+ holder.imageViewcategory.setImageResource(0)
+ holder.dragHandle.setOnTouchListener { v, _ ->
+ startDrag?.let { it(holder) }
+ v.performClick()
+ }
+
+ if (colorCategory) {
+ mainActivityViewModel.categoryColor(currentNote.category) {
+
+ if (DarkModeUtil.isDarkMode(holder.textViewTitle.context)) {
+ val color: Int = it?.let { Color.parseColor(it) } ?: run {
+ val value = TypedValue()
+ holder.itemView.context.theme.resolveAttribute(R.attr.colorOnBackground, value, true)
+ value.data
+ }
+ holder.textViewTitle.setTextColor(color)
+ holder.textViewExtraText.setTextColor(color)
+ } else {
+ val color: Int = it?.let { Color.parseColor(it) } ?: run {
+ val value = TypedValue()
+ holder.itemView.context.theme.resolveAttribute(R.attr.colorSurface, value, true)
+ value.data
+ }
+ holder.viewNoteItem.setBackgroundColor(color)
+ }
+ }
+ }
+
+ when (currentNote.type) {
+ DbContract.NoteEntry.TYPE_TEXT -> {
+ holder.textViewDescription.text = Html.fromHtml(currentNote.content)
+ holder.textViewDescription.maxLines = 3
+ }
+
+ DbContract.NoteEntry.TYPE_AUDIO -> {
+ holder.imageViewcategory.visibility = View.VISIBLE
+ holder.imageViewcategory.setImageResource(R.drawable.ic_mic_icon_24dp)
+ }
+
+ DbContract.NoteEntry.TYPE_SKETCH -> {
+ holder.imageViewcategory.visibility = View.VISIBLE
+ holder.imageViewcategory.setBackgroundColor(run {
+ val value = TypedValue()
+ holder.itemView.context.theme.resolveAttribute(R.attr.colorSurfaceVariantLight, value, true)
+ value.data
+ })
+ if (pref.getBoolean("settings_show_preview", true)) {
+ val bitmap = mainActivityViewModel.sketchPreview(currentNote, 200)
+ if (bitmap != null) {
+ holder.imageViewcategory.setImageBitmap(mainActivityViewModel.sketchPreview(currentNote, 200))
+ } else {
+ holder.imageViewcategory.setImageResource(R.drawable.ic_photo_icon_24dp)
+ }
+ } else {
+ holder.imageViewcategory.setImageResource(R.drawable.ic_photo_icon_24dp)
+ }
+ }
+
+ DbContract.NoteEntry.TYPE_CHECKLIST -> {
+ val preview = mainActivityViewModel.checklistPreview(currentNote)
+ holder.textViewExtraText.text = "${preview.filter { it.first }.count()}/${preview.size}"
+ holder.textViewExtraText.visibility = View.VISIBLE
+ holder.imageViewcategory.visibility = View.GONE
+ holder.textViewDescription.text = preview.take(3).joinToString(System.lineSeparator()) { it.second }
+ holder.textViewDescription.maxLines = 3
+ }
+ }
+
+ // if the Description is empty, don't show it
+ if (holder.textViewDescription.text.toString().isEmpty()) {
+ holder.textViewDescription.visibility = View.GONE
+ }
+ holder.dragHandle.visibility = if (mainActivityViewModel.isCustomOrdering()) View.VISIBLE else View.GONE
+ }
+
+ override fun getItemCount(): Int {
+ return notes.size
+ }
+
+ fun setNotes(notes: List) {
+ this.notes.clear()
+ this.notes.addAll(notes)
+ notifyDataSetChanged()
+ }
+
+ inner class NoteHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val textViewTitle: TextView
+ val textViewDescription: TextView
+ val imageViewcategory: ImageView
+ val textViewExtraText: TextView
+ val viewNoteItem: View
+ val dragHandle: View
+
+ init {
+ textViewTitle = itemView.findViewById(R.id.text_view_title)
+ textViewDescription = itemView.findViewById(R.id.text_view_description)
+ imageViewcategory = itemView.findViewById(R.id.imageView_category)
+ textViewExtraText = itemView.findViewById(R.id.note_text_extra)
+ viewNoteItem = itemView.findViewById(R.id.note_item)
+ dragHandle = itemView.findViewById(R.id.drag_handle)
+ itemView.setOnClickListener {
+ bindingAdapterPosition.apply {
+ if (listener != null && this != RecyclerView.NO_POSITION) {
+ listener!!(notes[this], this@NoteHolder)
+ }
+ }
+ }
+ }
+ }
+
+ fun setOnItemClickListener(listener: (Note, NoteHolder) -> Unit) {
+ this.listener = listener
+ }
+
+ fun getNoteAt(pos: Int): Note {
+ return notes[pos]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/MainFABFragment.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/MainFABFragment.kt
new file mode 100644
index 00000000..e8e7654d
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/MainFABFragment.kt
@@ -0,0 +1,101 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.fragments
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.widget.LinearLayout
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionManager
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.transition.MaterialContainerTransform
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.DbContract
+
+/**
+ * This fragment represents the FAB of the main notes overview.
+ * It transforms on-click to a sheet containing elements to create each note type.
+ *
+ * @author Patrick Schneider
+ */
+class MainFABFragment(
+ private val onCreateNote: (Int) -> Unit
+) : Fragment(R.layout.main_content_fab_menu) {
+ private val fabContainer: LinearLayout by lazy { requireView().findViewById(R.id.fab_container) }
+ private val fab: FloatingActionButton by lazy { requireView().findViewById(R.id.fab) }
+ private val closeFab: MaterialButton by lazy { requireView().findViewById(R.id.fabClose) }
+ private val fabMenu: View by lazy { requireView().findViewById(R.id.fab_menu) }
+ private var fabMenuExpanded = false
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ fab.setOnClickListener { open() }
+ closeFab.setOnClickListener { close() }
+ close()
+
+ requireView().findViewById(R.id.fab_text).setOnClickListener {
+ onCreateNote(DbContract.NoteEntry.TYPE_TEXT)
+ close()
+ }
+ requireView().findViewById(R.id.fab_checklist).setOnClickListener {
+ onCreateNote(DbContract.NoteEntry.TYPE_CHECKLIST)
+ close()
+ }
+ requireView().findViewById(R.id.fab_audio).setOnClickListener {
+ onCreateNote(DbContract.NoteEntry.TYPE_AUDIO)
+ close()
+ }
+ requireView().findViewById(R.id.fab_sketch).setOnClickListener {
+ onCreateNote(DbContract.NoteEntry.TYPE_SKETCH)
+ close()
+ }
+ super.onViewCreated(view, savedInstanceState)
+ }
+
+ private val openTransition by lazy {
+ MaterialContainerTransform().apply {
+ startView = fabContainer
+ endView = fabMenu
+
+ addTarget(endView!!)
+
+ scrimColor = Color.TRANSPARENT
+ }
+ }
+ private val closeTransition by lazy {
+ MaterialContainerTransform().apply {
+ startView = fabMenu
+ endView = fabContainer
+
+ addTarget(endView!!)
+
+ scrimColor = Color.TRANSPARENT
+ }
+ }
+
+ fun close() {
+ fabMenu.visibility = View.GONE
+ TransitionManager.beginDelayedTransition(fabContainer, closeTransition)
+ fab.visibility = View.VISIBLE
+ fabMenuExpanded = false
+ }
+
+ fun open() {
+ fab.visibility = View.GONE
+ TransitionManager.beginDelayedTransition(fabContainer, openTransition)
+ fabMenu.visibility = View.VISIBLE
+ fabMenuExpanded = true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt
new file mode 100644
index 00000000..2fe9a07d
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt
@@ -0,0 +1,34 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.fragments
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceFragmentCompat
+import org.secuso.privacyfriendlynotes.R
+
+/**
+ * Fragment that provides the settings.
+ * Created by Robin on 11.09.2016.
+ */
+class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.pref_settings, rootKey)
+ findPreference("settings_day_night_theme")?.setOnPreferenceChangeListener { _, newValue ->
+ AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt())
+ true
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/FirstLaunchManager.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/FirstLaunchManager.java
index 758e1869..648a71e2 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/FirstLaunchManager.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/FirstLaunchManager.java
@@ -21,26 +21,23 @@
* Class structure taken from tutorial at http://www.androidhive.info/2016/05/android-build-intro-slider-app/
*/
public class FirstLaunchManager {
- private SharedPreferences pref;
-
- // shared pref mode
- private final int PRIVATE_MODE = 0;
-
// Shared preferences file name
private static final String PREF_NAME = "androidhive-welcome";
-
private static final String IS_FIRST_TIME_LAUNCH = "IsFirstTimeLaunch";
+ // shared pref mode
+ private final int PRIVATE_MODE = 0;
+ private final SharedPreferences pref;
public FirstLaunchManager(Context context) {
pref = context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);
}
- public void setFirstTimeLaunch(boolean isFirstTime) {
- pref.edit().putBoolean(IS_FIRST_TIME_LAUNCH, isFirstTime).apply();
- }
-
public boolean isFirstTimeLaunch() {
return pref.getBoolean(IS_FIRST_TIME_LAUNCH, true);
}
+ public void setFirstTimeLaunch(boolean isFirstTime) {
+ pref.edit().putBoolean(IS_FIRST_TIME_LAUNCH, isFirstTime).apply();
+ }
+
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/NotificationHelper.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/NotificationHelper.kt
index 27174f3d..664b8b28 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/NotificationHelper.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/NotificationHelper.kt
@@ -1,13 +1,27 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
package org.secuso.privacyfriendlynotes.ui.helper
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.util.Log
import android.widget.Toast
import org.secuso.privacyfriendlynotes.R
-import org.secuso.privacyfriendlynotes.service.NotificationService
+import org.secuso.privacyfriendlynotes.receiver.NotificationReceiver
object NotificationHelper {
private const val TAG = "NotificationHelper"
@@ -21,12 +35,14 @@ object NotificationHelper {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) {
- // For versions < S, we do not need to check for the permission
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTimeMillis, pi)
- } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S && alarmManager.canScheduleExactAlarms()) {
- // For versions >= S, we need to check for the permission
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTimeMillis, pi)
+ // For versions < S, we do not need to check for the permission
+ // For versions >= S, we need to check for the permission
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && alarmManager.canScheduleExactAlarms()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeMillis, pi)
+ } else {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTimeMillis, pi)
+ }
} else {
// We don't have the permission to schedule exact alarms
return
@@ -43,12 +59,12 @@ object NotificationHelper {
}
private fun createNotificationPendingIntent(context: Context, noteId: Int, noteType: Int, notificationTitle: String): PendingIntent {
- val i = Intent(context, NotificationService::class.java)
- i.putExtra(NotificationService.NOTIFICATION_ID, noteId)
- i.putExtra(NotificationService.NOTIFICATION_TYPE, noteType)
- i.putExtra(NotificationService.NOTIFICATION_TITLE, notificationTitle)
+ val i = Intent(context, NotificationReceiver::class.java)
+ i.putExtra(NotificationReceiver.NOTIFICATION_ID, noteId)
+ i.putExtra(NotificationReceiver.NOTIFICATION_TYPE, noteType)
+ i.putExtra(NotificationReceiver.NOTIFICATION_TITLE, notificationTitle)
- return PendingIntent.getService(context, noteId, i, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getBroadcast(context, noteId, i, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
}
@JvmStatic
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt
new file mode 100644
index 00000000..74d6b88c
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt
@@ -0,0 +1,131 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.helper
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.util.Consumer
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.model.SortingOrder
+
+/**
+ * Handles the dialog to change the sorting options.
+ *
+ * @author Patrick Schneider
+ */
+class SortingOptionDialog(
+ context: Context,
+ sortingOptionTextResId: Int,
+ sortingOptionIconResId: Int,
+ current: SortingOrder,
+ reversed: Boolean,
+ onChosen: Consumer
+) {
+
+ private val dialog = BottomSheetDialog(context)
+ private val recyclerView by lazy {
+ dialog.findViewById(R.id.sorting_options)!!
+ }
+
+ init {
+ dialog.setContentView(R.layout.dialog_sorting_options)
+ recyclerView.layoutManager = LinearLayoutManager(context)
+
+ val icons = context.resources.obtainTypedArray(sortingOptionIconResId)
+ val options = context.resources.getStringArray(sortingOptionTextResId)
+ .zip((0 until icons.length()).map { icons.getResourceId(it, 0) })
+ .mapIndexed { i, (text, icon) ->
+ SortingOptionData(
+ text,
+ icon,
+ SortingOrder.values()[i]
+ )
+ }
+ icons.recycle()
+ recyclerView.adapter = SortingOptionAdapter(options, current, reversed) { option ->
+ onChosen.accept(option)
+ dialog.dismiss()
+ }
+ }
+
+ fun chooseSortingOption() {
+ dialog.show()
+ }
+
+ /**
+ * The data needed to display a sorting option.
+ * @author Patrick Schneider
+ */
+ data class SortingOptionData(
+ val text: String,
+ val icon: Int,
+ val option: SortingOrder
+ )
+
+ /**
+ * Provides binding to display a sorting option.
+ * @author Patrick Schneider
+ */
+ inner class SortingOptionAdapter(
+ private val options: List,
+ private val current: SortingOrder,
+ private val reversed: Boolean,
+ private val onChosen: Consumer,
+ ) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SortingOptionHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.dialog_sorting_options_item, parent, false)
+ return SortingOptionHolder(view)
+ }
+
+ override fun getItemCount(): Int {
+ return options.size
+ }
+
+ override fun onBindViewHolder(holder: SortingOptionHolder, position: Int) {
+ val tint = run {
+ val data = TypedValue()
+ holder.itemView.context.theme.resolveAttribute(R.attr.colorOnSurface, data, true)
+ return@run data.data
+ }
+ holder.textView.text = options[position].text
+ holder.imgView.setImageResource(options[position].icon)
+ holder.imgView.setColorFilter(tint, PorterDuff.Mode.SRC_IN)
+ holder.itemView.setOnClickListener { _ -> onChosen.accept(options[position].option) }
+ if (options[position].option == current) {
+ holder.reverseOrder.setImageResource(if (reversed) R.drawable.baseline_arrow_downward_24 else R.drawable.baseline_arrow_upward_24)
+ }
+ }
+
+ /**
+ * The view holder associated to an sorting option.
+ * @author Patrick Schneider
+ */
+ inner class SortingOptionHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val textView: TextView = view.findViewById(R.id.sorting_option_text)
+ val imgView: ImageView = view.findViewById(R.id.sorting_option_icon)
+ val reverseOrder: ImageView = view.findViewById(R.id.sorting_option_reversed)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java
deleted file mode 100644
index b735ddd9..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui.main;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import com.google.android.material.navigation.NavigationView;
-
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import android.widget.SearchView;
-
-import androidx.arch.core.util.Function;
-import androidx.core.view.GravityCompat;
-import androidx.appcompat.app.ActionBarDrawerToggle;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.drawerlayout.widget.DrawerLayout;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.ItemTouchHelper;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Toast;
-
-import com.getbase.floatingactionbutton.FloatingActionsMenu;
-
-import org.secuso.privacyfriendlynotes.room.DbContract;
-import org.secuso.privacyfriendlynotes.room.model.Category;
-import org.secuso.privacyfriendlynotes.ui.adapter.NoteAdapter;
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.room.model.Note;
-import org.secuso.privacyfriendlynotes.ui.AboutActivity;
-import org.secuso.privacyfriendlynotes.ui.TutorialActivity;
-import org.secuso.privacyfriendlynotes.ui.notes.AudioNoteActivity;
-import org.secuso.privacyfriendlynotes.ui.notes.BaseNoteActivity;
-import org.secuso.privacyfriendlynotes.ui.notes.ChecklistNoteActivity;
-import org.secuso.privacyfriendlynotes.ui.HelpActivity;
-import org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActivity;
-import org.secuso.privacyfriendlynotes.ui.RecycleActivity;
-import org.secuso.privacyfriendlynotes.ui.SettingsActivity;
-import org.secuso.privacyfriendlynotes.ui.notes.SketchActivity;
-import org.secuso.privacyfriendlynotes.ui.notes.TextNoteActivity;
-
-import java.util.List;
-
-/**
- * The MainActivity includes the functionality of the primary screen.
- * It provides the possibility to access existing notes and add new ones.
- * Data is provided by the MainActivityViewModel.
- * @see MainActivityViewModel
- */
-
-public class MainActivity extends AppCompatActivity
- implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
-
- private static final int CAT_ALL = -1;
- private static final String TAG_WELCOME_DIALOG = "welcome_dialog";
- FloatingActionsMenu fabMenu;
- Boolean alphabeticalAsc = false;
- Boolean categoryActivated = false;
-
- private int selectedCategory = CAT_ALL; //ID of the currently selected category. Defaults to "all"
-
- //New Room variables
- private MainActivityViewModel mainActivityViewModel;
- NoteAdapter adapter;
- SearchView searchView;
-
- // A launcher to receive and react to a NoteActivity returning a category
- // The category is used to set the selectecCategory
- ActivityResultLauncher setCategoryResultAfter =
- registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- result -> {
- if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
- selectedCategory = result.getData().getIntExtra(BaseNoteActivity.EXTRA_CATEGORY, CAT_ALL);
- }
- }
- );
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
-
- //set the OnClickListeners
- findViewById(R.id.fab_text).setOnClickListener(this);
- findViewById(R.id.fab_checklist).setOnClickListener(this);
- findViewById(R.id.fab_audio).setOnClickListener(this);
- findViewById(R.id.fab_sketch).setOnClickListener(this);
-
- fabMenu = (FloatingActionsMenu) findViewById(R.id.fab_menu);
- searchView = findViewById(R.id.searchViewFilter);
-
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
- this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
- drawer.setDrawerListener(toggle);
- toggle.syncState();
-
- NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
- navigationView.setNavigationItemSelectedListener(this);
-
- //Fill from Room database
-
- RecyclerView recyclerView = findViewById(R.id.recycler_view);
- recyclerView.setLayoutManager(new LinearLayoutManager(this));
- recyclerView.setHasFixedSize(true);
- adapter = new NoteAdapter();
- recyclerView.setAdapter(adapter);
-
- mainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
- mainActivityViewModel.getActiveNotes().observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List notes) {
- adapter.setNotes(notes);
- }
- });
-
- new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT) {
- @Override
- public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
- return false;
- }
-
- @Override
- public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
- Note note = adapter.getNoteAt(viewHolder.getAdapterPosition());
- note.setIn_trash(1);
- mainActivityViewModel.update(note);
- Toast.makeText(MainActivity.this,getString(R.string.toast_deleted),Toast.LENGTH_SHORT).show();
- }
- }).attachToRecyclerView(recyclerView);
-
- searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextChange(String newText) {
- if(!categoryActivated){
- applyFilter(newText);
- } else {
- applyFilterCategory(newText,selectedCategory);
- }
- return true;
- }
-
- @Override
- public boolean onQueryTextSubmit(String query) {
-
- return true;
- }
- });
-
-
-
- /**
- * Handels when a note is clicked.
- */
- adapter.setOnItemClickListener(note -> {
- Function, Void> launchActivity = activity -> {
- Intent i = new Intent(getApplication(), activity);
- i.putExtra(BaseNoteActivity.EXTRA_ID, note.get_id());
- i.putExtra(BaseNoteActivity.EXTRA_TITLE, note.getName());
- i.putExtra(BaseNoteActivity.EXTRA_CONTENT, note.getContent());
- i.putExtra(BaseNoteActivity.EXTRA_CATEGORY, note.getCategory());
- i.putExtra(BaseNoteActivity.EXTRA_ISTRASH, note.getIn_trash());
- startActivity(i);
- return null;
- };
- switch (note.getType()) {
- case DbContract.NoteEntry.TYPE_TEXT:
- launchActivity.apply(TextNoteActivity.class);
- break;
- case DbContract.NoteEntry.TYPE_AUDIO:
- launchActivity.apply(AudioNoteActivity.class);
- break;
- case DbContract.NoteEntry.TYPE_SKETCH:
- launchActivity.apply(SketchActivity.class);
- break;
- case DbContract.NoteEntry.TYPE_CHECKLIST:
- launchActivity.apply(ChecklistNoteActivity.class);
- break;
- }
- });
-
- PreferenceManager.setDefaultValues(this, R.xml.pref_settings, false);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- buildDrawerMenu();
- }
-
- @Override
- public void onBackPressed() {
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- if (drawer.isDrawerOpen(GravityCompat.START)) {
- drawer.closeDrawer(GravityCompat.START);
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_sort_alphabetical) {
- //switch to an alphabetically ascending or descending order
- updateListAlphabetical(searchView.getQuery().toString());
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * Handles clicks on navigation items
- * @param item
- * @return
- */
- @SuppressWarnings("StatementWithEmptyBody")
- @Override
- public boolean onNavigationItemSelected(MenuItem item) {
- // Handle navigation view item clicks here.
- item.setCheckable(true);
- item.setChecked(true);
- int id = item.getItemId();
- if (id == R.id.nav_trash) {
- startActivity(new Intent(getApplication(), RecycleActivity.class));
- } else if (id == R.id.nav_all) {
- mainActivityViewModel.getActiveNotes().observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List notes) {
- adapter.setNotes(notes);
- }
- });
- categoryActivated = false;
- } else if (id == R.id.nav_manage_categories) {
- startActivity(new Intent(getApplication(), ManageCategoriesActivity.class));
- } else if (id == R.id.nav_settings) {
- startActivity(new Intent(getApplication(), SettingsActivity.class));
- } else if (id == R.id.nav_help) {
- startActivity(new Intent(getApplication(), HelpActivity.class));
- } else if (id == R.id.nav_about) {
- startActivity(new Intent(getApplication(), AboutActivity.class));
- } else if (id == R.id.nav_tutorial) {
- startActivity(new Intent(getApplication(), TutorialActivity.class));
- }else {
- selectedCategory = id;
- categoryActivated = true;
- applyFilterCategory(searchView.getQuery().toString(),selectedCategory);
- }
-
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- drawer.closeDrawer(GravityCompat.START);
- return true;
- }
-
- /**
- * Handles when notes are added.
- * @param v
- */
- @Override
- public void onClick(View v) {
- Function, Intent> intent = activity -> {
- Intent i = new Intent(getApplication(), activity);
- i.putExtra(BaseNoteActivity.EXTRA_CATEGORY, selectedCategory);
- return i;
- };
- Intent i = null;
- switch (v.getId()) {
- case R.id.fab_text:
- i = intent.apply(TextNoteActivity.class);
- break;
- case R.id.fab_checklist:
- i = intent.apply(ChecklistNoteActivity.class);
- break;
- case R.id.fab_audio:
- i = intent.apply(AudioNoteActivity.class);
- break;
- case R.id.fab_sketch:
- i = intent.apply(SketchActivity.class);
- break;
- }
- setCategoryResultAfter.launch(i);
- fabMenu.collapseImmediately();
- }
-
- private void buildDrawerMenu() {
- NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
- Menu navMenu = navigationView.getMenu();
- //reset the menu
- navMenu.clear();
- //Inflate the standard stuff
- MenuInflater menuInflater = new MenuInflater(getApplicationContext());
- menuInflater.inflate(R.menu.activity_main_drawer, navMenu);
-
- //Get the rest from the database
-
- MainActivityViewModel mainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
-
- mainActivityViewModel.getAllCategoriesLive().observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List categories) {
- navMenu.add(R.id.drawer_group2, 0, Menu.NONE, getString(R.string.default_category)).setIcon(R.drawable.ic_label_black_24dp);
-
- for(Category currentCat : categories){
- navMenu.add(R.id.drawer_group2, currentCat.get_id(), Menu.NONE, currentCat.getName()).setIcon(R.drawable.ic_label_black_24dp);
- }
- }
- });
-
- }
-
-
- /**
- * Sorts filtered notes alphabetical in descending or ascending order.
- * @param filter
- */
- private void updateListAlphabetical(String filter) {
- LiveData> data = alphabeticalAsc ?
- mainActivityViewModel.getActiveNotesFiltered(filter)
- : mainActivityViewModel.getActiveNotesFilteredAlphabetical(filter);
-
- data.observe(this, notes -> {
- adapter.setNotes(notes);
- alphabeticalAsc = !alphabeticalAsc;
- });
- }
-
- /**
- * Filters active notes.
- * @param filter
- */
- private void applyFilter(String filter){
- mainActivityViewModel.getActiveNotesFiltered(filter).observe(this, notes -> {
- adapter.setNotes(notes);
- });
- }
-
- /**
- * Filters active notes from category.
- * @param filter
- * @param category
- */
- private void applyFilterCategory(String filter, Integer category){
- mainActivityViewModel.getActiveNotesFilteredFromCategory(filter, category).observe(this, notes -> {
- adapter.setNotes(notes);
- });
- }
-
-}
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
new file mode 100644
index 00000000..9459c026
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.kt
@@ -0,0 +1,395 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.main
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Bundle
+import android.preference.PreferenceManager
+import android.util.Log
+import android.util.TypedValue
+import android.view.ContextThemeWrapper
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.Toast
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.ActionBarDrawerToggle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.appcompat.widget.SearchView
+import androidx.appcompat.widget.Toolbar
+import androidx.arch.core.util.Function
+import androidx.core.view.GravityCompat
+import androidx.drawerlayout.widget.DrawerLayout
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentFactory
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.navigation.NavigationView
+import kotlinx.coroutines.launch
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.model.SortingOrder
+import org.secuso.privacyfriendlynotes.room.DbContract
+import org.secuso.privacyfriendlynotes.room.model.Note
+import org.secuso.privacyfriendlynotes.ui.AboutActivity
+import org.secuso.privacyfriendlynotes.ui.HelpActivity
+import org.secuso.privacyfriendlynotes.ui.RecycleActivity
+import org.secuso.privacyfriendlynotes.ui.SettingsActivity
+import org.secuso.privacyfriendlynotes.ui.TutorialActivity
+import org.secuso.privacyfriendlynotes.ui.adapter.NoteAdapter
+import org.secuso.privacyfriendlynotes.ui.fragments.MainFABFragment
+import org.secuso.privacyfriendlynotes.ui.helper.SortingOptionDialog
+import org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActivity
+import org.secuso.privacyfriendlynotes.ui.notes.AudioNoteActivity
+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.util.Collections
+
+
+/**
+ * The MainActivity includes the functionality of the primary screen.
+ * It provides the possibility to access existing notes and add new ones.
+ * Data is provided by the MainActivityViewModel.
+ * @see MainActivityViewModel
+ */
+class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
+
+ //New Room variables
+ private val mainActivityViewModel: MainActivityViewModel by lazy { ViewModelProvider(this)[MainActivityViewModel::class.java] }
+ lateinit var adapter: NoteAdapter
+ private val searchView: SearchView by lazy { findViewById(R.id.searchViewFilter) }
+ private lateinit var fab: MainFABFragment
+ private var skipNextNoteFlow = false
+
+ // A launcher to receive and react to a NoteActivity returning a category
+ // The category is used to set the selectecCategory
+ private var setCategoryResultAfter = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result: ActivityResult ->
+ val data = result.data
+ if (result.resultCode == RESULT_OK && data != null) {
+ mainActivityViewModel.setCategory(data.getIntExtra(BaseNoteActivity.EXTRA_CATEGORY, CAT_ALL))
+ }
+ fab.close()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ supportFragmentManager.fragmentFactory = object : FragmentFactory() {
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+ if (className == MainFABFragment::class.java.name) {
+ this@MainActivity.fab = MainFABFragment {
+ Log.d("Received", "$it")
+ Intent(
+ application, when (it) {
+ DbContract.NoteEntry.TYPE_TEXT -> TextNoteActivity::class.java
+ DbContract.NoteEntry.TYPE_CHECKLIST -> ChecklistNoteActivity::class.java
+ DbContract.NoteEntry.TYPE_AUDIO -> AudioNoteActivity::class.java
+ DbContract.NoteEntry.TYPE_SKETCH -> SketchActivity::class.java
+ else -> throw NotImplementedError("Note of type $it cannot be created")
+ }
+ ).let { intent -> setCategoryResultAfter.launch(intent) }
+ fab.close()
+ }
+ return fab
+ }
+ return super.instantiate(classLoader, className)
+ }
+ }
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ val toolbar = findViewById(R.id.toolbar) as Toolbar
+ setSupportActionBar(toolbar)
+ val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
+ val toggle = ActionBarDrawerToggle(
+ this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
+ )
+ drawer.setDrawerListener(toggle)
+ toggle.syncState()
+ val navigationView = findViewById(R.id.nav_view) as NavigationView
+ navigationView.setNavigationItemSelectedListener(this)
+
+ //Fill from Room database
+ val recyclerView = findViewById(R.id.recycler_view)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.setHasFixedSize(true)
+ adapter = NoteAdapter(
+ mainActivityViewModel,
+ PreferenceManager.getDefaultSharedPreferences(this).getBoolean("settings_color_category", true)
+ && mainActivityViewModel.getCategory() == CAT_ALL
+ )
+ recyclerView.adapter = adapter
+
+ lifecycleScope.launch {
+ mainActivityViewModel.activeNotes.collect { notes ->
+ if (!skipNextNoteFlow) {
+ adapter.setNotes(notes)
+ }
+ skipNextNoteFlow = false
+ }
+ }
+
+ val ith = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
+ override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
+ val to = target.bindingAdapterPosition
+ val from = viewHolder.bindingAdapterPosition
+
+ // swap custom_orders
+ val temp = adapter.notes[from].custom_order
+ adapter.notes[from].custom_order = adapter.notes[to].custom_order
+ adapter.notes[to].custom_order = temp
+ Collections.swap(adapter.notes, from, to)
+ skipNextNoteFlow = true
+ mainActivityViewModel.updateAll(listOf(adapter.notes[from], adapter.notes[to]))
+
+ adapter.notifyItemMoved(to, from)
+
+ return true
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
+ val note = adapter.getNoteAt(viewHolder.adapterPosition)
+ if (PreferenceManager.getDefaultSharedPreferences(this@MainActivity).getBoolean("settings_dialog_on_trashing", false)) {
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@MainActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(String.format(getString(R.string.dialog_delete_title), note.name))
+ .setMessage(String.format(getString(R.string.dialog_delete_message), note.name))
+ .setPositiveButton(R.string.dialog_option_delete) { _, _ ->
+ adapter.notifyItemRemoved(viewHolder.adapterPosition)
+ trashNote(note)
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setOnDismissListener { adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
+ .show()
+ } else {
+ trashNote(note)
+ }
+ }
+ })
+ ith.attachToRecyclerView(recyclerView)
+ adapter.startDrag = { holder -> ith.startDrag(holder) }
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextChange(newText: String): Boolean {
+ mainActivityViewModel.setFilter(newText)
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String): Boolean {
+ return true
+ }
+ })
+ searchView.setOnClickListener { searchView.onActionViewExpanded() }
+ searchView.setOnQueryTextFocusChangeListener { _, focus ->
+ TypedValue().apply {
+ Log.d("Focus", focus.toString())
+ this@MainActivity.theme.resolveAttribute(if (focus) R.attr.colorSurfaceVariant else R.attr.colorBackground, this, true)
+ searchView.setBackgroundColor(this.data)
+ }
+ }
+
+ /*
+ * Handels when a note is clicked.
+ */
+ adapter.setOnItemClickListener { (_id, name, content, type, category, in_trash): Note, _ ->
+ val launchActivity =
+ Function, Void?> { activity: Class? ->
+ val i = Intent(application, activity)
+ i.putExtra(BaseNoteActivity.EXTRA_ID, _id)
+ i.putExtra(BaseNoteActivity.EXTRA_TITLE, name)
+ i.putExtra(BaseNoteActivity.EXTRA_CONTENT, content)
+ i.putExtra(BaseNoteActivity.EXTRA_CATEGORY, category)
+ i.putExtra(BaseNoteActivity.EXTRA_ISTRASH, in_trash)
+ startActivity(i)
+ null
+ }
+ when (type) {
+ DbContract.NoteEntry.TYPE_TEXT -> launchActivity.apply(TextNoteActivity::class.java)
+ DbContract.NoteEntry.TYPE_AUDIO -> launchActivity.apply(AudioNoteActivity::class.java)
+ DbContract.NoteEntry.TYPE_SKETCH -> launchActivity.apply(SketchActivity::class.java)
+ DbContract.NoteEntry.TYPE_CHECKLIST -> launchActivity.apply(ChecklistNoteActivity::class.java)
+ }
+ fab.close()
+ }
+ val theme = PreferenceManager.getDefaultSharedPreferences(this).getString("settings_day_night_theme", "-1")
+ AppCompatDelegate.setDefaultNightMode(theme!!.toInt())
+ }
+
+ // taken from https://dev.to/ahmmedrejowan/hide-the-soft-keyboard-and-remove-focus-from-edittext-in-android-ehp on 14/03/2024
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ val v = currentFocus
+ if (v is EditText) {
+ Rect().apply {
+ v.getGlobalVisibleRect(this)
+ if (!this.contains(event.rawX.toInt(), event.rawY.toInt())) {
+ v.clearFocus()
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0)
+ }
+ }
+ }
+ }
+ return super.dispatchTouchEvent(event)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ buildDrawerMenu()
+ }
+
+ override fun onBackPressed() {
+ val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START)
+ } else {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ val id = item.itemId
+ if (id == R.id.action_sort_alphabetical) {
+ val dialog = SortingOptionDialog(
+ this,
+ R.array.notes_sort_ordering_text,
+ R.array.notes_sort_ordering_icons,
+ mainActivityViewModel.getOrder(),
+ mainActivityViewModel.isReversed(),
+ ) { option: SortingOrder? ->
+ mainActivityViewModel.setOrder(option!!)
+ updateList(searchView.query.toString())
+ }
+ dialog.chooseSortingOption()
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ /**
+ * Handles clicks on navigation items
+ * @param item
+ * @return
+ */
+ override fun onNavigationItemSelected(item: MenuItem): Boolean {
+ // Handle navigation view item clicks here.
+ item.isCheckable = true
+ item.isChecked = true
+ val id = item.itemId
+ if (id == R.id.nav_trash) {
+ startActivity(Intent(application, RecycleActivity::class.java))
+ } else if (id == R.id.nav_all) {
+ mainActivityViewModel.setCategory(CAT_ALL)
+ } else if (id == R.id.nav_manage_categories) {
+ startActivity(Intent(application, ManageCategoriesActivity::class.java))
+ } else if (id == R.id.nav_settings) {
+ startActivity(Intent(application, SettingsActivity::class.java))
+ } else if (id == R.id.nav_help) {
+ startActivity(Intent(application, HelpActivity::class.java))
+ } else if (id == R.id.nav_about) {
+ startActivity(Intent(application, AboutActivity::class.java))
+ } else if (id == R.id.nav_tutorial) {
+ startActivity(Intent(application, TutorialActivity::class.java))
+ } else {
+ mainActivityViewModel.setCategory(id)
+ }
+ val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
+ drawer.closeDrawer(GravityCompat.START)
+ return true
+ }
+
+ /**
+ * Handles when notes are added.
+ * @param v
+ */
+ override fun onClick(v: View) {
+ val intent =
+ Function { activity: Class? ->
+ val i = Intent(application, activity)
+ i.putExtra(BaseNoteActivity.EXTRA_CATEGORY, mainActivityViewModel.getCategory())
+ i
+ }
+ var i: Intent? = null
+ when (v.id) {
+ R.id.fab_text -> i = intent.apply(TextNoteActivity::class.java)
+ R.id.fab_checklist -> i = intent.apply(ChecklistNoteActivity::class.java)
+ R.id.fab_audio -> i = intent.apply(AudioNoteActivity::class.java)
+ R.id.fab_sketch -> i = intent.apply(SketchActivity::class.java)
+ }
+ setCategoryResultAfter.launch(i)
+ }
+
+ override fun onPause() {
+ // Save all changed orders if activity is paused
+// mainActivityViewModel.updateAll(adapter.notes)
+ super.onPause()
+ }
+
+ private fun buildDrawerMenu() {
+ val navigationView = findViewById(R.id.nav_view) as NavigationView
+ val navMenu = navigationView.menu
+ //reset the menu
+ navMenu.clear()
+ //Inflate the standard stuff
+ val menuInflater = MenuInflater(applicationContext)
+ menuInflater.inflate(R.menu.activity_main_drawer, navMenu)
+
+ //Get the rest from the database
+ lifecycleScope.launch {
+ mainActivityViewModel.categories.collect {
+ navMenu.add(R.id.drawer_group2, 0, Menu.NONE, getString(R.string.default_category)).setIcon(R.drawable.ic_label_black_24dp)
+ for ((id, name) in it) {
+ navMenu.add(R.id.drawer_group2, id, Menu.NONE, name).setIcon(R.drawable.ic_label_black_24dp)
+ }
+ }
+ }
+ }
+
+ /**
+ * Sorts filtered notes alphabetical in descending or ascending order.
+ * @param filter
+ */
+ private fun updateList(filter: String) {
+ mainActivityViewModel.setFilter(filter)
+ }
+
+ private fun trashNote(note: Note) {
+ note.in_trash = 1
+ Toast.makeText(this@MainActivity, getString(R.string.toast_deleted), Toast.LENGTH_SHORT).show()
+ mainActivityViewModel.update(note)
+ }
+
+ companion object {
+ private const val CAT_ALL = -1
+ private const val TAG_WELCOME_DIALOG = "welcome_dialog"
+ }
+}
\ No newline at end of file
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 4c38ee1c..c8bfcd41 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
@@ -14,16 +14,31 @@
package org.secuso.privacyfriendlynotes.ui.main
import android.app.Application
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.preference.PreferenceManager
import android.text.Html
+import android.util.Log
+import androidx.core.graphics.drawable.toBitmap
+import androidx.core.util.Consumer
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import org.json.JSONArray
+import org.secuso.privacyfriendlynotes.model.SortingOrder
+import org.secuso.privacyfriendlynotes.preference.PreferenceKeys
+import org.secuso.privacyfriendlynotes.room.DbContract
import org.secuso.privacyfriendlynotes.room.NoteDatabase
import org.secuso.privacyfriendlynotes.room.model.Category
import org.secuso.privacyfriendlynotes.room.model.Note
+import org.secuso.privacyfriendlynotes.ui.util.ChecklistUtil
+import java.io.File
+import java.io.FileNotFoundException
/**
* The MainActivityViewModel provides the data for the MainActivity.
@@ -34,100 +49,148 @@ import org.secuso.privacyfriendlynotes.room.model.Note
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
+ private val prefManager = PreferenceManager.getDefaultSharedPreferences(application)
+
private val repository: NoteDatabase = NoteDatabase.getInstance(application)
- val activeNotes: LiveData> = repository.noteDao().allActiveNotes
- val trashedNotes: LiveData> = repository.noteDao().allTrashedNotes
- val allCategoriesLive: LiveData> = repository.categoryDao().allCategoriesLive
+ private var filter: MutableStateFlow = MutableStateFlow("")
+ private var ordering: MutableStateFlow = MutableStateFlow(
+ SortingOrder.valueOf(prefManager.getString(PreferenceKeys.SP_NOTES_ORDERING, SortingOrder.AlphabeticalAscending.name)!!)
+ )
+ private var reversed: MutableStateFlow = MutableStateFlow(
+ prefManager.getBoolean(PreferenceKeys.SP_NOTES_REVERSED, false)
+ )
+ private var category: MutableStateFlow = MutableStateFlow(CAT_ALL)
+
+ val trashedNotes: Flow> = repository.noteDao().allTrashedNotes
+ .triggerOn(filter)
+ .filterNotes()
+ val activeNotes: Flow> = repository.noteDao().allActiveNotes
+ .triggerOn(filter, ordering, category, reversed)
+ .filterCategories()
+ .filterNotes()
+ .sortNotes()
+ val categories: Flow> = repository.categoryDao().allCategories
+ private val filesDir: File = application.filesDir
+ private val resources: Resources = application.resources
+
+ fun setFilter(filter: String) {
+ this.filter.value = filter
+ }
+
+ fun setOrder(ordering: SortingOrder) {
+ reversed.value = if (this.ordering.value != ordering) {
+ prefManager.edit()
+ .putString(PreferenceKeys.SP_NOTES_ORDERING, ordering.name)
+ .apply()
+ false
+ } else {
+ !reversed.value
+ }
+ prefManager.edit()
+ .putBoolean(PreferenceKeys.SP_NOTES_REVERSED, reversed.value)
+ .apply()
+ this.ordering.value = ordering
+ }
+
+ fun getOrder(): SortingOrder {
+ return this.ordering.value
+ }
+
+ fun isCustomOrdering(): Boolean {
+ return this.ordering.value == SortingOrder.Custom
+ }
+
+ fun setCategory(id: Int) {
+ this.category.value = id
+ }
+
+ fun getCategory(): Int {
+ return this.category.value
+ }
+
+ fun isReversed(): Boolean {
+ return this.reversed.value
+ }
fun insert(note: Note) {
- viewModelScope.launch(Dispatchers.Default) {
+ viewModelScope.launch(Dispatchers.IO) {
repository.noteDao().insert(note)
}
}
fun update(note: Note) {
- viewModelScope.launch(Dispatchers.Default) {
+ viewModelScope.launch(Dispatchers.IO) {
repository.noteDao().update(note)
}
}
+ fun updateAll(notes: List) {
+ viewModelScope.launch(Dispatchers.IO) {
+ repository.noteDao().updateAll(notes)
+ }
+ }
+
fun delete(note: Note) {
viewModelScope.launch(Dispatchers.Default) {
repository.noteDao().delete(note)
+ if (note.type == DbContract.NoteEntry.TYPE_AUDIO) {
+ File(filesDir.path + "/audio_notes" + note.content).delete()
+ } else if (note.type == DbContract.NoteEntry.TYPE_SKETCH) {
+ File(filesDir.path + "/sketches" + note.content).delete()
+ File(filesDir.path + "/sketches" + note.content.substring(0, note.content.length - 3) + "jpg").delete()
+ }
}
}
- private fun filterNoteFlow (filter: String, notes: Flow?>): Flow> {
- return notes.map {
- it.orEmpty().filter { note ->
- if (note!!.type == 1) {
- val spanned = Html.fromHtml(note!!.content)
- val text = spanned.toString()
- if (text.contains(filter)) {
- return@filter true
- }
- } else {
- if (note!!.type == 3) {
- try {
- val content = JSONArray(note!!.content)
- for (i in 0 until content.length()) {
- val o = content.getJSONObject(i)
- if (o.getString("name")
- .contains(filter) || note!!.name.contains(filter)
- ) {
- return@filter true
- }
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- } else {
- return@filter true
- }
- }
- return@filter false;
- };
- };
- }
-
- fun getActiveNotesFiltered(filter: String): LiveData?> {
- var filteredNotes = MutableLiveData>();
+ fun categoryColor(category: Int, consumer: Consumer) {
viewModelScope.launch(Dispatchers.Main) {
- filterNoteFlow(filter, repository.noteDao().activeNotesFiltered(filter)).collect {
- filteredNotes.value = it
- }
+ consumer.accept(repository.categoryDao().getCategoryColor(category))
}
- return filteredNotes
}
- fun getActiveNotesFilteredAlphabetical(filter: String): LiveData?>{
- var filteredNotes = MutableLiveData>();
- viewModelScope.launch(Dispatchers.Main) {
- filterNoteFlow(filter, repository.noteDao().activeNotesFilteredAlphabetical(filter)).collect {
- filteredNotes.value = it
- }
+ private fun StateFlow.comparator(): (Note, Note) -> Int {
+ return when (this.value) {
+ SortingOrder.AlphabeticalAscending -> { a, b -> a.name.compareTo(b.name) }
+ SortingOrder.LastModified -> { b, a -> a.last_modified.compareTo(b.last_modified) }
+ SortingOrder.Creation -> { b, a -> a._id.compareTo(b._id) }
+ SortingOrder.Custom -> { a, b -> a.custom_order.compareTo(b.custom_order) }
+ SortingOrder.TypeAscending -> { a, b -> a.type.compareTo(b.type) }
}
- return filteredNotes
}
- fun getTrashedNotesFiltered(filter: String): LiveData?>{
- var filteredNotes = MutableLiveData>();
- viewModelScope.launch(Dispatchers.Main) {
- filterNoteFlow(filter, repository.noteDao().trashedNotesFiltered(filter)).collect {
- filteredNotes.value = it
+ private fun Flow>.filterNotes(): Flow> {
+ return this.map {
+ it.filter { note ->
+ if (note.name.contains(filter.value)) {
+ return@filter true
+ }
+ when (note.type) {
+ DbContract.NoteEntry.TYPE_TEXT -> {
+ return@filter Html.fromHtml(note.content).toString().contains(filter.value)
+ }
+
+ DbContract.NoteEntry.TYPE_CHECKLIST -> {
+ return@filter ChecklistUtil.parse(note.content).joinToString(System.lineSeparator()).contains(filter.value)
+ }
+
+ else -> return@filter false
+ }
}
}
- return filteredNotes
}
- fun getActiveNotesFilteredFromCategory(filter: String,category: Integer): LiveData?>{
- var filteredNotes = MutableLiveData>();
- viewModelScope.launch(Dispatchers.Main) {
- filterNoteFlow(filter, repository.noteDao().activeNotesFilteredFromCategory(filter,category)).collect {
- filteredNotes.value = it
- }
+ private fun Flow>.sortNotes(): Flow> {
+ return this.map { it.sortedWith(ordering.comparator()).apply { return@map if (reversed.value) this.reversed() else this } }
+ }
+
+ private fun Flow>.filterCategories(): Flow> {
+ return this.map {
+ it.filter { note -> note.category == category.value || category.value == CAT_ALL }
}
- return filteredNotes
+ }
+
+ private fun Flow.triggerOn(vararg flows: Flow<*>): Flow {
+ return flows.fold(this) { acc, flow -> acc.combine(flow) { a, _ -> a } }
}
fun insert(category: Category) {
@@ -148,4 +211,39 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica
}
}
+ private fun loadSketchBitmap(file: String): BitmapDrawable? {
+ File("${filesDir.path}/sketches${file}").apply {
+ if (exists()) {
+ return BitmapDrawable(resources, path)
+ } else {
+ throw FileNotFoundException("Cannot open sketch: $path")
+ }
+ }
+ }
+
+ fun sketchPreview(note: Note, size: Int): Bitmap? {
+ if (note.type == DbContract.NoteEntry.TYPE_SKETCH) {
+ try {
+ return loadSketchBitmap(note.content)?.toBitmap(size, size, Bitmap.Config.ARGB_8888)
+ } catch (error: FileNotFoundException) {
+ Log.e("Sketch preview", error.stackTraceToString())
+ return null
+ }
+ } else {
+ throw IllegalArgumentException("Only sketch notes allowed")
+ }
+ }
+
+ fun checklistPreview(note: Note): List> {
+ if (note.type != DbContract.NoteEntry.TYPE_CHECKLIST) {
+ throw IllegalArgumentException("Only checklist notes allowed")
+ }
+ return ChecklistUtil.parse(note.content).map { (checked, name) ->
+ return@map Pair(checked, String.format("[%s] $name", if (checked) "x" else " "))
+ }
+ }
+
+ companion object {
+ private const val CAT_ALL = -1
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java
deleted file mode 100644
index 9aeab0cf..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui.manageCategories;
-
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import android.preference.PreferenceManager;
-import android.view.View;
-import android.widget.EditText;
-
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.room.model.Category;
-import org.secuso.privacyfriendlynotes.ui.adapter.CategoryAdapter;
-import org.secuso.privacyfriendlynotes.room.model.Note;
-import org.secuso.privacyfriendlynotes.ui.SettingsActivity;
-
-import java.util.List;
-
-/**
- * Activity provides possibility to add, delete categories.
- * Data is provided by the ManageCategoriesViewModel
- * @see ManageCategoriesViewModel
- */
-
-public class ManageCategoriesActivity extends AppCompatActivity implements View.OnClickListener {
-
- RecyclerView recycler_list;
- ManageCategoriesViewModel manageCategoriesViewModel;
- List allCategories;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_manage_categories);
-
- findViewById(R.id.btn_add).setOnClickListener(this);
-
- recycler_list = (RecyclerView) findViewById(R.id.recyclerview_category);
-
- recycler_list.setLayoutManager(new LinearLayoutManager(this));
- recycler_list.setHasFixedSize(true);
- final CategoryAdapter adapter = new CategoryAdapter();
- recycler_list.setAdapter(adapter);
-
- manageCategoriesViewModel = new ViewModelProvider(this).get(ManageCategoriesViewModel.class);
- manageCategoriesViewModel.getAllCategoriesLive().observe(this, new Observer>() {
- @Override
- public void onChanged(List categories) {
- adapter.setCategories(categories);
- allCategories = categories;
-
- }
- });
-
- adapter.setOnItemClickListener(new CategoryAdapter.OnItemClickListener() {
- @Override
- public void onItemClick(Category currentCategory) {
- new AlertDialog.Builder(ManageCategoriesActivity.this)
- .setTitle(String.format(getString(R.string.dialog_delete_title), currentCategory.getName()))
- .setMessage(String.format(getString(R.string.dialog_delete_message), currentCategory.getName()))
- .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- //do nothing
- }
- })
- .setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- deleteCategory(currentCategory);
- }
- })
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show();
- }
- });
-
-
- }
-
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btn_add:
- EditText name = (EditText) findViewById(R.id.etName);
- if (!name.getText().toString().isEmpty()){
- Category category = new Category(name.getText().toString());
- boolean duplicate = false;
- for(Category currentCat: allCategories){
- if(currentCat.getName().equals(category.getName())){
- duplicate = true;
- }
- }
- if(!duplicate){
- manageCategoriesViewModel.insert(category);
- }
- }
- name.setText("");
- break;
- }
- }
-
-
- private void deleteCategory(Category cat){
-
- // Delete all notes from category if the option is set
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- if (sp.getBoolean(SettingsActivity.PREF_DEL_NOTES, false)) {
- manageCategoriesViewModel.getAllNotesLiveData().observe(this, new Observer>() {
- @Override
- public void onChanged(@Nullable List notes) {
- for(Note currentNote: notes){
- if(currentNote.getCategory() == cat.get_id()){
- manageCategoriesViewModel.delete(currentNote);
- }
- }
- }
- });
- }
-
- manageCategoriesViewModel.delete(cat);
- }
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt
new file mode 100644
index 00000000..1c8acdb9
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt
@@ -0,0 +1,188 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.manageCategories
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.preference.PreferenceManager
+import android.util.TypedValue
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.widget.EditText
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.graphics.toColorInt
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener
+import eltos.simpledialogfragment.color.SimpleColorDialog
+import kotlinx.coroutines.launch
+import org.secuso.privacyfriendlynotes.R
+import org.secuso.privacyfriendlynotes.room.model.Category
+import org.secuso.privacyfriendlynotes.ui.SettingsActivity
+import org.secuso.privacyfriendlynotes.ui.adapter.CategoryAdapter
+
+/**
+ * Activity provides possibility to add, delete categories.
+ * Data is provided by the ManageCategoriesViewModel
+ * @see ManageCategoriesViewModel
+ */
+class ManageCategoriesActivity : AppCompatActivity(), OnDialogResultListener {
+ private val manageCategoriesViewModel: ManageCategoriesViewModel by lazy { ViewModelProvider(this)[ManageCategoriesViewModel::class.java] }
+ private val recyclerList: RecyclerView by lazy { findViewById(R.id.recyclerview_category) }
+ private val fab: FloatingActionButton by lazy { findViewById(R.id.fab_add) }
+ private var onColorResult: ((String?) -> Unit)? = null
+ private lateinit var adapter: CategoryAdapter
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_manage_categories)
+
+ this.recyclerList.layoutManager = LinearLayoutManager(this)
+ this.recyclerList.setHasFixedSize(true)
+ adapter = CategoryAdapter()
+ adapter.displayColorDialog = { category, categoryHolder ->
+ val bundle = Bundle()
+ bundle.putInt(CATEGORY_COLOR, categoryHolder.bindingAdapterPosition)
+ displayColorDialog(bundle)
+ }
+ adapter.updateCategory = { manageCategoriesViewModel.update(it) }
+ this.recyclerList.adapter = adapter
+
+ ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
+ override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
+ val currentCategory = adapter.categories[viewHolder.bindingAdapterPosition]
+ val deleteNotes = PreferenceManager.getDefaultSharedPreferences(this@ManageCategoriesActivity).getBoolean("settings_del_notes", false)
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@ManageCategoriesActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(String.format(getString(R.string.dialog_delete_title), currentCategory.name))
+ .setMessage(
+ String.format(
+ getString(
+ if (deleteNotes) R.string.dialog_delete_category_with_notes else R.string.dialog_delete_category_without_notes
+ ), currentCategory.name
+ )
+ )
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.dialog_option_delete) { dialog, which ->
+ adapter.notifyItemRemoved(viewHolder.adapterPosition)
+ deleteCategory(currentCategory)
+ }
+ .setOnDismissListener { adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show()
+ }
+ }).attachToRecyclerView(recyclerList)
+ fab.setOnClickListener {
+ val view = layoutInflater.inflate(R.layout.dialog_create_category, null)
+ val name = view.findViewById(R.id.etName)
+ val colorSelector = view.findViewById(R.id.btn_color_selector)
+ val colorMenu = view.findViewById(R.id.color_menu)
+ var color: String? = null
+ this.onColorResult = {
+ if (it == null) {
+ colorSelector.setIconResource(R.drawable.transparent_checker)
+ colorSelector.setBackgroundColor(resources.getColor(R.color.transparent))
+ } else {
+ colorSelector.icon = null
+ colorSelector.setBackgroundColor(it.toColorInt())
+ }
+ color = it
+ }
+ if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("settings_color_category", true)) {
+ val value = TypedValue()
+ theme.resolveAttribute(R.attr.colorOnSurface, value, true)
+ colorSelector.setBackgroundColor(value.data)
+ colorSelector.setOnClickListener { displayColorDialog() }
+ } else {
+ colorMenu.visibility = View.GONE
+ }
+
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@ManageCategoriesActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setView(view)
+ .setTitle(R.string.dialog_create_category_title)
+ .setPositiveButton(R.string.dialog_create_category_btn) { _, _ ->
+ if (name.text.isNotEmpty()) {
+ val category = Category(name.text.toString(), color)
+ if (manageCategoriesViewModel.allCategories.value.count { it.name == category.name } == 0) {
+ manageCategoriesViewModel.insert(category)
+ }
+ }
+ onColorResult = null
+ }
+ .setOnDismissListener { onColorResult = null }
+ .show()
+ }
+
+ lifecycleScope.launch {
+ manageCategoriesViewModel.allCategories.collect {
+ adapter.setCategories(it)
+ }
+ }
+ }
+
+ private fun deleteCategory(cat: Category) {
+
+ // Delete all notes from category if the option is set
+ val sp = PreferenceManager.getDefaultSharedPreferences(this)
+ if (sp.getBoolean(SettingsActivity.PREF_DEL_NOTES, false)) {
+ lifecycleScope.launch {
+ manageCategoriesViewModel.notes.collect { notes ->
+ notes.filter { it.category == cat._id }.forEach { manageCategoriesViewModel.delete(it) }
+ }
+ }
+ }
+ manageCategoriesViewModel.delete(cat)
+ }
+
+ private fun displayColorDialog(bundle: Bundle = Bundle.EMPTY) {
+ SimpleColorDialog.build()
+ .title("")
+ .allowCustom(true)
+ .cancelable(true) //allows close by tapping outside of dialog
+ .colors(this, R.array.mdcolor_500)
+ .choiceMode(SimpleColorDialog.SINGLE_CHOICE_DIRECT) //auto-close on selection
+ .neut(R.string.default_color)
+ .neg(android.R.string.cancel)
+ .extra(bundle)
+ .show(this, TAG_COLORDIALOG)
+ }
+
+ override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
+ // 0 is dismiss
+ if (dialogTag == TAG_COLORDIALOG && which != DialogInterface.BUTTON_NEGATIVE && which != 0) {
+ val color = if (which == DialogInterface.BUTTON_POSITIVE) "#${Integer.toHexString(extras.getInt(SimpleColorDialog.COLOR))}" else null
+ val position = extras.getInt(CATEGORY_COLOR, -1)
+
+ // Check if the user changes a category color
+ if (position != -1) {
+ manageCategoriesViewModel.update(adapter.categories[position], color)
+ } else {
+ onColorResult?.let { it(color) }
+ }
+ return true
+ }
+ return false
+ }
+
+ companion object {
+ private const val TAG_COLORDIALOG = "org.secuso.privacyfriendlynotes.COLORDIALOG"
+ private const val CATEGORY_COLOR = "org.secuso.privacyfriendlynotes.CATEGORY_COLOR"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesViewModel.kt
index f6f5b946..d44b405e 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesViewModel.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesViewModel.kt
@@ -15,12 +15,15 @@ package org.secuso.privacyfriendlynotes.ui.manageCategories
import android.app.Application
import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-import org.secuso.privacyfriendlynotes.room.model.Category
import org.secuso.privacyfriendlynotes.room.NoteDatabase
+import org.secuso.privacyfriendlynotes.room.model.Category
import org.secuso.privacyfriendlynotes.room.model.Note
/**
@@ -28,10 +31,10 @@ import org.secuso.privacyfriendlynotes.room.model.Note
* @see ManageCategoriesActivity
*/
-class ManageCategoriesViewModel (application: Application) : AndroidViewModel(application) {
+class ManageCategoriesViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteDatabase = NoteDatabase.getInstance(application)
- val allCategoriesLive: LiveData> = repository.categoryDao().allCategoriesLive
- val allNotesLiveData: LiveData> = repository.noteDao().allNotes
+ val allCategories: StateFlow> = repository.categoryDao().allCategories.stateIn(viewModelScope, SharingStarted.Lazily, listOf())
+ val notes: Flow> = repository.noteDao().allActiveNotes
fun insert(category: Category) {
viewModelScope.launch(Dispatchers.Default) {
@@ -45,6 +48,12 @@ class ManageCategoriesViewModel (application: Application) : AndroidViewModel(ap
}
}
+ fun update(category: Category, color: String?) {
+ viewModelScope.launch(Dispatchers.Default) {
+ repository.categoryDao().update(category._id, color)
+ }
+ }
+
fun delete(category: Category) {
viewModelScope.launch(Dispatchers.Default) {
repository.categoryDao().delete(category)
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt
index a6023e7f..c8718960 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt
@@ -59,6 +59,7 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) {
private var recording = false
private var playing = false
private var isEmpty = true
+ private var noteLoaded = false
private var startTime = System.currentTimeMillis()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -94,6 +95,7 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) {
}
override fun onNoteLoadedFromDB(note: Note) {
+ noteLoaded = true
mFileName = note.content
mFilePath = filesDir.path + "/audio_notes" + mFileName
btnPlayPause.visibility = View.VISIBLE
@@ -128,14 +130,12 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) {
return ActionResult(true, sendIntent)
}
- override fun determineToSave(title: String, category: Int): Pair {
- val intent = intent
- return Pair(
- seekBar.isEnabled && -5 != intent.getIntExtra(
- EXTRA_CATEGORY, -5
- ),
- R.string.toast_emptyNote
- )
+ override fun hasNoteChanged(title: String, category: Int): Pair {
+ return if (noteLoaded) {
+ Pair(false, null)
+ } else {
+ Pair(seekBar.isEnabled, R.string.toast_emptyNote)
+ }
}
override fun onClick(v: View) {
@@ -246,7 +246,7 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) {
}
private fun recordingFinished() {
- shouldSave = true
+ shouldSaveOnPause = true
btnRecord.visibility = View.INVISIBLE
btnPlayPause.visibility = View.VISIBLE
seekBar.isEnabled = true
@@ -254,17 +254,13 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) {
private fun togglePlayPauseButton() {
if (playing) {
- btnPlayPause.setBackgroundResource(R.drawable.ic_pause_black_24dp)
+ btnPlayPause.setBackgroundResource(R.drawable.ic_pause_icon_24dp)
} else {
- btnPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_black_24dp)
+ btnPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_icon_24dp)
}
}
- override fun updateNoteToSave(name: String, category: Int): ActionResult {
- return ActionResult(true, Note(name, mFileName, DbContract.NoteEntry.TYPE_AUDIO, category))
- }
-
- override fun noteToSave(name: String, category: Int): ActionResult {
+ override fun onNoteSave(name: String, category: Int): ActionResult {
if (isEmpty) {
return ActionResult(false, null, null)
}
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 3668bd52..e4fc22af 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
@@ -24,22 +24,29 @@ import android.app.TimePickerDialog.OnTimeSetListener
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import android.provider.Settings
+import android.view.ContextThemeWrapper
import android.view.Menu
import android.view.MenuItem
+import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
+import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.launch
import org.secuso.privacyfriendlynotes.R
import org.secuso.privacyfriendlynotes.preference.PreferenceKeys
import org.secuso.privacyfriendlynotes.room.DbContract
@@ -54,7 +61,12 @@ import org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActiv
import java.io.OutputStream
import java.util.*
-
+/**
+ * A abstract note.
+ * Provides title and category handling.
+ * Handles loading, saving and updating of notes as well as sharing.
+ * @author Patrick Schneider
+ */
abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnClickListener, OnDateSetListener, OnTimeSetListener, PopupMenu.OnMenuItemClickListener {
companion object {
const val EXTRA_ID = "org.secuso.privacyfriendlynotes.ID"
@@ -74,7 +86,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
private lateinit var reminder: MenuItem
private var fontSize: Float = 15.0F
- private var edit = false
+ private var isLoadedNote = false
private var hasAlarm = false
private var savedCat = 0
@@ -82,19 +94,19 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
private var monthOfYear = 0
private var year = 0
- protected var shouldSave = true
+ protected var shouldSaveOnPause = true
+ private var hasChanged = false
private var currentCat = 0
private var id = -1
private var notification: Notification? = null
- var allCategories: List? = null
- var adapter: ArrayAdapter? = null
+ private var allCategories: List? = null
+ private var adapter: ArrayAdapter? = null
private lateinit var createEditNoteViewModel: CreateEditNoteViewModel
private val noteType by lazy { noteType }
- protected abstract fun noteToSave(name: String, category: Int): ActionResult
- protected abstract fun updateNoteToSave(name: String, category: Int): ActionResult
+ protected abstract fun onNoteSave(name: String, category: Int): ActionResult
protected abstract fun onLoadActivity()
protected abstract fun onSaveExternalStorage(outputStream: OutputStream)
@@ -103,8 +115,9 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
protected abstract fun shareNote(name: String): ActionResult
protected abstract fun onNoteLoadedFromDB(note: Note)
protected abstract fun onNewNote()
- protected abstract fun determineToSave(title: String, category: Int): Pair
+ protected abstract fun hasNoteChanged(title: String, category: Int): Pair
+ @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -129,12 +142,23 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
currentCat = cat._id
}
}
+ hasChanged = true
}
- createEditNoteViewModel.allCategoriesLive.observe(this) { categories ->
- allCategories = categories
- adapter!!.addAll(categories!!.map { cat -> cat.name })
+
+ etName.doOnTextChanged { _, _, _, _ -> hasChanged = true }
+ etName.setOnTouchListener { _, _ ->
+ hasChanged = true
+ false
}
+ lifecycleScope.launch {
+ createEditNoteViewModel.categories.collect { categories ->
+ allCategories = categories
+ adapter!!.addAll(categories.map { cat -> cat.name })
+ }
+ }
+
+
val intent = intent
currentCat = intent.getIntExtra(EXTRA_CATEGORY, 0)
savedCat = currentCat
@@ -176,14 +200,13 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
return super.onPrepareOptionsMenu(menu)
}
- @SuppressLint("ClickableViewAccessibility")
private fun loadActivity(initial: Boolean) {
//Look for a note ID in the intent. If we got one, then we will edit that note. Otherwise we create a new one.
if (id == -1) {
val intent = intent
id = intent.getIntExtra(EXTRA_ID, -1)
}
- edit = id != -1
+ isLoadedNote = id != -1
// Should we set a custom font size?
val sp = PreferenceManager.getDefaultSharedPreferences(this)
@@ -199,7 +222,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
}
//fill in values if update
- if (edit) {
+ if (isLoadedNote) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
createEditNoteViewModel.getNoteByID(id.toLong()).observe(
this
@@ -215,6 +238,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
hasAlarm = notification!!._noteId >= 0
onNoteLoadedFromDB(note)
+ hasChanged = false
}
}
} else {
@@ -226,6 +250,24 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
onLoadActivity()
}
+ // taken from https://dev.to/ahmmedrejowan/hide-the-soft-keyboard-and-remove-focus-from-edittext-in-android-ehp on 14/03/2024
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ val v = currentFocus
+ if (v is EditText && (v == etName || v == catSelection)) {
+ Rect().apply {
+ v.getGlobalVisibleRect(this)
+ if (!this.contains(event.rawX.toInt(), event.rawY.toInt())) {
+ v.clearFocus()
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0)
+ }
+ }
+ }
+ }
+ return super.dispatchTouchEvent(event)
+ }
+
protected fun adaptFontSize(element: TextView) {
element.textSize = fontSize
}
@@ -244,7 +286,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
}
R.id.action_reminder -> {
- saveOrUpdateNote()
+ saveNote()
//Check for notification permission and exact alarm permission
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
@@ -286,7 +328,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
val year = c[Calendar.YEAR]
val month = c[Calendar.MONTH]
val day = c[Calendar.DAY_OF_MONTH]
- val dpd = DatePickerDialog(this, this, year, month, day)
+ val dpd = DatePickerDialog(ContextThemeWrapper(this, R.style.AppTheme_PopupOverlay_Calendar), this, year, month, day)
dpd.datePicker.minDate = c.timeInMillis
dpd.show()
}
@@ -294,7 +336,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
}
R.id.action_export -> {
- saveOrUpdateNote()
+ saveNote()
saveToExternalStorage()
return true
@@ -302,7 +344,7 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
R.id.action_share -> {
val result = shareNote(etName.text.toString())
- if (saveOrUpdateNote()) {
+ if (saveNote()) {
if (result.isOk()) {
startActivity(Intent.createChooser(result.ok, null))
} else {
@@ -317,13 +359,13 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
R.id.action_cancel -> {
Toast.makeText(baseContext, R.string.toast_canceled, Toast.LENGTH_SHORT).show()
- shouldSave = false
+ shouldSaveOnPause = false
finish()
}
R.id.action_save -> {
- shouldSave = true
- finish()
+ saveNote(showNotSaved = true)
+ loadActivity(false)
}
else -> {}
@@ -357,15 +399,16 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
}
override fun onPause() {
- super.onPause()
//The Activity is not visible anymore. Save the work!
- if (shouldSave) {
- saveOrUpdateNote()
+ if (shouldSaveOnPause) {
+ saveNote()
}
+ super.onPause()
}
+ @Deprecated("Deprecated in Java")
override fun onBackPressed() {
- shouldSave = true
+ shouldSaveOnPause = true
super.onBackPressed()
}
@@ -390,58 +433,43 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
}
}
- private fun saveOrUpdateNote(): Boolean {
- val (toSave, mes) = determineToSave(etName.text.toString(), if (currentCat >= 0) currentCat else savedCat)
- return if (toSave) {
- if (etName.text.isEmpty()) {
- etName.setText(generateStandardName())
+ private fun saveNote(force: Boolean = false, showNotSaved: Boolean = false): Boolean {
+ if (!force) {
+ val (changed, mes) = hasNoteChanged(etName.text.toString(), if (currentCat >= 0) currentCat else savedCat)
+ if (!changed && !hasChanged) {
+ if (mes != null) {
+ Toast.makeText(applicationContext, mes, Toast.LENGTH_SHORT).show()
+ } else if (showNotSaved) {
+ Toast.makeText(applicationContext, R.string.note_not_saved, Toast.LENGTH_SHORT).show()
+ }
+ return false
}
- if (edit) updateNote()
- else saveNote()
- } else {
- Toast.makeText(applicationContext, mes, Toast.LENGTH_SHORT).show()
- false
}
- }
-
- private fun saveNote(): Boolean {
- val note = noteToSave(
- etName.text.toString(),
- if (currentCat >= 0) currentCat else savedCat
- )
- if (note.isOk()) {
- insertNoteIntoDB(note.ok)
+ if (etName.text.isEmpty()) {
+ etName.setText(generateStandardName())
}
- return note.isOk()
- }
- private fun updateNote(): Boolean {
- val note = updateNoteToSave(
+ val result = onNoteSave(
etName.text.toString(),
if (currentCat >= 0) currentCat else savedCat
)
- if (note.isOk()) {
- insertNoteIntoDB(note.ok)
+ if (result.isErr()) {
+ Toast.makeText(applicationContext, getString(result.err ?: R.string.note_not_saved), Toast.LENGTH_SHORT).show()
+ return false
}
- return note.isOk()
- }
-
- private fun insertNoteIntoDB(note: Note?) {
- if (note != null) {
- if (etName.text.toString() != note.name) {
- etName.setText(note.name)
- }
- if (id != -1) {
- note._id = id
- createEditNoteViewModel.update(note)
- Toast.makeText(applicationContext, R.string.toast_updated, Toast.LENGTH_SHORT).show()
- } else {
- id = createEditNoteViewModel.insert(note)
- Toast.makeText(applicationContext, R.string.toast_saved, Toast.LENGTH_SHORT).show()
- }
+ val note = result.ok!!
+ if (etName.text.toString() != note.name) {
+ etName.setText(note.name)
+ }
+ if (isLoadedNote) {
+ note._id = id
+ createEditNoteViewModel.update(note)
+ Toast.makeText(applicationContext, R.string.toast_updated, Toast.LENGTH_SHORT).show()
} else {
- Toast.makeText(applicationContext, R.string.note_not_saved, Toast.LENGTH_SHORT).show()
+ id = createEditNoteViewModel.insert(note)
+ Toast.makeText(applicationContext, R.string.toast_saved, Toast.LENGTH_SHORT).show()
}
+ return true
}
private fun cancelNotification() {
@@ -472,44 +500,48 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
private fun displayTrashDialog() {
val sp = getSharedPreferences(PreferenceKeys.SP_DATA, MODE_PRIVATE)
- createEditNoteViewModel.getNoteByID(id.toLong()).observe(this) { note ->
- if (note == null) {
- shouldSave = false
- finish()
- return@observe
- }
- if (sp.getBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, true)) {
- //we never displayed the message before, so show it now
- AlertDialog.Builder(this)
- .setTitle(getString(R.string.dialog_trash_title))
- .setMessage(getString(R.string.dialog_trash_message))
- .setPositiveButton(R.string.dialog_ok) { _, _ ->
- shouldSave = false
- sp.edit().putBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, false).apply()
- note.in_trash = 1
- createEditNoteViewModel.update(note)
- finish()
- }
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show()
- sp.edit().putBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, false).apply()
- } else {
- shouldSave = false
- note.in_trash = intent.getIntExtra(EXTRA_ISTRASH, 0)
- if (note.in_trash == 1) {
- createEditNoteViewModel.delete(note)
+ if (sp.getBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, true)) {
+ //we never displayed the message before, so show it now
+ AlertDialog.Builder(this)
+ .setTitle(getString(R.string.dialog_trash_title))
+ .setMessage(getString(R.string.dialog_trash_message))
+ .setPositiveButton(R.string.dialog_ok) { _, _ ->
+ sp.edit().putBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, false).apply()
+ saveAndDeleteNote()
+ }
+ .setNegativeButton(android.R.string.cancel) { dialog, _ ->
+ dialog.cancel()
+ }
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show()
+ } else {
+ saveAndDeleteNote()
+ }
+ }
+
+ /**
+ * Move the given note to the trash or deletes it if it's already in the trash
+ */
+ private fun saveAndDeleteNote() {
+ saveNote()
+ shouldSaveOnPause = false
+ createEditNoteViewModel.getNoteByID(id.toLong()).observe(this) { updatedNote ->
+ updatedNote?.also {
+ updatedNote.in_trash = intent.getIntExtra(EXTRA_ISTRASH, 0)
+ if (updatedNote.in_trash == 1) {
+ createEditNoteViewModel.delete(updatedNote)
} else {
- note.in_trash = 1
- createEditNoteViewModel.update(note)
+ updatedNote.in_trash = 1
+ createEditNoteViewModel.update(updatedNote)
}
finish()
}
}
}
- val saveToExternalStorageResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ private val saveToExternalStorageResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
- result.data?.data?.let {uri ->
+ result.data?.data?.let { uri ->
val fileOutputStream: OutputStream? = contentResolver.openOutputStream(uri)
fileOutputStream?.let {
onSaveExternalStorage(it)
@@ -581,6 +613,23 @@ abstract class BaseNoteActivity(noteType: Int) : AppCompatActivity(), View.OnCli
loadActivity(false)
}
+ fun setTitle(title: String) {
+ etName.setText(title)
+ }
+
+ fun convertNote(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
+ createEditNoteViewModel.updateThen(it)
+ afterUpdate(it._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 d3ace959..5b7470c8 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
@@ -15,38 +15,34 @@ package org.secuso.privacyfriendlynotes.ui.notes
import android.content.Intent
import android.os.Bundle
-import android.view.ActionMode
+import android.text.Html
+import android.text.SpannedString
+import android.view.ContextThemeWrapper
import android.view.Menu
import android.view.MenuItem
import android.view.View
-import android.widget.AbsListView.MultiChoiceModeListener
-import android.widget.Adapter
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
+import android.widget.Button
import android.widget.EditText
-import android.widget.ListView
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.core.util.forEach
-import org.json.JSONArray
-import org.json.JSONException
-import org.json.JSONObject
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.secuso.privacyfriendlynotes.R
import org.secuso.privacyfriendlynotes.room.DbContract
import org.secuso.privacyfriendlynotes.room.model.Note
-import org.secuso.privacyfriendlynotes.ui.util.CheckListAdapter
-import org.secuso.privacyfriendlynotes.ui.util.CheckListItem
+import org.secuso.privacyfriendlynotes.ui.adapter.ChecklistAdapter
+import org.secuso.privacyfriendlynotes.ui.util.ChecklistUtil
import java.io.OutputStream
import java.io.PrintWriter
/**
* Activity that allows to add, edit and delete checklist notes.
*/
-class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLIST), AdapterView.OnItemClickListener {
+class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLIST) {
private val etNewItem: EditText by lazy { findViewById(R.id.etNewItem) }
- private val lvItemList: ListView by lazy { findViewById(R.id.itemList) }
- private val itemNamesList = ArrayList()
- private lateinit var checklistAdapter: CheckListAdapter
+ private val btnAdd: Button by lazy { findViewById(R.id.btn_add) }
+ private val checklist: RecyclerView by lazy { findViewById(R.id.itemList) }
+ private lateinit var adapter: ChecklistAdapter
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_checklist_note)
@@ -55,82 +51,73 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI
}
override fun onLoadActivity() {
- //get rid of the old data. Otherwise we would have duplicates.
- itemNamesList.clear()
- adaptFontSize(etNewItem)
- lvItemList.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
- lvItemList.onItemClickListener = this
- lvItemList.setMultiChoiceModeListener(object : MultiChoiceModeListener {
- override fun onItemCheckedStateChanged(
- mode: ActionMode,
- position: Int,
- id: Long,
- checked: Boolean
- ) {
+ etNewItem.setOnEditorActionListener { _, _, event ->
+ if (event == null && etNewItem.text.isNotEmpty()) {
+ addItem()
}
+ return@setOnEditorActionListener true
+ }
+
+ val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
+ ItemTouchHelper.UP or ItemTouchHelper.DOWN,
+ ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
+ ) {
+
+ override fun isLongPressDragEnabled() = false
+
+ override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
+ val to = target.bindingAdapterPosition
+ val from = viewHolder.bindingAdapterPosition
+ adapter.swap(from, to)
+ adapter.notifyItemMoved(to, from)
- override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
- // Inflate the menu for the CAB
- val inflater = mode.menuInflater
- inflater.inflate(R.menu.checklist_cab, menu)
return true
}
- override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- return false
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
+ adapter.removeItem(viewHolder.bindingAdapterPosition)
+ }
+ }
+ val ith = ItemTouchHelper(itemTouchCallback)
+ adapter = ChecklistAdapter(mutableListOf()) { holder -> ith.startDrag(holder) }
+ checklist.adapter = adapter
+ checklist.layoutManager = LinearLayoutManager(this)
+ btnAdd.setOnClickListener {
+ if (etNewItem.text.isNotEmpty()) {
+ addItem()
}
+ }
+ ith.attachToRecyclerView(checklist)
+ adaptFontSize(etNewItem)
+ }
- override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
- // Respond to clicks on the actions in the CAB
- return when (item.itemId) {
- R.id.action_delete -> {
- deleteSelectedItems()
- mode.finish() // Action picked, so close the CAB
- true
- }
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.activity_checklist, menu)
+ return super.onCreateOptionsMenu(menu)
+ }
- R.id.action_edit -> {
- val temp = ArrayList()
- lvItemList.checkedItemPositions.forEach { key, value -> if (value) temp.add(checklistAdapter.getItem(key)) }
- if (temp.size > 1) {
- Toast.makeText(
- applicationContext,
- R.string.toast_checklist_oneItem,
- Toast.LENGTH_SHORT
- ).show()
- false
- } else {
- val taskEditText = EditText(this@ChecklistNoteActivity)
- val dialog = AlertDialog.Builder(this@ChecklistNoteActivity)
- .setTitle(getString(R.string.dialog_checklist_edit) + " " + temp[0]!!.name)
- .setView(taskEditText)
- .setPositiveButton(
- R.string.action_edit
- ) { _, _ ->
- val text = taskEditText.text.toString()
- val pos = checklistAdapter.getPosition(temp[0])
- val newItem = CheckListItem(temp[0]!!.isChecked, text)
- checklistAdapter.remove(temp[0])
- checklistAdapter.insert(newItem, pos)
- }
- .setNegativeButton(R.string.action_cancel, null)
- .create()
- dialog.show()
- true
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_convert_to_note -> {
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@ChecklistNoteActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(R.string.dialog_convert_to_text_title)
+ .setMessage(R.string.dialog_convert_to_text_desc)
+ .setPositiveButton(R.string.dialog_convert_action) { _, _ ->
+ super.convertNote(Html.toHtml(SpannedString(getContentString())), DbContract.NoteEntry.TYPE_TEXT) {
+ val i = Intent(application, TextNoteActivity::class.java)
+ i.putExtra(EXTRA_ID, it)
+ startActivity(i)
+ finish()
}
}
-
- else -> false
- }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show()
}
- override fun onDestroyActionMode(mode: ActionMode) {
- val a = lvItemList.adapter as ArrayAdapter<*>
- a.notifyDataSetChanged()
- }
- })
- checklistAdapter = CheckListAdapter(baseContext, R.layout.item_checklist, itemNamesList)
- lvItemList.adapter = checklistAdapter
+ else -> {}
+ }
+ return super.onOptionsItemSelected(item)
}
override fun onNewNote() {
@@ -138,25 +125,11 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI
}
override fun onNoteLoadedFromDB(note: Note) {
- try {
- val content = JSONArray(note.content)
- itemNamesList.clear()
- for (i in 0 until content.length()) {
- val o = content.getJSONObject(i)
- checklistAdapter.add(CheckListItem(o.getBoolean("checked"), o.getString("name")))
- }
- checklistAdapter.notifyDataSetChanged()
- } catch (e: Exception) {
- e.printStackTrace()
- }
+ adapter.setAll(ChecklistUtil.parse(note.content))
}
- override fun determineToSave(title: String, category: Int): Pair {
- val intent = intent
- return Pair(
- (title.isNotEmpty() || !checklistAdapter.isEmpty) && -5 != intent.getIntExtra(EXTRA_CATEGORY, -5),
- R.string.toast_emptyNote
- )
+ override fun hasNoteChanged(title: String, category: Int): Pair {
+ return Pair(adapter.hasChanged, if (adapter.getItems().isEmpty() && title.isEmpty()) { R.string.toast_emptyNote } else { null })
}
override fun shareNote(name: String): ActionResult {
@@ -169,43 +142,12 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI
override fun onClick(v: View) {
if (v.id == R.id.btn_add && etNewItem.text.toString().isNotEmpty()) {
- itemNamesList.add(CheckListItem(false, etNewItem.text.toString()))
- etNewItem.setText("")
- (lvItemList.adapter as ArrayAdapter<*>).notifyDataSetChanged()
+ addItem()
}
}
- override fun updateNoteToSave(name: String, category: Int): ActionResult {
- val a: Adapter = lvItemList.adapter
- val jsonArray = JSONArray()
- try {
- for (i in itemNamesList.indices) {
- val temp = a.getItem(i) as CheckListItem
- val jsonObject = JSONObject()
- jsonObject.put("name", temp.name)
- jsonObject.put("checked", temp.isChecked)
- jsonArray.put(jsonObject)
- }
- } catch (e: JSONException) {
- e.printStackTrace()
- }
- return ActionResult(true, Note(name, jsonArray.toString(), DbContract.NoteEntry.TYPE_CHECKLIST, category))
- }
-
- override fun noteToSave(name: String, category: Int): ActionResult {
- val a: Adapter = lvItemList.adapter
- val jsonArray = JSONArray()
- try {
- for (i in itemNamesList.indices) {
- val temp = a.getItem(i) as CheckListItem
- val jsonObject = JSONObject()
- jsonObject.put("name", temp.name)
- jsonObject.put("checked", temp.isChecked)
- jsonArray.put(jsonObject)
- }
- } catch (e: JSONException) {
- e.printStackTrace()
- }
+ override fun onNoteSave(name: String, category: Int): ActionResult {
+ val jsonArray = ChecklistUtil.json(adapter.getItems())
if (name.isEmpty() && jsonArray.length() == 0) {
return ActionResult(false, null)
}
@@ -223,26 +165,11 @@ class ChecklistNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_CHECKLI
}
private fun getContentString(): String {
- return itemNamesList.joinToString(separator = "\n") { item -> "- ${item.name} [${if (item.isChecked) "✓" else " "}]" }
- }
-
- //Click on a listitem
- override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
- val temp = checklistAdapter.getItem(position)
- temp!!.isChecked = !temp.isChecked
- checklistAdapter.notifyDataSetChanged()
+ return adapter.getItems().joinToString(System.lineSeparator()) { (checked, name) -> "- [${if (checked) "x" else " "}] $name" }
}
- private fun deleteSelectedItems() {
- val checkedItemPositions = lvItemList.checkedItemPositions
- val temp = ArrayList()
- for (i in 0 until checkedItemPositions.size()) {
- if (checkedItemPositions.valueAt(i)) {
- temp.add(checklistAdapter.getItem(checkedItemPositions.keyAt(i)))
- }
- }
- if (temp.isNotEmpty()) {
- itemNamesList.removeAll(temp.toSet())
- }
+ private fun addItem() {
+ adapter.addItem(etNewItem.text.toString())
+ etNewItem.setText("")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/CreateEditNoteViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/CreateEditNoteViewModel.kt
index f74c7a78..e091a453 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/CreateEditNoteViewModel.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/CreateEditNoteViewModel.kt
@@ -15,13 +15,18 @@ package org.secuso.privacyfriendlynotes.ui.notes
import android.app.Application
import android.util.Log
-import androidx.lifecycle.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.secuso.privacyfriendlynotes.room.NoteDatabase
import org.secuso.privacyfriendlynotes.room.model.Category
import org.secuso.privacyfriendlynotes.room.model.Note
-import org.secuso.privacyfriendlynotes.room.NoteDatabase
import org.secuso.privacyfriendlynotes.room.model.Notification
/**
@@ -29,28 +34,30 @@ import org.secuso.privacyfriendlynotes.room.model.Notification
* @see AudioNoteActivity, ChecklistNoteActivity, SketchActivity, TextNoteActivity
*/
-class CreateEditNoteViewModel(application: Application) : AndroidViewModel(application){
+class CreateEditNoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteDatabase = NoteDatabase.getInstance(application)
val allNotifications: LiveData> = repository.notificationDao().allNotificationsLiveData
- val allCategoriesLive: LiveData> = repository.categoryDao().allCategoriesLive
+ val categories: Flow> = repository.categoryDao().allCategories
private val _categoryName: MediatorLiveData = MediatorLiveData()
private var _categoryNameLast: LiveData? = null
private val database: NoteDatabase = NoteDatabase.getInstance(application)
- fun insert(notification: Notification){
- viewModelScope.launch(Dispatchers.Default){
+ fun insert(notification: Notification) {
+ viewModelScope.launch(Dispatchers.Default) {
repository.notificationDao().insert(notification)
}
}
- fun update(notification: Notification){
- viewModelScope.launch(Dispatchers.Default){
+
+ fun update(notification: Notification) {
+ viewModelScope.launch(Dispatchers.Default) {
repository.notificationDao().update(notification)
}
}
- fun delete(notification: Notification){
- viewModelScope.launch(Dispatchers.Default){
+
+ fun delete(notification: Notification) {
+ viewModelScope.launch(Dispatchers.Default) {
repository.notificationDao().delete(notification)
}
}
@@ -76,16 +83,16 @@ class CreateEditNoteViewModel(application: Application) : AndroidViewModel(appli
fun getCategoryNameFromId(categoryId: Int): LiveData {
- viewModelScope.launch(Dispatchers.Default){
- withContext(Dispatchers.Main){
- if(_categoryNameLast != null){
+ viewModelScope.launch(Dispatchers.Default) {
+ withContext(Dispatchers.Main) {
+ if (_categoryNameLast != null) {
_categoryName.removeSource(_categoryNameLast!!)
}
}
_categoryNameLast = repository.categoryDao().categoryNameFromId(categoryId as Integer)
- withContext(Dispatchers.Main){
- _categoryName.addSource(_categoryNameLast!!){
+ withContext(Dispatchers.Main) {
+ _categoryName.addSource(_categoryNameLast!!) {
_categoryName.postValue(it)
}
}
@@ -101,7 +108,6 @@ class CreateEditNoteViewModel(application: Application) : AndroidViewModel(appli
val id = viewModelScope.run {
database.noteDao().insert(note).toInt()
}
- Log.e("id", "$id")
return id
}
@@ -111,6 +117,12 @@ class CreateEditNoteViewModel(application: Application) : AndroidViewModel(appli
}
}
+ fun updateThen(note: Note) {
+ viewModelScope.launch(Dispatchers.Default) {
+ database.noteDao().update(note)
+ }
+ }
+
fun delete(note: Note) {
viewModelScope.launch(Dispatchers.Default) {
database.noteDao().delete(note)
@@ -126,7 +138,7 @@ class CreateEditNoteViewModel(application: Application) : AndroidViewModel(appli
return note
}
- companion object{
+ companion object {
private const val TAG = "CreateEditNoteViewModel"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt
index 1e1ed318..9d42a8cb 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt
@@ -13,22 +13,36 @@
*/
package org.secuso.privacyfriendlynotes.ui.notes
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.DialogInterface
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.MotionEvent
import android.view.View
import android.widget.Button
+import android.widget.LinearLayout
+import androidx.annotation.ColorInt
import androidx.core.content.FileProvider
+import androidx.preference.PreferenceManager
import com.simplify.ink.InkView
+import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener
+import eltos.simpledialogfragment.color.SimpleColorDialog
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlynotes.R
import org.secuso.privacyfriendlynotes.room.DbContract
import org.secuso.privacyfriendlynotes.room.model.Note
-import petrov.kristiyan.colorpicker.ColorPicker
-import petrov.kristiyan.colorpicker.ColorPicker.OnFastChooseColorListener
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
@@ -38,12 +52,22 @@ import java.io.OutputStream
/**
* Activity that allows to add, edit and delete sketch notes.
*/
-class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
+class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH), OnDialogResultListener {
private val drawView: InkView by lazy { findViewById(R.id.draw_view) }
+ private val drawWrapper: LinearLayout by lazy { findViewById(R.id.sketch_wrapper) }
private val btnColorSelector: Button by lazy { findViewById(R.id.btn_color_selector) }
+ private lateinit var undoButton: MenuItem
+ private lateinit var redoButton: MenuItem
private var mFileName = "finde_die_datei.mp4"
private var mFilePath: String? = null
private var sketchLoaded = false
+ private val undoStates = mutableListOf()
+ private var redoStates = mutableListOf()
+ private var state: Bitmap? = null
+ private var oldSketch: BitmapDrawable? = null
+ private var initialSize: Pair? = null
+
+ private val undoRedoEnabled by lazy { PreferenceManager.getDefaultSharedPreferences(this).getBoolean("settings_sketch_undo_redo", true) }
private fun emptyBitmap(): Bitmap {
return Bitmap.createBitmap(
@@ -53,14 +77,56 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
)
}
+ @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_sketch)
+ drawView.viewTreeObserver.addOnGlobalLayoutListener {
+ if (initialSize == null) {
+ Log.d("Initial size", "${drawWrapper.width},${drawWrapper.height}")
+ initialSize = Pair(drawWrapper.width, drawWrapper.height)
+ }
+ if (initialSize!!.first != drawView.layoutParams.width || initialSize!!.second != drawView.layoutParams.height) {
+ Log.d("Set size", "to ${drawWrapper.width},${drawWrapper.height}, from ${drawView.width},${drawView.height}")
+ drawView.layoutParams = LinearLayout.LayoutParams(initialSize!!.first, initialSize!!.second)
+ if (oldSketch != null) {
+ drawView.background = oldSketch
+ } else {
+ drawView.background = BitmapDrawable(resources, Bitmap.createScaledBitmap(drawView.bitmap, initialSize!!.first, initialSize!!.second, false))
+ }
+ if (state != null) {
+ drawView.drawBitmap(Bitmap.createScaledBitmap(state!!, initialSize!!.first, initialSize!!.second, false), 0f, 0f, null)
+ }
+ }
+ }
+
btnColorSelector.setOnClickListener(this)
btnColorSelector.setBackgroundColor(Color.BLACK)
drawView.setColor(Color.BLACK)
drawView.setMinStrokeWidth(1.5f)
drawView.setMaxStrokeWidth(6f)
+
+ if (undoRedoEnabled) {
+ drawView.setOnTouchListener { view, motionEvent ->
+ view.onTouchEvent(motionEvent).let {
+ if (motionEvent.actionMasked == MotionEvent.ACTION_UP) {
+ if (state == null) {
+ state = emptyBitmap()
+ }
+ undoStates.add(state!!)
+ redoStates.clear()
+ if (undoStates.size > 32) {
+ undoStates.removeFirst()
+ }
+ state = drawView.bitmap.copy(Bitmap.Config.ARGB_8888, false)
+ undoButton.isEnabled = true
+ redoButton.isEnabled = false
+ }
+
+ return@setOnTouchListener it
+ }
+ }
+ }
super.onCreate(savedInstanceState)
}
@@ -68,7 +134,14 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
override fun onNoteLoadedFromDB(note: Note) {
mFileName = note.content
mFilePath = filesDir.path + "/sketches" + mFileName
- drawView.background = BitmapDrawable(resources, mFilePath)
+ File(cacheDir.path + "/sketches").mkdirs()
+ oldSketch = try {
+ loadSketchBitmap(this, note.content)
+ } catch (e: FileNotFoundException) {
+ Log.d(TAG, "Cannot load sketch: ${e.printStackTrace()}")
+ BitmapDrawable(resources, emptyBitmap())
+ }
+ drawView.background = oldSketch
sketchLoaded = true
}
@@ -76,22 +149,65 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
mFileName = "/sketch_" + System.currentTimeMillis() + ".PNG"
mFilePath = filesDir.path + "/sketches"
File(mFilePath!!).mkdirs() //ensure that the file exists
+ File(cacheDir.path + "/sketches").mkdirs()
mFilePath = filesDir.path + "/sketches" + mFileName
}
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.activity_sketch, menu)
+ undoButton = menu!!.findItem(R.id.action_sketch_undo)
+ redoButton = menu.findItem(R.id.action_sketch_redo)
+ undoButton.isEnabled = false
+ redoButton.isEnabled = false
+ if (!undoRedoEnabled) {
+ undoButton.setVisible(false)
+ redoButton.setVisible(false)
+ }
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_sketch_undo -> {
+ drawView.clear()
+ if (undoStates.isNotEmpty()) {
+ redoStates.add(state!!)
+ undoRedoState(undoStates.removeLast())
+ }
+ }
+
+ R.id.action_sketch_redo -> {
+ if (redoStates.isNotEmpty()) {
+ undoStates.add(state!!)
+ undoRedoState(redoStates.removeLast())
+ }
+ }
+
+ else -> {}
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun undoRedoState(state: Bitmap) {
+ this.state = state
+ drawView.drawBitmap(state, 0F, 0F, null)
+ undoButton.isEnabled = undoStates.isNotEmpty()
+ redoButton.isEnabled = redoStates.isNotEmpty()
+ }
+
override fun shareNote(name: String): ActionResult {
val tempPath = mFilePath!!.substring(0, mFilePath!!.length - 3) + "jpg"
val sketchFile = File(tempPath)
val map = BitmapDrawable(resources, mFilePath).bitmap ?: emptyBitmap()
- val bm = overlay(map, drawView.bitmap)
+ val bm = map.overlay(drawView.bitmap)
val canvas = Canvas(bm)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(
- overlay(
- map,
- drawView.bitmap
- ), 0f, 0f, null
+ map.overlay(drawView.bitmap),
+ 0f,
+ 0f,
+ null
)
try {
bm.compress(Bitmap.CompressFormat.JPEG, 100, FileOutputStream(sketchFile))
@@ -111,11 +227,13 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
return ActionResult(true, sendIntent)
}
- override fun determineToSave(title: String, category: Int): Pair {
- val intent = intent
+ override fun hasNoteChanged(title: String, category: Int): Pair {
return Pair(
- sketchLoaded || !drawView.bitmap.sameAs(emptyBitmap()) && -5 != intent.getIntExtra(EXTRA_CATEGORY, -5),
- R.string.toast_emptyNote
+ if (undoRedoEnabled) {
+ undoStates.isNotEmpty()
+ } else {
+ drawView.bitmap != emptyBitmap()
+ }, if (sketchLoaded) null else R.string.toast_emptyNote
)
}
@@ -126,84 +244,103 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) {
}
}
- override fun updateNoteToSave(name: String, category: Int): ActionResult {
- val oldSketch = BitmapDrawable(resources, mFilePath).bitmap
- val newSketch = drawView.bitmap
- try {
- val fo = FileOutputStream(File(mFilePath!!))
- overlay(oldSketch, newSketch).compress(Bitmap.CompressFormat.PNG, 0, fo)
- fo.flush()
- fo.close()
- } catch (e: FileNotFoundException) {
- e.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
+ override fun onNoteSave(name: String, category: Int): ActionResult {
+ runBlocking {
+ saveBitmap(mFilePath!!)
+ }
+
+ if (name.isEmpty() && drawView.bitmap.sameAs(emptyBitmap())) {
+ return ActionResult(false, null)
}
return ActionResult(true, Note(name, mFileName, DbContract.NoteEntry.TYPE_SKETCH, category))
}
- override fun noteToSave(name: String, category: Int): ActionResult {
- val bitmap = drawView.bitmap
+ private suspend fun saveBitmap(path: String) {
+ val bitmap = oldSketch?.overlay(drawView.bitmap) ?: emptyBitmap().overlay(drawView.bitmap)
+ // This function might get interrupted if it takes too long.
+ // To prevent damaged files we first write the image to a new location and then move it over to the old location
try {
- val fo = FileOutputStream(File(mFilePath!!))
- bitmap.compress(Bitmap.CompressFormat.PNG, 0, fo)
- fo.flush()
- fo.close()
+ val oldFile = File(path)
+ val newFile = File("$path.new")
+ withContext(Dispatchers.IO) {
+ FileOutputStream(newFile).use {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 90, it)
+ }
+ // Move new file to old location
+ newFile.renameTo(oldFile)
+ }
} catch (e: FileNotFoundException) {
+ Log.d("Bitmap Error", e.stackTraceToString())
e.printStackTrace()
} catch (e: IOException) {
+ Log.d("Bitmap Error", e.stackTraceToString())
e.printStackTrace()
}
- if (name.isEmpty() && bitmap.sameAs(emptyBitmap())) {
- return ActionResult(false, null)
- }
- return ActionResult(true, Note(name, mFileName, DbContract.NoteEntry.TYPE_SKETCH, category))
}
private fun displayColorDialog() {
- ColorPicker(this)
- .setOnFastChooseColorListener(object : OnFastChooseColorListener {
- override fun setOnFastChooseColorListener(position: Int, color: Int) {
- drawView.setColor(color)
- btnColorSelector.setBackgroundColor(color)
- }
+ SimpleColorDialog.build()
+ .title("")
+ .allowCustom(true)
+ .cancelable(true) //allows close by tapping outside of dialog
+ .colors(this, R.array.mdcolor_500)
+ .choiceMode(SimpleColorDialog.SINGLE_CHOICE_DIRECT) //auto-close on selection
+ .show(this, COLOR_DIALOG_TAG)
+ }
- override fun onCancel() {}
- })
- .setColors(R.array.mdcolor_500)
- .setTitle(null)
- .show()
+ override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
+ if (dialogTag == COLOR_DIALOG_TAG && which == DialogInterface.BUTTON_POSITIVE) {
+ @ColorInt val color = extras.getInt(SimpleColorDialog.COLOR)
+ drawView.setColor(color)
+ btnColorSelector.setBackgroundColor(color)
+ return true
+ }
+ return false
}
override fun getFileExtension() = ".jpeg"
override fun getMimeType() = "image/jpeg"
override fun onSaveExternalStorage(outputStream: OutputStream) {
- val bm = overlay(
- BitmapDrawable(
- resources, mFilePath
- ).bitmap, drawView.bitmap
- )
+ val bm = BitmapDrawable(resources, mFilePath).bitmap.overlay(drawView.bitmap)
val canvas = Canvas(bm)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(
- overlay(
- BitmapDrawable(
- resources, mFilePath
- ).bitmap, drawView.bitmap
- ), 0f, 0f, null
+ BitmapDrawable(resources, mFilePath).bitmap.overlay(drawView.bitmap),
+ 0f,
+ 0f,
+ null
)
bm.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
}
companion object {
+ private const val TAG = "SketchActivity"
+ private const val COLOR_DIALOG_TAG = "org.secuso.privacyfriendlynotes.COLORDIALOG"
+
//taken from http://stackoverflow.com/a/10616868
- fun overlay(bmp1: Bitmap, bmp2: Bitmap): Bitmap {
- val bmOverlay = Bitmap.createBitmap(bmp1.width, bmp1.height, bmp1.config)
+ fun Bitmap.overlay(bitmap: Bitmap): Bitmap {
+ val bmOverlay = Bitmap.createBitmap(width, height, config)
val canvas = Canvas(bmOverlay)
- canvas.drawBitmap(bmp1, Matrix(), null)
- canvas.drawBitmap(bmp2, 0f, 0f, null)
+ canvas.drawBitmap(this, Matrix(), null)
+ if (width != bitmap.width || height != bitmap.height) {
+ canvas.drawBitmap(bitmap, Rect(0, 0, bitmap.width, bitmap.height), Rect(0, 0, width, height), null)
+ } else {
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
+ }
return bmOverlay
}
+
+ fun BitmapDrawable.overlay(bitmap: Bitmap): Bitmap = this.bitmap.overlay(bitmap)
+
+ fun loadSketchBitmap(context: Context, file: String): BitmapDrawable {
+ File("${context.filesDir.path}/sketches${file}").apply {
+ if (exists()) {
+ return BitmapDrawable(context.resources, path)
+ } else {
+ throw FileNotFoundException("Cannot open sketch: $path")
+ }
+ }
+ }
}
}
\ 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 53dd51e8..c27806d1 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
@@ -17,6 +17,7 @@ import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Typeface
+import android.net.Uri
import android.os.Bundle
import android.text.Html
import android.text.Spannable
@@ -24,17 +25,23 @@ import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
+import android.util.Log
+import android.view.ContextThemeWrapper
+import android.view.Menu
+import android.view.MenuItem
import android.view.View
import android.widget.EditText
import androidx.lifecycle.MutableLiveData
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.secuso.privacyfriendlynotes.R
import org.secuso.privacyfriendlynotes.room.DbContract
import org.secuso.privacyfriendlynotes.room.model.Note
+import org.secuso.privacyfriendlynotes.ui.util.ChecklistUtil
+import java.io.InputStreamReader
import java.io.OutputStream
import java.io.PrintWriter
-
/**
* Activity that allows to add, edit and delete text notes.
*/
@@ -49,17 +56,24 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) {
private val isItalic = MutableLiveData(false)
private val isUnderline = MutableLiveData(false)
+ private var hasChanged = false
+ private var oldText: String? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_text_note)
- val fabMenu = findViewById(R.id.fab_menu)
- fabMenu.setOnClickListener {
- if (fabMenu.isExpanded) {
- fabMenu.isExpanded = false
- fabMenu.setImageResource(R.drawable.ic_baseline_format_color_text_24)
+ val fabMenuBtn = findViewById(R.id.fab_menu)
+ val fabMenu = findViewById(R.id.fab_menu_wrapper)
+ var expanded = false
+ fabMenuBtn.setOnClickListener {
+ if (expanded) {
+ expanded = false
+ fabMenuBtn.setImageResource(R.drawable.ic_baseline_format_color_text_24)
+ fabMenu.visibility = View.GONE
} else {
- fabMenu.isExpanded = true
- fabMenu.setImageResource(R.drawable.ic_baseline_close_24)
+ expanded = true
+ fabMenuBtn.setImageResource(R.drawable.ic_baseline_close_24)
+ fabMenu.visibility = View.VISIBLE
}
}
boldBtn.setOnClickListener(this)
@@ -67,23 +81,65 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) {
underlineBtn.setOnClickListener(this)
isBold.observe(this) { b: Boolean ->
- boldBtn.backgroundTintList = ColorStateList.valueOf(Color.parseColor(if (b) "#000000" else "#0274b2"))
+ boldBtn.backgroundTintList = ColorStateList.valueOf(if (b) Color.parseColor("#000000") else resources.getColor(R.color.colorSecuso))
}
isItalic.observe(this) { b: Boolean ->
- italicsBtn.backgroundTintList = ColorStateList.valueOf(Color.parseColor(if (b) "#000000" else "#0274b2"))
+ italicsBtn.backgroundTintList = ColorStateList.valueOf(if (b) Color.parseColor("#000000") else resources.getColor(R.color.colorSecuso))
}
isUnderline.observe(this) { b: Boolean ->
- underlineBtn.backgroundTintList = ColorStateList.valueOf(Color.parseColor(if (b) "#000000" else "#0274b2"))
+ underlineBtn.backgroundTintList = ColorStateList.valueOf(if (b) Color.parseColor("#000000") else resources.getColor(R.color.colorSecuso))
}
super.onCreate(savedInstanceState)
}
override fun onNoteLoadedFromDB(note: Note) {
etContent.setText(Html.fromHtml(note.content))
+ oldText = etContent.text.toString()
}
- override fun onNewNote() {
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.activity_text, menu)
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_convert_to_checklist -> {
+ MaterialAlertDialogBuilder(ContextThemeWrapper(this@TextNoteActivity, R.style.AppTheme_PopupOverlay_DialogAlert))
+ .setTitle(R.string.dialog_convert_to_checklist_title)
+ .setMessage(R.string.dialog_convert_to_checklist_desc)
+ .setPositiveButton(R.string.dialog_convert_action) { _, _ ->
+ val json = ChecklistUtil.json(etContent.text.lines().filter { it.isNotBlank() }.map(ChecklistUtil::textToItem))
+ super.convertNote(json.toString(), DbContract.NoteEntry.TYPE_CHECKLIST) {
+ val i = Intent(application, ChecklistNoteActivity::class.java)
+ i.putExtra(EXTRA_ID, it)
+ startActivity(i)
+ finish()
+ }
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show()
+ }
+
+ else -> {}
+ }
+ return super.onOptionsItemSelected(item)
+ }
+ override fun onNewNote() {
+ if (intent != null) {
+ val uri: Uri? = listOf(intent.data, intent.getParcelableExtra(Intent.EXTRA_STREAM)).firstNotNullOfOrNull { it }
+ if (uri != null) {
+ val text = InputStreamReader(contentResolver.openInputStream(uri)).readLines()
+ super.setTitle(text[0])
+ etContent.setText(Html.fromHtml(text.subList(1, text.size).joinToString("
")))
+ }
+ val text = intent.getStringExtra(Intent.EXTRA_TEXT)
+ if (text != null) {
+ etContent.setText(Html.fromHtml(text))
+ }
+ }
}
override fun onLoadActivity() {
@@ -98,12 +154,13 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) {
return ActionResult(true, sendIntent)
}
- override fun determineToSave(title: String, category: Int): Pair {
- val intent = intent
- return Pair(
- (title.isNotEmpty() || Html.toHtml(etContent.text) != "") && -5 != intent.getIntExtra(EXTRA_CATEGORY, -5),
- R.string.toast_emptyNote
- )
+ override fun hasNoteChanged(title: String, category: Int): Pair {
+ hasChanged = hasChanged || (oldText?.trim() != etContent.text.toString().trim())
+ return if (!hasChanged) {
+ Pair(false, null)
+ } else {
+ Pair(title.isNotEmpty() || Html.toHtml(etContent.text).isNotEmpty(), R.string.toast_emptyNote)
+ }
}
override fun onClick(v: View) {
@@ -112,9 +169,16 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) {
val underlined: UnderlineSpan
val totalText: SpannableStringBuilder
when (v.id) {
- R.id.btn_bold -> applyStyle(Typeface.BOLD, isBold)
- R.id.btn_italics -> applyStyle(Typeface.ITALIC, isItalic)
+ R.id.btn_bold -> {
+ hasChanged = true
+ applyStyle(Typeface.BOLD, isBold)
+ }
+ R.id.btn_italics -> {
+ hasChanged = true
+ applyStyle(Typeface.ITALIC, isItalic)
+ }
R.id.btn_underline -> {
+ hasChanged = true
underlined = UnderlineSpan()
var alreadyUnderlined = false
totalText = etContent.text as SpannableStringBuilder
@@ -348,11 +412,7 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) {
etContent.setSelection(startSelection)
}
- override fun updateNoteToSave(name: String, category: Int): ActionResult {
- return ActionResult(true, Note(name, Html.toHtml(etContent.text), DbContract.NoteEntry.TYPE_TEXT, category))
- }
-
- override fun noteToSave(name: String, category: Int): ActionResult {
+ override fun onNoteSave(name: String, category: Int): ActionResult {
return if (name.isEmpty() && etContent.text.toString().isEmpty()) {
ActionResult(false, null)
} else {
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListAdapter.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListAdapter.java
deleted file mode 100644
index 7055e8e0..00000000
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/CheckListAdapter.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- This file is part of the application Privacy Friendly Notes.
- Privacy Friendly Notes is free software:
- you can redistribute it and/or modify it under the terms of the
- GNU General Public License as published by the Free Software Foundation,
- either version 3 of the License, or any later version.
- Privacy Friendly Notes is distributed in the hope
- that it will be useful, but WITHOUT ANY WARRANTY; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Privacy Friendly Notes. If not, see .
- */
-package org.secuso.privacyfriendlynotes.ui.util;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
-import android.widget.TextView;
-
-import org.secuso.privacyfriendlynotes.R;
-import org.secuso.privacyfriendlynotes.ui.SettingsActivity;
-
-import java.util.List;
-
-/**
- * Created by Robin on 12.09.2016.
- */
-public class CheckListAdapter extends ArrayAdapter {
- public CheckListAdapter(Context context, int resource) {
- super(context, resource);
- }
-
- public CheckListAdapter(Context context, int resource, List objects) {
- super(context, resource, objects);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = convertView;
- if (v == null) {
- LayoutInflater inflater = LayoutInflater.from(getContext());
- v = inflater.inflate(R.layout.item_checklist, null);
- }
- CheckListItem item = getItem(position);
-
- if (item != null) {
- CheckBox checkBox = (CheckBox) v.findViewById(R.id.item_checkbox);
- TextView textView = (TextView) v.findViewById(R.id.item_name);
-
- checkBox.setChecked(item.isChecked());
- textView.setText(item.getName());
- // Should we set a custom font size?
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
- if (sp.getBoolean(SettingsActivity.PREF_CUSTOM_FONT, false)) {
- textView.setTextSize(Float.parseFloat(sp.getString(SettingsActivity.PREF_CUSTOM_FONT_SIZE, "15")));
- }
- }
-
- return v;
- }
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/ChecklistUtil.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/ChecklistUtil.kt
new file mode 100644
index 00000000..1e496cd9
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/ChecklistUtil.kt
@@ -0,0 +1,66 @@
+/*
+ This file is part of the application Privacy Friendly Notes.
+ Privacy Friendly Notes is free software:
+ you can redistribute it and/or modify it under the terms of the
+ GNU General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or any later version.
+ Privacy Friendly Notes is distributed in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Privacy Friendly Notes. If not, see .
+ */
+package org.secuso.privacyfriendlynotes.ui.util
+
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.regex.Pattern
+
+/**
+ * Provides common utilities to interact with a checklist.
+ * @author Patrick Schneider
+ */
+class ChecklistUtil {
+
+ companion object {
+ fun parse(checklist: String): List> {
+ try {
+ val content = JSONArray(checklist)
+ return (0 until content.length()).map {
+ val obj = content.getJSONObject(it)
+ return@map Pair(obj.getBoolean("checked"), obj.getString("name"))
+ }
+ } catch (ex: JSONException) {
+ return ArrayList()
+ }
+ }
+
+ fun json(checklist: List>): JSONArray {
+ val jsonArray = JSONArray()
+ try {
+ for ((checked, name) in checklist) {
+ val jsonObject = JSONObject()
+ jsonObject.put("name", name)
+ jsonObject.put("checked", checked)
+ jsonArray.put(jsonObject)
+ }
+ } catch (e: JSONException) {
+ e.printStackTrace()
+ }
+ return jsonArray
+ }
+
+ fun textToItem(text: String): Pair {
+ Pattern.compile("-\\s*\\[(.*)]\\s*(.*)").matcher(text).apply {
+ if (matches()) {
+ val checked = group(1)
+ val name = group(2)
+ return Pair(checked !== null && checked.isNotEmpty() && checked.isNotBlank(), name!!)
+ }
+ }
+ return Pair(false, text)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/DarkModeUtil.kt
similarity index 61%
rename from app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java
rename to app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/DarkModeUtil.kt
index 3e1c1b02..12c84e80 100644
--- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java
+++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/util/DarkModeUtil.kt
@@ -11,20 +11,19 @@
You should have received a copy of the GNU General Public License
along with Privacy Friendly Notes. If not, see .
*/
-package org.secuso.privacyfriendlynotes.ui.fragments;
+package org.secuso.privacyfriendlynotes.ui.util
-import android.os.Bundle;
-import android.preference.PreferenceFragment;
-
-import org.secuso.privacyfriendlynotes.R;
+import android.content.Context
+import android.content.res.Configuration
/**
- * Fragment that provides the settings.
- * Created by Robin on 11.09.2016.
+ * Provides common utilities to interact with the system dark mode.
+ * @author Patrick Schneider
*/
-public class SettingsFragment extends PreferenceFragment {
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.pref_settings);
+class DarkModeUtil {
+ companion object {
+ fun isDarkMode(context: Context): Boolean {
+ return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/color/menu_state_list.xml b/app/src/main/res/color/menu_state_list.xml
new file mode 100644
index 00000000..3b7c012a
--- /dev/null
+++ b/app/src/main/res/color/menu_state_list.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/arrow_back.xml b/app/src/main/res/drawable/arrow_back.xml
new file mode 100644
index 00000000..31e7df2e
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_back.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_arrow_downward_24.xml b/app/src/main/res/drawable/baseline_arrow_downward_24.xml
new file mode 100644
index 00000000..dcfb7840
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_arrow_downward_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_arrow_upward_24.xml b/app/src/main/res/drawable/baseline_arrow_upward_24.xml
new file mode 100644
index 00000000..46d5d587
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_arrow_upward_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_more_vert_white.xml b/app/src/main/res/drawable/baseline_more_vert_white.xml
new file mode 100644
index 00000000..0302ac25
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_more_vert_white.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/fab_border.xml b/app/src/main/res/drawable/fab_border.xml
new file mode 100644
index 00000000..1efaa830
--- /dev/null
+++ b/app/src/main/res/drawable/fab_border.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml
new file mode 100644
index 00000000..d72f9645
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_drag_indicator_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_drag_indicator_icon_24dp.xml
new file mode 100644
index 00000000..69ec316f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_drag_indicator_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml
new file mode 100644
index 00000000..ad87c776
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml
new file mode 100644
index 00000000..a31d8302
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml
new file mode 100644
index 00000000..48368f35
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_format_color_reset_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_format_color_reset_icon_24dp.xml
new file mode 100644
index 00000000..4da91bd3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_format_color_reset_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml b/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml
new file mode 100644
index 00000000..6787c41a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml b/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml
new file mode 100644
index 00000000..0cad938e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_sort_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_sort_icon_24dp.xml
new file mode 100644
index 00000000..5156647f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_sort_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml
new file mode 100644
index 00000000..37616f0f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_block_icon_24dp.xml b/app/src/main/res/drawable/ic_block_icon_24dp.xml
new file mode 100644
index 00000000..6ced7636
--- /dev/null
+++ b/app/src/main/res/drawable/ic_block_icon_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete_forever_white_24dp.xml b/app/src/main/res/drawable/ic_delete_forever_icon_24dp.xml
similarity index 90%
rename from app/src/main/res/drawable/ic_delete_forever_white_24dp.xml
rename to app/src/main/res/drawable/ic_delete_forever_icon_24dp.xml
index 771c5829..a947f0cd 100644
--- a/app/src/main/res/drawable/ic_delete_forever_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_delete_forever_icon_24dp.xml
@@ -5,5 +5,5 @@
android:viewportHeight="24.0">
+ android:fillColor="?attr/colorIconFill"/>
diff --git a/app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml b/app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml
similarity index 92%
rename from app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml
rename to app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml
index 5937a4eb..a2a187ec 100644
--- a/app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_mic_black_24dp.xml b/app/src/main/res/drawable/ic_mic_icon_24dp.xml
similarity index 90%
rename from app/src/main/res/drawable/ic_mic_black_24dp.xml
rename to app/src/main/res/drawable/ic_mic_icon_24dp.xml
index 4f0dc044..e6a2385b 100644
--- a/app/src/main/res/drawable/ic_mic_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_mic_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_pause_black_24dp.xml b/app/src/main/res/drawable/ic_pause_icon_24dp.xml
similarity index 85%
rename from app/src/main/res/drawable/ic_pause_black_24dp.xml
rename to app/src/main/res/drawable/ic_pause_icon_24dp.xml
index bb28a6c4..2a09bfd9 100644
--- a/app/src/main/res/drawable/ic_pause_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_pause_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_photo_black_24dp.xml b/app/src/main/res/drawable/ic_photo_icon_24dp.xml
similarity index 88%
rename from app/src/main/res/drawable/ic_photo_black_24dp.xml
rename to app/src/main/res/drawable/ic_photo_icon_24dp.xml
index b2018595..c3363c70 100644
--- a/app/src/main/res/drawable/ic_photo_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_photo_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml
similarity index 84%
rename from app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
rename to app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml
index bf9b895a..a34b479e 100644
--- a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_short_text_black_24dp.xml b/app/src/main/res/drawable/ic_short_text_icon_24dp.xml
similarity index 85%
rename from app/src/main/res/drawable/ic_short_text_black_24dp.xml
rename to app/src/main/res/drawable/ic_short_text_icon_24dp.xml
index 11c24c5a..c0ee7a03 100644
--- a/app/src/main/res/drawable/ic_short_text_black_24dp.xml
+++ b/app/src/main/res/drawable/ic_short_text_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml b/app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml
similarity index 92%
rename from app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml
rename to app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml
index c8d41361..712797c8 100644
--- a/app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable/secuso_logo_blau_blau.png b/app/src/main/res/drawable/secuso_logo_blau_blau.png
deleted file mode 100644
index 9c82d7c8..00000000
Binary files a/app/src/main/res/drawable/secuso_logo_blau_blau.png and /dev/null differ
diff --git a/app/src/main/res/drawable/secuso_logo_blau_blau.xml b/app/src/main/res/drawable/secuso_logo_blau_blau.xml
new file mode 100644
index 00000000..1d9eee3f
--- /dev/null
+++ b/app/src/main/res/drawable/secuso_logo_blau_blau.xml
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/transparent_checker.xml b/app/src/main/res/drawable/transparent_checker.xml
new file mode 100644
index 00000000..4be169e3
--- /dev/null
+++ b/app/src/main/res/drawable/transparent_checker.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_about.xml b/app/src/main/res/layout-land/activity_about.xml
index 97a6685f..b68508ca 100644
--- a/app/src/main/res/layout-land/activity_about.xml
+++ b/app/src/main/res/layout-land/activity_about.xml
@@ -3,13 +3,13 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:background="@color/white">
+ android:background="?attr/colorSurface">
@@ -53,13 +54,26 @@
android:layout_marginTop="20dp"
android:text="@string/app_name_long" />
-
+
+
+
+
+
@@ -38,7 +38,7 @@
android:id="@+id/btn_record"
android:layout_width="48dp"
android:layout_height="48dp"
- android:background="@drawable/ic_mic_black_24dp"
+ android:background="@drawable/ic_mic_icon_24dp"
android:padding="16dp" />
diff --git a/app/src/main/res/layout-land/activity_tutorial.xml b/app/src/main/res/layout-land/activity_tutorial.xml
index 5fc8a539..86dbe93d 100644
--- a/app/src/main/res/layout-land/activity_tutorial.xml
+++ b/app/src/main/res/layout-land/activity_tutorial.xml
@@ -19,7 +19,7 @@
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/dots_margin_bottom"
android:gravity="center"
- android:orientation="horizontal">
+ android:orientation="horizontal" />
@@ -47,6 +48,7 @@
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:background="@null"
+ android:backgroundTint="@color/colorAccent"
android:text="@string/skip"
android:textColor="@android:color/white" />
diff --git a/app/src/main/res/layout-land/app_bar_main.xml b/app/src/main/res/layout-land/app_bar_main.xml
index 060b91f4..c5a15c01 100644
--- a/app/src/main/res/layout-land/app_bar_main.xml
+++ b/app/src/main/res/layout-land/app_bar_main.xml
@@ -2,7 +2,6 @@
@@ -25,62 +24,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:id="@+id/fab_menu_wrapper"
+ android:name="org.secuso.privacyfriendlynotes.ui.fragments.MainFABFragment" />
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index 259b71b0..1549fd44 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -1,25 +1,25 @@
+ android:background="?attr/colorSurface">
+ tools:context=".ui.AboutActivity">
@@ -78,7 +78,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
- android:text="@string/about_author_names" />
+ android:text="@string/about_author_names"
+ android:gravity="center" />
+ android:background="@drawable/ic_mic_icon_24dp" />
+ android:background="@drawable/ic_play_arrow_icon_24dp"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_checklist_note.xml b/app/src/main/res/layout/activity_checklist_note.xml
index add0c28e..bccbcf1b 100644
--- a/app/src/main/res/layout/activity_checklist_note.xml
+++ b/app/src/main/res/layout/activity_checklist_note.xml
@@ -23,7 +23,9 @@
android:id="@+id/etNewItem"
android:singleLine="true"
android:layout_weight="1"
- android:hint="@string/hint_new_item" />
+ android:hint="@string/hint_new_item"
+ android:textColor="?attr/colorOnBackground"
+ android:inputType="textCapSentences" />
-
+ android:layout_height="match_parent"
+ android:id="@+id/itemList" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 8e69e22f..bf3e097a 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -19,6 +19,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
+ android:background="?attr/colorBackground"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
diff --git a/app/src/main/res/layout/activity_manage_categories.xml b/app/src/main/res/layout/activity_manage_categories.xml
index 45fff6fc..6aff468a 100644
--- a/app/src/main/res/layout/activity_manage_categories.xml
+++ b/app/src/main/res/layout/activity_manage_categories.xml
@@ -1,55 +1,31 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_gravity="bottom|end"
+ android:layout_margin = "@dimen/fab_margin"
+ android:src="@drawable/ic_add_white_24dp"
+ android:contentDescription="@string/category_fab_desc"
+ app:tint="@color/white"
+ app:backgroundTint="@color/colorSecuso"
+ app:borderWidth="0dp" />
+
+
diff --git a/app/src/main/res/layout/activity_sketch.xml b/app/src/main/res/layout/activity_sketch.xml
index f1217573..8ef2f4b0 100644
--- a/app/src/main/res/layout/activity_sketch.xml
+++ b/app/src/main/res/layout/activity_sketch.xml
@@ -1,6 +1,7 @@
-
+ android:layout_height="match_parent"
+ android:id="@+id/sketch_wrapper"
+ android:scrollbarSize="0dp"
+ android:background="?attr/colorSurfaceVariantLight">
+
+
+
diff --git a/app/src/main/res/layout/activity_text_note.xml b/app/src/main/res/layout/activity_text_note.xml
index 7813a7fc..edfcaada 100644
--- a/app/src/main/res/layout/activity_text_note.xml
+++ b/app/src/main/res/layout/activity_text_note.xml
@@ -20,15 +20,15 @@
+ android:orientation="horizontal" />
@@ -47,6 +48,7 @@
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:background="@null"
+ android:backgroundTint="@color/colorAccent"
android:text="@string/skip"
android:textColor="@android:color/white" />
diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml
index 118d5f39..d13cd497 100644
--- a/app/src/main/res/layout/app_bar_main.xml
+++ b/app/src/main/res/layout/app_bar_main.xml
@@ -2,7 +2,6 @@
+ android:layout_height="wrap_content">
-
+ android:layout_height="?attr/actionBarSize" />
-
-
-
-
-
-
-
-
-
-
-
-
+ android:id="@+id/fab_menu_wrapper"
+ android:name="org.secuso.privacyfriendlynotes.ui.fragments.MainFABFragment" />
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index f6e0d5be..0d2b273d 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -13,18 +13,23 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+ android:theme="@style/AppTheme.SearchView"
+ app:iconifiedByDefault="false"
+ app:queryBackground="@color/transparent"
+ android:imeOptions="flagNoExtractUi" />
+ tools:listitem="@layout/note_item"
+ android:paddingBottom="80dp"
+ android:clipToPadding="false" />
diff --git a/app/src/main/res/layout/dialog_create_category.xml b/app/src/main/res/layout/dialog_create_category.xml
new file mode 100644
index 00000000..e8ea622e
--- /dev/null
+++ b/app/src/main/res/layout/dialog_create_category.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_sorting_options.xml b/app/src/main/res/layout/dialog_sorting_options.xml
new file mode 100644
index 00000000..befc3eeb
--- /dev/null
+++ b/app/src/main/res/layout/dialog_sorting_options.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_sorting_options_item.xml b/app/src/main/res/layout/dialog_sorting_options_item.xml
new file mode 100644
index 00000000..9bd9e606
--- /dev/null
+++ b/app/src/main/res/layout/dialog_sorting_options_item.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml
index 62b17a1a..724d003a 100644
--- a/app/src/main/res/layout/item_category.xml
+++ b/app/src/main/res/layout/item_category.xml
@@ -1,23 +1,88 @@
-
-
-
-
+
+
+ android:layout_height="wrap_content">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_checklist.xml b/app/src/main/res/layout/item_checklist.xml
index 9b79cc70..5942d233 100644
--- a/app/src/main/res/layout/item_checklist.xml
+++ b/app/src/main/res/layout/item_checklist.xml
@@ -1,24 +1,31 @@
+ android:layout_height="wrap_content">
-
+ android:inputType="text|textCapSentences"
+ android:padding="10dp"
+ android:background="@null"/>
-
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_content_fab_menu.xml b/app/src/main/res/layout/main_content_fab_menu.xml
new file mode 100644
index 00000000..4cc94e77
--- /dev/null
+++ b/app/src/main/res/layout/main_content_fab_menu.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml
index a514fb20..02a82dcd 100644
--- a/app/src/main/res/layout/nav_header_main.xml
+++ b/app/src/main/res/layout/nav_header_main.xml
@@ -2,37 +2,41 @@
+ android:orientation="horizontal"
+ android:background="?attr/colorNavbarHeaderSurface"
+ android:paddingTop="7dp"
+ android:paddingBottom="7dp">
+ android:layout_toRightOf="@id/imageView"
+ android:textColor="?attr/colorNavbarHeaderText"
+ android:textSize="18sp"/>
diff --git a/app/src/main/res/layout/note_header.xml b/app/src/main/res/layout/note_header.xml
index ae136e43..dd1e848b 100644
--- a/app/src/main/res/layout/note_header.xml
+++ b/app/src/main/res/layout/note_header.xml
@@ -13,12 +13,12 @@
android:layout_gravity="center"
android:layout_marginEnd="10dp"
android:hint="@string/hint_name"
- style="@style/AppTheme.Note.Header.Title">
+ android:theme="@style/AppTheme.NoteHeader.Title">
@@ -28,13 +28,15 @@
android:layout_height="match_parent"
android:hint="@string/action_category"
android:layout_weight="0.3"
- style="@style/AppTheme.Note.Header.Category">
+ android:theme="@style/AppTheme.NoteHeader.Category">
+ android:singleLine="true"
+ android:textColor="?attr/editTextColor"
+ android:scrollbars="vertical" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/note_item.xml b/app/src/main/res/layout/note_item.xml
index 44176335..a0fa7986 100644
--- a/app/src/main/res/layout/note_item.xml
+++ b/app/src/main/res/layout/note_item.xml
@@ -4,24 +4,23 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:layout_marginHorizontal="@dimen/activity_horizontal_margin">
+ android:layout_marginHorizontal="@dimen/activity_horizontal_margin"
+ app:cardBackgroundColor="?attr/colorSurface"
+ app:cardCornerRadius="8dp"
+ >
-
-
-
+ android:textAppearance="@style/TextAppearance.AppCompat.Large"
+ />
-
+
-
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/preference_switch.xml b/app/src/main/res/layout/preference_switch.xml
new file mode 100644
index 00000000..0fb0132b
--- /dev/null
+++ b/app/src/main/res/layout/preference_switch.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/simple_spinner_item.xml b/app/src/main/res/layout/simple_spinner_item.xml
index 05de07a6..64b34102 100644
--- a/app/src/main/res/layout/simple_spinner_item.xml
+++ b/app/src/main/res/layout/simple_spinner_item.xml
@@ -1,10 +1,10 @@
\ No newline at end of file
diff --git a/app/src/main/res/layout/text_note_fab.xml b/app/src/main/res/layout/text_note_fab.xml
index 197d9847..c8d7631d 100644
--- a/app/src/main/res/layout/text_note_fab.xml
+++ b/app/src/main/res/layout/text_note_fab.xml
@@ -11,20 +11,20 @@
android:clipChildren="false"
android:clipToPadding="false">
-
-
-
-
+
+
diff --git a/app/src/main/res/menu/activity_checklist.xml b/app/src/main/res/menu/activity_checklist.xml
new file mode 100644
index 00000000..30b7b0ba
--- /dev/null
+++ b/app/src/main/res/menu/activity_checklist.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
index aa8bd675..a0cbfec1 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -18,7 +18,7 @@
diff --git a/app/src/main/res/menu/activity_sketch.xml b/app/src/main/res/menu/activity_sketch.xml
new file mode 100644
index 00000000..c41bb537
--- /dev/null
+++ b/app/src/main/res/menu/activity_sketch.xml
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_text.xml b/app/src/main/res/menu/activity_text.xml
new file mode 100644
index 00000000..a246712f
--- /dev/null
+++ b/app/src/main/res/menu/activity_text.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/base_note.xml b/app/src/main/res/menu/base_note.xml
index e422f02a..d877873e 100644
--- a/app/src/main/res/menu/base_note.xml
+++ b/app/src/main/res/menu/base_note.xml
@@ -6,13 +6,13 @@
android:orderInCategory="50"
android:title="@string/action_reminder"
android:icon="@drawable/ic_alarm_add_white_24dp"
- app:showAsAction="ifRoom" />
+ app:showAsAction="always" />
+ app:showAsAction="always" />
diff --git a/app/src/main/res/menu/recycle.xml b/app/src/main/res/menu/recycle.xml
index 8f8fb9d9..2b4dfd0d 100644
--- a/app/src/main/res/menu/recycle.xml
+++ b/app/src/main/res/menu/recycle.xml
@@ -5,7 +5,7 @@
android:id="@+id/action_delete_all"
android:orderInCategory="100"
android:title="@string/action_delete_all"
- android:icon="@drawable/ic_delete_forever_white_24dp"
+ android:icon="@drawable/ic_delete_forever_icon_24dp"
app:showAsAction="ifRoom"/>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index ddbc3a6c..d52d029a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -12,7 +12,10 @@
Privacy Friendly Notizen
Speichern
Standard
- Wollen Sie %1s endgültig löschen?
+ Standard
+ Wollen Sie %1s löschen?
+ Wollen Sie %1s endgültig löschen? Alle Notizen der Kategorie werden entgültig gelöscht!
+ Wollen Sie %1s endgültig löschen? Keine Notizen werden gelöscht.
%1s löschen?
Sie müssen mindestens eine Kategorie erstellen. Möchten Sie dies jetzt tun?
Keine Kategorien
@@ -24,10 +27,15 @@
Löschen abgehakter Punkte
Notizen werden in den Papierkorb verschoben. Dort können sie endgültig gelöscht oder wiederhergestellt werden.
In den Papierkorb verschieben
+ Erstelle Kategorie
+ Erstelle
+ Erstelle Kategorie
Audio
Checklist
Skizze
Text
+ Rückgängig
+ Wiederherstellen
Name
Neuer Punkt
Notiz
@@ -68,6 +76,9 @@
Bearbeiten
Exportieren
Kategorie
+ In Checkliste umwandeln
+ In Textnotiz umwandeln
+
Autoren:
Version
Über
@@ -78,6 +89,8 @@
Notiz %d
Hilfe
Wilkommen auf der Hilfe Seite. Mit dieser App können Sie Notizen erstellen und verwalten ohne ihre Privatsphäre zu beeinträchtigen.
+ Färbe Notizen nach Kategorie
+ Nutze eine ausgewählte Farbe pro Kategorie um Notizen nach ihrer Kategorie einzufärben.
Übersicht
Hilfe
Privatsphäre
@@ -91,7 +104,7 @@
Hilfe anzeigen
Mit dieser App ist es möglich Notizen in Form von Text, Checklisten, Memos oder Skizzen zu erstellen und zu teilen.
Willkommen
- Sie verändern den Punkt:
+ Sie verändern den Punkt: %s
Einstellungen
Einstellungen
Allgemein
@@ -99,6 +112,8 @@
Lösche die Notizen einer Kategorie, wenn diese gelöscht wird.
Schriftgröße
Hier klicken um die Schriftgröße auszuwählen.
+ Bestätige Löschung einer Notiz
+ Frage nach Bestätigung bevor eine Notiz in den Papierkorb geschoben wird.
Eigene Schriftgröße
Nutze eine eigene Schriftgröße im Text Notiz Editor.
Skizze
@@ -131,4 +146,25 @@
Notiz nicht gespeichert
Benachrichtigungen aktivieren
Für diese Funktion ist die Berechtigung zum Senden von Benachrichtigungen und zur Planung von Alarmen und Erinnerungen erforderlich.
+ Alle Notizen im Papierkorb löschen
+ Wollen Sie alle momentan sichtbaren Notizen im Papierkorb entgültig löschen?
+
+
+
+
+ - Alphabetisch
+ - Notizart
+ - Erstellungszeit
+ - Zuletzt verändert
+ - Benutzerdefinierte Sortierung
+
+ Aussehen
+ Mit Farbe
+ In Checkliste umwandeln
+ Dadurch wird diese Notiz in eine Checkliste umgewandelt. Jede Zeile muss mit \"- [ ]\" für einen nicht abgehakten Punkt oder mit \"- [x]\" für einen abgehakten Punkt beginnen. Möchten Sie forfahren?
+ Umwandeln
+ In Textnotiz umwandeln
+ Dadurch wird diese Notiz in eine Textnotiz umgewandelt. Möchten Sie fortfahren?
+ Aktiviere die Rückgängig/Wiederherstellen Funktionalität in Skizzen. Diese Funktion kann auf älteren Geräten zu Leistungsproblemen führen.
+ Rückgängig/Wiederherstellen in Skizzen
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 59b0e39d..fb515f0d 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -2,6 +2,7 @@
Notes
Privacy Friendly Notes
デフォルト
+ デフォルト
カテゴリー
テキスト メモ
@@ -46,6 +47,8 @@
キ
+ チェックリストに変換
+ テキストメモに変換
名前
メモ
新しいアイテム
@@ -68,6 +71,8 @@
%1s を削除してもよろしいですか?
ゴミ箱に移動しています
メモはゴミ箱に移動されます。 そこから復元したり永久に削除することができます。 通知は復元できません。
+ %1sを永久に削除しますか?カテゴリのメモはすべて永久削除されます
+ %1sを完全に削除しますか?いいえメモは削除されます。
選択したアイテムを削除しています。
メモを更新または保存する際にチェックしたアイテムは削除されます。
%1s を復元または削除しますか?
@@ -77,13 +82,16 @@
ようこそ
ヘルプを表示
このアプリで、テキスト、チェックリスト、メモ、スケッチの形式でメモを作成および共有することができます。
+ カテゴリー作成
+ 作成
+ カテゴリー作成
通知を有効にする
この機能を使用するには、通知の送信、アラームとリマインダーのスケジュールの許可が必要です。
削除
復元
- アイテムを変更します。
+ アイテムを変更します。 %s
新しいテキスト
@@ -147,5 +155,12 @@
カテゴリ
フォーマッティング
メモが保存されていません
+ チェックリストに変換
+ これにより、このメモがチェックリストに変換されます。チェックされていない項目は\"- [ ]\"で、チェックされている項目は\"- [x]\"で始まります。続けますか?
+ コンバート
+ テキストメモに変換
+ このメモはテキストメモに変換されます。続けますか?
+ スケッチの元に戻す/やり直し機能を有効にします。このオプションは、古いデバイスでパフォーマンスの問題を引き起こす可能性があります。
+ スケッチの元に戻す/やり直し
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..118bd0f1
--- /dev/null
+++ b/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
deleted file mode 100644
index dbbdd40f..00000000
--- a/app/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 6b46c5bb..e7aedde5 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -47,4 +47,21 @@
- 30
+
+ - -1
+ - 1
+ - 2
+
+
+
+
+
+ - @drawable/ic_sort_by_alpha_icon_24dp
+ - @drawable/ic_baseline_edit_note_icon_24dp
+ - @drawable/ic_baseline_access_time_icon_24dp
+ - @drawable/ic_baseline_mode_edit_24dp
+ - @drawable/ic_baseline_sort_icon_24dp
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..de60cc83
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ef80205e..89d1dca6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,8 +1,14 @@
SecUSo colors: <-->
- #024265
+ #024265
#024265
+ #FFFFFF
+ #373a3d
+ #FFFFFF
+ #050a0f
+ #FFFFFF
+ #222222
#0274B2
#00000000
#A8A8A8
@@ -11,6 +17,71 @@
#000000
#8aa5ce
+
+ #024265
+ #006496
+ #FFFFFF
+ #CCE5FF
+ #001E31
+ #006399
+ #FFFFFF
+ #CDE5FF
+ #001D32
+ #006B56
+ #FFFFFF
+ #7EF8D5
+ #002018
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FCFCFF
+ #1A1C1E
+ #FCFCFF
+ #1A1C1E
+ #DEE3EB
+ #42474E
+ #72787E
+ #F0F0F4
+ #2F3133
+ #91CDFF
+ #000000
+ #006496
+ #C2C7CE
+ #000000
+
+ #91CDFF
+ #003350
+ #004B72
+ #CCE5FF
+ #95CCFF
+ #003352
+ #004A75
+ #CDE5FF
+ #5FDBB9
+ #00382C
+ #005140
+ #7EF8D5
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #1A1C1E
+ #E2E2E5
+ #1A1C1E
+ #E2E2E5
+ #42474E
+ #C2C7CE
+ #8C9198
+ #1A1C1E
+ #E2E2E5
+ #006496
+ #000000
+ #91CDFF
+ #42474E
+ #000000
+
+
#B2000000
#e5e5e5
@@ -42,5 +113,4 @@
- @color/white
- @color/black
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 3eac1800..1f31acec 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -14,5 +14,6 @@
16dp
20dp
40dp
+ 12dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 82f783a3..ab8f3fc8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,6 +2,7 @@
Notes
Privacy Friendly Notes
Default
+ Default
Categories
Text Note
@@ -48,7 +49,10 @@
U
Export
Category
-
+ Undo
+ Redo
+ Convert to checklist
+ Convert to text note
Name
Note
@@ -71,6 +75,10 @@
Delete %1s?
Are you sure you want to delete %1s?
+ Are you sure you want to delete %1s? All notes in the category will be deleted!
+ Are you sure you want to delete %1s? No notes will be deleted.
+ Delete all Notes in recycle bin
+ Are you sure you want to delete all currently filtered Notes in the recycle bin?
Moving to recycle bin
Notes will be moved to the recycle bin. You can recover or permanently delete them there. Notifications cannot be restored.
Deleting selected items.
@@ -83,13 +91,16 @@
View Help
With this app you can create and share notes in the form of text, checklists, memos and sketches.
OKAY
+ Create category
+ Create
+ Create category
Enable notifications
Permission to send notifications and schedule alarms and reminders is needed for this feature.
Delete
Restore
- You change the item:
+ You change the item: %s
New text
@@ -104,10 +115,14 @@
General
Delete notes in category
Also delete notes that are in the category that is deleted.
+ Confirmation before deleting note
+ Ask for confirmation before moving a note into the recycling bin.
Custom font size
Use a custom font size in the text note editor.
Font size
Click here to select the font size.
+ Color notes by category
+ Use a custom color per category and display notes colored in their category.
Overview
Welcome to the help page. This app lets you take and manage notes without compromising your privacy.
@@ -121,7 +136,7 @@
Version
Authors:
- Robin Morawetz, Maximilian Zimmermann
+ Robin Morawetz, Maximilian Zimmermann,\n Patrick Schneider
and contributors.
In affiliation with
This application belongs to the group of Privacy Friendly Apps developed by Karlsruhe Institute of Technology (KIT). Sourcecode licensed under GPLv3. Images copyright KIT and Google Inc.
@@ -154,5 +169,29 @@
Formatting
Note not saved
+ Design
+ With Color
+
+ - System
+ - Light
+ - Dark
+
+
+
+
+
+ - Alphabetical
+ - Note Type
+ - Creation Time
+ - Last Modified
+ - Custom Order
+
+ Convert to checklist
+ This will convert this note to a checklist. Every line is expected to start with \"- [ ]\" for an unchecked item or with \"- [x]\" for a checked item. Do you want to continue?
+ Convert
+ Convert to text note
+ This will convert this note into a text note. Do you want to continue?
+ Enable the undo/redo functionality in sketches. This option may lead to performance issues on older devices.
+ Undo/Redo in sketches
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 0ee66388..b8d79855 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,13 +1,68 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/pref_settings.xml b/app/src/main/res/xml/pref_settings.xml
index a4e2cc68..363d440a 100644
--- a/app/src/main/res/xml/pref_settings.xml
+++ b/app/src/main/res/xml/pref_settings.xml
@@ -1,29 +1,64 @@
-
-
-
+
+
-
-
-
+
+
+
+
+
+
+
-
+
-
+
-
-
\ No newline at end of file
+ android:summary="@string/settings_settings_settings_show_preview_sum"
+ app:iconSpaceReserved="false" />
+
+
\ No newline at end of file
diff --git a/backup-api b/backup-api
index 8687a8db..ac8e3a0c 160000
--- a/backup-api
+++ b/backup-api
@@ -1 +1 @@
-Subproject commit 8687a8dbd170866707dc41bcb879375400ef697a
+Subproject commit ac8e3a0ca2df3b212815585bf55be0b187b21e71
diff --git a/build.gradle b/build.gradle
index 656911a4..1001e6e0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@
buildscript {
ext.kotlin_version = "1.8.22"
+ ext.room_version = "2.6.1"
repositories {
mavenCentral()
google()
@@ -10,6 +11,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "androidx.room:androidx.room.gradle.plugin:$room_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle.properties b/gradle.properties
index 10c8f5e7..d293afbe 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,5 @@ android.defaults.buildfeatures.buildconfig=true
android.enableJetifier=true
android.nonFinalResIds=false
android.nonTransitiveRClass=false
-android.useAndroidX=true
\ No newline at end of file
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1024m
\ No newline at end of file