zuraffa_intents 1.0.1
zuraffa_intents: ^1.0.1 copied to clipboard
The incoming-share seam for the Zuraffa ecosystem: receive shared text/media (boot-time plus live stream) and record sent-message share suggestions.
zuraffa_intents #
The incoming-share seam for the Zuraffa ecosystem: receive shared text/media (a boot-time initial share plus a live stream while the app runs) and record sent messages so the OS share menu can suggest conversations.
zuraffa_intents is the drop-in replacement for zikzak_share_handler,
rebuilt on the Zuraffa port/adapter architecture and produced entirely under
the repo's zfa TDD discipline (spec-001: the pure-Dart contract, 19
behaviors; spec-002: the platform-interface layer + native ports, 22
behaviors; deliberate-mutant matrices 0 SURVIVED on both).
Platform support #
| Platform | Status | Implementation |
|---|---|---|
| Android | ✅ full | Kotlin plugin (ZuraffaIntentsPlugin) — share-sheet intents, shortcuts |
| iOS | ✅ full | Swift plugin (SPM + CocoaPods) — URL-scheme shares, INSendMessageIntent donation |
| macOS | ✅ full | Swift plugin (SPM + CocoaPods) — same wire contract |
Getting started #
dependencies:
zuraffa_intents: ^1.0.0
import 'package:zuraffa_intents/zuraffa_intents.dart';
final intents = ShareIntentService.platform();
// 1. Boot-time initial share (repeatable reads; reset when consumed).
final boot = await intents.getInitialSharedMedia();
if (boot != null) {
handle(boot);
await intents.resetInitialSharedMedia();
}
// 2. Live shares while the app runs.
intents.sharedMediaStream.listen(handle);
// 3. Record sent messages so share menus suggest conversations.
await intents.recordSentMessage(
conversationIdentifier: 'conv-9',
conversationName: 'Mom',
conversationImageFilePath: '/tmp/mom.png',
serviceName: 'WhatsApp',
);
Or through the composition root (binds the platform driver as the default
ShareIntentPort, both lazy singletons):
final getIt = GetIt.instance;
registerShareIntentDependencies(getIt);
final intents = getIt<ShareIntentService>();
To route platform channels over a custom BinaryMessenger (tests,
background isolates):
registerShareIntentDependencies(getIt, binaryMessenger: myMessenger);
final intents = getIt<ShareIntentService>();
ShareIntentService() (direct construction) keeps binding the pure-Dart
InMemoryShareIntentAdapter — the test/dev driver that plays the platform's
producer role (storeInitial, emit, sentMessageRecords).
Migrating from zikzak_share_handler #
| zikzak_share_handler | zuraffa_intents |
|---|---|
ShareHandler.instance.getInitialSharedMedia() |
ShareIntentService.platform().getInitialSharedMedia() |
ShareHandler.instance.sharedMediaStream |
ShareIntentService.platform().sharedMediaStream |
ShareHandler.instance.recordSentMessage(...) |
same named arguments, same mapping |
ShareHandler.instance.resetInitialSharedMedia() |
same |
SharedMedia / SharedAttachment / SharedAttachmentType |
same names, same nine-field shape — now immutable zorphy entities with JSON round-trips |
federated packages (…_android, …_ios, …) |
not needed — one package, six platforms |
platform_interface instance swap |
ShareIntentPort binding in the GetIt composition root (or inject a port) |
The wire contract (pigeon ZuraffaIntentsApi channels, type-tagged codec,
…/sharedMediaStream event channel) is rebranded but shape-identical to the
source plugin, and the native implementations are a faithful port of its
proven Android/iOS/macOS code.
iOS setup (same as the source plugin) #
Register the share URL scheme in Info.plist and the share extension with
the AppGroupId entry — the plugin consumes ShareMedia-<bundleId> URLs.
See the source plugin's setup guide; the flow is unchanged.
Android notes #
recordSentMessage donates dynamic shortcuts (conversation id, label,
person icon from conversationImageFilePath); the receiving activity is
resolved as <packageName>.MainActivity.
What the TDD evidence pins (specs/002-publishable-plugin) #
- Wire maps: all nine
SharedMediakeys always present, nulls preserved; attachment types ride as declaration-order indices. - Codec:
StandardMessageCodecsubclass with tags 128/129/130; outgoing media under the 129 envelope; native-emitted plain maps decode cleanly. - Quirk: iOS/macOS attachment paths run through
Uri.decodeFullbehind an injectable predicate that short-circuits onkIsWebBEFORE touchingdart:io(the source plugin crashed web builds here). - Channels: result/error/channel-error pigeon semantics; EventChannel lazy-singleton stream.
- Consistency: the Dart channel literals and the ported native files are pinned equal by the suite — drift is a failing test (U38–U41).
Development #
flutter pub get
flutter test # 42 behaviors (spec-001: 19, spec-002: 22, bootstrap: 1)
flutter analyze
flutter pub publish --dry-run
Evidence chain per spec: specs/<id>/spec.md, tasks.md, and
tdd/{test-list,cycle-log,mutant-run,verification}.md.