mobiler 0.14.0

Build mobile apps in Rust — one core, native UI on Android, iOS, and the web (CLI)
package {{PACKAGE}}

import {{PACKAGE_SHARED_TYPES}}.PluginResponse

import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

// Barcode/QR scanner (free, bundled). Uses ML Kit's GmsBarcodeScanner — a complete
// prebuilt scanner UI from Google Play Services that handles the camera preview AND the
// camera permission itself, so the plugin needs no CameraX/Activity-launcher plumbing and
// no CAMERA permission in the manifest. Single-shot: opens the scanner, returns the first
// code as "<format>:<value>" (e.g. "qr:https://…", "ean13:978…"), then closes.
//
// Needs the foreground Activity (the scanner is launched from an Activity context); the
// generic shell already tracks it via the MobilerActivity WeakReference.
class ScannerPlugin(private val application: android.app.Application) : MobilerPlugin {
    override suspend fun handle(op: String, input: String): PluginResponse {
        if (op != "scan") return PluginResponse(false, "unknown op '$op'")
        val activity = MobilerActivity.current?.get()
            ?: return PluginResponse(false, "no foreground activity")

        val options = GmsBarcodeScannerOptions.Builder()
            .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
            .enableAutoZoom()
            .build()
        val scanner = GmsBarcodeScanning.getClient(activity, options)

        return suspendCancellableCoroutine { cont ->
            scanner.startScan()
                .addOnSuccessListener { barcode ->
                    val value = barcode.rawValue ?: barcode.displayValue
                    if (value != null) {
                        cont.resume(PluginResponse(true, "${formatName(barcode.format)}:$value"))
                    } else {
                        cont.resume(PluginResponse(false, "empty barcode"))
                    }
                }
                .addOnCanceledListener { cont.resume(PluginResponse(false, "cancelled")) }
                .addOnFailureListener { e -> cont.resume(PluginResponse(false, e.message ?: "scan failed")) }
        }
    }

    // A short, app-friendly symbology tag so the Rust core can branch on format.
    private fun formatName(format: Int): String = when (format) {
        Barcode.FORMAT_QR_CODE -> "qr"
        Barcode.FORMAT_EAN_13 -> "ean13"
        Barcode.FORMAT_EAN_8 -> "ean8"
        Barcode.FORMAT_UPC_A -> "upca"
        Barcode.FORMAT_UPC_E -> "upce"
        Barcode.FORMAT_CODE_128 -> "code128"
        Barcode.FORMAT_CODE_39 -> "code39"
        Barcode.FORMAT_CODE_93 -> "code93"
        Barcode.FORMAT_CODABAR -> "codabar"
        Barcode.FORMAT_ITF -> "itf"
        Barcode.FORMAT_DATA_MATRIX -> "datamatrix"
        Barcode.FORMAT_PDF417 -> "pdf417"
        Barcode.FORMAT_AZTEC -> "aztec"
        else -> "other"
    }
}