Files
SpotiFLAC-Mobile/lib/services/share_intent_service.dart
T
2026-04-13 23:35:02 +07:00

106 lines
2.9 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:spotiflac_android/utils/logger.dart';
final _log = AppLogger('ShareIntent');
class ShareIntentService {
static final ShareIntentService _instance = ShareIntentService._internal();
factory ShareIntentService() => _instance;
ShareIntentService._internal();
static final RegExp _spotifyUriPattern = RegExp(
r'spotify:(track|album|playlist|artist):[a-zA-Z0-9]+',
);
static final RegExp _genericHttpUrlPattern = RegExp(
"https?://[^\\s<>\\\"']+",
caseSensitive: false,
);
final _sharedUrlController = StreamController<String>.broadcast();
StreamSubscription<List<SharedMediaFile>>? _mediaSubscription;
bool _initialized = false;
String? _pendingUrl;
Stream<String> get sharedUrlStream => _sharedUrlController.stream;
String? consumePendingUrl() {
final url = _pendingUrl;
_pendingUrl = null;
return url;
}
Future<void> initialize() async {
if (_initialized) return;
_initialized = true;
if (!Platform.isAndroid && !Platform.isIOS) {
_log.i('Share intent is not supported on this platform');
return;
}
_mediaSubscription = ReceiveSharingIntent.instance.getMediaStream().listen(
_handleSharedMedia,
onError: (Object err) => _log.e('Error: $err'),
);
final initialMedia = await ReceiveSharingIntent.instance.getInitialMedia();
if (initialMedia.isNotEmpty) {
_handleSharedMedia(initialMedia, isInitial: true);
ReceiveSharingIntent.instance.reset();
}
}
void _handleSharedMedia(
List<SharedMediaFile> files, {
bool isInitial = false,
}) {
for (final file in files) {
final textsToCheck = [file.path, if (file.message != null) file.message!];
for (final textToCheck in textsToCheck) {
final url = _extractMusicUrl(textToCheck);
if (url != null) {
_log.i('Received music URL: $url (initial: $isInitial)');
if (isInitial) {
_pendingUrl = url;
}
_sharedUrlController.add(url);
return;
}
}
}
}
String? _extractMusicUrl(String text) {
if (text.isEmpty) return null;
final uriMatch = _spotifyUriPattern.firstMatch(text);
if (uriMatch != null) {
return uriMatch.group(0);
}
// Keep share parsing generic and let manifest-based URL handlers decide
// which installed extension can handle the incoming link.
for (final match in _genericHttpUrlPattern.allMatches(text)) {
final rawUrl = match.group(0);
if (rawUrl == null || rawUrl.isEmpty) {
continue;
}
final sanitizedUrl = rawUrl.replaceFirst(RegExp(r'[.,;:!?)\]}]+$'), '');
if (sanitizedUrl.isNotEmpty) {
return sanitizedUrl;
}
}
return null;
}
void dispose() {
_mediaSubscription?.cancel();
_sharedUrlController.close();
}
}