Skip to content

feat: added configuration screen for luxmeter and stored settings #2769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: flutter
Choose a base branch
from

Conversation

Yugesh-Kumar-S
Copy link
Collaborator

@Yugesh-Kumar-S Yugesh-Kumar-S commented Jul 4, 2025

Part of #2768

Changes

  • Added a configuration screen for lux meter instrument.
  • Stored the settings using provider and shared preferences
  • luxmeter_config.dart file define configuration data .
  • Created reusable config widgets in config_widgets.dart

Screenshots / Recordings

screen-20250705-005818.4.mp4

Checklist:

  • No hard coding: I have used resources from strings.xml, dimens.xml and colors.xml without hard coding any value.
  • No end of file edits: No modifications done at end of resource files strings.xml, dimens.xml or colors.xml.
  • Code reformatting: I have reformatted code and fixed indentation in every file included in this pull request.
  • No extra space: My code does not contain any extra lines or extra spaces than the ones that are necessary.

Summary by Sourcery

Introduce a new configuration screen for the LuxMeter instrument with customizable settings persisted via SharedPreferences and integrated into the app navigation.

New Features:

  • Add LuxMeterConfigScreen for adjusting lux meter parameters (update period, high limit, sensor gain, active sensor, include location data)
  • Persist LuxMeter configuration using SharedPreferences through a LuxMeterConfigProvider

Enhancements:

  • Add options button to LuxMeter screen via CommonScaffold and display a menu to navigate to the configuration screen
  • Register LuxMeterConfigProvider in the app’s Provider tree and GetIt locator
  • Define LuxMeterConfig model with JSON serialization for default and custom settings
  • Introduce string constants and theme color for the configuration UI

Build:

  • Add shared_preferences dependency to pubspec.yaml

Summary by Sourcery

Provide a dedicated configuration screen for the LuxMeter instrument, implement persistence of user-adjustable settings using SharedPreferences, and integrate the configuration provider into the existing LuxMeter workflow and UI

New Features:

  • Add LuxMeterConfigScreen for adjusting lux meter parameters (update period, high limit, sensor gain, sensor type, and location data)
  • Introduce ConfigInputItem, ConfigDropdownItem, and ConfigCheckboxItem widgets to build the configuration UI
  • Implement LuxMeterConfigProvider to load and persist settings via SharedPreferences
  • Define LuxMeterConfig model with JSON serialization and default values

Enhancements:

  • Integrate LuxMeterConfigProvider into LuxMeterScreen using MultiProvider and add an options menu to navigate to the configuration screen
  • Update LuxMeterStateProvider to accept and listen for configuration changes
  • Extend CommonScaffold to include an options button for accessing instrument configurations
  • Add new string constants and theme color for the configuration interface

Build:

  • Add shared_preferences dependency for persisting lux meter settings

Copy link

sourcery-ai bot commented Jul 4, 2025

Reviewer's Guide

Introduces a comprehensive configuration workflow for the LuxMeter instrument by adding a dedicated settings screen with reusable widgets, persisting user preferences via SharedPreferences, integrating a menu-driven navigation in the main LuxMeter UI, and wiring the configuration changes into the existing state provider.

Sequence diagram for persisting and applying LuxMeter configuration changes

sequenceDiagram
    actor User
    participant LuxMeterConfigScreen
    participant LuxMeterConfigProvider
    participant SharedPreferences
    participant LuxMeterStateProvider
    User->LuxMeterConfigScreen: Changes a configuration value
    LuxMeterConfigScreen->LuxMeterConfigProvider: Calls update method
    LuxMeterConfigProvider->SharedPreferences: Saves config to storage
    LuxMeterConfigProvider->LuxMeterStateProvider: Notifies listeners
    LuxMeterStateProvider->LuxMeterConfigProvider: Reads updated config
    LuxMeterStateProvider->LuxMeterScreen: Applies new configuration
Loading

Class diagram for LuxMeter configuration model and provider

classDiagram
    class LuxMeterConfig {
      +int updatePeriod
      +int highLimit
      +String activeSensor
      +int sensorGain
      +bool includeLocationData
      +copyWith()
      +toJson()
      +fromJson()
    }
    class LuxMeterConfigProvider {
      -LuxMeterConfig _config
      +LuxMeterConfig get config
      +updateConfig()
      +updateUpdatePeriod()
      +updateHighLimit()
      +updateActiveSensor()
      +updateSensorGain()
      +updateIncludeLocationData()
      +resetToDefaults()
    }
    LuxMeterConfigProvider --> LuxMeterConfig : manages
Loading

Class diagram for reusable configuration widgets

classDiagram
    class ConfigInputItem {
      +String title
      +String value
      +TextEditingController controller
      +Function(String) onChanged
      +String? hint
    }
    class ConfigDropdownItem {
      +String title
      +String selectedValue
      +List~ConfigOption~ options
      +Function(String) onChanged
    }
    class ConfigCheckboxItem {
      +String title
      +String subtitle
      +bool value
      +Function(bool) onChanged
    }
    class ConfigOption {
      +String value
      +String displayName
    }
    ConfigDropdownItem --> ConfigOption : uses
Loading

File-Level Changes

Change Details Files
Persist LuxMeter settings using SharedPreferences
  • Implement LuxMeterConfigProvider with async load/save logic
  • Define LuxMeterConfig model with JSON (de)serialization and default values
  • Add shared_preferences dependency in pubspec.yaml
lib/providers/luxmeter_config_provider.dart
lib/models/luxmeter_config.dart
pubspec.yaml
Build configuration UI and reusable widget library
  • Create LuxMeterConfigScreen with TextEditingControllers, validation, and Consumer integration
  • Add config_widgets.dart for input, dropdown, and checkbox list items
lib/view/luxmeter_config_screen.dart
lib/view/widgets/config_widgets.dart
Integrate configuration navigation into LuxMeter main screen
  • Extend CommonScaffold to support an options menu and hook onOptionsPressed
  • Add popup menu and navigation to config screen in luxmeter_screen.dart
  • Switch to MultiProvider for state and config providers
  • Introduce new string constants and hint text color
lib/view/luxmeter_screen.dart
lib/view/widgets/common_scaffold_widget.dart
lib/constants.dart
lib/theme/colors.dart
Wire config changes into the LuxMeter state provider
  • Add configProvider field, setter, and listener in LuxMeterStateProvider
  • Invoke removeListener on dispose
  • Link configProvider in initState and pass to the sensor initialization
lib/providers/luxmeter_state_provider.dart

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

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @Yugesh-Kumar-S - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `lib/view/luxmeter_config_screen.dart:197` </location>
<code_context>
+            ),
+            TextButton(
+              onPressed: () {
+                onChanged(controller.text);
+                Navigator.of(context).pop();
+              },
</code_context>

<issue_to_address>
Dialog closes even if input is invalid.

Validate the input before closing the dialog so users can correct errors without needing to reopen it.

Suggested implementation:

```
            TextButton(
              onPressed: () {
                final value = controller.text;
                final intValue = int.tryParse(value);
                if (intValue != null && intValue >= 10 && intValue <= 10000) {
                  onChanged(value);
                  Navigator.of(context).pop();
                } else {
                  // Optionally, show an error message to the user
                  // You may want to setState to display an error below the TextField
                  // For now, do nothing so the dialog stays open
                }
              },

```

If you want to display an error message to the user when the input is invalid, you should add a state variable (e.g., `_inputError`) and update the dialog's content to show the error. This will require converting the dialog to a `StatefulBuilder` or extracting it to a `StatefulWidget`.
</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.

Copy link

github-actions bot commented Jul 4, 2025

@Yugesh-Kumar-S Yugesh-Kumar-S added the Status: Review Required Requested reviews from peers and maintainers label Jul 5, 2025
@Yugesh-Kumar-S Yugesh-Kumar-S requested a review from AsCress July 5, 2025 16:28
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @Yugesh-Kumar-S - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `lib/view/luxmeter_screen.dart:56` </location>
<code_context>
   }

+  void _showOptionsMenu() {
+    showMenu(
+      context: context,
+      position: RelativeRect.fromLTRB(
+        MediaQuery.of(context).size.width,
+        0,
+        0,
+        MediaQuery.of(context).size.height,
+      ),
+      items: [
+        PopupMenuItem(
+          value: 'show_logged_data',
+          child: Text(showLoggedData),
+        ),
+        PopupMenuItem(
+          value: 'lux_meter_config',
+          child: Text(showLuxmeterConfig),
+        ),
+      ],
+      elevation: 8,
+    ).then((value) {
+      if (value != null) {
+        switch (value) {
</code_context>

<issue_to_address>
Options menu positioning may not align with user expectations.

Using the screen's full width and height for `RelativeRect.fromLTRB` may cause the menu to appear in unexpected places on different devices. Anchor the menu to the options button's position for more consistent placement.

Suggested implementation:

```
class _LuxMeterScreenState extends State<LuxMeterScreen> {
  late LuxMeterStateProvider _provider;
  late LuxMeterConfigProvider _configProvider;
  bool _showGuide = false;
  static const imagePath = 'assets/images/bh1750_schematic.png';

  final GlobalKey _optionsButtonKey = GlobalKey();

  void _showInstrumentGuide() {
    ];
  }

  void _showOptionsMenu() {
    final RenderBox button = _optionsButtonKey.currentContext!.findRenderObject() as RenderBox;
    final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
    final Offset buttonPosition = button.localToGlobal(Offset.zero, ancestor: overlay);
    final Size buttonSize = button.size;

    showMenu(
      context: context,
      position: RelativeRect.fromLTRB(
        buttonPosition.dx,
        buttonPosition.dy + buttonSize.height,
        buttonPosition.dx + buttonSize.width,
        buttonPosition.dy,
      ),
      items: [
        PopupMenuItem(
          value: 'show_logged_data',
          child: Text(showLoggedData),
        ),
        PopupMenuItem(
          value: 'lux_meter_config',
          child: Text(showLuxmeterConfig),
        ),
      ],
      elevation: 8,
    ).then((value) {
      if (value != null) {
        switch (value) {
          case 'show_logged_data':
            // TODO
            break;
          case 'lux_meter_config':
            _navigateToConfig();
            break;
        }
      }
    });
  }

```

You must attach `_optionsButtonKey` to the widget (e.g., `IconButton`, `PopupMenuButton`, etc.) that triggers `_showOptionsMenu()`. For example:

```
IconButton(
  key: _optionsButtonKey,
  icon: Icon(Icons.more_vert),
  onPressed: _showOptionsMenu,
)
```

This ensures the menu is anchored to the button's position.
</issue_to_address>

### Comment 2
<location> `lib/providers/luxmeter_state_provider.dart:33` </location>
<code_context>

+  void setConfigProvider(LuxMeterConfigProvider configProvider) {
+    _configProvider = configProvider;
+    _configProvider?.addListener(_onConfigChanged);
+  }
+
+  void _onConfigChanged() {
+    if (_configProvider != null) {
+      // TODO
</code_context>

<issue_to_address>
Config change handler is a placeholder.

Please implement this method to handle configuration changes if they impact sensor behavior or data collection.
</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 on lines +56 to +65
showMenu(
context: context,
position: RelativeRect.fromLTRB(
MediaQuery.of(context).size.width,
0,
0,
MediaQuery.of(context).size.height,
),
items: [
PopupMenuItem(
Copy link

Choose a reason for hiding this comment

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

suggestion: Options menu positioning may not align with user expectations.

Using the screen's full width and height for RelativeRect.fromLTRB may cause the menu to appear in unexpected places on different devices. Anchor the menu to the options button's position for more consistent placement.

Suggested implementation:

class _LuxMeterScreenState extends State<LuxMeterScreen> {
  late LuxMeterStateProvider _provider;
  late LuxMeterConfigProvider _configProvider;
  bool _showGuide = false;
  static const imagePath = 'assets/images/bh1750_schematic.png';

  final GlobalKey _optionsButtonKey = GlobalKey();

  void _showInstrumentGuide() {
    ];
  }

  void _showOptionsMenu() {
    final RenderBox button = _optionsButtonKey.currentContext!.findRenderObject() as RenderBox;
    final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
    final Offset buttonPosition = button.localToGlobal(Offset.zero, ancestor: overlay);
    final Size buttonSize = button.size;

    showMenu(
      context: context,
      position: RelativeRect.fromLTRB(
        buttonPosition.dx,
        buttonPosition.dy + buttonSize.height,
        buttonPosition.dx + buttonSize.width,
        buttonPosition.dy,
      ),
      items: [
        PopupMenuItem(
          value: 'show_logged_data',
          child: Text(showLoggedData),
        ),
        PopupMenuItem(
          value: 'lux_meter_config',
          child: Text(showLuxmeterConfig),
        ),
      ],
      elevation: 8,
    ).then((value) {
      if (value != null) {
        switch (value) {
          case 'show_logged_data':
            // TODO
            break;
          case 'lux_meter_config':
            _navigateToConfig();
            break;
        }
      }
    });
  }

You must attach _optionsButtonKey to the widget (e.g., IconButton, PopupMenuButton, etc.) that triggers _showOptionsMenu(). For example:

IconButton(
  key: _optionsButtonKey,
  icon: Icon(Icons.more_vert),
  onPressed: _showOptionsMenu,
)

This ensures the menu is anchored to the button's position.

providers: [
ChangeNotifierProvider<LuxMeterStateProvider>.value(value: _provider),
ChangeNotifierProvider<LuxMeterConfigProvider>.value(
value: _configProvider),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to do the initialization for the providers in three steps ? Can't we initialize them here:

providers: [
        ChangeNotifierProvider(
          create: (context) => LuxMeterStateProvider(),
        ),
ChangeNotifierProvider(
          create: (context) => LuxMeterConfigProvider(),
        ),
      ],

builder: (context) =>
ChangeNotifierProvider<LuxMeterConfigProvider>.value(
value: _configProvider,
child: const LuxMeterConfigScreen(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar to what I've mentioned. Let's keep the concerns of the providers separated.

@marcnause marcnause self-requested a review July 6, 2025 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flutter Status: Review Required Requested reviews from peers and maintainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants