Move SSD edit actions to typed Kotlin

ober

7e51ca0b7dc0e91987fde6bfac64d1ac43a780fc

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 722b8db..5bbe7a2 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -119,7 +119,7 @@
     (typed-kotlin-file "com/sfb/ssdreview/BoxTypeAndroid.kt"
       (kotlin-imports (android content Context))
       (typed-library (com sfb ssdreview)
-        (export boxTypeRawIsWeapon)
+        (export boxTypeRawIsWeapon resolvedBoxTypeNameOr resolvedBoxTypeIdOr)
         (type Context)
         (extern (boxTypesResolve (context : Context) (raw : String)) : (Nullable BoxType)
           (kotlin-call BoxTypes resolve))
@@ -127,6 +127,10 @@
           (if (nullable-null? boxType)
               fallback
               (BoxType-name (nullable-get boxType))))
+        (def (resolvedBoxTypeIdOr (boxType : (Nullable BoxType)) (fallback : String)) : String
+          (if (nullable-null? boxType)
+              fallback
+              (BoxType-id (nullable-get boxType))))
         (def (boxTypeRawIsWeapon (context : Context) (raw : String)) : Bool
           (let ((text (string-trim raw)))
             (if (string-blank? text)
@@ -6994,14 +6998,15 @@
        "        firingArc: String,"
        "        count: Int"
        "    ) {"
-       "        val session = currentSession ?: return"
+       "        if (sessionMissing(currentSession)) return"
+       "        val session = ssdSessionOrEmpty(currentSession)"
        "        val resolvedType = BoxTypes.resolve(this, boxType)"
-       "        val cleanLabel = manualGroupLabel(resolvedLabelForBoxTypeAndroid(this, label, boxType, \"\"), resolvedType?.name, \"\")"
+       "        val cleanLabel = manualGroupLabel(resolvedLabelForBoxTypeAndroid(this, label, boxType, \"\"), resolvedBoxTypeNameOr(resolvedType, \"\"), \"\")"
        "        val ids = selectedOrManualCellIds(session, rect, detectedIds, count)"
        "        val group = SsdGroup("
        "            id = ssdSessionNextGroupId(session),"
        "            label = cleanLabel,"
-       "            boxTypeId = resolvedType?.id ?: boxType,"
+       "            boxTypeId = resolvedBoxTypeIdOr(resolvedType, boxType),"
        "            status = \"reviewed\","
        "            count = ids.size,"
        "            bbox = ssdSessionBBoxForCellIds(session, ids),"
@@ -7024,7 +7029,8 @@
        "    }"
        ""
        "    private fun appendAreaToGroup(rect: FloatArray, detectedIds: List<String>, group: SsdGroup, count: Int) {"
-       "        val session = currentSession ?: return"
+       "        if (sessionMissing(currentSession)) return"
+       "        val session = ssdSessionOrEmpty(currentSession)"
        "        if (ssdSessionGroupMissing(session, group)) return"
        "        val ids = selectedOrManualCellIds(session, rect, detectedIds, count)"
        "        val added = appendUniqueCellIdsToGroup(group, ids)"
@@ -7045,7 +7051,8 @@
        "    }"
        ""
        "    private fun showGroupEditDialog(group: SsdGroup) {"
-       "        val session = currentSession ?: return"
+       "        if (sessionMissing(currentSession)) return"
+       "        val session = ssdSessionOrEmpty(currentSession)"
        "        val layout = LinearLayout(this).apply {"
        "            orientation = LinearLayout.VERTICAL"
        "            setPadding(32, 16, 32, 8)"
@@ -7089,8 +7096,8 @@
        "            firingArc.dismissDropDown()"
        "            dialog.dismiss()"
        "            val resolved = BoxTypes.resolve(this, boxTypeText)"
-       "            group.label = manualGroupLabel(labelText, resolved?.name, group.label)"
-       "            group.boxTypeId = resolved?.id ?: boxTypeText"
+       "            group.label = manualGroupLabel(labelText, resolvedBoxTypeNameOr(resolved, group.label), group.label)"
+       "            group.boxTypeId = resolvedBoxTypeIdOr(resolved, boxTypeText)"
        "            group.firingArc = normalizeFiringArc(firingArcText)"
        "            group.count = editedGroupCountText(count.text.toString(), group.count)"
        "            group.status = \"reviewed\""
@@ -7115,7 +7122,8 @@
        "    }"
        ""
        "    private fun saveTruth() {"
-       "        val session = currentSession ?: return"
+       "        if (sessionMissing(currentSession)) return"
+       "        val session = ssdSessionOrEmpty(currentSession)"
        "        saveSessionAsync("
        "            session,"
        "            saveTruthSavingStatus(),"
@@ -7124,12 +7132,15 @@
        "    }"
        ""
        "    private fun deleteSelectedGroup() {"
-       "        val session = currentSession ?: return"
-       "        val group = selectedGroup() ?: return"
-       "        session.groups.remove(group)"
-       "        group.status = \"suppressed\""
-       "        group.notes = \"Deleted on Android\""
-       "        session.suppressedGroups.add(group)"
+       "        if (sessionMissing(currentSession)) return"
+       "        val session = ssdSessionOrEmpty(currentSession)"
+       "        val group = selectedGroup()"
+       "        if (ssdGroupMissing(group)) return"
+       "        val deletedGroup = ssdGroupOrEmpty(group)"
+       "        session.groups.remove(deletedGroup)"
+       "        deletedGroup.status = \"suppressed\""
+       "        deletedGroup.notes = \"Deleted on Android\""
+       "        session.suppressedGroups.add(deletedGroup)"
        "        truthStore.appendEvent(\"group_deleted\", session)"
 	       "        selectedGroupId = ssdSessionSelectedGroupId(session, null)"
        "        reviewView.selectedGroupId = selectedGroupId"
@@ -7137,8 +7148,8 @@
        "        reviewView.invalidate()"
        "        saveSessionAsync("
        "            session,"
-       "            deleteSavingStatus(group),"
-       "            deleteSavedStatus(group)"
+       "            deleteSavingStatus(deletedGroup),"
+       "            deleteSavedStatus(deletedGroup)"
        "        )"
        "    }"
        ""