187 lines
5.3 KiB
Dart
187 lines
5.3 KiB
Dart
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<WatchCommandEnvelope> get commands;
|
|
|
|
Stream<WatchSensorSummary> get sensorSummaries;
|
|
|
|
Stream<WatchSensorSample> get sensorSamples;
|
|
|
|
Stream<WatchBridgeConnectionEvent> get connectionEvents;
|
|
|
|
Future<void> publishProjection(WatchSessionProjection projection);
|
|
|
|
Future<void> publishAlert(WatchAlertEnvelope alert);
|
|
|
|
Future<void> sendCommandAck(
|
|
WatchCommandEnvelope command,
|
|
WatchCommandAck ack, {
|
|
int? revisionAtAck,
|
|
});
|
|
|
|
Future<void> requestCapabilityRefresh();
|
|
|
|
Future<void> startForegroundService();
|
|
|
|
Future<void> 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<WatchCommandEnvelope> get commands {
|
|
return _commandChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) {
|
|
return event is Map;
|
|
})
|
|
.map((event) {
|
|
return WatchCommandEnvelope.fromJson(_stringObjectMap(event));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Stream<WatchSensorSummary> get sensorSummaries {
|
|
return _sensorSummaryChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) {
|
|
return event is Map;
|
|
})
|
|
.map((event) {
|
|
return WatchSensorSummary.fromJson(_stringObjectMap(event));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Stream<WatchSensorSample> get sensorSamples {
|
|
return _sensorSampleChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) {
|
|
return event is Map;
|
|
})
|
|
.map((event) {
|
|
return WatchSensorSample.fromJson(_stringObjectMap(event));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Stream<WatchBridgeConnectionEvent> 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<void> publishProjection(WatchSessionProjection projection) {
|
|
return _invokeIgnoringMissingPlugin(
|
|
'publishProjection',
|
|
projection.toJson(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> publishAlert(WatchAlertEnvelope alert) {
|
|
return _invokeIgnoringMissingPlugin('publishAlert', alert.toJson());
|
|
}
|
|
|
|
@override
|
|
Future<void> requestCapabilityRefresh() {
|
|
return _invokeIgnoringMissingPlugin('requestCapabilityRefresh');
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<void> startForegroundService() {
|
|
return _invokeIgnoringMissingPlugin('startForegroundService');
|
|
}
|
|
|
|
@override
|
|
Future<void> stopForegroundService() {
|
|
return _invokeIgnoringMissingPlugin('stopForegroundService');
|
|
}
|
|
|
|
Future<void> _invokeIgnoringMissingPlugin(
|
|
String method, [
|
|
Object? arguments,
|
|
]) {
|
|
return _methodChannel
|
|
.invokeMethod<void>(method, arguments)
|
|
.onError<MissingPluginException>((_, _) {});
|
|
}
|
|
}
|
|
|
|
Map<String, Object?> _stringObjectMap(Object? value) {
|
|
if (value is Map) {
|
|
return value.map((key, value) => MapEntry(key.toString(), value));
|
|
}
|
|
return const {};
|
|
}
|