Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Tests / test (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
179 lines
4.6 KiB
Dart
179 lines
4.6 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io' show Platform;
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import '../utils/logger.dart';
|
|
import 'call_controller.dart';
|
|
|
|
class CallBridge {
|
|
CallBridge._();
|
|
static final CallBridge instance = CallBridge._();
|
|
|
|
static const _method = MethodChannel('ru.ikar.app/calls');
|
|
static const _events = EventChannel('ru.ikar.app/calls_events');
|
|
|
|
bool _started = false;
|
|
|
|
bool get _android {
|
|
try {
|
|
return Platform.isAndroid;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void init() {
|
|
if (_started || !_android) return;
|
|
_started = true;
|
|
_events.receiveBroadcastStream().listen(
|
|
_handle,
|
|
onError: (e) => logger.w('CallBridge.init: events stream error: $e'),
|
|
);
|
|
}
|
|
|
|
Future<void> checkInitialCall() async {
|
|
if (!_android) return;
|
|
try {
|
|
_handle(await _method.invokeMethod<dynamic>('consumeInitialCall'));
|
|
} catch (e) {
|
|
logger.w('CallBridge.checkInitialCall: $e');
|
|
}
|
|
}
|
|
|
|
void _handle(Object? event) {
|
|
if (event is! Map) return;
|
|
final action = event['action']?.toString();
|
|
if (action == 'hangup') {
|
|
unawaited(CallController.instance.endActive());
|
|
return;
|
|
}
|
|
if (action == 'ended') {
|
|
final id = event['conversationId']?.toString();
|
|
if (id != null) CallController.instance.cancelConversation(id);
|
|
return;
|
|
}
|
|
final dataStr = event['data'];
|
|
if (dataStr is! String) return;
|
|
Object? decoded;
|
|
try {
|
|
decoded = jsonDecode(dataStr);
|
|
} catch (e) {
|
|
logger.w('CallBridge._handle: action=$action jsonDecode failed: $e');
|
|
return;
|
|
}
|
|
if (decoded is! Map) return;
|
|
CallController.instance.injectFromNative(
|
|
decoded,
|
|
autoAccept: action == 'answer',
|
|
);
|
|
}
|
|
|
|
Future<void> showIncoming(Map<dynamic, dynamic> payload) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('showIncoming', {
|
|
for (final entry in payload.entries)
|
|
entry.key.toString(): entry.value?.toString() ?? '',
|
|
'type': 'InboundCall',
|
|
'iv': payload['type'] == 'VIDEO' ? 'true' : 'false',
|
|
});
|
|
} catch (error) {
|
|
logger.w('CallBridge.showIncoming: $error');
|
|
}
|
|
}
|
|
|
|
Future<void> cancelConversation(String id) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('cancelConversation', {
|
|
'conversationId': id,
|
|
});
|
|
} catch (error) {
|
|
logger.w('CallBridge.cancelConversation: $error');
|
|
}
|
|
}
|
|
|
|
Future<void> notifyAccepted({String? caller, String? conversationId}) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('notifyAccepted', {
|
|
'caller': caller,
|
|
'conversationId': conversationId,
|
|
});
|
|
} catch (e) {
|
|
logger.w('CallBridge.notifyAccepted: caller=$caller $e');
|
|
}
|
|
}
|
|
|
|
Future<void> ensureOngoing({String? caller}) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('ensureOngoing', {'caller': caller});
|
|
} catch (e) {
|
|
logger.w('CallBridge.ensureOngoing: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> setScreenShare(bool enabled, {String? caller}) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('setScreenShare', {
|
|
'enabled': enabled,
|
|
'caller': caller,
|
|
});
|
|
} catch (e) {
|
|
logger.w('CallBridge.setScreenShare: enabled=$enabled $e');
|
|
}
|
|
}
|
|
|
|
Future<void> dropOngoing() async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('dropOngoing');
|
|
} catch (e) {
|
|
logger.w('CallBridge.dropOngoing: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> notifyEnded({String? conversationId}) async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('notifyEnded', {
|
|
'conversationId': conversationId,
|
|
});
|
|
} catch (e) {
|
|
logger.w('CallBridge.notifyEnded: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> cancelIncoming() async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('cancelIncoming');
|
|
} catch (e) {
|
|
logger.w('CallBridge.cancelIncoming: $e');
|
|
}
|
|
}
|
|
|
|
Future<bool> canUseFullScreenIntent() async {
|
|
if (!_android) return true;
|
|
try {
|
|
return await _method.invokeMethod<bool>('canUseFullScreenIntent') ?? true;
|
|
} catch (e) {
|
|
logger.w('CallBridge.canUseFullScreenIntent: $e');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Future<void> openFullScreenIntentSettings() async {
|
|
if (!_android) return;
|
|
try {
|
|
await _method.invokeMethod<void>('openFullScreenIntentSettings');
|
|
} catch (e) {
|
|
logger.w('CallBridge.openFullScreenIntentSettings: $e');
|
|
}
|
|
}
|
|
}
|