Skip to content

Files and media picker

PAM Native imports selected content into the application sandbox and returns a FileReference. The absolute device path stays private; use uri() as the opaque source for Image and MediaPlayer.

use Pam\Native\FileReference;
use Pam\Native\MediaPickerType;
use Pam\Native\System\Files;
Files::pick(
MediaPickerType::Image,
function (FileReference $file): void {
$this->previewSource = $file->uri();
},
);

The available filters are sequential integer enum cases:

Case Value Native filter
MediaPickerType::Image 1 Images
MediaPickerType::Video 2 Videos
MediaPickerType::Audio 3 Audio
MediaPickerType::Any 4 Any openable document
MediaPickerType::Media 5 Images and videos

Use Media for social galleries. Unlike Any, it prevents PDFs, XML files, archives, and unrelated documents from appearing alongside photos and videos. The filter is implemented with Android MIME filters and iOS UTType filters.

Files::pickMany(
MediaPickerType::Media,
function (array $files): void {
$this->media = array_map(
static fn (FileReference $file): array => [
'mimeType' => $file->mimeType,
'name' => $file->name,
'size' => $file->size,
'uri' => $file->uri(),
],
$files,
);
},
limit: 10,
);

The native picker owns selection UI and enforces a bounded limit between 1 and 50. PAM copies each accepted item once into its sandbox, preserving the provider’s display name and MIME type.

PAM Native 0.5.57 adds a direct, typed Android gallery API. It queries MediaStore on a dedicated worker, returns bounded pages of metadata, and gives the renderer stable content:// thumbnail sources. No thumbnail bytes cross the PHP bridge and no source file is copied while the user scrolls.

use Pam\Native\FileReference;
use Pam\Native\MediaAsset;
use Pam\Native\MediaAssetPage;
use Pam\Native\MediaPickerType;
use Pam\Native\PermissionKind;
use Pam\Native\System\Files;
use Pam\Native\System\MediaLibrary;
use Pam\Native\System\Permissions;
Permissions::requestKind(PermissionKind::Photos, function ($decision): void {
if (!$decision->granted()) {
return;
}
MediaLibrary::assets(
MediaPickerType::Media,
function (MediaAssetPage $page): void {
$this->assets = array_map(
static fn (MediaAsset $asset): array => [
'id' => $asset->id,
'source' => $asset->uri,
'width' => $asset->width,
'height' => $asset->height,
'durationMs' => $asset->durationMs,
'video' => $asset->video(),
],
$page->items,
);
$this->nextOffset = $page->nextOffset;
$this->hasMore = $page->hasMore;
},
limit: 80,
offset: $this->nextOffset,
albumId: $this->albumId ?: null,
);
});
MediaLibrary::albums(
MediaPickerType::Media,
fn (array $albums) => $this->albums = $albums,
);

Android 13 distinguishes image and video access. Android 14 may grant only the photos selected by the user; PAM reports this as PermissionStatus::Limited, and PermissionDecision::granted() accepts it. The query naturally returns only assets visible to the application.

Keep gallery cells pointed at $asset->uri. When the user confirms an item, materialize only that asset into app-owned storage:

public function select(MediaAsset $asset): void
{
Files::importUri(
$asset->uri,
function (FileReference $file): void {
$this->editorSource = $file->uri();
$this->uploadPath = $file->path;
},
);
}

MediaLibrary is Android-only in 0.5.57. Keep Files::pick() or Files::pickMany() visible as the portable fallback and as an escape hatch when gallery access is denied or blocked.

<Image
:source="$selectedImage"
resizeMode="cover"
fadeDuration="0"
/>
<MediaPlayer
:source="$selectedVideo"
controls="true"
cache="none"
/>

Keep the returned URI in component state while the editor is mounted. This avoids reopening the provider, copying the file again, or decoding a new source on every render.

DrawingCanvas places a native freehand surface over an aspect-fit image. Brush and eraser movement stays on the platform UI thread, including coalesced touch samples; PHP receives one compact drawing document only after a stroke finishes.

<DrawingCanvas
class="editor-canvas"
:source="$selectedImage"
:value="$drawing"
brushColor="#FFFFFFFF"
brushWidth="6"
drawingMode="brush"
:undoRequest="$undoRequest"
:clearRequest="$clearRequest"
on:change="updateDrawing"
/>

Use drawingMode="eraser" to remove previously drawn pixels. Increment undoRequest or clearRequest to issue those commands; they are request tokens, not booleans. The versioned JSON value is bounded to 256 strokes and 2,048 points per stroke. Coordinates and brush widths are normalized to the actual displayed image content, so letterboxing, device density, and export dimensions do not shift the drawing.

ImageEditor::render() performs crop, rotation, horizontal flip, filters, color adjustments, text/sticker composition, resize, and JPEG encoding on a dedicated native worker. The callback returns another sandboxed FileReference.

use Pam\Native\FileReference;
use Pam\Native\ImageCropRatio;
use Pam\Native\ImageFilterType;
use Pam\Native\System\ImageEditor;
ImageEditor::render(
source: $selected,
cropRatio: ImageCropRatio::Square,
filter: ImageFilterType::Original,
quarterTurns: 0,
flipHorizontal: false,
overlayText: '',
callback: function (?FileReference $image, string $error): void {
if ($image instanceof FileReference) {
$this->uploadPath = $image->path;
}
},
maxWidth: 1080,
maxHeight: 1080,
outputQuality: 92,
drawing: $drawing,
);

maxWidth and maxHeight default to 0, which preserves the cropped dimensions. Positive values bound the output without upscaling it. outputQuality is clamped to the inclusive range 1–100 and defaults to 94. On Android, PAM reads source bounds and selects a decode sample before bitmap allocation, then applies one final filtered resize. Large camera photos therefore avoid a needless full-resolution decode when the destination is a thumbnail, avatar, or bounded attachment.

When drawing is provided, PAM flattens the native drawing document into the source before rotation, crop, filters, overlays, resize, and encoding. Android and iOS do this work outside the UI thread.