From 7917e13d08d5e3c38afe7ccb85e66b55116666cf Mon Sep 17 00:00:00 2001 From: Anashuman Singh Date: Fri, 17 Jan 2025 17:05:46 +0530 Subject: [PATCH] feat: initialized Oscilloscope Screen --- .gitignore | 19 ++ assets/images/channel_parameters.gif | Bin 0 -> 10949 bytes assets/images/data_analysis.png | Bin 0 -> 15336 bytes assets/images/timebase.png | Bin 0 -> 3272 bytes assets/images/xymode.png | Bin 0 -> 21561 bytes lib/main.dart | 2 + .../oscilloscope_state_provider.dart | 12 + lib/view/instruments_screen.dart | 49 ++- lib/view/oscilloscope_screen.dart | 98 ++++++ lib/view/widgets/applications_list_item.dart | 161 +++++---- .../widgets/channel_parameters_widget.dart | 313 ++++++++++++++++++ lib/view/widgets/common_scaffold_widget.dart | 56 ++-- lib/view/widgets/data_analysis_widget.dart | 276 +++++++++++++++ lib/view/widgets/main_scaffold_widget.dart | 83 +++++ lib/view/widgets/navigation_drawer.dart | 23 +- .../widgets/oscilloscope_screen_tabs.dart | 239 +++++++++++++ lib/view/widgets/timebase_trigger_widget.dart | 194 +++++++++++ lib/view/widgets/xyplot_widget.dart | 111 +++++++ pubspec.lock | 44 ++- pubspec.yaml | 7 +- 20 files changed, 1548 insertions(+), 139 deletions(-) create mode 100644 assets/images/channel_parameters.gif create mode 100644 assets/images/data_analysis.png create mode 100644 assets/images/timebase.png create mode 100644 assets/images/xymode.png create mode 100644 lib/providers/oscilloscope_state_provider.dart create mode 100644 lib/view/oscilloscope_screen.dart create mode 100644 lib/view/widgets/channel_parameters_widget.dart create mode 100644 lib/view/widgets/data_analysis_widget.dart create mode 100644 lib/view/widgets/main_scaffold_widget.dart create mode 100644 lib/view/widgets/oscilloscope_screen_tabs.dart create mode 100644 lib/view/widgets/timebase_trigger_widget.dart create mode 100644 lib/view/widgets/xyplot_widget.dart diff --git a/.gitignore b/.gitignore index d1c5f5329..666fc6a56 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,25 @@ migrate_working_dir/ .pub/ /build/ +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + # Symbolication related app.*.symbols diff --git a/assets/images/channel_parameters.gif b/assets/images/channel_parameters.gif new file mode 100644 index 0000000000000000000000000000000000000000..3871f89ff39652e020f127119067e9571ba8f59e GIT binary patch literal 10949 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

&vb3LQh)fh^L97#Kk=_7t>1a! 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 literal 0 HcmV?d00001 diff --git a/assets/images/data_analysis.png b/assets/images/data_analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..fea9d594f3d831f1dcded093e2272ae6eecd88de GIT binary patch literal 15336 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 g5|{Hx*fN5y&7~O(SY1Kkox$N;olX const InstrumentsScreen(), + '/oscilloscope': (context) => const OscilloscopeScreen(), '/connectDevice': (context) => const ConnectDeviceScreen(), '/faq': (context) => const FAQScreen(), }, diff --git a/lib/providers/oscilloscope_state_provider.dart b/lib/providers/oscilloscope_state_provider.dart new file mode 100644 index 000000000..3172ecee7 --- /dev/null +++ b/lib/providers/oscilloscope_state_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class OscilloscopeStateProvider extends ChangeNotifier { + int _selectedIndex = 0; + + int get selectedIndex => _selectedIndex; + + void updateSelectedIndex(int index) { + _selectedIndex = index; + notifyListeners(); + } +} diff --git a/lib/view/instruments_screen.dart b/lib/view/instruments_screen.dart index 42627ee53..bead05324 100644 --- a/lib/view/instruments_screen.dart +++ b/lib/view/instruments_screen.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:pslab/constants.dart'; import 'package:pslab/view/widgets/applications_list_item.dart'; -import 'package:pslab/view/widgets/common_scaffold_widget.dart'; +import 'package:pslab/view/widgets/main_scaffold_widget.dart'; class InstrumentsScreen extends StatefulWidget { const InstrumentsScreen({super.key}); @@ -11,9 +12,42 @@ class InstrumentsScreen extends StatefulWidget { } class _InstrumentsScreenState extends State { + void _onItemTapped(int index) { + switch (index) { + case 0: + Navigator.pushNamed(context, '/oscilloscope'); + break; + default: + break; + } + } + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + _setOrientation(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + }); + super.initState(); + } + + void _setOrientation() { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + } + + @override + void dispose() { + super.dispose(); + } + @override Widget build(BuildContext context) { - return CommonScaffold( + return MainScaffold( index: 0, title: 'Instruments', body: SafeArea( @@ -22,10 +56,13 @@ class _InstrumentsScreenState extends State { child: ListView.builder( itemCount: instrumentHeadings.length, itemBuilder: (context, index) { - return ApplicationsListItem( - heading: instrumentHeadings[index], - description: instrumentDesc[index], - instrumentIcon: instrumentIcons[index], + return GestureDetector( + onTap: () => _onItemTapped(index), + child: ApplicationsListItem( + heading: instrumentHeadings[index], + description: instrumentDesc[index], + instrumentIcon: instrumentIcons[index], + ), ); }, ), diff --git a/lib/view/oscilloscope_screen.dart b/lib/view/oscilloscope_screen.dart new file mode 100644 index 000000000..455237836 --- /dev/null +++ b/lib/view/oscilloscope_screen.dart @@ -0,0 +1,98 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:pslab/view/widgets/channel_parameters_widget.dart'; +import 'package:pslab/view/widgets/common_scaffold_widget.dart'; +import 'package:pslab/view/widgets/data_analysis_widget.dart'; +import 'package:pslab/view/widgets/oscilloscope_screen_tabs.dart'; +import 'package:pslab/view/widgets/timebase_trigger_widget.dart'; +import 'package:pslab/view/widgets/xyplot_widget.dart'; + +import '../providers/oscilloscope_state_provider.dart'; + +class OscilloscopeScreen extends StatefulWidget { + const OscilloscopeScreen({super.key}); + + @override + State createState() => _OscilloscopeScreenState(); +} + +class _OscilloscopeScreenState extends State { + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + _setLandscapeOrientation(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + }); + super.initState(); + } + + void _setLandscapeOrientation() { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + } + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => OscilloscopeStateProvider()), + ], + child: SafeArea( + child: CommonScaffold( + title: 'Oscilloscope', + body: Container( + margin: const EdgeInsets.only(left: 5, top: 5), + child: Row( + children: [ + Container( + width: 310.w, + margin: const EdgeInsets.only(right: 5), + child: Column( + children: [ + SizedBox( + height: 380.h, + child: LineChart( + LineChartData( + backgroundColor: Colors.black, + titlesData: const FlTitlesData(show: false), + borderData: FlBorderData(show: false), + ), + ), + ), + Expanded( + child: Consumer( + builder: (context, provider, _) { + switch (provider.selectedIndex) { + case 0: + return const ChannelParametersWidget(); + case 1: + return const TimebaseTriggerWidget(); + case 2: + return const DataAnalysisWidget(); // Replace with your widget for Tab 3 + case 3: + return const XYPlotWidget(); + default: + return const ChannelParametersWidget(); + } + }, + ), + ), + ], + ), + ), + const Expanded( + child: OscilloscopeScreenTabs(), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/view/widgets/applications_list_item.dart b/lib/view/widgets/applications_list_item.dart index 3f7ffc0dd..728b2e43e 100644 --- a/lib/view/widgets/applications_list_item.dart +++ b/lib/view/widgets/applications_list_item.dart @@ -16,96 +16,91 @@ class ApplicationsListItem extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - /**/ - }, - child: Card( - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - elevation: 2, - child: Container( - height: 225, - decoration: BoxDecoration( - color: const Color(0xFFD32F2F), - borderRadius: BorderRadius.circular(5)), - child: Stack( - children: [ - Positioned( - top: 0, - bottom: 10, - right: 10, - child: Column( - children: [ - Expanded( - child: Image.asset( - verticalBarsIcon, - width: 100, - fit: BoxFit.fill, - ), - ), - const SizedBox( - height: 100, + return Card( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + elevation: 2, + child: Container( + height: 225, + decoration: BoxDecoration( + color: const Color(0xFFD32F2F), + borderRadius: BorderRadius.circular(5)), + child: Stack( + children: [ + Positioned( + top: 0, + bottom: 10, + right: 10, + child: Column( + children: [ + Expanded( + child: Image.asset( + verticalBarsIcon, width: 100, - ) - ], - ), - ), - Positioned( - right: 10, - bottom: 10, - left: 0, - child: Row( - children: [ - Expanded( - child: Image.asset( - horizontalBarsIcon, - height: 100, - fit: BoxFit.fill, - ), + fit: BoxFit.fill, ), - SizedBox( + ), + const SizedBox( + height: 100, + width: 100, + ) + ], + ), + ), + Positioned( + right: 10, + bottom: 10, + left: 0, + child: Row( + children: [ + Expanded( + child: Image.asset( + horizontalBarsIcon, height: 100, - width: 100, - child: Image.asset( - instrumentIcon, - fit: BoxFit.fill, - ), - ) - ], - ), + fit: BoxFit.fill, + ), + ), + SizedBox( + height: 100, + width: 100, + child: Image.asset( + instrumentIcon, + fit: BoxFit.fill, + ), + ) + ], ), - Positioned( - top: 20, - left: 20, - right: 100, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - heading, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - textAlign: TextAlign.start, + ), + Positioned( + top: 20, + left: 20, + right: 100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + heading, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, ), - Text( - description, - style: const TextStyle( - fontSize: 16, - color: Colors.white, - ), - textAlign: TextAlign.start, + textAlign: TextAlign.start, + ), + Text( + description, + style: const TextStyle( + fontSize: 16, + color: Colors.white, ), - ], - ), + textAlign: TextAlign.start, + ), + ], ), - ], - ), + ), + ], ), ), ); diff --git a/lib/view/widgets/channel_parameters_widget.dart b/lib/view/widgets/channel_parameters_widget.dart new file mode 100644 index 000000000..a37892acd --- /dev/null +++ b/lib/view/widgets/channel_parameters_widget.dart @@ -0,0 +1,313 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ChannelParametersWidget extends StatefulWidget { + const ChannelParametersWidget({super.key}); + + @override + State createState() => _ChannelParametersState(); +} + +class _ChannelParametersState extends State { + bool? isCH1Selected = false; + bool? isCH2Selected = false; + bool? isCH3Selected = false; + bool? isMICSelected = false; + bool? isInBuiltMICSelected = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 5, bottom: 5), + decoration: BoxDecoration( + border: Border.all(width: 1, color: const Color(0xFFD32F2F)), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Positioned( + top: 0.h, + left: 2.w, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: isCH1Selected, + activeColor: const Color(0xFFCE525F), + onChanged: (bool? value) { + setState( + () { + isCH1Selected = value; + }, + ); + }, + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: const Text( + 'CH1', + style: TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + fontSize: 15, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 1.h, left: 3.w), + child: const Text( + 'Range', + style: TextStyle( + color: Color(0xFF424242), + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + fontSize: 14, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 1.h, left: 4.w), + child: DropdownMenu( + initialSelection: '+/-16V', + width: 60.w, + dropdownMenuEntries: [ + '+/-16V', + '+/-8V', + '+/-4V', + '+/-3V', + '+/-2V', + '+/-1.5V', + '+/-1V', + '+/-500mV', + '+/-160V', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 0.h), + child: DropdownMenu( + width: 50.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + 'RES', + 'VOL', + 'CAP', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 15, + ), + ), + ), + ], + ), + ), + Positioned( + left: 2.w, + bottom: 8.h, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: isCH2Selected, + activeColor: const Color(0xFFCE525F), + onChanged: (bool? value) { + setState( + () { + isCH2Selected = value; + }, + ); + }, + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: const Text( + 'CH2', + style: TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + fontSize: 15, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 1.h, left: 3.w), + child: const Text( + 'Range', + style: TextStyle( + color: Color(0xFF424242), + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + fontSize: 14, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 2.h, left: 4.w), + child: SizedBox( + width: 60.w, + child: const Text( + '+/-16V', + style: TextStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 14, + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: SizedBox( + width: 45.w, + child: const Text( + 'CH2', + style: TextStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 15, + ), + ), + ), + ), + ], + ), + ), + Positioned( + top: 16.h, + right: 4.w, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: isCH3Selected, + activeColor: const Color(0xFFCE525F), + onChanged: (bool? value) { + setState( + () { + isCH3Selected = value; + }, + ); + }, + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: const Text( + 'CH3 (+/- 3.3V)', + style: TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + fontSize: 15, + ), + ), + ), + ], + ), + ), + Positioned( + bottom: 8.h, + right: 4.w, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Radio( + activeColor: const Color(0xFFCE525F), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: true, + groupValue: isInBuiltMICSelected, + toggleable: true, + onChanged: (bool? value) { + setState( + () { + if (value == null) { + isInBuiltMICSelected = false; + } else { + isInBuiltMICSelected = value; + isMICSelected = !value; + } + }, + ); + }, + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: const Text( + 'In-Built MIC', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + ), + ), + ), + Radio( + activeColor: const Color(0xFFCE525F), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: true, + groupValue: isMICSelected, + toggleable: true, + onChanged: (bool? value) { + setState( + () { + if (value == null) { + isMICSelected = false; + } else { + isMICSelected = value; + isInBuiltMICSelected = !value; + } + }, + ); + }, + ), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: const Text( + 'PSLab MIC', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/widgets/common_scaffold_widget.dart b/lib/view/widgets/common_scaffold_widget.dart index d4ce62245..adcec7b19 100644 --- a/lib/view/widgets/common_scaffold_widget.dart +++ b/lib/view/widgets/common_scaffold_widget.dart @@ -1,23 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'navigation_drawer.dart'; - class CommonScaffold extends StatefulWidget { final String title; final Widget body; final Key? scaffoldKey; - final int index; final List? actions; - final String icUsbDisconnected = 'assets/icons/ic_usb_disconnected.png'; - const CommonScaffold( - {super.key, - required this.body, - required this.title, - this.scaffoldKey, - this.actions, - required this.index}); + const CommonScaffold({ + super.key, + required this.body, + required this.title, + this.scaffoldKey, + this.actions, + }); @override State createState() => _CommonScaffoldState(); @@ -35,10 +31,19 @@ class _CommonScaffoldState extends State { leading: Builder(builder: (context) { return IconButton( onPressed: () { - Scaffold.of(context).openDrawer(); + if (Navigator.canPop(context) && + ModalRoute.of(context)?.settings.name == '/') { + Navigator.popUntil(context, ModalRoute.withName('/')); + } else { + Navigator.pushNamedAndRemoveUntil( + context, + '/', + (route) => route.isFirst, + ); + } }, icon: const Icon( - Icons.menu, + Icons.arrow_back, color: Colors.white, ), ); @@ -49,35 +54,14 @@ class _CommonScaffoldState extends State { widget.title, style: const TextStyle( color: Colors.white, - fontSize: 19, + fontSize: 15, ), ), actions: [ - IconButton( - icon: Image.asset( - widget.icUsbDisconnected, - width: 24, - height: 24, - ), - onPressed: () { - /**/ - }, - ), - IconButton( - icon: const Icon( - Icons.more_vert, - color: Colors.white, - ), - onPressed: () { - /**/ - }, - ), + if (widget.actions != null) ...widget.actions!, ], ), body: widget.body, - drawer: NavDrawer( - selectedIndex: widget.index, - ), ); } } diff --git a/lib/view/widgets/data_analysis_widget.dart b/lib/view/widgets/data_analysis_widget.dart new file mode 100644 index 000000000..23dd06d33 --- /dev/null +++ b/lib/view/widgets/data_analysis_widget.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class DataAnalysisWidget extends StatefulWidget { + const DataAnalysisWidget({super.key}); + + @override + State createState() => _DataAnalysisState(); +} + +class _DataAnalysisState extends State { + bool? isFourierTransformSelected = false; + double horizontalOffset = 0; + double verticalOffset = 0; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.only(top: 5, bottom: 5, right: 2.5), + decoration: BoxDecoration( + border: Border.all(width: 1, color: const Color(0xFFD32F2F)), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Positioned( + top: 0.h, + left: 2.w, + right: 0.h, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: Checkbox( + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + activeColor: const Color(0xFFCE525F), + value: isFourierTransformSelected, + onChanged: (bool? value) { + setState( + () { + isFourierTransformSelected = value; + }, + ); + }, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: const Text( + 'Fourier Transforms', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + ), + const Spacer(), + DropdownMenu( + width: 45.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + Positioned( + bottom: 0.h, + left: 6.w, + right: 0.h, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + DropdownMenu( + initialSelection: 'Sine Fit', + dropdownMenuEntries: [ + 'Sine Fit', + 'Square Fit', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + const Spacer(), + Padding( + padding: EdgeInsets.only(left: 4.w), + child: DropdownMenu( + width: 45.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + Expanded( + child: Container( + margin: const EdgeInsets.only(top: 5, bottom: 5, left: 2.5), + decoration: BoxDecoration( + border: Border.all(width: 1, color: const Color(0xFFD32F2F)), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Positioned( + bottom: 0.h, + top: 0.h, + left: 6.w, + child: Center( + child: DropdownMenu( + width: 50.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ), + ), + Positioned( + top: 4.h, + right: 4.w, + left: 45.w, + child: Row( + children: [ + Expanded( + child: SliderTheme( + data: const SliderThemeData( + trackHeight: 1, + thumbShape: + RoundSliderThumbShape(enabledThumbRadius: 6), + ), + child: Slider( + activeColor: const Color(0xFFCE525F), + min: -16, + max: 16, + value: verticalOffset, + onChanged: (double value) { + setState( + () { + verticalOffset = value; + }, + ); + }, + ), + ), + ), + Text( + '${verticalOffset.toStringAsFixed(2)} V', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + ], + ), + ), + Positioned( + bottom: 0.h, + right: 4.w, + left: 45.w, + child: Row( + children: [ + Expanded( + child: SliderTheme( + data: const SliderThemeData( + trackHeight: 1, + thumbShape: + RoundSliderThumbShape(enabledThumbRadius: 6), + ), + child: Slider( + activeColor: const Color(0xFFCE525F), + min: 0, + max: 1, + value: horizontalOffset, + onChanged: (double value) { + setState( + () { + horizontalOffset = value; + }, + ); + }, + ), + ), + ), + Text( + '${horizontalOffset.toStringAsFixed(2)} ms', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + ], + ), + ) + ], + ), + ), + ) + ], + ); + } +} diff --git a/lib/view/widgets/main_scaffold_widget.dart b/lib/view/widgets/main_scaffold_widget.dart new file mode 100644 index 000000000..9e50738dc --- /dev/null +++ b/lib/view/widgets/main_scaffold_widget.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'navigation_drawer.dart'; + +class MainScaffold extends StatefulWidget { + final String title; + final Widget body; + final Key? scaffoldKey; + final int index; + final List? actions; + final String icUsbDisconnected = 'assets/icons/ic_usb_disconnected.png'; + + const MainScaffold( + {super.key, + required this.body, + required this.title, + this.scaffoldKey, + this.actions, + required this.index}); + + @override + State createState() => _MainScaffoldState(); +} + +class _MainScaffoldState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + resizeToAvoidBottomInset: true, + appBar: AppBar( + systemOverlayStyle: + const SystemUiOverlayStyle(statusBarColor: Color(0xFFD32F2F)), + leading: Builder(builder: (context) { + return IconButton( + onPressed: () { + Scaffold.of(context).openDrawer(); + }, + icon: const Icon( + Icons.menu, + color: Colors.white, + ), + ); + }), + backgroundColor: const Color(0xFFD32F2F), + title: Text( + key: widget.scaffoldKey, + widget.title, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + actions: [ + IconButton( + icon: Image.asset( + widget.icUsbDisconnected, + width: 24, + height: 24, + ), + onPressed: () { + /**/ + }, + ), + IconButton( + icon: const Icon( + Icons.more_vert, + color: Colors.white, + ), + onPressed: () { + /**/ + }, + ), + ], + ), + body: widget.body, + drawer: NavDrawer( + selectedIndex: widget.index, + ), + ); + } +} diff --git a/lib/view/widgets/navigation_drawer.dart b/lib/view/widgets/navigation_drawer.dart index 8568dbcf6..01b867b22 100644 --- a/lib/view/widgets/navigation_drawer.dart +++ b/lib/view/widgets/navigation_drawer.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:google_fonts/google_fonts.dart'; class NavDrawer extends StatefulWidget { final int selectedIndex; @@ -39,17 +38,17 @@ class _NavDrawerState extends State { fit: BoxFit.contain, ), ), - Padding( - padding: const EdgeInsets.only(top: 16), + const Padding( + padding: EdgeInsets.only(top: 16), child: Text('Not Connected', - style: GoogleFonts.roboto( - fontSize: 14, - )), + style: TextStyle( + fontSize: 14, fontStyle: FontStyle.normal)), ), ], ), ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -81,6 +80,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -101,6 +101,7 @@ class _NavDrawerState extends State { ), const Divider(), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -120,6 +121,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -139,6 +141,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -159,6 +162,7 @@ class _NavDrawerState extends State { ), const Divider(), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -178,6 +182,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -197,6 +202,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -216,6 +222,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -235,6 +242,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -254,6 +262,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -273,6 +282,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( @@ -292,6 +302,7 @@ class _NavDrawerState extends State { }, ), ListTile( + minLeadingWidth: 30.w, focusColor: Colors.grey[350], dense: true, leading: Icon( diff --git a/lib/view/widgets/oscilloscope_screen_tabs.dart b/lib/view/widgets/oscilloscope_screen_tabs.dart new file mode 100644 index 000000000..16c8dd1e9 --- /dev/null +++ b/lib/view/widgets/oscilloscope_screen_tabs.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:pslab/providers/oscilloscope_state_provider.dart'; + +class OscilloscopeScreenTabs extends StatefulWidget { + final String channelParametersImage = 'assets/images/channel_parameters.gif'; + final String dataAnalysisImage = 'assets/images/data_analysis.png'; + final String timebaseTriggerImage = 'assets/images/timebase.png'; + final String xyPlotImage = 'assets/images/xymode.png'; + + const OscilloscopeScreenTabs({super.key}); + + @override + State createState() => _OscilloscopeTabsState(); +} + +class _OscilloscopeTabsState extends State { + @override + Widget build(BuildContext context) { + OscilloscopeStateProvider oscilloscopeStateProvider = + Provider.of(context); + return Container( + margin: const EdgeInsets.only(right: 5, bottom: 5), + decoration: BoxDecoration( + border: Border.all(width: 3, color: const Color(0xFFB1BCBE)), + color: const Color(0xFF7A7A7A), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GestureDetector( + onTap: () { + setState( + () { + oscilloscopeStateProvider.updateSelectedIndex(0); + }, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: const Color(0xF3DBDBDB), + borderRadius: BorderRadius.circular(2), + ), + margin: const EdgeInsets.all(4), + child: Image.asset( + widget.channelParametersImage, + ), + ), + ), + Container( + margin: + const EdgeInsets.symmetric(vertical: 4, horizontal: 2), + color: oscilloscopeStateProvider.selectedIndex == 0 + ? const Color(0xFFC72C2C) + : Colors.transparent, + child: const Text( + 'Channels', + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + color: Colors.black, + fontSize: 9, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold, + ), + ), + ), + const Divider( + color: Color(0xFFB1BCBE), + height: 2, + ) + ], + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + setState( + () { + oscilloscopeStateProvider.updateSelectedIndex(1); + }, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: const Color(0xF3DBDBDB), + borderRadius: BorderRadius.circular(2), + ), + margin: const EdgeInsets.all(4), + child: Image.asset( + widget.timebaseTriggerImage, + ), + ), + ), + Container( + margin: const EdgeInsets.symmetric( + vertical: 4, horizontal: 2), + color: oscilloscopeStateProvider.selectedIndex == 1 + ? const Color(0xFFC72C2C) + : Colors.transparent, + child: const Text( + 'Timebase', + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + color: Colors.black, + fontSize: 9, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold, + ), + )), + const Divider( + color: Color(0xFFB1BCBE), + height: 2, + ) + ], + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + setState( + () { + oscilloscopeStateProvider.updateSelectedIndex(2); + }, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: const Color(0xF3DBDBDB), + borderRadius: BorderRadius.circular(2), + ), + margin: const EdgeInsets.all(4), + child: Image.asset( + widget.dataAnalysisImage, + ), + ), + ), + Container( + margin: const EdgeInsets.symmetric( + vertical: 4, horizontal: 2), + color: oscilloscopeStateProvider.selectedIndex == 2 + ? const Color(0xFFC72C2C) + : Colors.transparent, + child: const Text( + 'Data Analysis', + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + color: Colors.black, + fontSize: 9, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold, + ), + )), + const Divider( + color: Color(0xFFB1BCBE), + height: 2, + ) + ], + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + setState( + () { + oscilloscopeStateProvider.updateSelectedIndex(3); + }, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: const Color(0xF3DBDBDB), + borderRadius: BorderRadius.circular(2), + ), + margin: const EdgeInsets.all(4), + child: Image.asset( + widget.xyPlotImage, + ), + ), + ), + Container( + margin: + const EdgeInsets.symmetric(vertical: 4, horizontal: 2), + color: oscilloscopeStateProvider.selectedIndex == 3 + ? const Color(0xFFC72C2C) + : Colors.transparent, + child: const Text( + 'XY Plot', + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + color: Colors.black, + fontSize: 9, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/widgets/timebase_trigger_widget.dart b/lib/view/widgets/timebase_trigger_widget.dart new file mode 100644 index 000000000..7179b762f --- /dev/null +++ b/lib/view/widgets/timebase_trigger_widget.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class TimebaseTriggerWidget extends StatefulWidget { + const TimebaseTriggerWidget({super.key}); + + @override + State createState() => _TimebaseTriggerState(); +} + +class _TimebaseTriggerState extends State { + double _timebaseSlider = 0; + double triggerValue = 0; + bool? isTriggerChecked = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 5, bottom: 5), + decoration: BoxDecoration( + border: Border.all(width: 1, color: const Color(0xFFD32F2F)), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Positioned( + top: 0.h, + left: 2.w, + right: 0.w, + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + activeColor: const Color(0xFFCE525F), + value: isTriggerChecked, + onChanged: (bool? value) { + setState( + () { + isTriggerChecked = value; + }, + ); + }, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: const Text( + 'Trigger', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 4.w), + child: DropdownMenu( + width: 50.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ), + Expanded( + child: SliderTheme( + data: const SliderThemeData( + trackHeight: 1, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6), + ), + child: Slider( + activeColor: const Color(0xFFCE525F), + min: -16, + max: 16, + value: triggerValue, + onChanged: (double value) { + setState( + () { + triggerValue = value; + }, + ); + }, + ), + ), + ), + Text( + '${triggerValue.toStringAsFixed(1)} V', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + Padding( + padding: EdgeInsets.only(left: 8.w), + child: DropdownMenu( + width: 70.w, + initialSelection: 'Rising Edge', + dropdownMenuEntries: [ + 'Rising Edge', + 'Falling Edge', + 'Dual Edge', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ), + ], + ), + ), + Positioned( + bottom: 0.h, + left: 8.w, + right: 8.w, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text( + 'Timebase', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + ), + ), + Expanded( + child: SliderTheme( + data: const SliderThemeData( + trackHeight: 1, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6), + ), + child: Slider( + activeColor: const Color(0xFFCE525F), + min: 0, + max: 8, + divisions: 8, + value: _timebaseSlider, + onChanged: (double value) { + setState( + () { + _timebaseSlider = value; + }, + ); + }, + ), + ), + ), + Text( + '${_timebaseSlider.toStringAsFixed(2)} ms', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/widgets/xyplot_widget.dart b/lib/view/widgets/xyplot_widget.dart new file mode 100644 index 000000000..d1b4e7d86 --- /dev/null +++ b/lib/view/widgets/xyplot_widget.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class XYPlotWidget extends StatefulWidget { + const XYPlotWidget({super.key}); + + @override + State createState() => _XYPlotState(); +} + +class _XYPlotState extends State { + bool? isXYPlotSelected = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 5, bottom: 5), + decoration: BoxDecoration( + border: Border.all(width: 1, color: const Color(0xFFD32F2F)), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Positioned( + top: 0.h, + left: 2.w, + right: 0.w, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + activeColor: const Color(0xFFCE525F), + value: isXYPlotSelected, + onChanged: (bool? value) { + setState( + () { + isXYPlotSelected = value; + }, + ); + }, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 2.h), + child: const Text( + 'Enable XY Plot', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.normal, + ), + ), + ), + const Spacer(), + DropdownMenu( + width: 50.w, + initialSelection: 'CH1', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + DropdownMenu( + width: 50.w, + initialSelection: 'CH2', + dropdownMenuEntries: [ + 'CH1', + 'CH2', + 'CH3', + 'MIC', + ].map( + (String value) { + return DropdownMenuEntry( + label: value, + value: value, + ); + }, + ).toList(), + inputDecorationTheme: const InputDecorationTheme( + border: InputBorder.none, + ), + textStyle: const TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 37d065117..90d864b1e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "10ddaf334fe84d59333a12d153043e366f243e0bdfff2df0313e1e249f5bf926" + url: "https://pub.dev" + source: hosted + version: "0.70.1" flutter: dependency: "direct main" description: flutter @@ -106,10 +122,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -135,10 +151,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -195,6 +211,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -283,6 +307,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" sky_engine: dependency: transitive description: flutter @@ -356,10 +388,10 @@ packages: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a5b7018c5..25ac38acb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,8 +36,10 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 flutter_screenutil: ^5.9.3 - flutter_svg: ^2.0.16 - google_fonts: 6.2.1 + flutter_svg: ^2.0.17 + google_fonts: ^6.2.1 + fl_chart: ^0.70.1 + provider: ^6.1.2 dev_dependencies: flutter_test: @@ -64,6 +66,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/icons/ + - assets/images/ # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg