8000 fix(cbio): render the closed GS1 phase, not the driver's status text by shroominic · Pull Request #169 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions openhealth/lib/src/sensor_connection_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,11 @@ class _SensorConnectionScreenState extends State<SensorConnectionScreen> {
};
}

/// The driver already publishes a closed, user-safe phase sentence for the
/// GS1 link: connecting, authenticating, fetching history, or live.
/// The closed GS1 phase mapped to product copy. The driver's own status text
/// is an internal, unbounded string and never reaches this card.
String? get _cbioProgressText {
final snapshot = _controller.snapshot;
if (snapshot == null || !isCbioSnapshot(snapshot)) {
return null;
}
final text = snapshot.statusText.trim();
return text.isEmpty ? null : text;
return snapshot == null ? null : cbioProgressTextForSnapshot(snapshot);
}

String get _connectionFailureMessage {
Expand Down
27 changes: 27 additions & 0 deletions openhealth/lib/src/session_presentation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,33 @@ bool isLibreGen1Snapshot(CgmSessionSnapshot snapshot) =>
bool isCbioSnapshot(CgmSessionSnapshot snapshot) =>
snapshot.sensor.driverId == 'cbio';

/// Product copy for the closed CBio session phase, or null to fall through.
///
/// The GS1 session publishes one of a fixed set of phases in
/// [cbioPhaseMetadataKey], and this card renders *that*, never
/// [CgmSessionSnapshot.statusText]. The status text is an internal, unbounded
/// field: rendering it would put raw driver wording on a product surface and
/// would let a session that never authenticates replace the bounded-sync stage
/// label the connection screen shows for every other driver, which is what the
/// session-sync failure guard asserts.
///
/// Returns null when the snapshot is not a CBio snapshot, carries no closed
/// phase, or is failing, so the caller keeps the generic stage label.
String? cbioProgressTextForSnapshot(CgmSessionSnapshot snapshot) {
if (!isCbioSnapshot(snapshot)) {
return null;
}
return switch (snapshot.metadata[cbioPhaseMetadataKey]) {
CbioSessionPhase.connecting => 'Connecting to the sensor',
CbioSessionPhase.authenticating => 'Checking the sensor link',
CbioSessionPhase.history => 'Fetching sensor history',
CbioSessionPhase.live => 'Receiving sensor readings',
CbioSessionPhase.disconnected =>
'Connection lost. Reconnecting to your sensor.',
_ => null,
};
}

/// The provisional marker every CBio surface shows.
///
/// The GS1 raw field is divided by ten by two independent clients of the
Expand Down
168 changes: 168 additions & 0 deletions openhealth/test/cbio_app_surface_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:openglucose/main.dart';
import 'package:openglucose/src/app_controller.dart';
import 'package:openglucose/src/healthkit_export.dart';
import 'package:openglucose/src/health_state_store.dart';
import 'package:openglucose/src/sensor_connection_screen.dart';
import 'package:openglucose/src/session_presentation.dart';
import 'package:shared_preferences/shared_preferences.dart';

Expand Down Expand Up @@ -118,6 +120,96 @@ void main() {
);
});

test('the progress card copy comes from the closed phase, never the driver', () {
// A syncing snapshot with no closed phase must fall through to the generic
// stage label. The driver's status text is an internal, unbounded string.
expect(
cbioProgressTextForSnapshot(
CgmSessionSnapshot(
stage: CgmSyncStage.syncing,
statusText: 'Listening for notifications',
sensor: _sensor,
capabilities: _sensor.capabilities,
),
),
isNull,
);
expect(
cbioProgressTextForSnapshot(
CgmSessionSnapshot(
stage: CgmSyncStage.syncing,
statusText: 'anything at all',
sensor: _sensor,
capabilities: _sensor.capabilities,
metadata: const <String, String>{
cbioPhaseMetadataKey: CbioSessionPhase.authenticating,
},
),
),
'Checking the sensor link',
);
expect(
cbioProgressTextForSnapshot(
_snapshot(
stage: CgmSyncStage.syncing,
statusText: 'whatever',
history: const <CgmReading>[],
historySync: const CgmHistorySyncState(),
),
),
isNot(contains('whatever')),
);
});

testWidgets(
'a syncing cbio session shows the bounded-sync stage label, not the driver text',
(tester) async {
SharedPreferences.setMockInitialValues(<String, Object>{
'openHealth.onboarding.completed': true,
});
final preferences = await SharedPreferences.getInstance();
final controller = CgmAppController(
preferences: preferences,
driver: _ScanningCbioDriver(
CgmSessionSnapshot(
stage: CgmSyncStage.syncing,
statusText: 'Listening for notifications',
sensor: _sensor,
capabilities: _sensor.capabilities,
),
),
healthStateStore: PreferencesHealthStateStore(preferences),
);
await controller.initialize();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: SensorConnectionScreen(
controller: controller,
inline: true,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const ValueKey<String>('connectButton-1')));
for (var attempt = 0; attempt < 12; attempt += 1) {
await tester.pump(const Duration(milliseconds: 1));
}

// The bounded-failure contract asserts this stage label while a session
// has connected but not yet decoded anything.
expect(find.text('Syncing sensor history'), findsOneWidget);
expect(find.text('Listening for notifications'), findsNothing);

await tester.pumpWidget(const SizedBox.shrink());
controller.dispose();
await tester.pump();
},
);

testWidgets('the dashboard shows the live value with its provisional unit', (
tester,
) async {
Expand Down Expand Up @@ -326,3 +418,79 @@ final class _CbioSurfaceSession implements CgmSession {
int? requestedStartOffset,
}) async {}
}

/// A driver that advertises one CBio sensor, so the connection screen can be
/// driven through its real connect button and reach the progress card.
final class _ScanningCbioDriver implements CgmDriver {
_ScanningCbioDriver(this.snapshot);

final CgmSessionSnapshot snapshot;

@override
String get driverId => 'cbio';

@override
Stream<DiscoveredSensor> scan({
Duration? timeout,
bool allowDuplicates = true,
}) async* {
yield _sensor;
}

@override
Future<CgmSession> connect(DiscoveredSensor sensor) async =>
_FrozenCbioSession(sensor: sensor, snapshot: snapshot);
}

/// A session frozen on one snapshot; it never emits anything else.
final class _FrozenCbioSession implements CgmSession {
_FrozenCbioSession({required this.sensor, required this.snapshot})
: currentSnapshot = snapshot;

final CgmSessionSnapshot snapshot;

@override
final DiscoveredSensor sensor;

@override
CgmSessionSnapshot currentSnapshot;

@override
Stream<CgmLogEntry> get logs => const Stream<CgmLogEntry>.empty();

@override
Stream<CgmSessionSnapshot> get snapshots => const Stream.empty();

@override
CgmUnsafeAdmin? get unsafeAdmin => null;

@override
Future<void> disconnect() async {}

@override
Future<List<CgmCalibrationEntry>> fetchCalibrations() async =>
const <CgmCalibrationEntry>[];

@override
Future<void> refresh() async {}

@override
Future<List<CgmDiagnosticItem>> refreshDiagnostics() async =>
const <CgmDiagnosticItem>[];

@override
Future<void> refreshLiveData() async {}

@override
Future<void> submitCalibration({
required int glucoseMgdl,
int? sensorMinute,
DateTime? recordedAt,
}) async {}

@override
Future<void> syncHistory({
bool includeRawHistory = false,
int? requestedStartOffset,
}) async {}
}
Loading
0