clipboard: add UxClipboard.readImage native bridge

Flutter's Clipboard API only exposes text shapes. Banlu's chat composer
needs image bytes from the system clipboard for desktop paste, so add
a UxClipboard facade backed by per-platform native plugins:

* iOS: prefer raw PNG/JPEG bytes off the pasteboard, fall back to
  re-encoding `UIPasteboard.image` as PNG.
* macOS: prefer NSPasteboard `.png`, fall back to TIFF transcoded
  through NSBitmapImageRep so screenshots / Preview hand-offs still
  work.
* Android: read primary ClipData's first item URI and stream the bytes
  through ContentResolver — don't trust the clip description's MIME,
  copy whatever the resolver returns.

Returns null (never throws) when the clipboard has no image — callers
treat null as "fall through to text paste".
This commit is contained in:
agra
2026-05-09 07:29:14 +03:00
parent cc28782119
commit fb00e98681
8 changed files with 180 additions and 0 deletions

28
lib/src/clipboard.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'package:flutter/services.dart';
/// OS clipboard access for shapes Flutter's [Clipboard] doesn't cover.
/// Right now this is image bytes — the system text path is already
/// handled by the SDK's `Clipboard.getData(Clipboard.kTextPlain)`.
class UxClipboard {
UxClipboard._();
static const _channel = MethodChannel('ux/clipboard');
/// Returns the image currently on the system clipboard as raw bytes
/// (PNG on iOS / macOS / Android), or `null` if no image is available.
///
/// On macOS the call also handles TIFF clipboards by transcoding to
/// PNG; Android resolves `content://` URIs through ContentResolver and
/// returns the underlying image bytes verbatim. Never throws — channel
/// failures resolve to `null`.
static Future<Uint8List?> readImage() async {
try {
final bytes = await _channel.invokeMethod<Uint8List>('readImage');
return bytes;
} on PlatformException {
return null;
} on MissingPluginException {
return null;
}
}
}