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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:open_earable_flutter/open_earable_flutter.dart';
import 'package:open_wearable/apps/seal_check/audio_response_measurement_session.dart';
import 'package:open_wearable/apps/seal_check/seal_check_quality.dart';
import 'package:open_wearable/apps/seal_check/seal_check_quality.dart'
show sealCheckTargetMagnitudes;

// NOTE: We intentionally do NOT support writing files on web here.
// If you want web downloads, we can add a proper conditional import helper.
Expand Down Expand Up @@ -347,72 +348,9 @@ class _SealCheckMeasurementViewState extends State<SealCheckMeasurementView> {
? 1.0
: allMags.reduce((a, b) => a + b) / allMags.length;

final leftQuality =
leftPoints.isNotEmpty ? computeSealCheckQuality(leftPoints) : null;
final rightQuality =
rightPoints.isNotEmpty ? computeSealCheckQuality(rightPoints) : null;

return SingleChildScrollView(
child: Column(
children: [
// Summary cards
Row(
children: [
if (leftQuality != null) ...[
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Left Quality',
style: theme.textTheme.labelMedium,
),
const SizedBox(height: 4),
Text(
'${leftQuality.round()} / 100',
style: theme.textTheme.titleLarge,
),
],
),
),
),
),
if (rightQuality != null) const SizedBox(width: 8),
],
if (rightQuality != null)
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Right Quality',
style: theme.textTheme.labelMedium,
),
const SizedBox(height: 4),
Text(
'${rightQuality.round()} / 100',
style: theme.textTheme.titleLarge,
),
],
),
),
),
),
],
),
const SizedBox(height: 8),
SizedBox(
height: 320,
child: Card(
Expand Down Expand Up @@ -710,14 +648,11 @@ class _SealCheckMeasurementViewState extends State<SealCheckMeasurementView> {

// Single side: show table directly without tabs
if (tabs.length == 1) {
return SizedBox(
height: 300,
child: _buildRawValuesTable(
theme,
tabs.first.points,
normMag,
label: tabs.first.label,
),
return _buildRawValuesTable(
theme,
tabs.first.points,
normMag,
label: tabs.first.label,
);
}

Expand Down
18 changes: 9 additions & 9 deletions open_wearable/lib/models/app_upgrade_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ class AppUpgradeRegistry {
AppUpgradeHighlight(
version: '1.5.0',
eyebrow: 'OpenWearables 1.5.0',
title: 'Label recordings and\ntune your earables',
title: 'Labels and more control\nfor your OpenEarables',
summary:
'Mark important moments, check ear-tip seals, and fine-tune microphone gain.',
'Label recordings, check the seal, tune microphones, and choose compatible devices.',
heroDescription:
'OpenWearables 1.5.0 adds richer tools for experiments and everyday device setup. '
'Annotate recordings as they happen, run a guided Seal Check, adjust microphone gain, and find compatible devices more easily.',
'OpenWearables 1.5.0 adds reusable recording labels, Seal Check, and microphone gain controls. '
'Capability-aware device selection helps you start each app with a compatible wearable.',
accentColor: Color(0xFF8F6A67),
useHeroGradient: false,
features: <AppUpgradeFeatureHighlight>[
Expand All @@ -198,22 +198,22 @@ class AppUpgradeRegistry {
'Create reusable label sets and mark events while recording. Label states are saved alongside sensor data with timestamps for easier review and analysis.',
),
AppUpgradeFeatureHighlight(
icon: Icons.hearing_rounded,
icon: Icons.graphic_eq_rounded,
title: 'Seal Check',
description:
'Run a guided audio-response measurement for one or both earables and get a clear seal-quality result with live progress.',
'Measure the ear seal and review the audio-response graph directly in the app.',
),
AppUpgradeFeatureHighlight(
icon: Icons.mic_rounded,
title: 'Microphone gain controls',
description:
'Adjust inner and outer microphone gain, link channels, mute microphones, and keep stereo pairs in sync from the device controls.',
'Adjust inner and outer mic levels, link both channels, mute them, or reset the gain.',
),
AppUpgradeFeatureHighlight(
icon: Icons.bluetooth_searching_rounded,
icon: Icons.devices_rounded,
title: 'Smarter device selection',
description:
'Filter nearby devices by name and see which connected wearables support an app before starting a workflow.',
'Find compatible wearables faster with clearer, capability-aware app selection.',
),
],
),
Expand Down
27 changes: 26 additions & 1 deletion open_wearable/lib/view_models/sensor_configuration_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class SensorConfigurationProvider with ChangeNotifier {
}) : _sensorConfigurationManager = sensorConfigurationManager {
_sensorConfigurationSubscription =
_sensorConfigurationManager.sensorConfigurationStream.listen((event) {
final hadReceivedConfigurationReport = _hasReceivedConfigurationReport;
final previousReportedConfigurations =
Map<String, SensorConfigurationValue>.of(
_lastReportedConfigurations,
);
_hasReceivedConfigurationReport = true;
_lastReportedConfigurations
..clear()
Expand All @@ -70,7 +75,11 @@ class SensorConfigurationProvider with ChangeNotifier {
..clear()
..addAll(_lastReportedConfigurations.keys);

var hasStateChange = false;
var hasStateChange = !hadReceivedConfigurationReport ||
!_reportedConfigurationsMatch(
previousReportedConfigurations,
_lastReportedConfigurations,
);
for (final e in event.entries) {
final sensorConfiguration = e.key;
final sensorConfigurationValue = e.value;
Expand Down Expand Up @@ -242,6 +251,22 @@ class SensorConfigurationProvider with ChangeNotifier {
return _normalizeName(current.key) == _normalizeName(expected.key);
}

bool _reportedConfigurationsMatch(
Map<String, SensorConfigurationValue> first,
Map<String, SensorConfigurationValue> second,
) {
if (first.length != second.length) {
return false;
}
for (final entry in first.entries) {
final other = second[entry.key];
if (other == null || !_configurationValuesMatch(entry.value, other)) {
return false;
}
}
return true;
}

Set<String> _optionNameSet(SensorConfigurationValue value) {
if (value is! ConfigurableSensorConfigurationValue) {
return const <String>{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,23 +243,23 @@ class _DeviceDetailPageState extends State<DeviceDetailPage> {
),
),
),
if (widget.device.hasCapability<MicrophoneGainManager>())
if (widget.device.hasCapability<MicrophoneManager>())
Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: MicrophoneGainControls(device: widget.device),
child: MicrophoneSelectionWidget(
device: widget.device,
applyScope: StereoPairApplyScope.individualOnly,
),
),
),
if (widget.device.hasCapability<MicrophoneManager>())
if (widget.device.hasCapability<MicrophoneGainManager>())
Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: MicrophoneSelectionWidget(
device: widget.device,
applyScope: StereoPairApplyScope.individualOnly,
),
child: MicrophoneGainControls(device: widget.device),
),
),
if (widget.device.hasCapability<PowerSavingModeManager>())
Expand Down
Loading
Loading