{
+ return ankiNidPattern.findAll(report).mapNotNull { result ->
+ // for an entry like [anki:nid:1234] we should have 4 groups: the entire match(
+ // [anki:nid:1234]) and the three groups we defined: '[anki:nid:', '1234', ']'
+ if (result.groups.size != 4) return@mapNotNull null
+ val matchedPrefix = result.groups[1] ?: return@mapNotNull null
+ val matchedNid = result.groups[2] ?: return@mapNotNull null
+ val nid = matchedNid.value.toLongOrNull() ?: return@mapNotNull null
+ val matchedSuffix = result.groups[3] ?: return@mapNotNull null
+
+ AnkiNidTag(matchedPrefix, matchedNid, nid, matchedSuffix)
+ }
+ }
+ }
+ }
+
+ /**
+ * A specialized [ClickableSpan] that on click will open the [CardBrowser] and initiate a
+ * search with the passed [nid].
+ *
+ * @see CardBrowser
+ */
+ private class BrowserSearchByNidSpan(
+ val context: Context,
+ val nid: Long,
+ ) : ClickableSpan() {
+ override fun onClick(widget: View) {
+ val browserSearchIntent = Intent(context, CardBrowser::class.java)
+ browserSearchIntent.putExtra("search_query", "nid:$nid")
+ browserSearchIntent.putExtra("all_decks", true)
+ context.startActivity(browserSearchIntent)
+ }
}
/**
- * The [WebView] doesn't properly fit the allocated space in the dialog so this method manually
- * updates the [WebView]'s height to a value that fits, depending on orientation and screen size.
+ * The [ScrollView] in this dialog's custom layout doesn't properly fit the allocated height so
+ * this method manually updates the [ScrollView]'s height to a value that fits, depending on
+ * orientation and screen size.
*/
- private fun WebView.updateWebViewHeight() {
+ private fun View.updateViewHeight() {
val currentOrientation = requireContext().resources.configuration.orientation
val targetPercent = if (currentOrientation == ORIENTATION_LANDSCAPE) 0.25 else 0.5
val screenHeight =
@@ -239,14 +292,14 @@ class EmptyCardsDialogFragment : DialogFragment() {
displayMetrics.heightPixels
}
val calculatedHeight = (screenHeight * targetPercent).toInt()
- (layoutParams as ConstraintLayout.LayoutParams).height = calculatedHeight
+ (layoutParams as ViewGroup.LayoutParams).height = calculatedHeight
layoutParams = layoutParams
requestLayout()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- reportWebView?.updateWebViewHeight()
+ reportScrollView?.updateViewHeight()
}
companion object {
diff --git a/AnkiDroid/src/main/res/layout/dialog_empty_cards.xml b/AnkiDroid/src/main/res/layout/dialog_empty_cards.xml
index 8ff232b50028..e89dc101c749 100644
--- a/AnkiDroid/src/main/res/layout/dialog_empty_cards.xml
+++ b/AnkiDroid/src/main/res/layout/dialog_empty_cards.xml
@@ -70,14 +70,21 @@
android:paddingStart="@dimen/side_margin"
android:paddingEnd="@dimen/side_margin">
-
+ android:layout_marginTop="8dp"
+ android:paddingHorizontal="8dp"
+ app:layout_constraintBottom_toTopOf="@+id/preserve_notes">
+
+
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/EmptyCardsReportTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/EmptyCardsReportTest.kt
new file mode 100644
index 000000000000..fe1033a27ccc
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/EmptyCardsReportTest.kt
@@ -0,0 +1,51 @@
+/****************************************************************************************
+ * Copyright (c) 2025 lukstbit <52494258+lukstbit@users.noreply.github.com> *
+ * *
+ * This program 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 (at your option) any later *
+ * version. *
+ * *
+ * This program 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 *
+ * this program. If not, see . *
+ ****************************************************************************************/
+package com.ichi2.anki.dialogs
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.RobolectricTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+class EmptyCardsReportTest : RobolectricTest() {
+ @Test
+ fun `backend report has expected html structure`() =
+ runTest {
+ addStandardNoteType("TestNotetype", arrayOf("f1", "f2"), "", "")
+ val note1 = addNoteUsingNoteTypeName("TestNotetype", "f1text1", "f2text1")
+ val note2 = addNoteUsingNoteTypeName("TestNotetype", "f1text2", "f2text2")
+ val emptyCardsReport = withCol { getEmptyCards() }
+ assertEquals(getExpectedEmptyCardsReport(note1.id, note2.id), emptyCardsReport.report)
+ }
+
+ private fun getExpectedEmptyCardsReport(
+ nid1: Long,
+ nid2: Long,
+ ) =
+ "Empty cards for \u2068TestNotetype\u2069:
- [anki:nid:$nid1] \u20681\u2069 of \u20681\u2069 cards empty (\u2068Card 1\u2069).
- [anki:nid:$nid2] \u20681\u2069 of \u20681\u2069 cards empty (\u2068Card 1\u2069).
"
+
+ @Test
+ fun `backend report is empty when there are no empty cards`() =
+ runTest {
+ addNotes(12)
+ val emptyCardsReport = withCol { getEmptyCards() }
+ assertTrue(emptyCardsReport.report.isEmpty())
+ }
+}