import 'dart:async'; import 'package:flutter/services.dart'; import 'package:watch_bridge_contract/watch_bridge_contract.dart'; final class WatchBridgeConnectionEvent { const WatchBridgeConnectionEvent({ required this.isReachable, this.requestsResync = false, }); final bool isReachable; final bool requestsResync; } abstract interface class WatchBridgeNativeChannel { Stream get commands; Stream get sensorSummaries; Stream get sensorSamples; Stream get connectionEvents; Future publishProjection( WatchSessionProjection projection, { bool urgent = true, }); Future publishAlert(WatchAlertEnvelope alert); Future sendCommandAck( WatchCommandEnvelope command, WatchCommandAck ack, { int? revisionAtAck, }); Future requestCapabilityRefresh(); Future startForegroundService(); Future stopForegroundService(); } final class MethodChannelWatchBridgeNativeChannel implements WatchBridgeNativeChannel { const MethodChannelWatchBridgeNativeChannel({ MethodChannel methodChannel = const MethodChannel(_methodChannelName), EventChannel commandChannel = const EventChannel(_commandChannelName), EventChannel sensorSummaryChannel = const EventChannel( _sensorSummaryChannelName, ), EventChannel sensorSampleChannel = const EventChannel( _sensorSampleChannelName, ), EventChannel connectionChannel = const EventChannel(_connectionChannelName), }) : _methodChannel = methodChannel, _commandChannel = commandChannel, _sensorSummaryChannel = sensorSummaryChannel, _sensorSampleChannel = sensorSampleChannel, _connectionChannel = connectionChannel; static const _methodChannelName = 'gametime.watch_bridge/methods'; static const _commandChannelName = 'gametime.watch_bridge/commands'; static const _sensorSummaryChannelName = 'gametime.watch_bridge/sensor_summaries'; static const _sensorSampleChannelName = 'gametime.watch_bridge/sensor_samples'; static const _connectionChannelName = 'gametime.watch_bridge/connection'; final MethodChannel _methodChannel; final EventChannel _commandChannel; final EventChannel _sensorSummaryChannel; final EventChannel _sensorSampleChannel; final EventChannel _connectionChannel; @override Stream get commands { return _commandChannel .receiveBroadcastStream() .where((event) { return event is Map; }) .map((event) { return WatchCommandEnvelope.fromJson(_stringObjectMap(event)); }); } @override Stream get sensorSummaries { return _sensorSummaryChannel .receiveBroadcastStream() .where((event) { return event is Map; }) .map((event) { return WatchSensorSummary.fromJson(_stringObjectMap(event)); }); } @override Stream get sensorSamples { return _sensorSampleChannel .receiveBroadcastStream() .where((event) { return event is Map; }) .map((event) { return WatchSensorSample.fromJson(_stringObjectMap(event)); }); } @override Stream get connectionEvents { return _connectionChannel .receiveBroadcastStream() .where((event) { return event is Map; }) .map((event) { final json = _stringObjectMap(event); return WatchBridgeConnectionEvent( isReachable: json['isReachable'] == true, requestsResync: json['requestsResync'] == true, ); }); } @override Future publishProjection( WatchSessionProjection projection, { bool urgent = true, }) { return _invokeIgnoringMissingPlugin('publishProjection', { ...projection.toJson(), 'urgent': urgent, }); } @override Future publishAlert(WatchAlertEnvelope alert) { return _invokeIgnoringMissingPlugin('publishAlert', alert.toJson()); } @override Future requestCapabilityRefresh() { return _invokeIgnoringMissingPlugin('requestCapabilityRefresh'); } @override Future sendCommandAck( WatchCommandEnvelope command, WatchCommandAck ack, { int? revisionAtAck, }) { return _invokeIgnoringMissingPlugin('sendCommandAck', { 'schemaVersion': watchBridgeSchemaVersion, 'commandId': command.commandId, 'sessionId': command.sessionId, 'expectedRevision': command.expectedRevision, 'status': ack.name, 'revisionAtAck': revisionAtAck, 'ackedAtEpochMs': DateTime.now().toUtc().millisecondsSinceEpoch, }); } @override Future startForegroundService() { return _invokeIgnoringMissingPlugin('startForegroundService'); } @override Future stopForegroundService() { return _invokeIgnoringMissingPlugin('stopForegroundService'); } Future _invokeIgnoringMissingPlugin( String method, [ Object? arguments, ]) { return _methodChannel .invokeMethod(method, arguments) .onError((_, _) {}); } } Map _stringObjectMap(Object? value) { if (value is Map) { return value.map((key, value) => MapEntry(key.toString(), value)); } return const {}; }