Move SSD dialog count parsing to typed Kotlin

ober

9ee78b881db8f8fd72b9340a659e00e327962766

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 3b2ac5a..806ba11 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3753,6 +3753,8 @@
                 noSsdPdfsInFolderMessage noSsdPdfsChooseFolderStatus
                 openSsdPdfTitle noSsdPdfsDialogMessage
                 noSsdPdfsFoundStatus
+                dialogCountValue editedGroupCountText
+                selectionIgnoredLoadingStatus
                 clampPageIndex shiftPageIndex displayGroupCanvasLabel
                 dropdownHeight dialogPageNumber nullableLowercaseOrEmpty
                 selectModeStatus selectionResultStatus detectStartStatus
@@ -4116,6 +4118,15 @@
             "No SSD PDFs were found through Android's Downloads index. Grant Downloads access for a direct scan, choose a folder, or open a PDF manually."))
         (def (noSsdPdfsFoundStatus) : String
           "No SSD PDFs found")
+        (def (parsedCountOrFallback (text : String) (fallback : Int32)) : Int32
+          (let ((parsed (string->int32-or-null text)))
+            (if (nullable-null? parsed) fallback (nullable-get parsed))))
+        (def (dialogCountValue (text : String)) : Int32
+          (parsedCountOrFallback text (int32 1)))
+        (def (editedGroupCountText (text : String) (fallback : Int32)) : Int32
+          (parsedCountOrFallback text fallback))
+        (def (selectionIgnoredLoadingStatus) : String
+          "Selection ignored: truth/detection is still loading")
         (def (dropdownHeight (screenHeight : Int32)) : Int32
           (let ((third (/ screenHeight (int32 3))))
             (if (> third (int32 620))
@@ -5836,7 +5847,7 @@
 	       ""
 	       "    private fun handleAreaSelected(rect: FloatArray, detectedIds: List<String>) {"
 	       "        if (currentSession == null) {"
-	       "            setStatus(\"Selection ignored: truth/detection is still loading\")"
+	       "            setStatus(selectionIgnoredLoadingStatus())"
 	       "            return"
 	       "        }"
 	       "        val applyAsSsdArea = ssdAreaMode"
@@ -5897,7 +5908,7 @@
        "            val boxTypeText = boxType.text.toString()"
        "            val labelText = resolvedLabelForBoxTypeAndroid(this, label.text.toString(), boxTypeText, \"\")"
        "            val firingArcText = firingArc.text.toString()"
-       "            val countValue = count.text.toString().toIntOrNull() ?: 1"
+       "            val countValue = dialogCountValue(count.text.toString())"
        "            boxType.dismissDropDown()"
        "            firingArc.dismissDropDown()"
        "            dialog.dismiss()"
@@ -5933,7 +5944,7 @@
        "            .setPositiveButton(\"Append\", null)"
        "            .show()"
        "        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {"
-       "            val countValue = count.text.toString().toIntOrNull() ?: 1"
+       "            val countValue = dialogCountValue(count.text.toString())"
        "            dialog.dismiss()"
        "            appendAreaToGroup(rect, detectedIds, group, countValue)"
        "        }"
@@ -6075,7 +6086,6 @@
        "            val boxTypeText = boxType.text.toString()"
        "            val labelText = resolvedLabelForBoxTypeAndroid(this, label.text.toString(), boxTypeText, group.boxTypeId)"
        "            val firingArcText = firingArc.text.toString()"
-       "            val countValue = count.text.toString().toIntOrNull()"
        "            boxType.dismissDropDown()"
        "            firingArc.dismissDropDown()"
        "            dialog.dismiss()"
@@ -6083,7 +6093,7 @@
        "            group.label = manualGroupLabel(labelText, resolved?.name, group.label)"
        "            group.boxTypeId = resolved?.id ?: boxTypeText"
        "            group.firingArc = normalizeFiringArc(firingArcText)"
-       "            group.count = editedGroupCount(countValue, group.count)"
+       "            group.count = editedGroupCountText(count.text.toString(), group.count)"
        "            group.status = \"reviewed\""
        "            ssdSessionRecomputeGroup(session, group)"
        "            truthStore.appendEvent(\"group_edited\", session)"