Skip to main content

lib/mob_scanner.ex

defmodule MobScanner do
  @moduledoc """
  QR code / barcode scanner — a Mob plugin (extracted from mob core in
  Wave 3).

  Opens a full-screen camera preview. When a code is detected the view
  dismisses automatically and the result is delivered to `handle_info`:

      handle_info({:scan, :result,    %{type: :qr, value: "https://..."}}, socket)
      handle_info({:scan, :cancelled},                                       socket)

  iOS additionally delivers `{:scan, :not_available}` when no camera input
  can be opened (core parity, ios/mob_nif.m:2957).

  **Requires the mob_camera plugin for the `:camera` permission.** The
  `:camera` runtime-permission capability (its registry handler on iOS and
  the `MobPermissionProvider` mapping on Android) is owned by mob_camera —
  activate mob_camera alongside this plugin and request `:camera` via
  `Mob.Permissions.request/2` before calling `scan/2`. mob_camera's manifest
  also carries the iOS `NSCameraUsageDescription` plist key.

  iOS: `AVCaptureMetadataOutput`. Android: `CameraX` + ML Kit
  `BarcodeScanning` in a plugin-owned full-screen Activity
  (`io.mob.scanner.MobScannerActivity`) — the host AndroidManifest must
  declare it (see `host_requirements` in `priv/mob_plugin.exs`).
  """

  @type format ::
          :qr
          | :ean13
          | :ean8
          | :code128
          | :code39
          | :upca
          | :upce
          | :pdf417
          | :aztec
          | :data_matrix

  @doc """
  Open the barcode scanner.

  Options:
    - `formats: [format]` — list of barcode formats to detect (default `[:qr]`)

  Core-parity note: both native sides currently ignore the formats list
  (iOS hardcodes its `metadataObjectTypes`, ios/mob_nif.m:2966-2971; the
  Android activity scans all ML Kit formats) — it is encoded and passed
  through so honoring it later is a non-breaking change.
  """
  @spec scan(Mob.Socket.t(), keyword()) :: Mob.Socket.t()
  def scan(socket, opts \\ []) do
    formats = Keyword.get(opts, :formats, [:qr]) |> Enum.map(&Atom.to_string/1)
    formats_json = :json.encode(formats)
    :mob_scanner_nif.scanner_scan(formats_json)
    socket
  end
end