Move SSD nullable session checks to typed Kotlin

ober

3793609a9623abfc9c8f3931bc102e22bdad7da5

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 6fce4db..44a8e16 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -543,6 +543,7 @@
                 ssdSessionFindGroupById ssdSessionSelectedGroup
                 ssdSessionSelectedGroupId ssdSessionRelativeGroupId
                 ssdSessionCellIdsForGroupId
+                ssdSessionGroupPresent ssdSessionGroupMissing
                 ssdSessionGroupContainsCellId
                 ssdSessionCellIdSet
                 ssdSessionFindCellById ssdSessionCellsByIds
@@ -819,6 +820,15 @@
             (if (nullable-null? group)
               (mutable-list-empty String)
               (SsdGroup-cellIds (nullable-get group)))))
+        (def (ssdSessionGroupPresent (session : SsdSession)
+                                     (group : SsdGroup)) : Bool
+          (not (nullable-null?
+                 (ssdSessionFindGroupById
+                   session
+                   (nullable-some (SsdGroup-id group))))))
+        (def (ssdSessionGroupMissing (session : SsdSession)
+                                     (group : SsdGroup)) : Bool
+          (not (ssdSessionGroupPresent session group)))
         (def (ssdSessionGroupContainsCellId (session : SsdSession)
                                             (groupId : (Nullable String))
                                             (cellId : String)) : Bool
@@ -4239,13 +4249,14 @@
                 selectionIgnoredLoadingStatus
                 shouldShowFiringArc shouldFocusFiringArc
                 canvasLabelPresent canvasLabelMissing groupIdSelected
-                shouldCaptureViewport
+                shouldCaptureViewport pdfUriLoaded sessionMissing
                 hasReplacementLabel missingReplacementLabel hasResolvedLabel
                 ssdBrowserResumeReady focusChangeShouldShowDropdown
                 dialogReadyToShowDropdown focusedDialogReadyToShowDropdown
                 permissionResultGranted downloadAccessBeforeRuntimePermissions
                 downloadAccessFromPermission activityResultRejected
-                pageNavigationBlocked selectModeDisabled ssdAreaModeInactive
+                pageNavigationBlocked pageNavigationBlockedForUri
+                selectModeDisabled ssdAreaModeInactive
                 shouldPersistLoadedPdfState viewportScalePreferencePresent
                 viewportScalePreferenceMissing
                 lastPdfUriMismatch lastViewportPageMismatch
@@ -4284,6 +4295,7 @@
         (type Uri)
         (type DocumentFile)
         (type JSONObject)
+        (type SsdSession)
         (extern (fileIsFile (file : File)) : Bool
           (kotlin-member-get isFile))
         (extern (fileName (file : File)) : String
@@ -4724,6 +4736,10 @@
             (equal? groupId (nullable-get selectedGroupId))))
         (def (shouldCaptureViewport (includeViewport : Bool)) : Bool
           includeViewport)
+        (def (pdfUriLoaded (uri : (Nullable Uri))) : Bool
+          (not (nullable-null? uri)))
+        (def (sessionMissing (session : (Nullable SsdSession))) : Bool
+          (nullable-null? session))
         (def (pdfLoaded (hasPdf : Bool)) : Bool
           hasPdf)
         (def (pageNavigationReady (hasPdf : Bool) (pageCount : Int32)) : Bool
@@ -4736,6 +4752,9 @@
           (not (string-blank? resolved)))
         (def (pageNavigationBlocked (hasPdf : Bool) (pageCount : Int32)) : Bool
           (not (pageNavigationReady hasPdf pageCount)))
+        (def (pageNavigationBlockedForUri (uri : (Nullable Uri))
+                                          (pageCount : Int32)) : Bool
+          (pageNavigationBlocked (pdfUriLoaded uri) pageCount))
         (def (selectModeDisabled (enabled : Bool)) : Bool
           (not enabled))
         (def (ssdAreaModeInactive (enabled : Bool)) : Bool
@@ -6051,7 +6070,7 @@
        "            if (positiveCount(imported)) {"
        "                runOnUiThread {"
        "                    setStatus(truthSeedMergedStatus(imported, truthStore.storageSummary()))"
-       "                    if (pdfLoaded(pdfUri != null)) renderCurrentPage()"
+       "                    if (pdfUriLoaded(pdfUri)) renderCurrentPage()"
        "                }"
        "            }"
        "        }"
@@ -6329,7 +6348,7 @@
        "    }"
        ""
        "    private fun changePage(delta: Int) {"
-       "        if (pageNavigationBlocked(pdfUri != null, pageCount)) return"
+       "        if (pageNavigationBlockedForUri(pdfUri, pageCount)) return"
        "        pageIndex = shiftPageIndex(pageIndex, pageCount, delta)"
        "        persistCurrentPdfState(includeViewport = false)"
        "        renderCurrentPage()"
@@ -6400,7 +6419,7 @@
        "    }"
        ""
        "    private fun showPageDialog() {"
-       "        if (pageNavigationBlocked(pdfUri != null, pageCount)) return"
+       "        if (pageNavigationBlockedForUri(pdfUri, pageCount)) return"
        "        val input = EditText(this).apply {"
        "            inputType = InputType.TYPE_CLASS_NUMBER"
        "            setText(pageNumberText(pageIndex))"
@@ -6690,7 +6709,7 @@
 	       "        currentLoadStale(generation, loadGeneration, pdfUri, uri, pageIndex, page)"
 	       ""
 	       "    private fun handleAreaSelected(rect: FloatArray, detectedIds: List<String>) {"
-	       "        if (currentSession == null) {"
+	       "        if (sessionMissing(currentSession)) {"
 	       "            setStatus(selectionIgnoredLoadingStatus())"
 	       "            return"
 	       "        }"
@@ -6698,7 +6717,7 @@
 	       "        val statusText = areaSelectionStatus(applyAsSsdArea, detectedIds.size)"
 	       "        setStatus(statusText)"
 	       "        reviewView.postDelayed({"
-	       "            if (currentSession == null) {"
+	       "            if (sessionMissing(currentSession)) {"
 	       "                setStatus(\"Selection ignored: truth/detection is still loading\")"
 	       "                return@postDelayed"
 	       "            }"
@@ -6862,7 +6881,7 @@
        ""
        "    private fun appendAreaToGroup(rect: FloatArray, detectedIds: List<String>, group: SsdGroup, count: Int) {"
        "        val session = currentSession ?: return"
-       "        if (!session.groups.contains(group)) return"
+       "        if (ssdSessionGroupMissing(session, group)) return"
        "        val ids = selectedOrManualCellIds(session, rect, detectedIds, count)"
        "        val added = appendUniqueCellIdsToGroup(group, ids)"
        "        ssdSessionRecomputeGroup(session, group)"
@@ -7017,7 +7036,7 @@
        "                runOnUiThread {"
        "                    Toast.makeText(this, \"Truth pull complete\", Toast.LENGTH_SHORT).show()"
        "                    setStatus(pulledTruthStatus(imported, truthStore.storageSummary()))"
-       "                    if (pdfLoaded(pdfUri != null)) renderCurrentPage()"
+       "                    if (pdfUriLoaded(pdfUri)) renderCurrentPage()"
        "                }"
        "            } catch (error: Exception) {"
        "                runOnUiThread {"