From 84a4da0520614643d4b31ce6329633af6e551934 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Tue, 5 Aug 2025 22:38:25 +0530 Subject: [PATCH 1/5] csv functionality for gyroscope --- android/app/src/main/AndroidManifest.xml | 1 - lib/others/csv_service.dart | 15 +- lib/providers/gyroscope_state_provider.dart | 41 +++++ lib/view/gyroscope_screen.dart | 181 ++++++++++++++++---- lib/view/logged_data_chart_screen.dart | 63 +++++-- lib/view/logged_data_screen.dart | 4 +- 6 files changed, 242 insertions(+), 63 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e01bfbba7..f84359346 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ - getInstrumentDirectory(String instrumentName) async { if (Platform.isAndroid) { - await requestStoragePermission(); - final directory = - Directory('/storage/emulated/0/Android/media/PSLab/$instrumentName'); + final externalDir = await getExternalStorageDirectory(); + final directory = Directory('${externalDir?.path}/PSLab/$instrumentName'); if (!await directory.exists()) { await directory.create(recursive: true); } @@ -38,15 +36,6 @@ class CsvService { } } - Future requestStoragePermission() async { - if (Platform.isAndroid) { - final status = await Permission.manageExternalStorage.request(); - if (!status.isGranted) { - await openAppSettings(); - } - } - } - Future saveCsvFile( String instrumentName, String fileName, List> data) async { try { diff --git a/lib/providers/gyroscope_state_provider.dart b/lib/providers/gyroscope_state_provider.dart index 64f9e7196..542718292 100644 --- a/lib/providers/gyroscope_state_provider.dart +++ b/lib/providers/gyroscope_state_provider.dart @@ -4,6 +4,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/foundation.dart'; import 'package:sensors_plus/sensors_plus.dart'; import 'package:pslab/others/logger_service.dart'; +import 'package:intl/intl.dart'; class GyroscopeProvider extends ChangeNotifier { StreamSubscription? _gyroscopeSubscription; @@ -22,6 +23,9 @@ class GyroscopeProvider extends ChangeNotifier { double _yMin = 0, _yMax = 0; double _zMin = 0, _zMax = 0; + bool _isRecording = false; + List> _recordedData = []; + double get xValue => _gyroscopeEvent.x; double get yValue => _gyroscopeEvent.y; double get zValue => _gyroscopeEvent.z; @@ -34,6 +38,7 @@ class GyroscopeProvider extends ChangeNotifier { double get zMax => _zMax; bool get isListening => _gyroscopeSubscription != null; + bool get isRecording => _isRecording; void initializeSensors() { if (_gyroscopeSubscription != null) return; @@ -61,6 +66,20 @@ class GyroscopeProvider extends ChangeNotifier { final y = _gyroscopeEvent.y; final z = _gyroscopeEvent.z; + if (_isRecording) { + final now = DateTime.now(); + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS'); + _recordedData.add([ + now.millisecondsSinceEpoch.toString(), + dateFormat.format(now), + x.toStringAsFixed(6), + y.toStringAsFixed(6), + z.toStringAsFixed(6), + 0, + 0 + ]); + } + _xData.add(x); _yData.add(y); _zData.add(z); @@ -88,6 +107,28 @@ class GyroscopeProvider extends ChangeNotifier { notifyListeners(); } + void startRecording() { + _isRecording = true; + _recordedData = [ + [ + 'Timestamp', + 'DateTime', + 'ReadingsX', + 'ReadingsY', + 'ReadingsZ', + 'Latitude', + 'Longitude' + ] + ]; + notifyListeners(); + } + + List> stopRecording() { + _isRecording = false; + notifyListeners(); + return _recordedData; + } + List getAxisData(String axis) { switch (axis) { case 'x': diff --git a/lib/view/gyroscope_screen.dart b/lib/view/gyroscope_screen.dart index b3039c2f5..ce8a41588 100644 --- a/lib/view/gyroscope_screen.dart +++ b/lib/view/gyroscope_screen.dart @@ -7,7 +7,10 @@ import 'package:pslab/view/widgets/gyroscope_card.dart'; import 'package:pslab/view/widgets/common_scaffold_widget.dart'; import 'package:pslab/l10n/app_localizations.dart'; import 'package:pslab/providers/locator.dart'; +import 'package:pslab/others/csv_service.dart'; +import 'package:pslab/view/logged_data_screen.dart'; import '../theme/colors.dart'; +import '../constants.dart'; import 'gyroscope_config_screen.dart'; class GyroscopeScreen extends StatefulWidget { @@ -21,6 +24,27 @@ class _GyroscopeScreenState extends State { AppLocalizations appLocalizations = getIt.get(); bool _showGuide = false; static const imagePath = 'assets/images/gyroscope_axes_orientation.png'; + final CsvService _csvService = CsvService(); + late GyroscopeProvider _provider; + + @override + void initState() { + super.initState(); + _provider = GyroscopeProvider(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _provider.initializeSensors(); + } + }); + } + + @override + void dispose() { + _provider.disposeSensors(); + _provider.dispose(); + super.dispose(); + } + void _showInstrumentGuide() { setState(() { _showGuide = true; @@ -72,7 +96,7 @@ class _GyroscopeScreenState extends State { if (value != null) { switch (value) { case 'show_logged_data': - // TODO + _navigateToLoggedData(); break; case 'gyroscope_config': _navigateToConfig(); @@ -82,6 +106,19 @@ class _GyroscopeScreenState extends State { }); } + Future _navigateToLoggedData() async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoggedDataScreen( + instrumentName: 'gyroscope', + appBarName: 'Gyroscope', + instrumentIcon: instrumentIcons[10], + ), + ), + ); + } + void _navigateToConfig() { Navigator.push( context, @@ -93,40 +130,122 @@ class _GyroscopeScreenState extends State { )); } + Future _toggleRecording() async { + if (_provider.isRecording) { + final data = _provider.stopRecording(); + await _showSaveFileDialog(data); + } else { + _provider.startRecording(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${appLocalizations.recordingStarted}...', + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } + } + + Future _showSaveFileDialog(List> data) async { + final TextEditingController filenameController = TextEditingController(); + final String defaultFilename = ''; + filenameController.text = defaultFilename; + + final String? fileName = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(appLocalizations.saveRecording), + content: TextField( + controller: filenameController, + decoration: InputDecoration( + hintText: appLocalizations.enterFileName, + labelText: appLocalizations.fileName, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(appLocalizations.cancel.toUpperCase()), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context, filenameController.text); + }, + child: Text(appLocalizations.save), + ), + ], + ); + }, + ); + + if (fileName != null) { + _csvService.writeMetaData('gyroscope', data); + final file = await _csvService.saveCsvFile('gyroscope', fileName, data); + if (mounted) { + if (file != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${appLocalizations.fileSaved}: ${file.path.split('/').last}', + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + appLocalizations.failedToSave, + style: TextStyle(color: snackBarContentColor), + ), + backgroundColor: snackBarBackgroundColor, + ), + ); + } + } + } + } + @override Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (_) => GyroscopeProvider()..initializeSensors(), - ), - ], + return ChangeNotifierProvider.value( + value: _provider, child: Stack(children: [ - CommonScaffold( - title: appLocalizations.gyroscopeTitle, - onGuidePressed: _showInstrumentGuide, - onOptionsPressed: _showOptionsMenu, - body: SafeArea( - child: Column( - children: [ - Expanded( - child: GyroscopeCard( - color: xOrientationChartLineColor, - axis: appLocalizations.xAxis), + Consumer( + builder: (context, provider, child) { + return CommonScaffold( + title: appLocalizations.gyroscopeTitle, + onGuidePressed: _showInstrumentGuide, + onOptionsPressed: _showOptionsMenu, + onRecordPressed: _toggleRecording, + isRecording: provider.isRecording, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: GyroscopeCard( + color: xOrientationChartLineColor, + axis: appLocalizations.xAxis), + ), + Expanded( + child: GyroscopeCard( + color: yOrientationChartLineColor, + axis: appLocalizations.yAxis), + ), + Expanded( + child: GyroscopeCard( + color: zOrientationChartLineColor, + axis: appLocalizations.zAxis), + ), + ], ), - Expanded( - child: GyroscopeCard( - color: yOrientationChartLineColor, - axis: appLocalizations.yAxis), - ), - Expanded( - child: GyroscopeCard( - color: zOrientationChartLineColor, - axis: appLocalizations.zAxis), - ), - ], - ), - ), + ), + ); + }, ), if (_showGuide) InstrumentOverviewDrawer( diff --git a/lib/view/logged_data_chart_screen.dart b/lib/view/logged_data_chart_screen.dart index 01fa0a56a..a84cb6252 100644 --- a/lib/view/logged_data_chart_screen.dart +++ b/lib/view/logged_data_chart_screen.dart @@ -20,7 +20,7 @@ class LoggedDataChartScreen extends StatefulWidget { required this.fileName, this.xAxisLabel = 'Time (s)', this.yAxisLabel = 'Value', - this.xDataColumnIndex = 1, + this.xDataColumnIndex = 0, this.yDataColumnIndex = 2, }); @@ -105,18 +105,7 @@ class _LoggedDataChartScreenState extends State { sideTitles: SideTitles( showTitles: true, reservedSize: reservedSizeBottom, - getTitlesWidget: (value, meta) { - return SideTitleWidget( - meta: meta, - child: Text( - value.toStringAsFixed(1), - style: TextStyle( - color: blackTextColor, - fontSize: chartFontSize, - ), - ), - ); - }, + getTitlesWidget: _sideTitleWidgets, interval: timeInterval, ), ), @@ -195,6 +184,7 @@ class _LoggedDataChartScreenState extends State { double maxY = 0; double maxX = 0; double minX = 0; + double? startTime; for (int i = 1; i < widget.data.length; i++) { final row = widget.data[i]; @@ -204,10 +194,16 @@ class _LoggedDataChartScreenState extends State { final yValue = _parseDouble(row[widget.yDataColumnIndex]); if (xValue != null && yValue != null) { - spots.add(FlSpot(xValue, yValue)); + if (startTime == null) { + startTime = xValue; + minX = 0; + } + + final relativeTime = ((xValue - startTime) / 1000.0); + + spots.add(FlSpot(relativeTime, yValue)); if (yValue > maxY) maxY = yValue; - if (xValue > maxX) maxX = xValue; - if (spots.length == 1 || xValue < minX) minX = xValue; + if (relativeTime > maxX) maxX = relativeTime; } } } @@ -252,4 +248,39 @@ class _LoggedDataChartScreenState extends State { ), ); } + + Widget _sideTitleWidgets(double value, TitleMeta meta) { + final screenWidth = MediaQuery.of(context).size.width; + final fontSize = screenWidth < 400 + ? 7.0 + : screenWidth < 600 + ? 8.0 + : 9.0; + final style = TextStyle( + color: blackTextColor, + fontSize: fontSize, + ); + + String timeText; + if (value < 60) { + timeText = '${value.toInt()}s'; + } else if (value < 3600) { + int minutes = (value / 60).floor(); + int seconds = (value % 60).toInt(); + timeText = '${minutes}m${seconds}s'; + } else { + int hours = (value / 3600).floor(); + int minutes = ((value % 3600) / 60).floor(); + timeText = '${hours}h${minutes}m'; + } + + return SideTitleWidget( + meta: meta, + child: Text( + maxLines: 1, + timeText, + style: style, + ), + ); + } } diff --git a/lib/view/logged_data_screen.dart b/lib/view/logged_data_screen.dart index 9fd151c50..fb3a97b37 100644 --- a/lib/view/logged_data_screen.dart +++ b/lib/view/logged_data_screen.dart @@ -112,14 +112,14 @@ class _LoggedDataScreenState extends State { return { 'xAxisLabel': appLocalizations.timeAxisLabel, 'yAxisLabel': appLocalizations.lx, - 'xDataColumnIndex': 1, + 'xDataColumnIndex': 0, 'yDataColumnIndex': 2, }; case 'soundmeter': return { 'xAxisLabel': appLocalizations.timeAxisLabel, 'yAxisLabel': appLocalizations.db, - 'xDataColumnIndex': 1, + 'xDataColumnIndex': 0, 'yDataColumnIndex': 2, }; case 'barometer': From 314e72d83fb0137fa3d13ad1a2a749f742b5f8c8 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Wed, 6 Aug 2025 22:28:00 +0530 Subject: [PATCH 2/5] adapted chart to handle negative values correclty --- lib/view/logged_data_chart_screen.dart | 53 ++++++++++++++++++++------ 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/lib/view/logged_data_chart_screen.dart b/lib/view/logged_data_chart_screen.dart index a84cb6252..db6103f96 100644 --- a/lib/view/logged_data_chart_screen.dart +++ b/lib/view/logged_data_chart_screen.dart @@ -55,6 +55,14 @@ class _LoggedDataChartScreenState extends State { return interval > 0 ? interval : 1.0; } + double _getSafeYInterval(double minValue, double maxValue, + {int divisions = 5}) { + final double range = maxValue - minValue; + if (range <= 0) return 1.0; + final double interval = (range / divisions).ceilToDouble(); + return interval > 0 ? interval : 1.0; + } + double? _parseDouble(dynamic value) { if (value == null) return null; if (value is num) return value.toDouble(); @@ -68,8 +76,8 @@ class _LoggedDataChartScreenState extends State { return null; } - Widget _buildChart(double screenWidth, double maxY, double maxX, double minX, - double timeInterval, List spots) { + Widget _buildChart(double screenWidth, double minY, double maxY, double maxX, + double minX, double timeInterval, double yInterval, List spots) { final chartFontSize = screenWidth < 400 ? 8.0 : screenWidth < 600 @@ -77,7 +85,7 @@ class _LoggedDataChartScreenState extends State { : 10.0; final axisNameFontSize = screenWidth < 400 ? 9.0 : 10.0; final reservedSizeBottom = screenWidth < 400 ? 25.0 : 30.0; - final reservedSizeLeft = screenWidth < 400 ? 27.0 : 30.0; + final reservedSizeLeft = screenWidth < 400 ? 35.0 : 40.0; final reservedSizeRight = screenWidth < 400 ? 27.0 : 30.0; return Padding( @@ -125,7 +133,7 @@ class _LoggedDataChartScreenState extends State { return SideTitleWidget( meta: meta, child: Text( - value.toInt().toString(), + value.toStringAsFixed(value.abs() < 10 ? 1 : 0), style: TextStyle( color: blackTextColor, fontSize: chartFontSize, @@ -133,7 +141,7 @@ class _LoggedDataChartScreenState extends State { ), ); }, - interval: maxY > 0 ? (maxY / 5).ceilToDouble() : 10, + interval: yInterval, ), ), rightTitles: AxisTitles( @@ -145,7 +153,7 @@ class _LoggedDataChartScreenState extends State { show: true, drawHorizontalLine: true, drawVerticalLine: true, - horizontalInterval: maxY > 0 ? (maxY / 5).ceilToDouble() : 10, + horizontalInterval: yInterval, verticalInterval: timeInterval, ), borderData: FlBorderData( @@ -157,8 +165,8 @@ class _LoggedDataChartScreenState extends State { right: BorderSide(color: chartBorderColor), ), ), - minY: 0, - maxY: maxY > 0 ? (maxY * 1.1) : 100, + minY: minY, + maxY: maxY, maxX: maxX > 0 ? maxX : 10, minX: minX, clipData: const FlClipData.all(), @@ -181,7 +189,8 @@ class _LoggedDataChartScreenState extends State { @override Widget build(BuildContext context) { final List spots = []; - double maxY = 0; + double maxY = double.negativeInfinity; + double minY = double.infinity; double maxX = 0; double minX = 0; double? startTime; @@ -202,14 +211,36 @@ class _LoggedDataChartScreenState extends State { final relativeTime = ((xValue - startTime) / 1000.0); spots.add(FlSpot(relativeTime, yValue)); + if (yValue > maxY) maxY = yValue; + if (yValue < minY) minY = yValue; if (relativeTime > maxX) maxX = relativeTime; } } } + if (spots.isEmpty) { + minY = 0; + maxY = 100; + } else if (minY == maxY) { + final padding = minY.abs() * 0.1; + if (padding == 0) { + minY = -1; + maxY = 1; + } else { + minY -= padding; + maxY += padding; + } + } else { + final range = maxY - minY; + final padding = range * 0.1; + minY -= padding; + maxY += padding; + } + final screenWidth = MediaQuery.of(context).size.width; final timeInterval = _getSafeInterval(maxX, divisions: 10); + final yInterval = _getSafeYInterval(minY, maxY, divisions: 5); return Scaffold( backgroundColor: scaffoldBackgroundColor, @@ -240,8 +271,8 @@ class _LoggedDataChartScreenState extends State { MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.bottom - 48, - child: _buildChart( - screenWidth, maxY, maxX, minX, timeInterval, spots), + child: _buildChart(screenWidth, minY, maxY, maxX, minX, + timeInterval, yInterval, spots), ), ), ), From 690f32733f0aa696cc20338e0ac875c09b98fe37 Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Wed, 6 Aug 2025 22:54:28 +0530 Subject: [PATCH 3/5] added option to view x,y and z axis gyroscope data separately in logged data chart screen --- lib/view/logged_data_chart_screen.dart | 55 +++++++++++++++++++++++++- lib/view/logged_data_screen.dart | 6 ++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/lib/view/logged_data_chart_screen.dart b/lib/view/logged_data_chart_screen.dart index db6103f96..2c88ec09d 100644 --- a/lib/view/logged_data_chart_screen.dart +++ b/lib/view/logged_data_chart_screen.dart @@ -13,6 +13,7 @@ class LoggedDataChartScreen extends StatefulWidget { final String yAxisLabel; final int xDataColumnIndex; final int yDataColumnIndex; + final String? instrumentName; const LoggedDataChartScreen({ super.key, @@ -22,6 +23,7 @@ class LoggedDataChartScreen extends StatefulWidget { this.yAxisLabel = 'Value', this.xDataColumnIndex = 0, this.yDataColumnIndex = 2, + this.instrumentName, }); @override @@ -30,6 +32,27 @@ class LoggedDataChartScreen extends StatefulWidget { class _LoggedDataChartScreenState extends State { AppLocalizations appLocalizations = getIt.get(); + String selectedAxis = 'x'; + bool get _shouldShowAxisSelector { + return widget.instrumentName?.toLowerCase() == 'gyroscope' || + widget.instrumentName?.toLowerCase() == 'accelerometer'; + } + + int _getYDataColumnIndex() { + if (_shouldShowAxisSelector) { + switch (selectedAxis) { + case 'x': + return 2; + case 'y': + return 3; + case 'z': + return 4; + default: + return 2; + } + } + return widget.yDataColumnIndex; + } @override void initState() { @@ -200,7 +223,7 @@ class _LoggedDataChartScreenState extends State { if (row.length > widget.xDataColumnIndex && row.length > widget.yDataColumnIndex) { final xValue = _parseDouble(row[widget.xDataColumnIndex]); - final yValue = _parseDouble(row[widget.yDataColumnIndex]); + final yValue = _parseDouble(row[_getYDataColumnIndex()]); if (xValue != null && yValue != null) { if (startTime == null) { @@ -245,6 +268,36 @@ class _LoggedDataChartScreenState extends State { return Scaffold( backgroundColor: scaffoldBackgroundColor, appBar: AppBar( + actions: _shouldShowAxisSelector + ? [ + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: DropdownButton( + value: selectedAxis, + dropdownColor: primaryRed, + underline: Container(), + icon: + Icon(Icons.arrow_drop_down, color: appBarContentColor), + items: ['x', 'y', 'z'].map((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value.toUpperCase(), + style: TextStyle(color: appBarContentColor), + ), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + setState(() { + selectedAxis = newValue; + }); + } + }, + ), + ), + ] + : null, title: Text( widget.fileName, style: TextStyle(color: appBarContentColor, fontSize: 15), diff --git a/lib/view/logged_data_screen.dart b/lib/view/logged_data_screen.dart index fb3a97b37..cdb05c530 100644 --- a/lib/view/logged_data_screen.dart +++ b/lib/view/logged_data_screen.dart @@ -126,14 +126,14 @@ class _LoggedDataScreenState extends State { return { 'xAxisLabel': appLocalizations.timeAxisLabel, 'yAxisLabel': appLocalizations.atm, - 'xDataColumnIndex': 1, + 'xDataColumnIndex': 0, 'yDataColumnIndex': 2, }; default: return { 'xAxisLabel': appLocalizations.timeAxisLabel, 'yAxisLabel': 'Value', - 'xDataColumnIndex': 1, + 'xDataColumnIndex': 0, 'yDataColumnIndex': 2, }; } @@ -153,6 +153,7 @@ class _LoggedDataScreenState extends State { yAxisLabel: config['yAxisLabel'], xDataColumnIndex: config['xDataColumnIndex'], yDataColumnIndex: config['yDataColumnIndex'], + instrumentName: widget.instrumentName, ), ), ); @@ -173,6 +174,7 @@ class _LoggedDataScreenState extends State { yAxisLabel: config['yAxisLabel'], xDataColumnIndex: config['xDataColumnIndex'], yDataColumnIndex: config['yDataColumnIndex'], + instrumentName: widget.instrumentName, ), ), ); From 942a54a509f4e484e8fed434616660cf27edb6db Mon Sep 17 00:00:00 2001 From: Yugesh-Kumar-S Date: Thu, 7 Aug 2025 19:47:15 +0530 Subject: [PATCH 4/5] fixes --- lib/view/logged_data_chart_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view/logged_data_chart_screen.dart b/lib/view/logged_data_chart_screen.dart index 2c88ec09d..12ff9241b 100644 --- a/lib/view/logged_data_chart_screen.dart +++ b/lib/view/logged_data_chart_screen.dart @@ -156,7 +156,7 @@ class _LoggedDataChartScreenState extends State { return SideTitleWidget( meta: meta, child: Text( - value.toStringAsFixed(value.abs() < 10 ? 1 : 0), + value.toStringAsFixed(1), style: TextStyle( color: blackTextColor, fontSize: chartFontSize, From 55d21ef8a55c3641a1b3c5207d53d1dc0687fca3 Mon Sep 17 00:00:00 2001 From: Marc Nause Date: Sat, 16 Aug 2025 15:21:38 +0200 Subject: [PATCH 5/5] fix deprecated member use --- lib/view/compass_screen.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/view/compass_screen.dart b/lib/view/compass_screen.dart index 07cf252b8..86a0d01d6 100644 --- a/lib/view/compass_screen.dart +++ b/lib/view/compass_screen.dart @@ -180,15 +180,17 @@ class _CompassScreenContentState extends State { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Radio( - value: axis, + RadioGroup( groupValue: compassProvider.selectedAxis, onChanged: (String? value) { if (value != null) { compassProvider.onAxisSelected(value); } }, - activeColor: radioButtonActiveColor, + child: Radio( + value: axis, + activeColor: radioButtonActiveColor, + ), ), Text( label,