Skip to content

fix: resolve unresponsive UI elements and Web Serial API issue by add…#3142

Closed
saad-aids wants to merge 2 commits into
fossasia:mainfrom
saad-aids:fix-web-serial-ui-issue
Closed

fix: resolve unresponsive UI elements and Web Serial API issue by add…#3142
saad-aids wants to merge 2 commits into
fossasia:mainfrom
saad-aids:fix-web-serial-ui-issue

Conversation

@saad-aids

@saad-aids saad-aids commented Mar 28, 2026

Copy link
Copy Markdown

Description

Fixes #3136

Certain UI elements (Multimeter options, Dust Sensor, Gas Sensor) were unresponsive due to improper callback handling and Web Serial API restrictions.

Root Cause

  • Buttons had null callbacks → not clickable
  • Serial connection (requestPort) was triggered without user gesture → blocked by browser

Changes

  • Fixed onOptionsPressed logic to ensure buttons are always interactive
  • Prevented automatic device connection on Web
  • Added user-triggered USB connection via CONNECT button
  • Implemented _connectUSB method with proper error handling
  • Added UI feedback using SnackBar

Result

  • No more dead UI elements
  • Web Serial API works correctly
  • Improved user experience

Checklist

  • Tested on Chrome (Web)
  • No console errors
  • UI responsive

Summary by Sourcery

Improve web USB connection flow and fix unresponsive multimeter options while updating platform plugins and copy text.

Bug Fixes:

  • Ensure multimeter options menu remains responsive and provides feedback when playback or device connection is required.
  • Prevent automatic USB device connection on web that violated Web Serial API user-gesture requirements.

Enhancements:

  • Add explicit CONNECT USB button and connection flow with progress state and SnackBar feedback on web.
  • Expose processing state on BoardStateProvider to coordinate UI disabling during connection attempts.
  • Guard non-web USB auto-connect logic so initialization behaves correctly across platforms.
  • Update audio and utility plugin registrations for Linux, macOS, and Windows targets and include mp_audio_stream FFI plugin.
  • Tweak PSLab description copy across all locales to use concise "science & engineering" phrasing.

@sourcery-ai

sourcery-ai Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements explicit, user-gesture-driven USB connection on web, adds processing state handling and UI feedback, adjusts multimeter options callback to avoid null handlers, updates platform plugin registrants, and tweaks a localization string.

Class diagram for BoardStateProvider and connection UI changes

classDiagram
    class BoardStateProvider {
        bool pslabIsConnected
        int pslabVersion
        int pslabFirmwareVersion
        bool isProcessing
        ValueNotifier legacyFirmwareNotifier
        ConfigProvider configProvider
        ScienceLabCommon scienceLabCommon
        initialize()
        initializeWiFi()
        connectUSB() bool
    }

    class ConnectDeviceScreen {
    }

    class _HomeScreenState {
        BuildContext context
        _connectUSB(BoardStateProvider provider) void
        build(BuildContext context) Widget
    }

    class MultimeterScreen {
    }

    class _MultimeterScreenState {
        BuildContext context
        _showOptionsMenu() void
        _showInstrumentGuide() void
        _toggleRecording() void
    }

    class ScienceLabCommon {
        isConnected() bool
        openDevice() bool
        openWiFiDevice() bool
    }

    class ConfigProvider {
        Config config
    }

    class Config {
        bool autoStart
    }

    ConnectDeviceScreen --> _HomeScreenState : creates
    MultimeterScreen --> _MultimeterScreenState : creates
    _HomeScreenState --> BoardStateProvider : uses_via_Provider
    _HomeScreenState --> ScienceLabCommon : uses_indirectly
    BoardStateProvider --> ScienceLabCommon : manages_connection
    BoardStateProvider --> ConfigProvider : reads_autoStart
    ConfigProvider --> Config : provides
Loading

File-Level Changes

Change Details Files
Add explicit, user-triggered USB connect flow on web with UI feedback and processing state handling.
  • Introduce _connectUSB helper in the connect device screen to call provider.connectUSB and show SnackBar on success/failure/errors
  • Render a CONNECT USB ElevatedButton only on web when the device is not connected, disabling it while a connection attempt is in progress and showing a spinner based on provider.isProcessing
  • Use kIsWeb to gate web-specific connection UI in the connect device screen
lib/view/connect_device_screen.dart
lib/providers/board_state_provider.dart
Refactor board initialization/connection logic to avoid automatic USB open on web and expose a reusable USB connect method with busy-state tracking.
  • Replace private _isProcessing with public isProcessing on BoardStateProvider and use it to guard initialize and USB event handling
  • Skip automatic scienceLabCommon.openDevice and related setup during initialize when running on web
  • Add connectUSB method that opens the USB device, fetches version/firmware info, handles errors via logger, and maintains isProcessing with notifyListeners
lib/providers/board_state_provider.dart
Adjust multimeter options button callback to avoid null handlers and provide user guidance via SnackBar.
  • Change onOptionsPressed from null when playing back to a non-null closure that conditionally shows a SnackBar and only opens the options menu when allowed
  • Keep other header callbacks (guide, record) behaviour consistent while ensuring the options button is always interactive
lib/view/multimeter_screen.dart
Update desktop plugin registrants and CMake lists to align with new audio/geo/wakelock plugins.
  • Replace flutter_audio_capture with record_linux in Linux plugin registrant and CMake plugin list
  • Register record_macos, geolocator_apple, wakelock_plus on macOS and remove path_provider_foundation from the registrant
  • Add record_windows and geolocator_windows to Windows plugin list and mp_audio_stream to FFI plugin lists on Linux and Windows
linux/flutter/generated_plugin_registrant.cc
linux/flutter/generated_plugins.cmake
macos/Flutter/GeneratedPluginRegistrant.swift
windows/flutter/generated_plugins.cmake
Minor localization copy tweak for PSLab description string across all supported locales.
  • Change phrase 'science and engineering experiments' to 'science & engineering experiments' in the pslabDescription string for every localization class
  • Keep the rest of the localization content intact across all languages
lib/l10n/app_localizations.dart
lib/l10n/app_localizations_de.dart
lib/l10n/app_localizations_en.dart
lib/l10n/app_localizations_es.dart
lib/l10n/app_localizations_fr.dart
lib/l10n/app_localizations_he.dart
lib/l10n/app_localizations_hi.dart
lib/l10n/app_localizations_id.dart
lib/l10n/app_localizations_it.dart
lib/l10n/app_localizations_ja.dart
lib/l10n/app_localizations_ml.dart
lib/l10n/app_localizations_my.dart
lib/l10n/app_localizations_nb.dart
lib/l10n/app_localizations_pt.dart
lib/l10n/app_localizations_ru.dart
lib/l10n/app_localizations_te.dart
lib/l10n/app_localizations_uk.dart
lib/l10n/app_localizations_vi.dart
lib/l10n/app_localizations_zh.dart

Assessment against linked issues

Issue Objective Addressed Explanation
#3136 Make the Multimeter options control responsive (no longer a dead click) and provide appropriate behavior or explanation when it is not usable.
#3136 Ensure the Dust Sensor and Gas Sensor UI elements respond to clicks (navigation or menu/toggle) instead of doing nothing. The diff only modifies the Multimeter options button behavior and connection logic (including a web-specific CONNECT USB button). There are no changes to the Dust Sensor or Gas Sensor click handlers or related navigation, so their previously unresponsive behavior is not clearly addressed.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new isProcessing flag in BoardStateProvider was made public; consider keeping it private with a public getter/setter or dedicated methods so that only the provider controls its lifecycle and external code cannot accidentally desync state.
  • In _MultimeterScreenState the new onOptionsPressed logic seems inverted: it shows 'Start playback or connect device first' when !provider.isPlayingBack; please double-check the condition and the message to ensure it matches the intended UX.
  • All newly added user-facing strings (USB connect SnackBars, multimeter SnackBar, 'CONNECT USB' label) are hardcoded in English; consider wiring these through AppLocalizations to keep them translatable and consistent with the rest of the app.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `isProcessing` flag in `BoardStateProvider` was made public; consider keeping it private with a public getter/setter or dedicated methods so that only the provider controls its lifecycle and external code cannot accidentally desync state.
- In `_MultimeterScreenState` the new `onOptionsPressed` logic seems inverted: it shows 'Start playback or connect device first' when `!provider.isPlayingBack`; please double-check the condition and the message to ensure it matches the intended UX.
- All newly added user-facing strings (USB connect SnackBars, multimeter SnackBar, 'CONNECT USB' label) are hardcoded in English; consider wiring these through `AppLocalizations` to keep them translatable and consistent with the rest of the app.

## Individual Comments

### Comment 1
<location path="lib/providers/board_state_provider.dart" line_range="35-41" />
<code_context>
   Future<void> initialize() async {
-    if (_isProcessing) return;
-    _isProcessing = true;
+    if (isProcessing) return;
+    isProcessing = true;
     if (!scienceLabCommon.isConnected()) {
       await scienceLabCommon.initialize();
-      pslabIsConnected = await scienceLabCommon.openDevice();
-      await setPSLabVersionIDs();
-      await fetchFirmwareVersion();
+      if (!kIsWeb) {
+        pslabIsConnected = await scienceLabCommon.openDevice();
</code_context>
<issue_to_address>
**issue (bug_risk):** `isProcessing` is never reset to false if `attemptToConnectPSLab()` or `openDevice()` fail in the USB attach handler.

If `attemptToConnectPSLab()` returns `false` or any of `openDevice`, `setPSLabVersionIDs`, or `fetchFirmwareVersion` throws, `isProcessing` is never reset. This can leave the provider stuck in a processing state and block later connection attempts (including via the web button). Consider wrapping this logic in a `try`/`finally` to always reset `isProcessing` and notify listeners as needed.
</issue_to_address>

### Comment 2
<location path="lib/view/multimeter_screen.dart" line_range="290-299" />
<code_context>
                 key: const Key(multimeterScreenTitleKey),
-                onOptionsPressed:
-                    provider.isPlayingBack ? null : _showOptionsMenu,
+                onOptionsPressed: () {
+                  if (!provider.isPlayingBack) {
+                    ScaffoldMessenger.of(context).showSnackBar(
+                      SnackBar(
+                          content:
+                              Text("Start playback or connect device first")),
+                    );
+                    return;
+                  }
+
+                  _showOptionsMenu();
+                },
                 onGuidePressed: _showInstrumentGuide,
</code_context>
<issue_to_address>
**question (bug_risk):** The enablement logic for the options menu seems inverted relative to the previous behavior and the snackbar message.

Previously, options were disabled while `isPlayingBack` was true, so they were only usable when *not* playing back. Now the snackbar blocks options when `!provider.isPlayingBack`, effectively reversing the behavior and conflicting with the existing UX. Also, the message mentions "connect device" but the condition only checks `isPlayingBack`. If you intend to allow options when either playback is active or a device is connected, consider encoding that combined condition explicitly and adjusting the message to match it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lib/providers/board_state_provider.dart Outdated
Comment thread lib/view/multimeter_screen.dart
@saad-aids saad-aids force-pushed the fix-web-serial-ui-issue branch from 57647d9 to 4f24070 Compare March 28, 2026 12:22
@saad-aids

Copy link
Copy Markdown
Author

Thanks for the review! 🙌

  • Removed duplicate openDevice() call
  • Ensured isProcessing is always reset using try-finally
  • Fixed multimeter options logic and improved Snackbar message
  • Kept changes minimal and avoided unrelated modifications

Please review again.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes unresponsive instrument UI actions and adjusts the Web USB/Serial connection flow to comply with browser user-gesture requirements, while updating generated platform plugin registrants and refreshing the PSLab description copy across locales.

Changes:

  • Make Multimeter options always tappable, showing feedback instead of disabling the callback.
  • Prevent automatic USB connection on Web; add an explicit user-triggered “CONNECT USB” flow with processing state + SnackBar feedback.
  • Update generated platform plugin registration files and tweak pslabDescription copy in all localizations.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
windows/flutter/generated_plugins.cmake Adds Windows plugin entries (incl. FFI plugin) in generated CMake list.
macos/Flutter/GeneratedPluginRegistrant.swift Updates generated macOS plugin imports/registrations (but currently drops path_provider registration).
linux/flutter/generated_plugins.cmake Updates Linux generated plugin list (switches audio plugin and adds FFI plugin).
linux/flutter/generated_plugin_registrant.cc Updates Linux generated plugin registrant to register record_linux.
lib/view/multimeter_screen.dart Ensures options button remains interactive; shows SnackBar when blocked by playback.
lib/view/connect_device_screen.dart Adds Web-only CONNECT USB button and _connectUSB flow with UI progress state.
lib/providers/board_state_provider.dart Adds connectUSB(), introduces processing state exposure, and skips auto-open on Web in initialize().
lib/l10n/app_localizations.dart Updates the documented English pslabDescription string.
lib/l10n/app_localizations_de.dart Updates pslabDescription copy.
lib/l10n/app_localizations_en.dart Updates pslabDescription copy.
lib/l10n/app_localizations_es.dart Updates pslabDescription copy.
lib/l10n/app_localizations_fr.dart Updates pslabDescription copy.
lib/l10n/app_localizations_he.dart Updates pslabDescription copy.
lib/l10n/app_localizations_hi.dart Updates pslabDescription copy.
lib/l10n/app_localizations_id.dart Updates pslabDescription copy.
lib/l10n/app_localizations_it.dart Updates pslabDescription copy.
lib/l10n/app_localizations_ja.dart Updates pslabDescription copy.
lib/l10n/app_localizations_ml.dart Updates pslabDescription copy.
lib/l10n/app_localizations_my.dart Updates pslabDescription copy.
lib/l10n/app_localizations_nb.dart Updates pslabDescription copy.
lib/l10n/app_localizations_pt.dart Updates pslabDescription copy.
lib/l10n/app_localizations_ru.dart Updates pslabDescription copy.
lib/l10n/app_localizations_te.dart Updates pslabDescription copy.
lib/l10n/app_localizations_uk.dart Updates pslabDescription copy.
lib/l10n/app_localizations_vi.dart Updates pslabDescription copy.
lib/l10n/app_localizations_zh.dart Updates pslabDescription copy.

),
)
: Text(
'CONNECT USB',

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button label 'CONNECT USB' is hard-coded and not localized. Please add a localized string (similar to the WiFi button using appLocalizations.wifi.toUpperCase()) so this text is translated consistently.

Suggested change
'CONNECT USB',
appLocalizations.usb.toUpperCase(),

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 26
int pslabVersion = 0;
int pslabFirmwareVersion = 0;
bool _isProcessing = false;
bool isProcessing = false;

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isProcessing was changed to a public mutable field. This makes it easy for external code to toggle it without calling notifyListeners, which can lead to stale UI state. Prefer keeping a private backing field with a read-only getter (and update it only via provider methods that always notify).

Copilot uses AI. Check for mistakes.
Comment on lines 34 to 96
@@ -80,6 +96,28 @@ class BoardStateProvider extends ChangeNotifier {
});

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialize() is called from MaterialApp.builder and from the main scaffold action (so it can be invoked many times). This method registers new UsbSerial/connectivity listeners on every call and never cancels previous subscriptions, which can cause duplicated event handling and leaks. Please ensure these listeners are registered only once (store StreamSubscriptions and guard/cancel on re-init).

Copilot uses AI. Check for mistakes.
Comment on lines 10 to 26
import file_picker
import geolocator_apple
import package_info_plus
import path_provider_foundation
import record_macos
import share_plus
import shared_preferences_foundation
import url_launcher_macos
import wakelock_plus

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeneratedPluginRegistrant.swift no longer registers PathProviderPlugin, but pubspec.yaml still depends on path_provider and the app imports it (e.g., CsvService). This will likely break path_provider on macOS at runtime. Please re-run plugin generation / fix macOS plugin registration so path_provider_foundation is imported and PathProviderPlugin is registered.

Copilot uses AI. Check for mistakes.
Comment on lines +292 to +294
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Stop playback to use multimeter options"),

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SnackBar text is hard-coded and bypasses the app’s localization system. Please source this message from AppLocalizations (and consider hiding any existing SnackBar before showing a new one, as other parts of this screen do).

Suggested change
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Stop playback to use multimeter options"),
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
appLocalizations.stopPlaybackToUseMultimeterOptions,
),

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +103
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Device connected successfully")),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to connect device")),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_connectUSB awaits provider.connectUSB() and then uses context (ScaffoldMessenger) without checking mounted; this can throw if the widget is disposed during the await. Align this with _connectWifi by adding a mounted check after the await, and consider routing messages through _showSnackBar and AppLocalizations instead of hard-coded strings / raw exception text.

Suggested change
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Device connected successfully")),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Failed to connect device")),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),
if (!mounted) return;
if (success) {
_showSnackBar(
"Device connected successfully",
backgroundColor: Theme.of(context).colorScheme.primary,
);
} else {
_showSnackBar(
"Failed to connect device",
backgroundColor: Theme.of(context).colorScheme.error,
);
}
} catch (e) {
if (!mounted) return;
_showSnackBar(
"Failed to connect device",
backgroundColor: Theme.of(context).colorScheme.error,

Copilot uses AI. Check for mistakes.
@rahul31124

Copy link
Copy Markdown
Member

Closing due to irrelevant changes

@rahul31124 rahul31124 closed this May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Certain elements do nothing when clicked

4 participants