Skip to content
Merged
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
72 changes: 72 additions & 0 deletions openhealth/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import 'package:openglucose/src/app_controller.dart';
import 'package:openglucose/src/dashboard_chart.dart';
import 'package:openglucose/src/display_preferences.dart';
import 'package:openglucose/src/driver_factory.dart';
import 'package:openglucose/src/mock_scenarios.dart';
import 'package:openglucose/src/session_presentation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';

/// When built with `--dart-define=OG_DEMO=true`, the app runs the in-memory
/// demo driver (see [buildDefaultDriver]) and auto-connects on launch so it
/// lands directly on the populated dashboard — used for simulator/feature
/// verification. Defaults to false; production builds are unaffected.
const bool kOgDemo = bool.fromEnvironment('OG_DEMO', defaultValue: false);

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

Expand All @@ -35,9 +42,31 @@ Future<CgmAppController> _bootstrap() async {
driver: buildDefaultDriver(),
);
await controller.initialize();
if (kOgDemo) {
// In demo mode (simulator/feature verification) the demo driver only
// surfaces its sensor after a scan, so auto-scan and auto-connect to land
// directly on the populated dashboard. Strictly gated behind OG_DEMO and
// skipped when a previous session was already restored from preferences.
unawaited(_autoConnectDemoSensor(controller));
}
return controller;
}

/// Scans with the demo driver and connects to the first discovered sensor so
/// OG_DEMO builds open straight onto the populated dashboard.
Future<void> _autoConnectDemoSensor(CgmAppController controller) async {
if (controller.snapshot != null) {
// A persisted session is already (re)connecting; don't interfere.
return;
}
await controller.scan();
final sensors = controller.sensors;
if (sensors.isEmpty) {
return;
}
await controller.connect(sensors.first);
}

class _BootstrapApp extends StatefulWidget {
const _BootstrapApp();

Expand Down Expand Up @@ -936,10 +965,15 @@ Future<void> _showSettings(
),
_buildDeveloperSettingsPane(
context: context,
controller: controller,
snapshot: snapshot,
diagnostics: diagnostics,
calibrations: calibrations,
logs: logs,
onScenarioChanged: (scenario) {
controller.applyMockScenario(scenario);
setState(() {});
},
),
],
),
Expand Down Expand Up @@ -1113,10 +1147,12 @@ Widget _buildSensorSettingsPane(

Widget _buildDeveloperSettingsPane({
required BuildContext context,
required CgmAppController controller,
required CgmSessionSnapshot snapshot,
required List<CgmDiagnosticItem> diagnostics,
required List<CgmCalibrationEntry> calibrations,
required List<CgmLogEntry> logs,
required ValueChanged<MockScenario> onScenarioChanged,
}) {
final metadataEntries = <MapEntry<String, String>>[
MapEntry('deviceId', snapshot.sensor.deviceId),
Expand All @@ -1138,6 +1174,42 @@ Widget _buildDeveloperSettingsPane({
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
),
const SizedBox(height: 16),
if (controller.isMockDriver) ...<Widget>[
Text(
'Mock scenario',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w900),
),
const SizedBox(height: 8),
DropdownButtonFormField<MockScenario>(
key: const ValueKey<String>('mockScenarioPicker'),
initialValue: controller.mockScenario ?? MockScenario.activeNormal,
isExpanded: true,
decoration: const InputDecoration(labelText: 'Simulated sensor state'),
items: MockScenario.values
.map(
(scenario) => DropdownMenuItem<MockScenario>(
value: scenario,
child: Text(scenario.label),
),
)
.toList(growable: false),
onChanged: (scenario) {
if (scenario != null) {
onScenarioChanged(scenario);
}
},
),
const SizedBox(height: 6),
Text(
(controller.mockScenario ?? MockScenario.activeNormal).description,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: const Color(0xFF5B6E6A),
),
),
const Divider(height: 28),
],
Text(
'Metadata',
style: Theme.of(
Expand Down
34 changes: 34 additions & 0 deletions openhealth/lib/src/app_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'android_live_update_bridge.dart';
import 'demo_driver.dart';
import 'display_preferences.dart';
import 'ios_live_activity_bridge.dart';
import 'live_activity_payload.dart';
import 'mock_scenarios.dart';

class CgmAppController extends ChangeNotifier {
CgmAppController({
Expand Down Expand Up @@ -425,6 +427,38 @@ class CgmAppController extends ChangeNotifier {
notifyListeners();
}

/// Whether the active driver is the OG_DEMO mock driver, i.e. the Developer
/// scenario switcher should be shown.
bool get isMockDriver => _driver is DemoCgmDriver;

/// The mock scenario currently driving the demo session, or null when not in
/// demo mode.
MockScenario? get mockScenario {
final driver = _driver;
return driver is DemoCgmDriver ? driver.scenario : null;
}

/// Switches the live mock scenario without a rebuild. No-op outside OG_DEMO.
/// The demo session emits a fresh snapshot through the existing stream, so
/// the dashboard updates automatically.
void applyMockScenario(MockScenario scenario) {
final driver = _driver;
if (driver is! DemoCgmDriver) {
return;
}
final session = driver.applyScenario(scenario);
if (session == null) {
// Not connected yet; the new scenario becomes the default for the next
// connect. Trigger a (re)connect to surface it immediately.
unawaited(connect(_selectedSensor ?? driver.scenarioSensor));
return;
}
_snapshot = session.currentSnapshot;
_lastError = _snapshot?.lastError;
unawaited(_pushLiveActivity());
notifyListeners();
}

void clearPersistedHistory() {
final sensor = _selectedSensor;
if (sensor == null) {
Expand Down
Loading