%{ name: :mob_photos, mob_version: "~> 0.6", plugin_spec_version: 1, description: "Photo/video library picker + MediaStore enumeration — extracted from mob core in Wave 2", nifs: [ # iOS: Objective-C NIF — PHPickerViewController (iOS 14+) + the :media # permission flow (PHPhotoLibrary). lang: :objc -> compiled as ObjC # (-fobjc-arc); platform: :ios so it isn't pulled into the Android build. %{module: :mob_photos_nif, native_dir: "priv/native/ios", lang: :objc, platform: :ios}, # Android: zig NIF bridging to the system Photo Picker (PickVisualMedia / # PickMultipleVisualMedia) and MediaStore enumeration (ContentResolver) # via the Kotlin MobPhotosBridge. %{module: :mob_photos_nif, native_dir: "priv/native/jni", lang: :zig, platform: :android} ], # Runtime-permission capability OWNED by this plugin: :media. The picker # (pick/2) runs out of process and needs no permission, but library # ENUMERATION (list_media/2) reads the whole MediaStore / Photos library and # genuinely requires READ_MEDIA_IMAGES / READ_MEDIA_VIDEO (Android 33+) or a # PHPhotoLibrary authorization (iOS). `Mob.Permissions.request(socket, :media)` # pops that dialog. Registered exactly like mob_camera's :camera — # * iOS: handler self-registered at NIF load # (mob_photos_request_permission -> PHPhotoLibrary # requestAuthorizationForAccessLevel:PHAccessLevelReadWrite) # * Android: cap->permission mapping via MobPhotosBridge implementing # MobPermissionProvider (permissionsFor("media")). # NOTE: core also ships a :photo_library capability that maps to the same # OS permissions; :media is this plugin's self-contained capability for the # enumeration feature so the feature's permission lives with the code that # owns it (parity with how mob_camera owns :camera rather than leaning on a # core capability). Both end up requesting the same READ_MEDIA_* set on # Android; declaring it here is harmless (set-unioned in the manifest merge). permissions: [ %{capability: :media, ios: %{handler: "mob_photos_request_permission"}} ], android: %{ bridge_kt: "priv/native/android/MobPhotosBridge.kt", bridge_class: "io.mob.photos.MobPhotosBridge", # Moved here from the mob_new AndroidManifest template (Media library # block). The system Photo Picker itself doesn't require these, but # MediaStore enumeration (list_media/2) DOES, and they keep parity with # what core shipped (and cover OEM fallback pickers on API <= 32). NOTE: # the template scoped READ_EXTERNAL_STORAGE with android:maxSdkVersion="32"; # plugin permission entries are plain strings today, so that attribute is # lost in the merge — harmless (the permission is a no-op on 33+) but worth # restoring if the manifest schema grows attribute support. permissions: [ "android.permission.READ_MEDIA_IMAGES", "android.permission.READ_MEDIA_VIDEO", "android.permission.READ_EXTERNAL_STORAGE" ] }, # PHPickerViewController / PHPickerConfiguration / PHPickerResult all live # in PhotosUI; PHPhotoLibrary (permission + enumeration) lives in Photos. # UIKit/Foundation are implicit. plist_keys: NSPhotoLibraryUsageDescription # is required by iOS the moment PHPhotoLibrary authorization is requested # (the :media permission flow) — without it the dialog is suppressed. The # string is a placeholder the host must replace (App Store review rejects # the default text — intentional friction, same gate as mob_camera). ios: %{ frameworks: ["PhotosUI", "Photos"], plist_keys: %{ "NSPhotoLibraryUsageDescription" => "Required by mob_photos to list your photo library — replace this string in your Info.plist" } } # No host_requirements: the picker reads content:// URIs via contentResolver # and enumeration queries MediaStore via contentResolver — no AndroidManifest # fragment needed from the host. }