%{ name: :mob_scanner, mob_version: "~> 0.6", plugin_spec_version: 1, description: "QR / barcode scanner — extracted from mob core in Wave 3", nifs: [ # iOS: Objective-C NIF — AVCaptureMetadataOutput in a full-screen view # controller (MobScannerVC, extracted from core ios/mob_nif.m:2939-3046). # lang: :objc -> compiled as ObjC (-fobjc-arc); platform: :ios so it # isn't pulled into the Android build. %{module: :mob_scanner_nif, native_dir: "priv/native/ios", lang: :objc, platform: :ios}, # Android: zig NIF bridging to the plugin-owned MobScannerActivity # (CameraX + ML Kit BarcodeScanning) via the Kotlin MobScannerBridge. %{module: :mob_scanner_nif, native_dir: "priv/native/jni", lang: :zig, platform: :android} ], # NO :permissions capability entry on purpose: the :camera runtime # permission is OWNED by the mob_camera plugin (its iOS registry handler # mob_camera_request_permission + Android MobPermissionProvider mapping # live there). Scanner users must ALSO activate mob_camera and request # :camera via Mob.Permissions before scanning — declaring a second # :camera capability here would double-register the handler. android: %{ bridge_kt: "priv/native/android/MobScannerBridge.kt", bridge_class: "io.mob.scanner.MobScannerBridge", # android.permission.CAMERA is also declared by mob_camera's manifest, # but scanner must declare it too: if mob_scanner is activated WITHOUT # mob_camera the CAMERA uses-permission would otherwise be missing and # CameraX throws at bind time. The manifest merge set-unions permission # strings, so the duplicate is harmless when both plugins are active. permissions: [ "android.permission.CAMERA" ], # CameraX artifacts are ALSO declared by mob_camera's gradle_deps — # the native merge set-unions gradle_deps, so the duplicates de-dup. # appcompat is needed because MobScannerActivity extends # AppCompatActivity (CameraX PreviewView + ML Kit want an AppCompat # theme/context); versions match the mob_new template # (build.gradle.eex:122-133). gradle_deps: [ "androidx.appcompat:appcompat:1.6.1", "androidx.camera:camera-camera2:1.3.4", "androidx.camera:camera-lifecycle:1.3.4", "androidx.camera:camera-view:1.3.4", "com.google.mlkit:barcode-scanning:17.2.0" ] # The scanner Activity itself is an AndroidManifest fragment the plugin # manifest can't yet contribute (same Stage-2 gap as mob_camera's # FileProvider / mob_screencast's ). Declared in # :host_requirements below so every native build reminds the host # author; the MobScannerActivity.kt class DOES ship in this plugin # (priv/native/android/MobScannerActivity.kt, package io.mob.scanner). }, # AVCaptureSession / AVCaptureMetadataOutput / AVCaptureVideoPreviewLayer # all live in AVFoundation; UIKit/Foundation are implicit. No plist_keys — # NSCameraUsageDescription is owned by mob_camera (which scanner users # must activate anyway for the :camera permission); declaring it here too # would collide in the plist merge. ios: %{frameworks: ["AVFoundation"]}, # Manual host-app steps the build can't automate; printed as a warning on # every `mix mob.deploy --native` of the host. mob_new-generated apps # already satisfy the via their template AndroidManifest. host_requirements: [ "AndroidManifest.xml must declare the scanner activity inside : " <> ~s( ~s(android:exported="false" android:theme="@style/Theme.AppCompat.NoActionBar" />) <> " — MobScannerActivity extends AppCompatActivity (CameraX + ML Kit need it) and " <> "throws IllegalStateException at setContentView under a non-AppCompat theme; " <> "without the declaration the app builds + boots fine and throws " <> "ActivityNotFoundException at first scan.", "The :camera runtime permission is owned by the mob_camera plugin — activate " <> "mob_camera alongside mob_scanner and request :camera via Mob.Permissions " <> "before scanning (mob_camera also carries the iOS NSCameraUsageDescription " <> "plist key; without it iOS kills the app at first camera access)." ] }