implemented native share intent support for android and ios with chat and folder pickers
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
class PendingShare {
|
||||
final List<String> filePaths;
|
||||
final String? text;
|
||||
final DateTime receivedAt;
|
||||
|
||||
const PendingShare({
|
||||
required this.filePaths,
|
||||
required this.text,
|
||||
required this.receivedAt,
|
||||
});
|
||||
|
||||
bool get hasFiles => filePaths.isNotEmpty;
|
||||
bool get hasText => text != null && text!.isNotEmpty;
|
||||
bool get isEmpty => !hasFiles && !hasText;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
import 'pending_share.dart';
|
||||
|
||||
/// Bridges native share intents (Android ACTION_SEND, iOS Share Extension)
|
||||
/// into a single [ValueNotifier] that the app routes off of.
|
||||
class ShareIntentListener {
|
||||
ShareIntentListener._();
|
||||
static final ShareIntentListener instance = ShareIntentListener._();
|
||||
|
||||
static final ValueNotifier<PendingShare?> pending = ValueNotifier(null);
|
||||
|
||||
StreamSubscription<List<SharedMediaFile>>? _streamSub;
|
||||
bool _initialized = false;
|
||||
|
||||
/// Reads the cold-start payload exactly once. Call from `main()` before
|
||||
/// `runApp` so the share is queued before the UI mounts.
|
||||
Future<void> initialize() async {
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
try {
|
||||
final initial = await ReceiveSharingIntent.instance.getInitialMedia();
|
||||
final share = _toPendingShare(initial);
|
||||
if (share != null) pending.value = share;
|
||||
await ReceiveSharingIntent.instance.reset();
|
||||
} catch (e) {
|
||||
debugPrint('ShareIntentListener.initialize failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to warm-share stream events. Safe to call multiple times.
|
||||
void attach() {
|
||||
_streamSub ??= ReceiveSharingIntent.instance.getMediaStream().listen(
|
||||
(items) {
|
||||
final share = _toPendingShare(items);
|
||||
if (share != null) pending.value = share;
|
||||
},
|
||||
onError: (Object e) =>
|
||||
debugPrint('ShareIntentListener stream error: $e'),
|
||||
);
|
||||
}
|
||||
|
||||
/// Cancels the warm-share subscription. The singleton survives, so a
|
||||
/// subsequent [attach] re-subscribes.
|
||||
void detach() {
|
||||
_streamSub?.cancel();
|
||||
_streamSub = null;
|
||||
}
|
||||
|
||||
/// Discards the current share and removes any temp files the plugin copied
|
||||
/// into the app cache. Idempotent.
|
||||
void clear() {
|
||||
final current = pending.value;
|
||||
pending.value = null;
|
||||
if (current != null) {
|
||||
for (final path in current.filePaths) {
|
||||
try {
|
||||
final f = File(path);
|
||||
if (f.existsSync()) f.deleteSync();
|
||||
} catch (_) {
|
||||
// best-effort cleanup; OS will reclaim cache eventually
|
||||
}
|
||||
}
|
||||
}
|
||||
unawaited(ReceiveSharingIntent.instance.reset());
|
||||
}
|
||||
|
||||
PendingShare? _toPendingShare(List<SharedMediaFile> items) {
|
||||
if (items.isEmpty) return null;
|
||||
final files = <String>[];
|
||||
final texts = <String>[];
|
||||
for (final item in items) {
|
||||
switch (item.type) {
|
||||
case SharedMediaType.image:
|
||||
case SharedMediaType.video:
|
||||
case SharedMediaType.file:
|
||||
files.add(item.path);
|
||||
case SharedMediaType.text:
|
||||
case SharedMediaType.url:
|
||||
texts.add(item.path);
|
||||
}
|
||||
}
|
||||
if (files.isEmpty && texts.isEmpty) return null;
|
||||
return PendingShare(
|
||||
filePaths: files,
|
||||
text: texts.isEmpty ? null : texts.join('\n'),
|
||||
receivedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user