diff --git a/assets/images/robotic_arm_guide.png b/assets/images/robotic_arm_guide.png new file mode 100644 index 000000000..9ee6df333 Binary files /dev/null and b/assets/images/robotic_arm_guide.png differ diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 21791e28e..4b36d9d80 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -311,40 +311,42 @@ "baroMeterBulletPoint2": "If you want to use the sensor BMP-180, connect the sensor to PSLab device as shown in the figure.", "baroMeterBulletPoint3": "The above pin configuration has to be same except for the pin GND. GND is meant for Ground and any of the PSLab device GND pins can be used since they are common.", "baroMeterBulletPoint4": "Select the sensor by going to the Configure tab from the bottom navigation bar and choose BMP-180 in the drop down menu under Select Sensor.", - "sharingMessage" : "Sharing PSLab Data", - "delete" : "Delete", + "sharingMessage": "Sharing PSLab Data", + "delete": "Delete", "deleteHint": "Are you sure you want to delete this file?", - "deleteFile" : "Delete File", - "deleteAllData" : "Delete All Data", - "deleteCautionMessage" : "Are you sure you want to delete all logged data for this instrument?", - "deleteAll" : "Delete All", - "noLoggedData" : "No logged data found.", - "importLog" : "Import Log", - "failedToSave" : "Failed to save file. No data was recorded.", - "fileSaved" : "File saved", - "save" : "Save", - "enterFileName" : "Enter filename (leave empty for auto-generated name)", - "fileName" : "Filename", - "saveRecording" : "Save Recording", - "recordingStarted" : "Recording started", - "noValidData" : "No valid data to display.", - "csvPickingError" : "Error picking or reading CSV file", - "csvReadingError" : "Error reading CSV from file", - "sharingError" : "Error sharing file", - "csvGettingError" : "Error getting saved files", - "unsupportedPlatform" : "Unsupported platform", - "noDataRecorded" : "No data recorded to save for", - "csvFileSaved" : "CSV file saved at", - "csvSavingError" : "Error saving CSV file", - "csvDeletingError" : "Error deleting file", - "fileDeleted" : "File deleted", - "soundmeterConfig" : "Soundmeter Configurations", - "barometerConfig" : "Barometer Configurations", - "baroUpdatePeriodHint" : "Please provide time interval at which data will be updated (100 ms to 2000 ms)", - "barometerHighLimitHint" : "Please provide the maximum limit of lux value to be recorded (0 atm to 1.10 atm)", - "gyroscopeConfigurations" : "Gyroscope Configurations", - "gyroscopeHighLimitHint" : "Please provide the maximum limit of lux value to be recorded (0 rad/s to 1000 rad/s)", - "accelerometerConfigurations" : "Accelerometer Configurations", - "accelerometerUpdatePeriodHint" : "Please provide time interval at which data will be updated", - "accelerometerHighLimitHint" : "Please provide the maximum limit of lux value to be recorded" + "deleteFile": "Delete File", + "deleteAllData": "Delete All Data", + "deleteCautionMessage": "Are you sure you want to delete all logged data for this instrument?", + "deleteAll": "Delete All", + "noLoggedData": "No logged data found.", + "importLog": "Import Log", + "failedToSave": "Failed to save file. No data was recorded.", + "fileSaved": "File saved", + "save": "Save", + "enterFileName": "Enter filename (leave empty for auto-generated name)", + "fileName": "Filename", + "saveRecording": "Save Recording", + "recordingStarted": "Recording started", + "noValidData": "No valid data to display.", + "csvPickingError": "Error picking or reading CSV file", + "csvReadingError": "Error reading CSV from file", + "sharingError": "Error sharing file", + "csvGettingError": "Error getting saved files", + "unsupportedPlatform": "Unsupported platform", + "noDataRecorded": "No data recorded to save for", + "csvFileSaved": "CSV file saved at", + "csvSavingError": "Error saving CSV file", + "csvDeletingError": "Error deleting file", + "fileDeleted": "File deleted", + "soundmeterConfig": "Soundmeter Configurations", + "barometerConfig": "Barometer Configurations", + "baroUpdatePeriodHint": "Please provide time interval at which data will be updated (100 ms to 2000 ms)", + "barometerHighLimitHint": "Please provide the maximum limit of lux value to be recorded (0 atm to 1.10 atm)", + "gyroscopeConfigurations": "Gyroscope Configurations", + "gyroscopeHighLimitHint": "Please provide the maximum limit of lux value to be recorded (0 rad/s to 1000 rad/s)", + "accelerometerConfigurations": "Accelerometer Configurations", + "accelerometerUpdatePeriodHint": "Please provide time interval at which data will be updated", + "accelerometerHighLimitHint": "Please provide the maximum limit of lux value to be recorded", + "roboticArmIntro": "• A robotic arm is a programmable mechanical device that mimics the movement of a human arm.\n• It uses servo motors to control its motion, and these motors are operated using PWM signals.\n• The PSLab provides four PWM square wave generators (SQ1, SQ2, SQ3, SQ4), allowing control of up to four servo motors and enabling a robotic arm with up to four degrees of freedom.", + "roboticArmConnection": "• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo's GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement." } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4ceae67bd..438081543 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2175,6 +2175,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Please provide the maximum limit of lux value to be recorded'** String get accelerometerHighLimitHint; + + /// No description provided for @roboticArmIntro. + /// + /// In en, this message translates to: + /// **'• A robotic arm is a programmable mechanical device that mimics the movement of a human arm.\n• It uses servo motors to control its motion, and these motors are operated using PWM signals.\n• The PSLab provides four PWM square wave generators (SQ1, SQ2, SQ3, SQ4), allowing control of up to four servo motors and enabling a robotic arm with up to four degrees of freedom.'** + String get roboticArmIntro; + + /// No description provided for @roboticArmConnection. + /// + /// In en, this message translates to: + /// **'• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo\'s GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement.'** + String get roboticArmConnection; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f8bcc9f42..c114ad842 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1115,4 +1115,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get accelerometerHighLimitHint => 'Please provide the maximum limit of lux value to be recorded'; + + @override + String get roboticArmIntro => + '• A robotic arm is a programmable mechanical device that mimics the movement of a human arm.\n• It uses servo motors to control its motion, and these motors are operated using PWM signals.\n• The PSLab provides four PWM square wave generators (SQ1, SQ2, SQ3, SQ4), allowing control of up to four servo motors and enabling a robotic arm with up to four degrees of freedom.'; + + @override + String get roboticArmConnection => + '• In the above figure, SQ1 is connected to the signal pin of the first servo motor. The servo\'s GND pin is connected to both the PSLab’s GND and the external power supply GND, while the VCC pin is connected to the external power supply VCC.\n• Similarly, connect the remaining servos to SQ2, SQ3, and SQ4 along with their respective GND and power supply connections.\n• Once connected, each servo can be controlled using either circular sliders for manual control or a timeline-based sequence for automated movement.'; } diff --git a/lib/view/robotic_arm_screen.dart b/lib/view/robotic_arm_screen.dart index e31ec49ad..5c21e7762 100644 --- a/lib/view/robotic_arm_screen.dart +++ b/lib/view/robotic_arm_screen.dart @@ -8,6 +8,7 @@ import 'package:pslab/view/widgets/robotic_arm_dialog.dart'; import 'package:pslab/view/widgets/robotic_arm_summary.dart'; import 'package:pslab/view/widgets/robotic_arm_timeline.dart'; import '../providers/robotic_arm_state_provider.dart'; +import 'package:pslab/view/widgets/guide_widget.dart'; import 'widgets/servo_card.dart'; class RoboticArmScreen extends StatefulWidget { @@ -20,6 +21,8 @@ class RoboticArmScreen extends StatefulWidget { class _RoboticArmScreenState extends State { late RoboticArmStateProvider provider; late List servoLabels; + bool _showGuide = false; + static const imagePath = 'assets/images/robotic_arm_guide.png'; AppLocalizations appLocalizations = getIt.get(); @override void initState() { @@ -46,6 +49,26 @@ class _RoboticArmScreenState extends State { }; } + void _hideInstrumentGuide() { + setState(() { + _showGuide = false; + }); + } + + List _getRoboticArmContent() { + return [ + InstrumentIntroText( + text: appLocalizations.roboticArmIntro, + ), + const InstrumentImage( + imagePath: imagePath, + ), + InstrumentIntroText( + text: appLocalizations.roboticArmConnection, + ), + ]; + } + void _showAngleInputDialog(BuildContext context, int index) { final currentValue = provider.servoValues[index]; @@ -83,7 +106,7 @@ class _RoboticArmScreenState extends State { final screenWidth = MediaQuery.of(context).size.width; final scrollAmount = (screenWidth / 6); return CommonScaffold( - title: appLocalizations.roboticArm, + title: appLocalizations.roboticArmTitle, actions: [ IconButton( icon: Icon( @@ -160,12 +183,16 @@ class _RoboticArmScreenState extends State { IconButton( icon: const Icon(Icons.save, color: Colors.white), tooltip: appLocalizations.saveData, - onPressed: () {}, //TODO + onPressed: () {}, // TODO ), IconButton( icon: const Icon(Icons.info, color: Colors.white), tooltip: appLocalizations.showGuide, - onPressed: () {}, //TODO + onPressed: () { + setState(() { + _showGuide = !_showGuide; + }); + }, ), PopupMenuButton( icon: const Icon(Icons.more_vert, color: Colors.white), @@ -182,67 +209,78 @@ class _RoboticArmScreenState extends State { ], ), ], - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: servoHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(4, (index) { - return Expanded( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 1), - child: SizedBox( - height: servoHeight, - child: ServoCard( - value: provider.servoValues[index], - label: servoLabels[index], - servoId: index, - onChanged: (val) { - setState(() { - provider.updateServoValue(index, val); - }); - }, - onTap: () => - _showAngleInputDialog(context, index), - cardHeight: servoHeight, + body: Stack( + children: [ + SafeArea( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: servoHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(4, (index) { + return Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 1), + child: SizedBox( + height: servoHeight, + child: ServoCard( + value: provider.servoValues[index], + label: servoLabels[index], + servoId: index, + onChanged: (val) { + setState(() { + provider.updateServoValue(index, val); + }); + }, + onTap: () => + _showAngleInputDialog(context, index), + cardHeight: servoHeight, + ), + ), ), - ), + ); + }), + ), + ), + const SizedBox(height: 2), + Expanded( + child: Scrollbar( + controller: provider.timelineScrollController, + thumbVisibility: true, + thickness: screenHeight * 0.006, + radius: const Radius.circular(4), + child: TimelineScrollView( + totalTimelineItems: provider.totalTimelineItems, + screenHeight: screenHeight, + timelinePosition: provider.timelinePosition, + timelineDegrees: provider.timelineDegrees, + scrollController: + provider.timelineScrollController, + onUpdate: (index, servo, value) { + setState(() { + provider.updateTimelineDegree( + index, servo, value); + }); + }, ), - ); - }), - ), + ), + ) + ], ), - const SizedBox(height: 2), - Expanded( - child: Scrollbar( - controller: provider.timelineScrollController, - thumbVisibility: true, - thickness: screenHeight * 0.006, - radius: const Radius.circular(4), - child: TimelineScrollView( - totalTimelineItems: provider.totalTimelineItems, - screenHeight: screenHeight, - timelinePosition: provider.timelinePosition, - timelineDegrees: provider.timelineDegrees, - scrollController: provider.timelineScrollController, - onUpdate: (index, servo, value) { - setState(() { - provider.updateTimelineDegree( - index, servo, value); - }); - }, - ), - ), - ) - ], + ), ), - ), + if (_showGuide) + InstrumentOverviewDrawer( + instrumentName: appLocalizations.roboticArmTitle, + content: _getRoboticArmContent(), + onHide: _hideInstrumentGuide, + ), + ], ), ); },