Move SSD load failure statuses to typed Kotlin

ober

3b4b386d512f5f87247e760a9e0800cebf29875e

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index e828a88..0613dd1 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3742,6 +3742,13 @@
                 pushTruthStatus pushedTruthStatus
                 exportedTruthZipStatus importedTruthZipStatus
                 syncTruthFolderStatus syncedTruthFolderStatus
+                renderedLoadingTruthStatus renderFailedStatus
+                detectFailedStatus saveFailedStatus
+                truthPullFailedStatus truthPushFailedStatus
+                ssdBrowserFailedStatus folderSyncFailedStatus
+                hashingPdfStatus loadedPdfStatus openFailedStatus
+                appendDialogTitle appendCanceledNoGroupStatus
+                appendCanceledStatus
                 clampPageIndex shiftPageIndex displayGroupCanvasLabel
                 dropdownHeight dialogPageNumber nullableLowercaseOrEmpty
                 selectModeStatus selectionResultStatus detectStartStatus
@@ -4025,6 +4032,70 @@
                 (string-append
                   (int32->string exported)
                   (string-append "; " summary))))))
+        (def (nullableMessageText (message : (Nullable String))) : String
+          (if (nullable-null? message) "" (nullable-get message)))
+        (def (failureStatus (prefix : String)
+                            (message : (Nullable String))
+                            (suffix : String)) : String
+          (string-append
+            prefix
+            (string-append (nullableMessageText message) suffix)))
+        (def (renderedLoadingTruthStatus (pdfName : String)
+                                         (pageIndex : Int32)
+                                         (pageCount : Int32)) : String
+          (string-append
+            "Rendered "
+            (string-append
+              pdfName
+              (string-append
+                " page "
+                (string-append
+                  (int32->string (+ pageIndex (int32 1)))
+                  (string-append
+                    "/"
+                    (string-append
+                      (int32->string pageCount)
+                      "; loading truth")))))))
+        (def (renderFailedStatus (message : (Nullable String))) : String
+          (failureStatus "Render failed: " message ""))
+        (def (detectFailedStatus (message : (Nullable String))) : String
+          (failureStatus "Detect failed: " message ""))
+        (def (saveFailedStatus (message : (Nullable String))) : String
+          (failureStatus "Save failed: " message ""))
+        (def (truthPullFailedStatus (message : (Nullable String))) : String
+          (failureStatus
+            "Truth pull failed: "
+            message
+            ". Check Truth Server or use Sync Folder."))
+        (def (truthPushFailedStatus (message : (Nullable String))) : String
+          (failureStatus
+            "Truth push failed: "
+            message
+            ". Check Truth Server or use Sync Folder."))
+        (def (ssdBrowserFailedStatus (message : (Nullable String))) : String
+          (failureStatus "SSD PDF browser failed: " message ""))
+        (def (folderSyncFailedStatus (message : (Nullable String))) : String
+          (failureStatus "Folder sync failed: " message ""))
+        (def (hashingPdfStatus (pdfName : String)) : String
+          (string-append "Hashing " (string-append pdfName "...")))
+        (def (loadedPdfStatus (pdfName : String) (pageCount : Int32)) : String
+          (string-append
+            "Loaded "
+            (string-append
+              pdfName
+              (string-append
+                " ("
+                (string-append
+                  (int32->string pageCount)
+                  " pages)")))))
+        (def (openFailedStatus (message : (Nullable String))) : String
+          (failureStatus "Open failed: " message ""))
+        (def (appendDialogTitle (group : SsdGroup)) : String
+          (string-append "Append to " (groupDisplayLabel group)))
+        (def (appendCanceledNoGroupStatus) : String
+          "Append canceled: no selected group")
+        (def (appendCanceledStatus) : String
+          "Append canceled")
         (def (dropdownHeight (screenHeight : Int32)) : Int32
           (let ((third (/ screenHeight (int32 3))))
             (if (> third (int32 620))
@@ -5569,7 +5640,7 @@
 	       "                    reviewView.selectedGroupId = null"
 	       "                    reviewView.ssdArea = null"
        "                    restoreViewportAfterLayout(restoredViewport)"
-	       "                    setStatus(\"Rendered $renderPdfName page ${renderPageIndex + 1}/$renderPageCount; loading truth\")"
+	       "                    setStatus(renderedLoadingTruthStatus(renderPdfName, renderPageIndex, renderPageCount))"
        "                    detectCurrent("
        "                        expectedGeneration = renderGeneration,"
        "                        expectedPageIndex = renderPageIndex,"
@@ -5582,7 +5653,7 @@
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
        "                    if (isCurrentLoad(renderGeneration, uri, renderPageIndex)) {"
-       "                        setStatus(\"Render failed: ${error.message}\")"
+       "                        setStatus(renderFailedStatus(error.message))"
        "                    }"
        "                }"
        "            }"
@@ -5695,7 +5766,7 @@
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
        "                    if (isCurrentLoad(expectedGeneration, uri, expectedPageIndex)) {"
-       "                        setStatus(\"Detect failed: ${error.message}\")"
+       "                        setStatus(detectFailedStatus(error.message))"
        "                        logClientEvent("
        "                            \"ssd-detect-failed\","
        "                            detectFailedLogFields("
@@ -5821,7 +5892,7 @@
        "        if (group == null) {"
        "            appendMode = false"
        "            reviewView.selectMode = false"
-       "            setStatus(\"Append canceled: no selected group\")"
+       "            setStatus(appendCanceledNoGroupStatus())"
        "            return"
        "        }"
        "        val count = EditText(this).apply {"
@@ -5829,7 +5900,7 @@
        "            inputType = InputType.TYPE_CLASS_NUMBER"
        "            setText(detectedIdsCountText(detectedIds.size))"
        "        }"
-       "        val title = \"Append to ${groupDisplayLabel(group)}\""
+       "        val title = appendDialogTitle(group)"
        "        val dialog = AlertDialog.Builder(this)"
        "            .setTitle(title)"
        "            .setMessage(detectedBoxesMessage(detectedIds.size))"
@@ -5837,7 +5908,7 @@
        "            .setNegativeButton(\"Cancel\") { _, _ ->"
        "                appendMode = false"
        "                reviewView.selectMode = false"
-       "                setStatus(\"Append canceled\")"
+       "                setStatus(appendCanceledStatus())"
        "            }"
        "            .setPositiveButton(\"Append\", null)"
        "            .show()"
@@ -5871,7 +5942,7 @@
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
        "                    restoreViewportAfterLayout(viewport)"
-       "                    setStatus(\"Save failed: ${error.message}\")"
+       "                    setStatus(saveFailedStatus(error.message))"
        "                }"
        "            }"
        "        }"
@@ -6084,7 +6155,7 @@
        "                }"
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
-	       "                    setStatus(\"Truth pull failed: ${error.message}. Check Truth Server or use Sync Folder.\")"
+	       "                    setStatus(truthPullFailedStatus(error.message))"
        "                }"
        "            }"
        "        }"
@@ -6101,7 +6172,7 @@
        "                }"
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
-	       "                    setStatus(\"Truth push failed: ${error.message}. Check Truth Server or use Sync Folder.\")"
+	       "                    setStatus(truthPushFailedStatus(error.message))"
        "                }"
        "            }"
        "        }"
@@ -6174,7 +6245,7 @@
        "                }"
        "            } catch (error: Exception) {"
        "                runOnUiThread {"
-       "                    setStatus(\"SSD PDF browser failed: ${error.message}\")"
+       "                    setStatus(ssdBrowserFailedStatus(error.message))"
        "                    chooseSsdPdfFolder()"
        "                }"
        "            }"
@@ -6340,7 +6411,7 @@
        "                    setStatus(syncedTruthFolderStatus(imported, exported, truthStore.storageSummary()))"
        "                }"
        "            } catch (error: Exception) {"
-       "                runOnUiThread { setStatus(\"Folder sync failed: ${error.message}\") }"
+       "                runOnUiThread { setStatus(folderSyncFailedStatus(error.message)) }"
        "            }"
        "        }"
        "    }"
@@ -6356,7 +6427,7 @@
        "            .edit()"
        "            .putString(PREF_LAST_PDF_URI, uri.toString())"
        "            .apply()"
-       "        setStatus(\"Hashing $pdfName...\")"
+       "        setStatus(hashingPdfStatus(pdfName))"
        "        thread {"
        "            try {"
        "                val hash = contentResolver.openInputStream(uri)?.use { sha1Hex(it) } ?: \"\""
@@ -6366,11 +6437,11 @@
        "                    pageCount = count"
        "                    pageIndex = clampPageIndex(initialPageIndex, count)"
        "                    if (!restoring) persistCurrentPdfState(includeViewport = false)"
-       "                    setStatus(\"Loaded $pdfName ($pageCount pages)\")"
+       "                    setStatus(loadedPdfStatus(pdfName, pageCount))"
        "                    renderCurrentPage()"
        "                }"
        "            } catch (error: Exception) {"
-       "                runOnUiThread { setStatus(\"Open failed: ${error.message}\") }"
+       "                runOnUiThread { setStatus(openFailedStatus(error.message)) }"
        "            }"
        "        }"
        "    }"