diff --git a/app/schemas/org.fossify.notes.databases.NotesDatabase/5.json b/app/schemas/org.fossify.notes.databases.NotesDatabase/5.json new file mode 100644 index 000000000..92cf3f337 --- /dev/null +++ b/app/schemas/org.fossify.notes.databases.NotesDatabase/5.json @@ -0,0 +1,146 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "966c9b683bea02221758ffc16bf6247c", + "entities": [ + { + "tableName": "notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT NOT NULL, `value` TEXT NOT NULL, `type` INTEGER NOT NULL, `path` TEXT NOT NULL, `protection_type` INTEGER NOT NULL, `protection_hash` TEXT NOT NULL, `is_read_only` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "protectionType", + "columnName": "protection_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "protectionHash", + "columnName": "protection_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isReadOnly", + "columnName": "is_read_only", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_notes_id", + "unique": true, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_notes_id` ON `${TABLE_NAME}` (`id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `widget_id` INTEGER NOT NULL, `note_id` INTEGER NOT NULL, `widget_bg_color` INTEGER NOT NULL, `widget_text_color` INTEGER NOT NULL, `widget_show_title` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "widgetId", + "columnName": "widget_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "noteId", + "columnName": "note_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "widgetBgColor", + "columnName": "widget_bg_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "widgetTextColor", + "columnName": "widget_text_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "widgetShowTitle", + "columnName": "widget_show_title", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_widgets_widget_id", + "unique": true, + "columnNames": [ + "widget_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_widgets_widget_id` ON `${TABLE_NAME}` (`widget_id`)" + } + ], + "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, '966c9b683bea02221758ffc16bf6247c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/fossify/notes/activities/MainActivity.kt b/app/src/main/kotlin/org/fossify/notes/activities/MainActivity.kt index 4b3a8ad8b..13736daee 100644 --- a/app/src/main/kotlin/org/fossify/notes/activities/MainActivity.kt +++ b/app/src/main/kotlin/org/fossify/notes/activities/MainActivity.kt @@ -213,6 +213,7 @@ class MainActivity : SimpleActivity() { } } + applyReadOnlyStateToCurrentNote() refreshMenuItems() binding.pagerTabStrip.apply { val textSize = getPercentageFontSize() @@ -255,12 +256,12 @@ class MainActivity : SimpleActivity() { binding.mainToolbar.menu.apply { findItem(R.id.undo).apply { - isVisible = showUndoButton && mCurrentNote.type == NoteType.TYPE_TEXT + isVisible = showUndoButton && !mCurrentNote.isReadOnly && mCurrentNote.type == NoteType.TYPE_TEXT icon?.alpha = if (isEnabled) 255 else 127 } findItem(R.id.redo).apply { - isVisible = showRedoButton && mCurrentNote.type == NoteType.TYPE_TEXT + isVisible = showRedoButton && !mCurrentNote.isReadOnly && mCurrentNote.type == NoteType.TYPE_TEXT icon?.alpha = if (isEnabled) 255 else 127 } @@ -281,6 +282,9 @@ class MainActivity : SimpleActivity() { saveNoteButton = findItem(R.id.save_note) saveNoteButton!!.isVisible = !config.autosaveNotes && showSaveButton && (::mCurrentNote.isInitialized && mCurrentNote.type == NoteType.TYPE_TEXT) + + findItem(R.id.preview_mode).isVisible = (::mCurrentNote.isInitialized && !mCurrentNote.isReadOnly && mCurrentNote.type != NoteType.TYPE_CHECKLIST) + findItem(R.id.edit_mode).isVisible = (::mCurrentNote.isInitialized && mCurrentNote.isReadOnly && mCurrentNote.type != NoteType.TYPE_CHECKLIST) } binding.pagerTabStrip.beVisibleIf(multipleNotesExist) @@ -307,6 +311,8 @@ class MainActivity : SimpleActivity() { R.id.cab_create_shortcut -> createShortcut() R.id.lock_note -> lockNote() R.id.unlock_note -> unlockNote() + R.id.preview_mode -> toggleReadOnly() + R.id.edit_mode -> toggleReadOnly() R.id.open_file -> tryOpenFile() R.id.import_folder -> openFolder() R.id.export_as_file -> fragment?.handleUnlocking { tryExportAsFile() } @@ -564,6 +570,7 @@ class MainActivity : SimpleActivity() { onPageChangeListener { mCurrentNote = mNotes[it] config.currentNoteId = mCurrentNote.id!! + applyReadOnlyStateToCurrentNote() refreshMenuItems() } } @@ -571,6 +578,7 @@ class MainActivity : SimpleActivity() { if (!config.showKeyboard || mCurrentNote.type == NoteType.TYPE_CHECKLIST) { hideKeyboard() } + applyReadOnlyStateToCurrentNote() refreshMenuItems() } } @@ -723,6 +731,7 @@ class MainActivity : SimpleActivity() { val index = getNoteIndexWithId(id) binding.viewPager.currentItem = index mCurrentNote = mNotes[index] + applyReadOnlyStateToCurrentNote() } } @@ -747,6 +756,7 @@ class MainActivity : SimpleActivity() { showRedoButton = false initViewPager(newNoteId) updateSelectedNote(newNoteId) + applyReadOnlyStateToCurrentNote() binding.viewPager.onGlobalLayout { mAdapter?.focusEditText(getNoteIndexWithId(newNoteId)) } @@ -1561,4 +1571,20 @@ class MainActivity : SimpleActivity() { updateWidgets() } } + + private fun applyReadOnlyStateToCurrentNote() { + getCurrentFragment()?.let { fragment -> + if (fragment is TextFragment) { + fragment.updateReadOnlyState(mCurrentNote.isReadOnly) + } + } + } + + private fun toggleReadOnly() { + mCurrentNote.isReadOnly = !mCurrentNote.isReadOnly + NotesHelper(this).insertOrUpdateNote(mCurrentNote) { + applyReadOnlyStateToCurrentNote() + refreshMenuItems() + } + } } diff --git a/app/src/main/kotlin/org/fossify/notes/databases/NotesDatabase.kt b/app/src/main/kotlin/org/fossify/notes/databases/NotesDatabase.kt index 9e7aa1a65..4f85125ff 100644 --- a/app/src/main/kotlin/org/fossify/notes/databases/NotesDatabase.kt +++ b/app/src/main/kotlin/org/fossify/notes/databases/NotesDatabase.kt @@ -16,7 +16,7 @@ import org.fossify.notes.models.NoteType import org.fossify.notes.models.Widget import java.util.concurrent.Executors -@Database(entities = [Note::class, Widget::class], version = 4) +@Database(entities = [Note::class, Widget::class], version = 5) abstract class NotesDatabase : RoomDatabase() { abstract fun NotesDao(): NotesDao @@ -42,6 +42,7 @@ abstract class NotesDatabase : RoomDatabase() { .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) .build() db!!.openHelper.setWriteAheadLoggingEnabled(true) } @@ -85,5 +86,11 @@ abstract class NotesDatabase : RoomDatabase() { database.execSQL("ALTER TABLE widgets ADD COLUMN widget_show_title INTEGER NOT NULL DEFAULT 0") } } + + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE notes ADD COLUMN is_read_only INTEGER NOT NULL DEFAULT 0") + } + } } } diff --git a/app/src/main/kotlin/org/fossify/notes/fragments/TextFragment.kt b/app/src/main/kotlin/org/fossify/notes/fragments/TextFragment.kt index bf7ebcdd8..deac0bfb4 100644 --- a/app/src/main/kotlin/org/fossify/notes/fragments/TextFragment.kt +++ b/app/src/main/kotlin/org/fossify/notes/fragments/TextFragment.kt @@ -7,8 +7,10 @@ import android.os.Bundle import android.text.Editable import android.text.Selection import android.text.TextWatcher +import android.text.method.KeyListener import android.text.style.UnderlineSpan import android.text.util.Linkify +import android.text.InputType import android.util.TypedValue import android.view.LayoutInflater import android.view.MotionEvent @@ -21,6 +23,7 @@ import android.widget.TextView import androidx.viewbinding.ViewBinding import org.fossify.commons.extensions.* import org.fossify.commons.views.MyEditText +import org.fossify.commons.extensions.hideKeyboard import org.fossify.notes.R import org.fossify.notes.activities.MainActivity import org.fossify.notes.databinding.FragmentTextBinding @@ -47,6 +50,7 @@ class TextFragment : NoteFragment() { private var noteId = 0L private var touchDownX = 0f private var moveXThreshold = 0 // make sure swiping across notes works well, do not swallow the gestures + private var initialKeyListener: KeyListener? = null private lateinit var binding: FragmentTextBinding private lateinit var innerBinding: ViewBinding @@ -81,17 +85,17 @@ class TextFragment : NoteFragment() { casted.textNoteView.minWidth = casted.notesHorizontalScrollview.width } } - + initialKeyListener = noteEditText.keyListener return binding.root } override fun onResume() { super.onResume() - NotesHelper(requireActivity()).getNoteWithId(noteId) { if (it != null) { note = it setupFragment() + updateReadOnlyState(note!!.isReadOnly) } } } @@ -123,6 +127,7 @@ class TextFragment : NoteFragment() { super.onSaveInstanceState(outState) if (note != null) { outState.putString(TEXT, getCurrentNoteViewText()) + outState.putBoolean("isReadOnly", note!!.isReadOnly) } } @@ -132,6 +137,8 @@ class TextFragment : NoteFragment() { skipTextUpdating = true val newText = savedInstanceState.getString(TEXT) ?: "" innerBinding.root.findViewById(R.id.text_note_view).text = newText + note!!.isReadOnly = savedInstanceState.getBoolean("isReadOnly") + updateReadOnlyState(note!!.isReadOnly) } } @@ -178,6 +185,7 @@ class TextFragment : NoteFragment() { } else { imeOptions.removeBit(EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING) } + updateReadOnlyState(note!!.isReadOnly) } noteEditText.setOnTouchListener { v, event -> @@ -198,6 +206,7 @@ class TextFragment : NoteFragment() { setWordCounter(noteEditText.text.toString()) } + updateReadOnlyState(note!!.isReadOnly) checkLockState() setTextWatcher() } @@ -351,4 +360,22 @@ class TextFragment : NoteFragment() { override val noteLockedShow: TextView = it.noteLockedShow } } + + fun updateReadOnlyState(isReadOnly: Boolean) { + noteEditText.apply { + val baseInputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE + if (isReadOnly == true) { + inputType = baseInputType or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + keyListener = null + requireActivity().hideKeyboard(this) + } + if (isReadOnly == false) { + inputType = baseInputType + keyListener = initialKeyListener + } + isLongClickable = true + setTextIsSelectable(true) + saveText(force = true) + } + } } diff --git a/app/src/main/kotlin/org/fossify/notes/models/Note.kt b/app/src/main/kotlin/org/fossify/notes/models/Note.kt index 0aecc47b2..f467e4649 100644 --- a/app/src/main/kotlin/org/fossify/notes/models/Note.kt +++ b/app/src/main/kotlin/org/fossify/notes/models/Note.kt @@ -25,7 +25,8 @@ data class Note( @ColumnInfo(name = "type") var type: NoteType, @ColumnInfo(name = "path") var path: String, @ColumnInfo(name = "protection_type") var protectionType: Int, - @ColumnInfo(name = "protection_hash") var protectionHash: String + @ColumnInfo(name = "protection_hash") var protectionHash: String, + @ColumnInfo(name = "is_read_only") var isReadOnly: Boolean = false ) { fun getNoteStoredValue(context: Context): String? { diff --git a/app/src/main/res/drawable/ic_visibility_off_vector.xml b/app/src/main/res/drawable/ic_visibility_off_vector.xml new file mode 100644 index 000000000..8fc6602ed --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off_vector.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/drawable/ic_visibility_vector.xml b/app/src/main/res/drawable/ic_visibility_vector.xml new file mode 100644 index 000000000..a0ef2a1e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_vector.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/menu.xml index b9b161f30..c6025fc2f 100644 --- a/app/src/main/res/menu/menu.xml +++ b/app/src/main/res/menu/menu.xml @@ -21,6 +21,16 @@ android:icon="@drawable/ic_plus_vector" android:title="@string/create_new_note" app:showAsAction="always" /> + + New checklist The app cannot load files over the internet To backup notes automatically, please grant the app permission to schedule exact alarms. + Switch to preview mode + Switch to edit mode Open file