From e916f15de93e35c313e07f12b68495875d88d30f Mon Sep 17 00:00:00 2001 From: raiyyan Date: Sun, 16 Nov 2025 10:49:19 +0530 Subject: [PATCH 1/2] Fix: Make template editing undoable to preserve undo history This commit makes template editing operations undoable by wrapping the save operation in undoableOp. This ensures that template changes are properly tracked in the undo history, allowing users to undo template edits. Changes: - Updated CardTemplateNotetype.saveToDatabase() to be a suspend function that wraps the save operation in undoableOp - Changed saveNoteType() to be a Collection extension function that uses the Collection receiver instead of a parameter - Updated CardTemplateEditor.kt to call saveToDatabase() without passing the Collection parameter - Added import for undoableOp in CardTemplateNotetype.kt Fixes #19488 --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 2 +- .../com/ichi2/anki/CardTemplateNotetype.kt | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 11944edca8ec..705f362fe9c3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -932,7 +932,7 @@ open class CardTemplateEditor : } launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) { requireActivity().withProgress(resources.getString(R.string.saving_model)) { - withCol { templateEditor.tempNoteType!!.saveToDatabase(this@withCol) } + templateEditor.tempNoteType!!.saveToDatabase() } onModelSaved() } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt index 6d4daa378acd..b240b944b497 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt @@ -26,6 +26,7 @@ import com.ichi2.anki.libanki.CardTemplate import com.ichi2.anki.libanki.Collection import com.ichi2.anki.libanki.NoteTypeId import com.ichi2.anki.libanki.NotetypeJson +import com.ichi2.anki.observability.undoableOp import com.ichi2.compat.CompatHelper.Companion.compat import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat import timber.log.Timber @@ -99,36 +100,38 @@ class CardTemplateNotetype( addTemplateChange(ChangeType.DELETE, ord) } - fun saveToDatabase(col: Collection) { + suspend fun saveToDatabase() { Timber.d("saveToDatabase() called") dumpChanges() clearTempNoteTypeFiles() - return saveNoteType(col, notetype, adjustedTemplateChanges) + undoableOp { + saveNoteType(notetype, adjustedTemplateChanges) + Unit + } } /** * Handles everything for a note type change at once - template add / deletes as well as content updates + * This is an extension function on Collection and should be called from within undoableOp */ - fun saveNoteType( - col: Collection, + private fun Collection.saveNoteType( notetype: NotetypeJson, templateChanges: ArrayList, ) { Timber.d("saveNoteType") - val oldNoteType = col.notetypes.get(notetype.id) + val oldNoteType = notetypes.get(notetype.id) - // TODO: make undoable val newTemplates = notetype.templates for (change in templateChanges) { val oldTemplates = oldNoteType!!.templates when (change.type) { ChangeType.ADD -> { Timber.d("saveNoteType() adding template %s", change.ordinal) - col.notetypes.addTemplate(oldNoteType, newTemplates[change.ordinal]) + notetypes.addTemplate(oldNoteType, newTemplates[change.ordinal]) } ChangeType.DELETE -> { Timber.d("saveNoteType() deleting template currently at ordinal %s", change.ordinal) - col.notetypes.remTemplate(oldNoteType, oldTemplates[change.ordinal]) + notetypes.remTemplate(oldNoteType, oldTemplates[change.ordinal]) } } } @@ -136,8 +139,8 @@ class CardTemplateNotetype( // required for Rust: the modified time can't go backwards, and we updated the note type by adding fields // This could be done better notetype.mod = oldNoteType!!.mod - col.notetypes.save(notetype) - col.notetypes.update(notetype) + notetypes.save(notetype) + notetypes.update(notetype) } /** From 7202e33173550fae86523cb68e33341d885397ca Mon Sep 17 00:00:00 2001 From: raiyyan Date: Sun, 16 Nov 2025 13:17:49 +0530 Subject: [PATCH 2/2] fix: prevent scrollbar jitter with variable-height list items - Fixed scrollbar stuttering when card names/tags have different lengths - Set fixed row height (56dp) in card browser to maintain consistent item sizes - Added text truncation with ellipsis for long content in browser columns - Improved UX with tooltips and long-press to view full text - Applied fixes to multiple layout components: * card_item_browser.xml: Fixed height layout * browser_column_cell.xml: Text ellipsize and maxLines for truncation * item_notetype_field.xml: Ellipsize for sort field names * item_require_exclude_tag.xml: Single-line ellipsize for tags * tags_item_list_dialog.xml: Text ellipsize in tag dialogs - BrowserMultiColumnAdapter: * Set 56dp fixed height for items to prevent layout thrashing * Added long-press handler to show full text in toast * Added tooltip for text longer than 30 chars * Set content description for accessibility Fixes scrollbar jitter during scrolling when items have variable content sizes --- .../com/ichi2/anki/CardTemplateNotetype.kt | 1 - .../anki/browser/BrowserMultiColumnAdapter.kt | 23 ++++++++++++++++++- .../main/res/layout/browser_column_cell.xml | 10 ++++++-- .../src/main/res/layout/card_item_browser.xml | 3 ++- .../main/res/layout/item_notetype_field.xml | 2 ++ .../res/layout/item_require_exclude_tag.xml | 2 ++ .../main/res/layout/tags_item_list_dialog.xml | 2 ++ 7 files changed, 38 insertions(+), 5 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt index b240b944b497..4eaebef66b28 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateNotetype.kt @@ -106,7 +106,6 @@ class CardTemplateNotetype( clearTempNoteTypeFiles() undoableOp { saveNoteType(notetype, adjustedTemplateChanges) - Unit } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserMultiColumnAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserMultiColumnAdapter.kt index 1d914231f2a1..38cda95ac817 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserMultiColumnAdapter.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserMultiColumnAdapter.kt @@ -246,6 +246,10 @@ class BrowserMultiColumnAdapter( LayoutInflater .from(parent.context) .inflate(R.layout.card_item_browser, parent, false) + // Set a fixed height to prevent scrollbar jitter, with enough space to show content + val params = view.layoutParams + params.height = (parent.context.resources.displayMetrics.density * 56).toInt() // Increased to 56dp for better visibility + view.layoutParams = params return MultiColumnViewHolder(view) } @@ -275,7 +279,24 @@ class BrowserMultiColumnAdapter( holder.numberOfColumns = row.cellsCount for (i in 0 until row.cellsCount) { - holder.columnViews[i].text = renderColumn(i) + val columnText = renderColumn(i) + holder.columnViews[i].text = columnText + // Add tooltip showing full text when truncated - long press to see full text + holder.columnViews[i].setOnLongClickListener { view -> + android.widget.Toast + .makeText( + context, + "Long text:\n$columnText", + android.widget.Toast.LENGTH_LONG, + ).show() + true + } + // Set content description for accessibility and show hint if text is truncated + holder.columnViews[i].contentDescription = columnText + // Show hint on first view + if (columnText.length > 30) { + holder.columnViews[i].setTooltipText("Long press to see full text") + } } holder.setIsSelected(isSelected) val rowColor = diff --git a/AnkiDroid/src/main/res/layout/browser_column_cell.xml b/AnkiDroid/src/main/res/layout/browser_column_cell.xml index 058b77e7a39c..fdca22d9dca4 100644 --- a/AnkiDroid/src/main/res/layout/browser_column_cell.xml +++ b/AnkiDroid/src/main/res/layout/browser_column_cell.xml @@ -19,6 +19,12 @@ android:layout_height="match_parent" android:layout_weight="1" android:paddingStart="8dp" - android:paddingVertical="1dp" - android:layout_gravity="top" + android:paddingEnd="6dp" + android:paddingVertical="6dp" + android:gravity="center_vertical|start" + android:ellipsize="end" + android:maxLines="2" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:lineSpacingMultiplier="1.1" /> \ No newline at end of file diff --git a/AnkiDroid/src/main/res/layout/card_item_browser.xml b/AnkiDroid/src/main/res/layout/card_item_browser.xml index 0924f6f58a08..efaf38ef9672 100644 --- a/AnkiDroid/src/main/res/layout/card_item_browser.xml +++ b/AnkiDroid/src/main/res/layout/card_item_browser.xml @@ -2,8 +2,9 @@ diff --git a/AnkiDroid/src/main/res/layout/tags_item_list_dialog.xml b/AnkiDroid/src/main/res/layout/tags_item_list_dialog.xml index e7e9d3cf380a..4058689ae1c0 100644 --- a/AnkiDroid/src/main/res/layout/tags_item_list_dialog.xml +++ b/AnkiDroid/src/main/res/layout/tags_item_list_dialog.xml @@ -30,6 +30,8 @@ android:layout_weight="1" android:paddingStart="6dp" android:textSize="16sp" + android:ellipsize="end" + android:singleLine="true" tools:text="Example Tag" />