feat(watch): shared watch bridge contract package (#91-A)
This commit is contained in:
5
packages/watch_bridge_contract/analysis_options.yaml
Normal file
5
packages/watch_bridge_contract/analysis_options.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
prefer_single_quotes: true
|
||||||
@ -0,0 +1,458 @@
|
|||||||
|
const int watchBridgeSchemaVersion = 1;
|
||||||
|
|
||||||
|
enum WatchCommandType {
|
||||||
|
startCurrentExercise,
|
||||||
|
pauseSession,
|
||||||
|
resumeSession,
|
||||||
|
startPreparedTimedStep,
|
||||||
|
skipCurrentStep,
|
||||||
|
skipCurrentPassage,
|
||||||
|
finishCurrentSet,
|
||||||
|
skipCurrentSet,
|
||||||
|
skipCurrentRest,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WatchCommandAck {
|
||||||
|
accepted,
|
||||||
|
acceptedNoOp,
|
||||||
|
rejectedStaleRevision,
|
||||||
|
rejectedNotApplicable,
|
||||||
|
rejectedNoActiveSession,
|
||||||
|
rejectedSessionMismatch,
|
||||||
|
rejectedPhoneBusy,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WatchSessionPhase {
|
||||||
|
noActiveSession,
|
||||||
|
ready,
|
||||||
|
running,
|
||||||
|
paused,
|
||||||
|
nextTimerReady,
|
||||||
|
restRunning,
|
||||||
|
restPaused,
|
||||||
|
betweenSetsReady,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WatchPrimaryAction {
|
||||||
|
none,
|
||||||
|
startCurrentExercise,
|
||||||
|
pauseSession,
|
||||||
|
resumeSession,
|
||||||
|
startPreparedTimedStep,
|
||||||
|
skipCurrentRest,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WatchSecondaryAction {
|
||||||
|
skipCurrentStep,
|
||||||
|
skipCurrentPassage,
|
||||||
|
finishCurrentSet,
|
||||||
|
skipCurrentSet,
|
||||||
|
skipCurrentRest,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WatchTimerKind { rest, step, scoreStopwatch, setTimer }
|
||||||
|
|
||||||
|
enum WatchTimerDisplayMode { countdown, elapsed }
|
||||||
|
|
||||||
|
enum WatchTimerRunState { stopped, running, paused }
|
||||||
|
|
||||||
|
final class WatchCommandEnvelope {
|
||||||
|
const WatchCommandEnvelope({
|
||||||
|
this.schemaVersion = watchBridgeSchemaVersion,
|
||||||
|
required this.commandId,
|
||||||
|
required this.type,
|
||||||
|
required this.sessionId,
|
||||||
|
required this.expectedRevision,
|
||||||
|
required this.sentAtEpochMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory WatchCommandEnvelope.fromJson(Map<String, Object?> json) {
|
||||||
|
return WatchCommandEnvelope(
|
||||||
|
schemaVersion: _intFromJson(
|
||||||
|
json['schemaVersion'],
|
||||||
|
watchBridgeSchemaVersion,
|
||||||
|
),
|
||||||
|
commandId: _stringFromJson(json['commandId']),
|
||||||
|
type: _enumFromJson(
|
||||||
|
json['type'],
|
||||||
|
WatchCommandType.values,
|
||||||
|
WatchCommandType.startCurrentExercise,
|
||||||
|
),
|
||||||
|
sessionId: _stringFromJson(json['sessionId']),
|
||||||
|
expectedRevision: _intFromJson(json['expectedRevision'], 0),
|
||||||
|
sentAtEpochMs: _intFromJson(json['sentAtEpochMs'], 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int schemaVersion;
|
||||||
|
final String commandId;
|
||||||
|
final WatchCommandType type;
|
||||||
|
final String sessionId;
|
||||||
|
final int expectedRevision;
|
||||||
|
final int sentAtEpochMs;
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() {
|
||||||
|
return {
|
||||||
|
'schemaVersion': schemaVersion,
|
||||||
|
'commandId': commandId,
|
||||||
|
'type': type.name,
|
||||||
|
'sessionId': sessionId,
|
||||||
|
'expectedRevision': expectedRevision,
|
||||||
|
'sentAtEpochMs': sentAtEpochMs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is WatchCommandEnvelope &&
|
||||||
|
schemaVersion == other.schemaVersion &&
|
||||||
|
commandId == other.commandId &&
|
||||||
|
type == other.type &&
|
||||||
|
sessionId == other.sessionId &&
|
||||||
|
expectedRevision == other.expectedRevision &&
|
||||||
|
sentAtEpochMs == other.sentAtEpochMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return Object.hash(
|
||||||
|
schemaVersion,
|
||||||
|
commandId,
|
||||||
|
type,
|
||||||
|
sessionId,
|
||||||
|
expectedRevision,
|
||||||
|
sentAtEpochMs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WatchSessionProjection {
|
||||||
|
const WatchSessionProjection({
|
||||||
|
this.schemaVersion = watchBridgeSchemaVersion,
|
||||||
|
required this.deviceSessionId,
|
||||||
|
required this.revision,
|
||||||
|
required this.projectedAtEpochMs,
|
||||||
|
required this.phase,
|
||||||
|
required this.phoneReachable,
|
||||||
|
required this.seriesIndex,
|
||||||
|
required this.seriesTotal,
|
||||||
|
required this.exerciseName,
|
||||||
|
this.passageIndex,
|
||||||
|
this.passageTotal,
|
||||||
|
this.stepIndex,
|
||||||
|
this.stepTotal,
|
||||||
|
this.stepName,
|
||||||
|
this.dominantTimer,
|
||||||
|
this.secondaryTimers = const [],
|
||||||
|
required this.primaryAction,
|
||||||
|
this.secondaryActions = const [],
|
||||||
|
this.nextExerciseName,
|
||||||
|
this.statusLabel,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory WatchSessionProjection.fromJson(Map<String, Object?> json) {
|
||||||
|
return WatchSessionProjection(
|
||||||
|
schemaVersion: _intFromJson(
|
||||||
|
json['schemaVersion'],
|
||||||
|
watchBridgeSchemaVersion,
|
||||||
|
),
|
||||||
|
deviceSessionId: _stringFromJson(json['deviceSessionId']),
|
||||||
|
revision: _intFromJson(json['revision'], 0),
|
||||||
|
projectedAtEpochMs: _intFromJson(json['projectedAtEpochMs'], 0),
|
||||||
|
phase: _enumFromJson(
|
||||||
|
json['phase'],
|
||||||
|
WatchSessionPhase.values,
|
||||||
|
WatchSessionPhase.noActiveSession,
|
||||||
|
),
|
||||||
|
phoneReachable: _boolFromJson(json['phoneReachable'], false),
|
||||||
|
seriesIndex: _intFromJson(json['seriesIndex'], 0),
|
||||||
|
seriesTotal: _intFromJson(json['seriesTotal'], 0),
|
||||||
|
exerciseName: _stringFromJson(json['exerciseName']),
|
||||||
|
passageIndex: _nullableIntFromJson(json['passageIndex']),
|
||||||
|
passageTotal: _nullableIntFromJson(json['passageTotal']),
|
||||||
|
stepIndex: _nullableIntFromJson(json['stepIndex']),
|
||||||
|
stepTotal: _nullableIntFromJson(json['stepTotal']),
|
||||||
|
stepName: _nullableStringFromJson(json['stepName']),
|
||||||
|
dominantTimer: _timerFromJson(json['dominantTimer']),
|
||||||
|
secondaryTimers: _timerListFromJson(json['secondaryTimers']),
|
||||||
|
primaryAction: _enumFromJson(
|
||||||
|
json['primaryAction'],
|
||||||
|
WatchPrimaryAction.values,
|
||||||
|
WatchPrimaryAction.none,
|
||||||
|
),
|
||||||
|
secondaryActions: _enumListFromJson(
|
||||||
|
json['secondaryActions'],
|
||||||
|
WatchSecondaryAction.values,
|
||||||
|
),
|
||||||
|
nextExerciseName: _nullableStringFromJson(json['nextExerciseName']),
|
||||||
|
statusLabel: _nullableStringFromJson(json['statusLabel']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int schemaVersion;
|
||||||
|
final String deviceSessionId;
|
||||||
|
final int revision;
|
||||||
|
final int projectedAtEpochMs;
|
||||||
|
final WatchSessionPhase phase;
|
||||||
|
final bool phoneReachable;
|
||||||
|
final int seriesIndex;
|
||||||
|
final int seriesTotal;
|
||||||
|
final String exerciseName;
|
||||||
|
final int? passageIndex;
|
||||||
|
final int? passageTotal;
|
||||||
|
final int? stepIndex;
|
||||||
|
final int? stepTotal;
|
||||||
|
final String? stepName;
|
||||||
|
final WatchTimerProjection? dominantTimer;
|
||||||
|
final List<WatchTimerProjection> secondaryTimers;
|
||||||
|
final WatchPrimaryAction primaryAction;
|
||||||
|
final List<WatchSecondaryAction> secondaryActions;
|
||||||
|
final String? nextExerciseName;
|
||||||
|
final String? statusLabel;
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() {
|
||||||
|
return {
|
||||||
|
'schemaVersion': schemaVersion,
|
||||||
|
'deviceSessionId': deviceSessionId,
|
||||||
|
'revision': revision,
|
||||||
|
'projectedAtEpochMs': projectedAtEpochMs,
|
||||||
|
'phase': phase.name,
|
||||||
|
'phoneReachable': phoneReachable,
|
||||||
|
'seriesIndex': seriesIndex,
|
||||||
|
'seriesTotal': seriesTotal,
|
||||||
|
'exerciseName': exerciseName,
|
||||||
|
'passageIndex': passageIndex,
|
||||||
|
'passageTotal': passageTotal,
|
||||||
|
'stepIndex': stepIndex,
|
||||||
|
'stepTotal': stepTotal,
|
||||||
|
'stepName': stepName,
|
||||||
|
'dominantTimer': dominantTimer?.toJson(),
|
||||||
|
'secondaryTimers': secondaryTimers
|
||||||
|
.map((timer) => timer.toJson())
|
||||||
|
.toList(),
|
||||||
|
'primaryAction': primaryAction.name,
|
||||||
|
'secondaryActions': secondaryActions
|
||||||
|
.map((action) => action.name)
|
||||||
|
.toList(),
|
||||||
|
'nextExerciseName': nextExerciseName,
|
||||||
|
'statusLabel': statusLabel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is WatchSessionProjection &&
|
||||||
|
schemaVersion == other.schemaVersion &&
|
||||||
|
deviceSessionId == other.deviceSessionId &&
|
||||||
|
revision == other.revision &&
|
||||||
|
projectedAtEpochMs == other.projectedAtEpochMs &&
|
||||||
|
phase == other.phase &&
|
||||||
|
phoneReachable == other.phoneReachable &&
|
||||||
|
seriesIndex == other.seriesIndex &&
|
||||||
|
seriesTotal == other.seriesTotal &&
|
||||||
|
exerciseName == other.exerciseName &&
|
||||||
|
passageIndex == other.passageIndex &&
|
||||||
|
passageTotal == other.passageTotal &&
|
||||||
|
stepIndex == other.stepIndex &&
|
||||||
|
stepTotal == other.stepTotal &&
|
||||||
|
stepName == other.stepName &&
|
||||||
|
dominantTimer == other.dominantTimer &&
|
||||||
|
_listEquals(secondaryTimers, other.secondaryTimers) &&
|
||||||
|
primaryAction == other.primaryAction &&
|
||||||
|
_listEquals(secondaryActions, other.secondaryActions) &&
|
||||||
|
nextExerciseName == other.nextExerciseName &&
|
||||||
|
statusLabel == other.statusLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return Object.hash(
|
||||||
|
schemaVersion,
|
||||||
|
deviceSessionId,
|
||||||
|
revision,
|
||||||
|
projectedAtEpochMs,
|
||||||
|
phase,
|
||||||
|
phoneReachable,
|
||||||
|
seriesIndex,
|
||||||
|
seriesTotal,
|
||||||
|
exerciseName,
|
||||||
|
passageIndex,
|
||||||
|
passageTotal,
|
||||||
|
stepIndex,
|
||||||
|
stepTotal,
|
||||||
|
stepName,
|
||||||
|
dominantTimer,
|
||||||
|
Object.hashAll(secondaryTimers),
|
||||||
|
primaryAction,
|
||||||
|
Object.hashAll(secondaryActions),
|
||||||
|
nextExerciseName,
|
||||||
|
statusLabel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WatchTimerProjection {
|
||||||
|
const WatchTimerProjection({
|
||||||
|
required this.kind,
|
||||||
|
required this.label,
|
||||||
|
required this.displayMode,
|
||||||
|
required this.runState,
|
||||||
|
required this.referenceEpochMs,
|
||||||
|
required this.accumulatedMs,
|
||||||
|
this.startedAtEpochMs,
|
||||||
|
this.targetMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory WatchTimerProjection.fromJson(Map<String, Object?> json) {
|
||||||
|
return WatchTimerProjection(
|
||||||
|
kind: _enumFromJson(
|
||||||
|
json['kind'],
|
||||||
|
WatchTimerKind.values,
|
||||||
|
WatchTimerKind.setTimer,
|
||||||
|
),
|
||||||
|
label: _stringFromJson(json['label']),
|
||||||
|
displayMode: _enumFromJson(
|
||||||
|
json['displayMode'],
|
||||||
|
WatchTimerDisplayMode.values,
|
||||||
|
WatchTimerDisplayMode.elapsed,
|
||||||
|
),
|
||||||
|
runState: _enumFromJson(
|
||||||
|
json['runState'],
|
||||||
|
WatchTimerRunState.values,
|
||||||
|
WatchTimerRunState.stopped,
|
||||||
|
),
|
||||||
|
referenceEpochMs: _intFromJson(json['referenceEpochMs'], 0),
|
||||||
|
accumulatedMs: _intFromJson(json['accumulatedMs'], 0),
|
||||||
|
startedAtEpochMs: _nullableIntFromJson(json['startedAtEpochMs']),
|
||||||
|
targetMs: _nullableIntFromJson(json['targetMs']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final WatchTimerKind kind;
|
||||||
|
final String label;
|
||||||
|
final WatchTimerDisplayMode displayMode;
|
||||||
|
final WatchTimerRunState runState;
|
||||||
|
final int referenceEpochMs;
|
||||||
|
final int accumulatedMs;
|
||||||
|
final int? startedAtEpochMs;
|
||||||
|
final int? targetMs;
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() {
|
||||||
|
return {
|
||||||
|
'kind': kind.name,
|
||||||
|
'label': label,
|
||||||
|
'displayMode': displayMode.name,
|
||||||
|
'runState': runState.name,
|
||||||
|
'referenceEpochMs': referenceEpochMs,
|
||||||
|
'accumulatedMs': accumulatedMs,
|
||||||
|
'startedAtEpochMs': startedAtEpochMs,
|
||||||
|
'targetMs': targetMs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
other is WatchTimerProjection &&
|
||||||
|
kind == other.kind &&
|
||||||
|
label == other.label &&
|
||||||
|
displayMode == other.displayMode &&
|
||||||
|
runState == other.runState &&
|
||||||
|
referenceEpochMs == other.referenceEpochMs &&
|
||||||
|
accumulatedMs == other.accumulatedMs &&
|
||||||
|
startedAtEpochMs == other.startedAtEpochMs &&
|
||||||
|
targetMs == other.targetMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return Object.hash(
|
||||||
|
kind,
|
||||||
|
label,
|
||||||
|
displayMode,
|
||||||
|
runState,
|
||||||
|
referenceEpochMs,
|
||||||
|
accumulatedMs,
|
||||||
|
startedAtEpochMs,
|
||||||
|
targetMs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T _enumFromJson<T extends Enum>(Object? value, List<T> values, T fallback) {
|
||||||
|
if (value is String) {
|
||||||
|
for (final enumValue in values) {
|
||||||
|
if (enumValue.name == value) {
|
||||||
|
return enumValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> _enumListFromJson<T extends Enum>(Object? value, List<T> values) {
|
||||||
|
if (value is! List) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
for (final item in value)
|
||||||
|
if (item is String)
|
||||||
|
for (final enumValue in values)
|
||||||
|
if (enumValue.name == item) enumValue,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchTimerProjection? _timerFromJson(Object? value) {
|
||||||
|
if (value is! Map) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return WatchTimerProjection.fromJson(Map<String, Object?>.from(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WatchTimerProjection> _timerListFromJson(Object? value) {
|
||||||
|
if (value is! List) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
for (final item in value)
|
||||||
|
if (item is Map)
|
||||||
|
WatchTimerProjection.fromJson(Map<String, Object?>.from(item)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stringFromJson(Object? value) {
|
||||||
|
return value is String ? value : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _nullableStringFromJson(Object? value) {
|
||||||
|
return value is String ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _intFromJson(Object? value, int fallback) {
|
||||||
|
return value is int ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _nullableIntFromJson(Object? value) {
|
||||||
|
return value is int ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _boolFromJson(Object? value, bool fallback) {
|
||||||
|
return value is bool ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _listEquals<T>(List<T> left, List<T> right) {
|
||||||
|
if (identical(left, right)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (left.length != right.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var index = 0; index < left.length; index += 1) {
|
||||||
|
if (left[index] != right[index]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/// Shared transport contracts between the GameTime phone app and Wear OS app.
|
||||||
|
///
|
||||||
|
/// This package is pure Dart by design: no Flutter, Android, persistence, or
|
||||||
|
/// business logic belongs here.
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/watch_bridge_contract.dart';
|
||||||
389
packages/watch_bridge_contract/pubspec.lock
Normal file
389
packages/watch_bridge_contract/pubspec.lock
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "9a3386eea899815698dd55995277cf7cb8572ee52b399a6edfb7ae2b50e5fc19"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "105.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "62993bed6eadbe9596c5c20d5c167e7bc563c5fe266657a04ddeb93bdb84f4c9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "14.1.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
cli_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_config
|
||||||
|
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.20"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.31.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.13"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.19"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.11.0 <4.0.0"
|
||||||
11
packages/watch_bridge_contract/pubspec.yaml
Normal file
11
packages/watch_bridge_contract/pubspec.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
name: watch_bridge_contract
|
||||||
|
description: Shared Dart contracts for the GameTime phone/watch bridge.
|
||||||
|
publish_to: 'none'
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.10.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^6.0.0
|
||||||
|
test: ^1.25.0
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:watch_bridge_contract/watch_bridge_contract.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('WatchCommandEnvelope', () {
|
||||||
|
test('round-trips every command type through JSON', () {
|
||||||
|
for (final type in WatchCommandType.values) {
|
||||||
|
final command = WatchCommandEnvelope(
|
||||||
|
commandId: 'command-${type.name}',
|
||||||
|
type: type,
|
||||||
|
sessionId: 'session-1',
|
||||||
|
expectedRevision: 12,
|
||||||
|
sentAtEpochMs: 1710000000000,
|
||||||
|
);
|
||||||
|
|
||||||
|
final decoded = WatchCommandEnvelope.fromJson(
|
||||||
|
jsonDecode(jsonEncode(command.toJson())) as Map<String, Object?>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decoded, command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores unknown fields and falls back for missing fields', () {
|
||||||
|
final command = WatchCommandEnvelope.fromJson({
|
||||||
|
'type': 'pauseSession',
|
||||||
|
'unknown': 'ignored',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(command.schemaVersion, watchBridgeSchemaVersion);
|
||||||
|
expect(command.commandId, '');
|
||||||
|
expect(command.type, WatchCommandType.pauseSession);
|
||||||
|
expect(command.sessionId, '');
|
||||||
|
expect(command.expectedRevision, 0);
|
||||||
|
expect(command.sentAtEpochMs, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('WatchCommandAck', () {
|
||||||
|
test('round-trips every ack enum value by stable JSON name', () {
|
||||||
|
for (final ack in WatchCommandAck.values) {
|
||||||
|
final encoded = jsonEncode(ack.name);
|
||||||
|
final decodedName = jsonDecode(encoded) as String;
|
||||||
|
final decoded = WatchCommandAck.values.singleWhere(
|
||||||
|
(value) => value.name == decodedName,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decoded, ack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('WatchSessionProjection', () {
|
||||||
|
test('round-trips every phase, primary action, and secondary action', () {
|
||||||
|
for (final phase in WatchSessionPhase.values) {
|
||||||
|
for (final primaryAction in WatchPrimaryAction.values) {
|
||||||
|
final projection = WatchSessionProjection(
|
||||||
|
deviceSessionId: 'session-${phase.name}-${primaryAction.name}',
|
||||||
|
revision: 4,
|
||||||
|
projectedAtEpochMs: 1710000000100,
|
||||||
|
phase: phase,
|
||||||
|
phoneReachable: true,
|
||||||
|
seriesIndex: 2,
|
||||||
|
seriesTotal: 5,
|
||||||
|
exerciseName: 'Pompes tempo',
|
||||||
|
passageIndex: 1,
|
||||||
|
passageTotal: 3,
|
||||||
|
stepIndex: 2,
|
||||||
|
stepTotal: 4,
|
||||||
|
stepName: 'Descente',
|
||||||
|
dominantTimer: _stepTimer(),
|
||||||
|
secondaryTimers: [_scoreStopwatchTimer(), _setTimer()],
|
||||||
|
primaryAction: primaryAction,
|
||||||
|
secondaryActions: WatchSecondaryAction.values,
|
||||||
|
nextExerciseName: 'Fentes sautees',
|
||||||
|
statusLabel: 'Chrono etape',
|
||||||
|
);
|
||||||
|
|
||||||
|
final decoded = WatchSessionProjection.fromJson(
|
||||||
|
jsonDecode(jsonEncode(projection.toJson())) as Map<String, Object?>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decoded, projection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores unknown fields and accepts absent optional fields', () {
|
||||||
|
final projection = WatchSessionProjection.fromJson({
|
||||||
|
'schemaVersion': 1,
|
||||||
|
'deviceSessionId': 'session-1',
|
||||||
|
'revision': 9,
|
||||||
|
'projectedAtEpochMs': 1710000000200,
|
||||||
|
'phase': 'restPaused',
|
||||||
|
'phoneReachable': true,
|
||||||
|
'seriesIndex': 3,
|
||||||
|
'seriesTotal': 5,
|
||||||
|
'exerciseName': 'Burpees',
|
||||||
|
'primaryAction': 'resumeSession',
|
||||||
|
'secondaryActions': ['skipCurrentRest', 'futureAction'],
|
||||||
|
'extra': {'ignored': true},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(projection.phase, WatchSessionPhase.restPaused);
|
||||||
|
expect(projection.passageIndex, isNull);
|
||||||
|
expect(projection.stepIndex, isNull);
|
||||||
|
expect(projection.dominantTimer, isNull);
|
||||||
|
expect(projection.secondaryTimers, isEmpty);
|
||||||
|
expect(projection.primaryAction, WatchPrimaryAction.resumeSession);
|
||||||
|
expect(projection.secondaryActions, [
|
||||||
|
WatchSecondaryAction.skipCurrentRest,
|
||||||
|
]);
|
||||||
|
expect(projection.nextExerciseName, isNull);
|
||||||
|
expect(projection.statusLabel, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('falls back to neutral values for absent required fields', () {
|
||||||
|
final projection = WatchSessionProjection.fromJson({});
|
||||||
|
|
||||||
|
expect(projection.schemaVersion, watchBridgeSchemaVersion);
|
||||||
|
expect(projection.deviceSessionId, '');
|
||||||
|
expect(projection.revision, 0);
|
||||||
|
expect(projection.projectedAtEpochMs, 0);
|
||||||
|
expect(projection.phase, WatchSessionPhase.noActiveSession);
|
||||||
|
expect(projection.phoneReachable, false);
|
||||||
|
expect(projection.seriesIndex, 0);
|
||||||
|
expect(projection.seriesTotal, 0);
|
||||||
|
expect(projection.exerciseName, '');
|
||||||
|
expect(projection.primaryAction, WatchPrimaryAction.none);
|
||||||
|
expect(projection.secondaryActions, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('WatchTimerProjection', () {
|
||||||
|
test('round-trips every timer enum combination through JSON', () {
|
||||||
|
for (final kind in WatchTimerKind.values) {
|
||||||
|
for (final displayMode in WatchTimerDisplayMode.values) {
|
||||||
|
for (final runState in WatchTimerRunState.values) {
|
||||||
|
final timer = WatchTimerProjection(
|
||||||
|
kind: kind,
|
||||||
|
label: 'timer-${kind.name}',
|
||||||
|
displayMode: displayMode,
|
||||||
|
runState: runState,
|
||||||
|
referenceEpochMs: 1710000000300,
|
||||||
|
accumulatedMs: 42000,
|
||||||
|
startedAtEpochMs: 1710000000000,
|
||||||
|
targetMs: 90000,
|
||||||
|
);
|
||||||
|
|
||||||
|
final decoded = WatchTimerProjection.fromJson(
|
||||||
|
jsonDecode(jsonEncode(timer.toJson())) as Map<String, Object?>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decoded, timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores unknown fields and falls back for missing fields', () {
|
||||||
|
final timer = WatchTimerProjection.fromJson({
|
||||||
|
'kind': 'rest',
|
||||||
|
'displayMode': 'countdown',
|
||||||
|
'runState': 'paused',
|
||||||
|
'unknown': 'ignored',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(timer.kind, WatchTimerKind.rest);
|
||||||
|
expect(timer.label, '');
|
||||||
|
expect(timer.displayMode, WatchTimerDisplayMode.countdown);
|
||||||
|
expect(timer.runState, WatchTimerRunState.paused);
|
||||||
|
expect(timer.referenceEpochMs, 0);
|
||||||
|
expect(timer.accumulatedMs, 0);
|
||||||
|
expect(timer.startedAtEpochMs, isNull);
|
||||||
|
expect(timer.targetMs, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchTimerProjection _stepTimer() {
|
||||||
|
return const WatchTimerProjection(
|
||||||
|
kind: WatchTimerKind.step,
|
||||||
|
label: 'Chrono etape',
|
||||||
|
displayMode: WatchTimerDisplayMode.countdown,
|
||||||
|
runState: WatchTimerRunState.running,
|
||||||
|
referenceEpochMs: 1710000000000,
|
||||||
|
accumulatedMs: 18000,
|
||||||
|
startedAtEpochMs: 1710000000000,
|
||||||
|
targetMs: 20000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchTimerProjection _scoreStopwatchTimer() {
|
||||||
|
return const WatchTimerProjection(
|
||||||
|
kind: WatchTimerKind.scoreStopwatch,
|
||||||
|
label: 'Score chrono',
|
||||||
|
displayMode: WatchTimerDisplayMode.elapsed,
|
||||||
|
runState: WatchTimerRunState.running,
|
||||||
|
referenceEpochMs: 1710000000000,
|
||||||
|
accumulatedMs: 51000,
|
||||||
|
startedAtEpochMs: 1710000000000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchTimerProjection _setTimer() {
|
||||||
|
return const WatchTimerProjection(
|
||||||
|
kind: WatchTimerKind.setTimer,
|
||||||
|
label: 'Temps de serie',
|
||||||
|
displayMode: WatchTimerDisplayMode.elapsed,
|
||||||
|
runState: WatchTimerRunState.running,
|
||||||
|
referenceEpochMs: 1710000000000,
|
||||||
|
accumulatedMs: 102000,
|
||||||
|
startedAtEpochMs: 1710000000000,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1085,6 +1085,13 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.2.0"
|
||||||
|
watch_bridge_contract:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "packages/watch_bridge_contract"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.1.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -24,6 +24,8 @@ dependencies:
|
|||||||
video_player: ^2.11.1
|
video_player: ^2.11.1
|
||||||
file_picker: ^11.0.2
|
file_picker: ^11.0.2
|
||||||
share_plus: ^12.0.2
|
share_plus: ^12.0.2
|
||||||
|
watch_bridge_contract:
|
||||||
|
path: packages/watch_bridge_contract
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user