-
Notifications
You must be signed in to change notification settings - Fork 816
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
base: flutter
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis 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 exportsequenceDiagram
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)
Class diagram for CsvService and LuxMeterStateProvider changesclassDiagram
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
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this 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>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
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), | ||
]); |
There was a problem hiding this comment.
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.
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, | |
]); |
Build successful. APKs to test: https://github.com/fossasia/pslab-android/actions/runs/16248530925/artifacts/3521285329 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty cool!
Fixes #2782
Changes
Screenshots / Recordings
screen-20250713-010559.mp4
Checklist:
strings.xml
,dimens.xml
andcolors.xml
without hard coding any value.strings.xml
,dimens.xml
orcolors.xml
.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:
Enhancements:
Build: