Skip to content

feat: added csv functionalities #2785

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 7 commits into
base: flutter
Choose a base branch
from

Conversation

Yugesh-Kumar-S
Copy link
Collaborator

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

Fixes #2782

Changes

  • Added csv import and export functionality.
  • Added reusable csv_service to create , store , delete and read csv files.
  • Logged lux meter data in csv file.

Screenshots / Recordings

screen-20250713-010559.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 end-to-end CSV logging for instruments by adding a CsvService, recording controls in the LuxMeter UI, and new screens to manage, import, and visualize CSV logs, along with required dependencies, permissions, and localization updates

New Features:

  • Add CSV import, export, and sharing support via a new CsvService
  • Enable recording of live lux meter data and saving it to CSV
  • Provide screens to list, import, delete, share, and chart logged CSV data

Enhancements:

  • Extend LuxMeterStateProvider with recording state and timestamped data collection
  • Update CommonScaffold to include a recording button and indicator
  • Add Android storage permissions and legacy external storage support
  • Expand localization with CSV and recording-related messages

Build:

  • Add csv, share_plus, path_provider, and file_picker packages

Copy link

sourcery-ai bot commented Jul 12, 2025

Reviewer's Guide

This PR adds full CSV import/export and real-time data-logging capabilities by introducing a CsvService for file operations and permissions, extending the LuxMeter provider and common scaffold to support recording, integrating recording controls and save dialogs in the LuxMeter screen, and providing new screens for listing, sharing, deleting, importing, and charting logged data. Supporting updates include localization entries, Android storage permissions, and new dependencies.

Sequence diagram for LuxMeter data recording and CSV export

sequenceDiagram
    actor User
    participant LuxMeterScreen
    participant LuxMeterStateProvider
    participant CsvService
    participant Dialog as SaveFileDialog

    User->>LuxMeterScreen: Tap record button
    LuxMeterScreen->>LuxMeterStateProvider: startRecording()
    LuxMeterStateProvider-->>LuxMeterScreen: isRecording = true
    User->>LuxMeterScreen: Tap stop button
    LuxMeterScreen->>LuxMeterStateProvider: stopRecording()
    LuxMeterStateProvider-->>LuxMeterScreen: recordedData
    LuxMeterScreen->>Dialog: Show save file dialog
    Dialog-->>LuxMeterScreen: fileName
    LuxMeterScreen->>CsvService: writeMetaData('luxmeter', data)
    LuxMeterScreen->>CsvService: saveCsvFile('luxmeter', fileName, data)
    CsvService-->>LuxMeterScreen: File (or null)
    LuxMeterScreen-->>User: Show snackbar (success/failure)
Loading

Class diagram for CsvService and LuxMeterStateProvider changes

classDiagram
    class CsvService {
        +Future<Directory> getInstrumentDirectory(String instrumentName)
        +Future<void> requestStoragePermission()
        +Future<File?> saveCsvFile(String instrumentName, String fileName, List<List<dynamic>> data)
        +Future<List<FileSystemEntity>> getSavedFiles(String instrumentName)
        +Future<void> deleteFile(String filePath)
        +Future<void> deleteAllFiles(String instrumentName)
        +Future<void> shareFile(String filePath)
        +Future<List<List<dynamic>>?> pickAndReadCsvFile()
        +Future<List<List<dynamic>>> readCsvFromFile(File file)
        +void writeMetaData(String instrumentName, List<List<dynamic>> data)
    }

    class LuxMeterStateProvider {
        -bool _isRecording
        -List<List<dynamic>> _recordedData
        -double _recordingStartTime
        +bool get isRecording
        +void startRecording()
        +List<List<dynamic>> stopRecording()
    }

    CsvService <.. LuxMeterStateProvider : uses
Loading

File-Level Changes

Change Details Files
Introduce CsvService to handle CSV file operations and storage permissions
  • Implement instrument directory creation with platform-specific storage and permission requests
  • Provide saveCsvFile with auto-naming, metadata insertion, and logging
  • Support listing, deleting (single/all), sharing, and picking/importing CSV files
  • Add methods for reading CSV, writing metadata, and sharing via share_plus
lib/others/csv_service.dart
Extend LuxMeterStateProvider for recording state and data buffering
  • Add isRecording flag, startRecording() and stopRecording() methods
  • Capture timestamped data rows into a buffer during _updateData
  • Limit chart buffer length separately from recorded data
lib/providers/luxmeter_state_provider.dart
Enhance CommonScaffold to display a record/stop button
  • Add onRecordPressed callback and isRecording property
  • Render record icon in AppBar with dynamic tooltip
lib/view/widgets/common_scaffold_widget.dart
Integrate recording controls and logged data navigation in LuxMeterScreen
  • Instantiate CsvService and navigate to LoggedDataScreen
  • Implement _toggleRecording() and save-file dialog flow
  • Wrap scaffold in Consumer to pass recording props
lib/view/luxmeter_screen.dart
Add new screens for managing and charting logged data
  • LoggedDataScreen: list, refresh, delete (single/all), share, import logs via CsvService
  • LoggedDataChartScreen: render interactive FL Chart from imported or saved CSV data
lib/view/logged_data_screen.dart
lib/view/logged_data_chart_screen.dart
Extend localization for CSV and logging UI
  • Add strings for file naming, save/cancel dialogs, errors, delete/import labels
  • Provide English translations for all new keys
lib/l10n/app_localizations.dart
lib/l10n/app_localizations_en.dart
Update AndroidManifest and dependencies for external storage
  • Request READ/WRITE/MANAGE_EXTERNAL_STORAGE and legacy storage in manifest
  • Add csv, share_plus, path_provider, file_picker packages
android/app/src/main/AndroidManifest.xml
pubspec.yaml

Assessment against linked issues

Issue Objective Addressed Explanation
#2782 Implement exporting logged sensor data as .csv files.
#2782 Implement importing .csv files to view the data.

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

@Yugesh-Kumar-S Yugesh-Kumar-S marked this pull request as ready for review July 12, 2025 19:50
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 - here's some feedback:

  • Extract the filename‐input AlertDialog into a shared widget or helper function to avoid duplicating the save‐file dialog logic across instruments.
  • Move the hardcoded record button tooltips (“Start Recording”/“Stop Recording”) into your localization files to ensure they’re fully internationalized.
  • Centralize instrument metadata (icon paths, chart axis labels, CSV directory names) in a single config or model instead of using scattered switch statements and magic indices.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Extract the filename‐input AlertDialog into a shared widget or helper function to avoid duplicating the save‐file dialog logic across instruments.
- Move the hardcoded record button tooltips (“Start Recording”/“Stop Recording”) into your localization files to ensure they’re fully internationalized.
- Centralize instrument metadata (icon paths, chart axis labels, CSV directory names) in a single config or model instead of using scattered switch statements and magic indices.

## Individual Comments

### Comment 1
<location> `lib/others/csv_service.dart:120` </location>
<code_context>
+  Future<void> shareFile(String filePath) async {
+    try {
+      final xFile = XFile(filePath);
+      await SharePlus.instance.share(
+          ShareParams(files: [xFile], text: appLocalizations.sharingMessage));
+    } catch (e) {
</code_context>

<issue_to_address>
Use of SharePlus.instance.share may not be correct for the share_plus package.

Share_plus typically uses Share.share or Share.shareXFiles, not SharePlus.instance.share. Please verify the correct API in the documentation and update the method call.
</issue_to_address>

### Comment 2
<location> `lib/others/csv_service.dart:158` </location>
<code_context>
+    }
+  }
+
+  void writeMetaData(String instrumentName, List<List<dynamic>> data) {
+    final now = DateTime.now();
+    final sdf = DateFormat('yyyy-MM-dd HH:mm:ss');
</code_context>

<issue_to_address>
writeMetaData prepends metadata without checking for existing metadata rows.

If called multiple times, this will add duplicate metadata rows. Please check if the first row is already metadata before prepending.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
  void writeMetaData(String instrumentName, List<List<dynamic>> data) {
    final now = DateTime.now();
    final sdf = DateFormat('yyyy-MM-dd HH:mm:ss');
    final metaDataTime = sdf.format(now);
    final metaData = [
      instrumentName,
      metaDataTime.split(' ')[0],
      metaDataTime.split(' ')[1]
    ];
    data.insert(0, metaData);
  }
=======
  void writeMetaData(String instrumentName, List<List<dynamic>> data) {
    final now = DateTime.now();
    final sdf = DateFormat('yyyy-MM-dd HH:mm:ss');
    final metaDataTime = sdf.format(now);
    final metaData = [
      instrumentName,
      metaDataTime.split(' ')[0],
      metaDataTime.split(' ')[1]
    ];

    bool isMetaDataRow(List<dynamic> row) {
      if (row.length < 3) return false;
      if (row[0] != instrumentName) return false;
      // Check if row[1] is a date and row[2] is a time in expected format
      try {
        DateFormat('yyyy-MM-dd').parseStrict(row[1].toString());
        DateFormat('HH:mm:ss').parseStrict(row[2].toString());
        return true;
      } catch (_) {
        return false;
      }
    }

    if (data.isEmpty || !isMetaDataRow(data[0])) {
      data.insert(0, metaData);
    }
  }
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `lib/view/logged_data_chart_screen.dart:190` </location>
<code_context>
+      final row = widget.data[i];
+      if (row.length > widget.xDataColumnIndex &&
+          row.length > widget.yDataColumnIndex) {
+        final x = (row[widget.xDataColumnIndex] as num).toDouble();
+        final y = (row[widget.yDataColumnIndex] as num).toDouble();
+        spots.add(FlSpot(x, y));
+        if (y > maxY) maxY = y;
</code_context>

<issue_to_address>
Casting to num may throw if the CSV contains strings or nulls.

Add error handling or parsing to manage non-numeric values in the CSV, so the code doesn't throw when encountering invalid data.
</issue_to_address>

### Comment 4
<location> `lib/providers/luxmeter_state_provider.dart:118` </location>
<code_context>
+        final relativeTime = time - _recordingStartTime;
+        final now = DateTime.now();
+        final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
+        _recordedData.add([
+          dateFormat.format(now),
+          relativeTime.toStringAsFixed(2),
+          lux.toStringAsFixed(2),
+        ]);
+      }
</code_context>

<issue_to_address>
Recording uses formatted strings instead of raw values.

Storing formatted strings can hinder future data processing. Store raw numeric values and format them only when displaying or exporting.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        final relativeTime = time - _recordingStartTime;
        final now = DateTime.now();
        final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
        _recordedData.add([
          dateFormat.format(now),
          relativeTime.toStringAsFixed(2),
          lux.toStringAsFixed(2),
        ]);
=======
        final relativeTime = time - _recordingStartTime;
        final now = DateTime.now();
        _recordedData.add([
          now,
          relativeTime,
          lux,
        ]);
>>>>>>> REPLACE

</suggested_fix>

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 +115 to +122
final relativeTime = time - _recordingStartTime;
final now = DateTime.now();
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
_recordedData.add([
dateFormat.format(now),
relativeTime.toStringAsFixed(2),
lux.toStringAsFixed(2),
]);
Copy link

Choose a reason for hiding this comment

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

suggestion: Recording uses formatted strings instead of raw values.

Storing formatted strings can hinder future data processing. Store raw numeric values and format them only when displaying or exporting.

Suggested change
final relativeTime = time - _recordingStartTime;
final now = DateTime.now();
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
_recordedData.add([
dateFormat.format(now),
relativeTime.toStringAsFixed(2),
lux.toStringAsFixed(2),
]);
final relativeTime = time - _recordingStartTime;
final now = DateTime.now();
_recordedData.add([
now,
relativeTime,
lux,
]);

@Yugesh-Kumar-S Yugesh-Kumar-S requested a review from AsCress July 12, 2025 19:58
Copy link

github-actions bot commented Jul 12, 2025

Copy link
Collaborator

@CloudyPadmal CloudyPadmal left a comment

Choose a reason for hiding this comment

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

Looks pretty cool!

@Yugesh-Kumar-S Yugesh-Kumar-S added Status: Review Required Requested reviews from peers and maintainers flutter labels Jul 13, 2025
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.

Implement csv import and export functionality.
2 participants