Files
GameTime/lib/infrastructure/watch_bridge/native_watch_bridge_channel.dart
Blomios 7130635177 fix(watch): stabilise stats live, foreground et resync apres perte de connexion (#179-#189)
Reduit le cout radio des samples live et la cadence des projections telephone -> montre (#179-#183).
Restaure les statistiques live FC/distance/calories et le maintien foreground/ongoing activity (#184-#185).
Fiabilise le demarrage de seance et l'orchestration des permissions montre (#187).
Renforce la resynchronisation des statistiques live et du score apres perte puis retour de connexion (#188-#189).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 11:32:30 +02:00

193 lines
5.4 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, {
bool urgent = true,
});
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, {
bool urgent = true,
}) {
return _invokeIgnoringMissingPlugin('publishProjection', {
...projection.toJson(),
'urgent': urgent,
});
}
@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 {};
}