%{
name: :mob_camera,
mob_version: "~> 0.6",
plugin_spec_version: 1,
description:
"Native camera capture, live preview, and frame streaming — extracted from mob core in Wave 2",
# A sample screen the host can navigate to by route (auto-listed by a home
# that enumerates Mob.Plugins.screens/0). Pure-Elixir + hot-pushable; drop it
# and this entry in a real app that builds its own UI.
screens: [
%{module: MobCamera.DemoScreen, default_route: "/mob_camera/demo"}
],
nifs: [
# iOS: Objective-C NIF — UIImagePickerController capture, a shared
# AVCaptureSession for preview + frame streaming (vImage resize/convert),
# and the :camera permission flow. lang: :objc -> compiled as ObjC
# (-fobjc-arc); platform: :ios so it isn't pulled into the Android build.
%{module: :mob_camera_nif, native_dir: "priv/native/ios", lang: :objc, platform: :ios},
# Android: zig NIF bridging to CameraX via the Kotlin MobCameraBridge.
%{module: :mob_camera_nif, native_dir: "priv/native/jni", lang: :zig, platform: :android}
],
# DESCOPE: the live-preview VIEW stays in mob core for now (its renderer node
# reads the session this plugin's NIF owns, via a weak extern). So this plugin
# does NOT ship a preview component yet — apps use `Mob.UI.camera_preview`
# (core) for the view + `MobCamera.start_preview/2` (this plugin) to activate
# the session. The preview-as-plugin-component move waits on the plugin
# native-view-bound-to-state capability (see EXTRACTION.md).
permissions: [
# iOS handler self-registered at NIF load (mob_camera_request_permission ->
# AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo); Android mapping
# via MobCameraBridge implementing MobPermissionProvider. NOTE: :microphone
# stays in CORE (audio recording needs it); this plugin only owns :camera.
%{capability: :camera, ios: %{handler: "mob_camera_request_permission"}}
],
android: %{
bridge_kt: "priv/native/android/MobCameraBridge.kt",
bridge_class: "io.mob.camera.MobCameraBridge",
permissions: [
"android.permission.CAMERA",
# Video recording. Microphone permission is also declared by mob_audio /
# core; set-unioned, so declaring it here too is harmless.
"android.permission.RECORD_AUDIO"
],
# Frame streaming (ImageAnalysis). Capture (TakePicture/CaptureVideo) ships
# with the SDK. These are also used by the in-core QR scanner today — gradle
# de-dups, so both can declare them until mob_scanner is extracted.
gradle_deps: [
"androidx.camera:camera-camera2:1.3.4",
"androidx.camera:camera-lifecycle:1.3.4",
"androidx.camera:camera-view:1.3.4"
]
# The capture FileProvider + <uses-feature camera> are AndroidManifest
# fragments the plugin manifest can't yet contribute (Stage-2 decision,
# tracked in EXTRACTION.md). Declared in :host_requirements below so every
# native build reminds the host author; mob_new-generated apps already
# carry the FileProvider in their template.
},
ios: %{
frameworks: ["AVFoundation", "Photos", "MobileCoreServices"],
plist_keys: %{
"NSCameraUsageDescription" => "The camera is used for capture and preview."
# NSMicrophoneUsageDescription stays in core (audio); video capture relies
# on it but core/mob_audio declares it.
}
},
# 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 this via their template AndroidManifest.
host_requirements: [
"Photo/video capture saves through a FileProvider: AndroidManifest.xml must " <>
~s(declare <provider android:name="androidx.core.content.FileProvider" ...> ) <>
"with res/xml/file_provider_paths.xml (mob_new-generated apps include it; " <>
"hand-rolled hosts must add it or capture returns cancelled)."
]
}