Files
GameTime/lib/infrastructure/watch_bridge/native_watch_bridge_channel.dart

138 lines
3.8 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<WatchBridgeConnectionEvent> get connectionEvents;
Future<void> publishProjection(WatchSessionProjection projection);
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 connectionChannel = const EventChannel(_connectionChannelName),
}) : _methodChannel = methodChannel,
_commandChannel = commandChannel,
_connectionChannel = connectionChannel;
static const _methodChannelName = 'gametime.watch_bridge/methods';
static const _commandChannelName = 'gametime.watch_bridge/commands';
static const _connectionChannelName = 'gametime.watch_bridge/connection';
final MethodChannel _methodChannel;
final EventChannel _commandChannel;
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<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> 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 {};
}