From 9dcd004377b43c33b7adf551d29505634d6e9273 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:33:24 -0500 Subject: [PATCH] Added setting to change log level (#192) --- lib/main.dart | 8 +- lib/pages/dashboard_page.dart | 15 + lib/services/ds_interop.dart | 2 + lib/services/field_images.dart | 6 +- lib/services/log.dart | 4 +- lib/services/nt4_client.dart | 35 +- lib/services/settings.dart | 24 +- lib/widgets/settings_dialog.dart | 185 ++++++---- pubspec.lock | 4 +- pubspec.yaml | 2 +- test/widgets/settings_dialog_test.dart | 457 +++++++++++++++---------- 11 files changed, 479 insertions(+), 263 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0fa6efbc..530357e8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; +import 'package:logger/logger.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:screen_retriever/screen_retriever.dart'; @@ -53,6 +54,11 @@ void main() async { preferences = await SharedPreferences.getInstance(); } + Level logLevel = Settings.logLevels.firstWhereOrNull((level) => + level.levelName == preferences.getString(PrefKeys.logLevel)) ?? + Defaults.logLevel; + Logger.level = logLevel; + await windowManager.ensureInitialized(); NTWidgetBuilder.ensureInitialized(); @@ -143,7 +149,7 @@ Future _backupPreferences(String appFolderPath) async { if (await File(backup).exists()) await File(backup).delete(recursive: true); await File(original).copy(backup); - logger.info('Backup up shared_preferences.json to $backup'); + logger.info('Backed up shared_preferences.json to $backup'); } catch (_) { /* Do nothing */ } diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 0be892f4..190257b7 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -13,6 +13,7 @@ import 'package:elegant_notification/resources/stacked_options.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:http/http.dart'; +import 'package:logger/logger.dart'; import 'package:path/path.dart' as path; import 'package:popover/popover.dart'; import 'package:screen_retriever/screen_retriever.dart'; @@ -1627,6 +1628,17 @@ class _DashboardPageState extends State with WindowListener { }, onColorChanged: widget.onColorChanged, onThemeVariantChanged: widget.onThemeVariantChanged, + onLogLevelChanged: (level) async { + if (level == null) { + logger.info('Removing log level preference'); + await preferences.remove(PrefKeys.logLevel); + Logger.level = Defaults.logLevel; + return; + } + logger.info('Changing log level to ${level.levelName}'); + Logger.level = level; + await preferences.setString(PrefKeys.logLevel, level.levelName); + }, onGridDPIChanged: (value) async { if (value == null) { return; @@ -1636,9 +1648,11 @@ class _DashboardPageState extends State with WindowListener { return; } if (dpiOverride != null) { + logger.info('Setting DPI override to ${dpiOverride.toDouble()}'); await preferences.setDouble( PrefKeys.gridDpiOverride, dpiOverride.toDouble()); } else { + logger.info('Removing DPI override preference'); await preferences.remove(PrefKeys.gridDpiOverride); } setState(() {}); @@ -1647,6 +1661,7 @@ class _DashboardPageState extends State with WindowListener { Uri uri = Uri.file( '${path.dirname(Platform.resolvedExecutable)}/data/flutter_assets/assets/'); if (await canLaunchUrl(uri)) { + logger.info('Opening URL (assets folder): ${uri.toString()}'); launchUrl(uri); } }, diff --git a/lib/services/ds_interop.dart b/lib/services/ds_interop.dart index 3cca0ffe..904882fc 100644 --- a/lib/services/ds_interop.dart +++ b/lib/services/ds_interop.dart @@ -110,6 +110,7 @@ class DSInteropClient { } void _tcpSocketOnMessage(String data) { + logger.debug('Received data from TCP 1742: "$data"'); var jsonData = jsonDecode(data.toString()); if (jsonData is! Map) { @@ -140,6 +141,7 @@ class DSInteropClient { } void _dbModeServerOnMessage(Uint8List data) { + logger.trace('Received message from socket on TCP 1741: $data'); _tcpBuffer.addAll(data); Map mappedData = {}; diff --git a/lib/services/field_images.dart b/lib/services/field_images.dart index 71281945..cde3ff99 100644 --- a/lib/services/field_images.dart +++ b/lib/services/field_images.dart @@ -32,7 +32,8 @@ class FieldImages { return fields.map((e) => e.game).contains(game); } - static Future loadFields(String directory) async { + static Future loadFields(String directory) async { + logger.info('Loading fields'); AssetManifest assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle); @@ -49,6 +50,7 @@ class FieldImages { } static Future loadField(String filePath) async { + logger.trace('Loading field at $filePath'); String jsonString = await rootBundle.loadString(filePath); Map jsonData = jsonDecode(jsonString); @@ -121,6 +123,7 @@ class Field { } void loadFieldImage() { + logger.debug('Loading field image for $game'); fieldImage = Image.asset( jsonData['field-image'], fit: BoxFit.contain, @@ -128,6 +131,7 @@ class Field { fieldImage.image .resolve(ImageConfiguration.empty) .addListener(ImageStreamListener((image, synchronousCall) { + logger.trace('Initializing image width and height for $game'); fieldImageWidth = image.image.width; fieldImageHeight = image.image.height; diff --git a/lib/services/log.dart b/lib/services/log.dart index 8cdafd3a..c2ecc47d 100644 --- a/lib/services/log.dart +++ b/lib/services/log.dart @@ -33,12 +33,14 @@ class Log { ConsoleOutput(), if (kReleaseMode) FileOutput(file: logFile), ]), - level: kDebugMode ? Level.debug : Level.info, filter: ProductionFilter(), ); } void log(Level level, dynamic message, [dynamic error, StackTrace? trace]) { + if (Logger.level.value > level.value) { + return; + } _logger?.log( level, '[${_dateFormat.format(DateTime.now())}]: $message', diff --git a/lib/services/nt4_client.dart b/lib/services/nt4_client.dart index 52c11ad4..9bf4b7b5 100644 --- a/lib/services/nt4_client.dart +++ b/lib/services/nt4_client.dart @@ -69,6 +69,11 @@ class NT4Subscription extends ValueNotifier { this.uid = -1, }) : super(null); + @override + String toString() { + return 'NT4Subscription(Topic: $topic, Options: $options, Uid: $uid)'; + } + void listen(Function(Object?, int) onChanged) { _listeners.add(onChanged); } @@ -122,6 +127,8 @@ class NT4Subscription extends ValueNotifier { } void updateValue(Object? value, int timestamp) { + logger.trace( + 'Updating value for subscription: $this - Value: $value, Time: $timestamp'); for (var listener in _listeners) { listener(value, timestamp); } @@ -189,6 +196,11 @@ class NT4SubscriptionOptions { @override int get hashCode => Object.hashAll([periodicRateSeconds, all, topicsOnly, prefix]); + + @override + String toString() { + return 'NT4SubscriptionOptions(Periodic: $periodicRateSeconds, All: $all, TopicsOnly: $topicsOnly, Prefix: $prefix)'; + } } class NT4Topic { @@ -206,6 +218,11 @@ class NT4Topic { required this.properties, }); + @override + String toString() { + return 'NT4Topic(Name: $name, Type: $type, ID: $id, PubUID: $pubUID, Properties: $properties)'; + } + Map toPublishJson() { return { 'name': name, @@ -351,6 +368,8 @@ class NT4Client { options: options, ); + logger.trace('Creating new subscription: $newSub'); + _subscriptions[newSub.uid] = newSub; _subscribedTopics.add(newSub); _wsSubscribe(newSub); @@ -365,6 +384,7 @@ class NT4Client { } void unSubscribe(NT4Subscription sub) { + logger.trace('Unsubscribing: $sub'); _subscriptions.remove(sub.uid); _subscribedTopics.remove(sub); _wsUnsubscribe(sub); @@ -392,6 +412,8 @@ class NT4Client { } void setProperties(NT4Topic topic, bool isPersistent, bool isRetained) { + logger.trace( + 'Updating properties - Topic: $topic, Persistent: $isPersistent, Retained: $isRetained'); topic.properties['persistent'] = isPersistent; topic.properties['retained'] = isRetained; _wsSetProperties(topic); @@ -416,6 +438,7 @@ class NT4Client { topic.pubUID = _clientPublishedTopics[topic.name]!.pubUID; return; } + logger.trace('Publishing topic: $topic'); topic.pubUID = getNewPubUID(); _clientPublishedTopics[topic.name] = topic; @@ -423,6 +446,7 @@ class NT4Client { } void unpublishTopic(NT4Topic topic) { + logger.trace('Unpublishing topic: $topic'); _clientPublishedTopics.remove(topic.name); _wsUnpublish(topic); } @@ -430,6 +454,9 @@ class NT4Client { void addSample(NT4Topic topic, dynamic data, [int? timestamp]) { timestamp ??= getServerTimeUS(); + logger.trace( + 'Adding sample - Topic: $topic, Data: $data, Timestamp: $timestamp'); + _wsSendBinary( serialize([topic.pubUID, timestamp, topic.getTypeId(), data])); @@ -465,8 +492,10 @@ class NT4Client { if (timeTopic != null) { int timeToSend = _getClientTimeUS(); - var rawData = - serialize([timeTopic.pubUID, 0, timeTopic.getTypeId(), timeToSend]); + var rttValue = [timeTopic.pubUID, 0, timeTopic.getTypeId(), timeToSend]; + var rawData = serialize(rttValue); + + logger.trace('Sending RTT timestamp: $rttValue'); if (_useRTT) { if (rttWebsocketActive && mainWebsocketActive) { @@ -479,6 +508,8 @@ class NT4Client { } void _rttHandleRecieveTimestamp(int serverTimestamp, int clientTimestamp) { + logger.trace( + 'RTT Received - Server Time: $serverTimestamp, Client Time: $clientTimestamp'); int rxTime = _getClientTimeUS(); int rtt = rxTime - clientTimestamp; diff --git a/lib/services/settings.dart b/lib/services/settings.dart index 53ef573c..36402796 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -1,7 +1,24 @@ +import 'package:flutter/foundation.dart'; + import 'package:flex_seed_scheme/flex_seed_scheme.dart'; +import 'package:logger/logger.dart'; import 'package:elastic_dashboard/services/ip_address_util.dart'; +extension LogLevelUtil on Level { + String get levelName => switch (this) { + Level.all => 'All', + Level.trace => 'Trace', + Level.debug => 'Debug', + Level.info => 'Info', + Level.warning => 'Warning', + Level.error => 'Error', + Level.fatal => 'Fatal', + Level.off => 'Off', + _ => 'Unknown', + }; +} + class Settings { static const String repositoryLink = 'https://github.com/Gold872/elastic-dashboard'; @@ -11,6 +28,9 @@ class Settings { // disable on some platforms, this is a dumb workaround for it static bool isWindowDraggable = true; static bool isWindowMaximizable = true; + + static final List logLevels = + Level.values.where((level) => level.value % 1000 == 0).toList(); } class Defaults { @@ -19,6 +39,8 @@ class Defaults { static FlexSchemeVariant themeVariant = FlexSchemeVariant.material3Legacy; static const String defaultVariantName = 'Material-3 Legacy (Default)'; + static const String defaultLogLevelName = 'Automatic'; + static const Level logLevel = kDebugMode ? Level.debug : Level.info; static const String ipAddress = '127.0.0.1'; static const int teamNumber = 9999; @@ -49,7 +71,7 @@ class PrefKeys { static String rememberWindowPosition = 'remember_window_position'; static String defaultPeriod = 'default_period'; static String defaultGraphPeriod = 'default_graph_period'; + static String logLevel = 'log_level'; static String gridDpiOverride = 'grid_dpi_override'; - static String showOpenAssetsFolderWarning = "show_assets_folder_warning"; static String windowPosition = 'window_position'; } diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index 819c0ef5..1f22a2ad 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -1,8 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:collection/collection.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; +import 'package:logger/logger.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/ip_address_util.dart'; @@ -24,23 +27,30 @@ class SettingsDialog extends StatefulWidget { ..add(Defaults.defaultVariantName) ..sort(); + static final List logLevelNames = Level.values + .where((level) => level.value % 1000 == 0) + .map((e) => e.levelName) + .toList() + ..insert(0, Defaults.defaultLogLevelName); + final SharedPreferences preferences; - final Function(String? data)? onIPAddressChanged; - final Function(String? data)? onTeamNumberChanged; - final Function(IPAddressMode mode)? onIPAddressModeChanged; - final Function(Color color)? onColorChanged; - final Function(bool value)? onGridToggle; - final Function(String? gridSize)? onGridSizeChanged; - final Function(String? radius)? onCornerRadiusChanged; - final Function(bool value)? onResizeToDSChanged; - final Function(bool value)? onRememberWindowPositionChanged; - final Function(bool value)? onLayoutLock; - final Function(String? value)? onDefaultPeriodChanged; - final Function(String? value)? onDefaultGraphPeriodChanged; - final Function(FlexSchemeVariant variant)? onThemeVariantChanged; - final Function(String? value)? onGridDPIChanged; - final Function()? onOpenAssetsFolderPressed; + final FutureOr Function(String? data)? onIPAddressChanged; + final FutureOr Function(String? data)? onTeamNumberChanged; + final void Function(IPAddressMode mode)? onIPAddressModeChanged; + final void Function(Color color)? onColorChanged; + final void Function(bool value)? onGridToggle; + final FutureOr Function(String? gridSize)? onGridSizeChanged; + final FutureOr Function(String? radius)? onCornerRadiusChanged; + final void Function(bool value)? onResizeToDSChanged; + final void Function(bool value)? onRememberWindowPositionChanged; + final void Function(bool value)? onLayoutLock; + final FutureOr Function(String? value)? onDefaultPeriodChanged; + final FutureOr Function(String? value)? onDefaultGraphPeriodChanged; + final void Function(FlexSchemeVariant variant)? onThemeVariantChanged; + final void Function(Level? level)? onLogLevelChanged; + final FutureOr Function(String? value)? onGridDPIChanged; + final void Function()? onOpenAssetsFolderPressed; const SettingsDialog({ super.key, @@ -59,6 +69,7 @@ class SettingsDialog extends StatefulWidget { this.onDefaultPeriodChanged, this.onDefaultGraphPeriodChanged, this.onThemeVariantChanged, + this.onLogLevelChanged, this.onGridDPIChanged, this.onOpenAssetsFolderPressed, }); @@ -98,7 +109,10 @@ class _SettingsDialogState extends State { icon: Icon( Icons.code, ), - child: Text('Developer'), + child: Text( + 'Developer (Advanced)', + textAlign: TextAlign.center, + ), ), ], ), @@ -145,7 +159,7 @@ class _SettingsDialogState extends State { padding: const EdgeInsets.symmetric(horizontal: 4.0), child: SingleChildScrollView( child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 140), + constraints: const BoxConstraints(maxHeight: 205), child: Column( children: [ ..._advancedSettings(), @@ -170,58 +184,6 @@ class _SettingsDialogState extends State { ); } - List _advancedSettings() { - return [ - Row( - children: [ - const Icon(Icons.warning, color: Colors.yellow), - const SizedBox(width: 5), - Flexible( - child: Text( - 'WARNING: These are advanced settings that could cause issues if changed incorrectly. It is advised to not change anything here unless if you know what you are doing.', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, - ), - maxLines: 3, - ), - ), - const SizedBox(width: 5), - const Icon( - Icons.warning, - color: Colors.yellow, - ), - ], - ), - const Divider(), - Row( - children: [ - Flexible( - child: DialogTextInput( - initialText: widget.preferences - .getDouble(PrefKeys.gridDpiOverride) - ?.toString() ?? - '', - label: 'Grid DPI (Experimental)', - formatter: TextFormatterBuilder.decimalTextFormatter(), - allowEmptySubmission: true, - onSubmit: (value) { - widget.onGridDPIChanged?.call(value); - }, - ), - ), - TextButton.icon( - onPressed: () { - widget.onOpenAssetsFolderPressed?.call(); - }, - icon: const Icon(Icons.folder_outlined), - label: const Text('Open Assets Folder'), - ), - ], - ), - ]; - } - List _themeSettings() { Color currentColor = Color(widget.preferences.getInt(PrefKeys.teamColor) ?? Colors.blueAccent.value); @@ -413,10 +375,9 @@ class _SettingsDialogState extends State { Defaults.cornerRadius.toString()) .toString(), label: 'Corner Radius', - onSubmit: (value) { - setState(() { - widget.onCornerRadiusChanged?.call(value); - }); + onSubmit: (value) async { + await widget.onCornerRadiusChanged?.call(value); + setState(() {}); }, formatter: TextFormatterBuilder.decimalTextFormatter(), ), @@ -517,4 +478,82 @@ class _SettingsDialogState extends State { ), ]; } + + List _advancedSettings() { + String initialLogLevel = widget.preferences.getString(PrefKeys.logLevel) ?? + Defaults.defaultLogLevelName; + if (!SettingsDialog.logLevelNames.contains(initialLogLevel)) { + initialLogLevel = Defaults.defaultLogLevelName; + } + + return [ + Row( + children: [ + const Icon(Icons.warning, color: Colors.yellow), + const SizedBox(width: 5), + Flexible( + child: Text( + 'WARNING: These are advanced settings that could cause issues if changed incorrectly. It is advised to not change anything here unless if you know what you are doing.', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + ), + maxLines: 3, + ), + ), + const SizedBox(width: 5), + const Icon( + Icons.warning, + color: Colors.yellow, + ), + ], + ), + const Divider(), + Row( + children: [ + const Text('Log Level'), + const SizedBox(width: 5), + Flexible( + child: DialogDropdownChooser( + choices: SettingsDialog.logLevelNames, + initialValue: initialLogLevel, + onSelectionChanged: (value) { + Level? selectedLevel = Settings.logLevels + .firstWhereOrNull((level) => level.levelName == value); + widget.onLogLevelChanged?.call(selectedLevel); + setState(() {}); + }, + ), + ), + ], + ), + const SizedBox(height: 5), + Row( + children: [ + Flexible( + child: DialogTextInput( + initialText: widget.preferences + .getDouble(PrefKeys.gridDpiOverride) + ?.toString() ?? + '', + label: 'Grid DPI (Experimental)', + formatter: TextFormatterBuilder.decimalTextFormatter(), + allowEmptySubmission: true, + onSubmit: (value) async { + await widget.onGridDPIChanged?.call(value); + setState(() {}); + }, + ), + ), + TextButton.icon( + onPressed: () { + widget.onOpenAssetsFolderPressed?.call(); + }, + icon: const Icon(Icons.folder_outlined), + label: const Text('Open Assets Folder'), + ), + ], + ), + ]; + } } diff --git a/pubspec.lock b/pubspec.lock index 31e9534c..aea1dd35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -597,10 +597,10 @@ packages: dependency: "direct main" description: name: logger - sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 + sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.5.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a7d48bf5..a00376f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: http: ^1.2.0 import_sorter: ^4.6.0 intl: ^0.18.1 - logger: ^2.0.2+1 + logger: ^2.5.0 messagepack: ^0.2.1 msgpack_dart: ^1.0.1 package_info_plus: ^5.0.1 diff --git a/test/widgets/settings_dialog_test.dart b/test/widgets/settings_dialog_test.dart index 41cb5a32..da944d24 100644 --- a/test/widgets/settings_dialog_test.dart +++ b/test/widgets/settings_dialog_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:logger/logger.dart'; import 'package:mockito/mockito.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -42,6 +43,8 @@ class FakeSettingsMethods extends Mock { void changeThemeVariant(); + void changeLogLevel(); + void changeGridDPIOverride(); void openAssetsFolder(); @@ -55,7 +58,7 @@ void main() { final networkSettings = find.widgetWithText(Tab, 'Network'); final appearanceSettings = find.widgetWithText(Tab, 'Appearance'); - final devSettings = find.widgetWithText(Tab, 'Developer'); + final devSettings = find.widgetWithText(Tab, 'Developer (Advanced)'); setUpAll(() { fakeSettings = FakeSettingsMethods(); @@ -76,6 +79,7 @@ void main() { PrefKeys.defaultPeriod: 0.10, PrefKeys.defaultGraphPeriod: 0.033, PrefKeys.themeVariant: FlexSchemeVariant.chroma.variantName, + PrefKeys.logLevel: Level.trace.levelName, }); preferences = await SharedPreferences.getInstance(); @@ -86,14 +90,16 @@ void main() { testWidgets('Settings Dialog', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -142,6 +148,7 @@ void main() { await widgetTester.tap(devSettings); await widgetTester.pumpAndSettle(); + expect(find.text('Log Level'), findsOneWidget); expect(find.widgetWithText(DialogTextInput, 'Grid DPI (Experimental)'), findsOneWidget); expect(find.text('Open Assets Folder'), findsOneWidget); @@ -157,19 +164,21 @@ void main() { testWidgets('Change team number', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onTeamNumberChanged: (data) async { - fakeSettings.changeTeamNumber(); - - await preferences.setInt(PrefKeys.teamNumber, int.parse(data!)); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onTeamNumberChanged: (data) async { + fakeSettings.changeTeamNumber(); + + await preferences.setInt(PrefKeys.teamNumber, int.parse(data!)); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -191,17 +200,19 @@ void main() { testWidgets('Change IP address mode', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onIPAddressModeChanged: (mode) { - fakeSettings.changeIPAddressMode(); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onIPAddressModeChanged: (mode) { + fakeSettings.changeIPAddressMode(); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -236,19 +247,21 @@ void main() { testWidgets('Change IP address', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onIPAddressChanged: (data) async { - fakeSettings.changeIPAddress(); - - await preferences.setString(PrefKeys.ipAddress, data!); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onIPAddressChanged: (data) async { + fakeSettings.changeIPAddress(); + + await preferences.setString(PrefKeys.ipAddress, data!); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -267,20 +280,22 @@ void main() { testWidgets('Change default period', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onDefaultPeriodChanged: (period) async { - fakeSettings.changeDefaultPeriod(); - - await preferences.setDouble( - PrefKeys.defaultPeriod, double.parse(period!)); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onDefaultPeriodChanged: (period) async { + fakeSettings.changeDefaultPeriod(); + + await preferences.setDouble( + PrefKeys.defaultPeriod, double.parse(period!)); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -299,20 +314,22 @@ void main() { testWidgets('Change default graph period', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onDefaultGraphPeriodChanged: (period) async { - fakeSettings.changeDefaultGraphPeriod(); - - await preferences.setDouble( - PrefKeys.defaultGraphPeriod, double.parse(period!)); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onDefaultGraphPeriodChanged: (period) async { + fakeSettings.changeDefaultGraphPeriod(); + + await preferences.setDouble( + PrefKeys.defaultGraphPeriod, double.parse(period!)); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -333,19 +350,21 @@ void main() { testWidgets('Change team color', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOnlineNT4(), - preferences: preferences, - onColorChanged: (color) async { - fakeSettings.changeColor(); - - await preferences.setInt(PrefKeys.teamColor, color.value); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOnlineNT4(), + preferences: preferences, + onColorChanged: (color) async { + fakeSettings.changeColor(); + + await preferences.setInt(PrefKeys.teamColor, color.value); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -393,20 +412,22 @@ void main() { testWidgets('Change theme variant', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - onThemeVariantChanged: (variant) async { - fakeSettings.changeThemeVariant(); - - await preferences.setString( - PrefKeys.themeVariant, variant.variantName); - }, - preferences: preferences, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + onThemeVariantChanged: (variant) async { + fakeSettings.changeThemeVariant(); + + await preferences.setString( + PrefKeys.themeVariant, variant.variantName); + }, + preferences: preferences, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -450,19 +471,21 @@ void main() { testWidgets('Toggle grid', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onGridToggle: (value) async { - fakeSettings.changeShowGrid(); - - await preferences.setBool(PrefKeys.showGrid, value); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onGridToggle: (value) async { + fakeSettings.changeShowGrid(); + + await preferences.setBool(PrefKeys.showGrid, value); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -496,19 +519,21 @@ void main() { testWidgets('Change grid size', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onGridSizeChanged: (gridSize) async { - fakeSettings.changeGridSize(); - - await preferences.setInt(PrefKeys.gridSize, int.parse(gridSize!)); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onGridSizeChanged: (gridSize) async { + fakeSettings.changeGridSize(); + + await preferences.setInt(PrefKeys.gridSize, int.parse(gridSize!)); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -532,20 +557,22 @@ void main() { FlutterError.onError = ignoreOverflowErrors; createMockOfflineNT4(); - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onCornerRadiusChanged: (radius) async { - fakeSettings.changeCornerRadius(); - - await preferences.setDouble( - PrefKeys.cornerRadius, double.parse(radius!)); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onCornerRadiusChanged: (radius) async { + fakeSettings.changeCornerRadius(); + + await preferences.setDouble( + PrefKeys.cornerRadius, double.parse(radius!)); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -569,19 +596,21 @@ void main() { testWidgets('Toggle driver station auto resize', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onResizeToDSChanged: (value) async { - fakeSettings.changeDSAutoResize(); - - await preferences.setBool(PrefKeys.autoResizeToDS, value); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onResizeToDSChanged: (value) async { + fakeSettings.changeDSAutoResize(); + + await preferences.setBool(PrefKeys.autoResizeToDS, value); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -616,19 +645,21 @@ void main() { testWidgets('Toggle remember window position', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - ntConnection: createMockOfflineNT4(), - preferences: preferences, - onRememberWindowPositionChanged: (value) async { - fakeSettings.changeRememberWindow(); - - await preferences.setBool(PrefKeys.rememberWindowPosition, value); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, + onRememberWindowPositionChanged: (value) async { + fakeSettings.changeRememberWindow(); + + await preferences.setBool(PrefKeys.rememberWindowPosition, value); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -663,19 +694,21 @@ void main() { testWidgets('Toggle lock layout', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - preferences: preferences, - ntConnection: createMockOfflineNT4(), - onLayoutLock: (value) async { - fakeSettings.changeLockLayout(); - - await preferences.setBool(PrefKeys.layoutLocked, value); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + preferences: preferences, + ntConnection: createMockOfflineNT4(), + onLayoutLock: (value) async { + fakeSettings.changeLockLayout(); + + await preferences.setBool(PrefKeys.layoutLocked, value); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -707,33 +740,93 @@ void main() { verify(fakeSettings.changeLockLayout()).called(2); }); - testWidgets('Change Grid DPI Override', (widgetTester) async { + testWidgets('Change log level', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - preferences: preferences, - ntConnection: createMockOfflineNT4(), - onGridDPIChanged: (value) async { - fakeSettings.changeGridDPIOverride(); + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + onLogLevelChanged: (level) async { + fakeSettings.changeLogLevel(); + + if (level == null) { + await preferences.remove(PrefKeys.logLevel); + } else { + await preferences.setString(PrefKeys.logLevel, level.levelName); + } + }, + preferences: preferences, + ), + ), + ), + ); - if (value == null) { - return; - } + await widgetTester.pumpAndSettle(); - double? newOverride = double.tryParse(value); + expect(devSettings, findsOneWidget); + await widgetTester.tap(devSettings); + await widgetTester.pumpAndSettle(); - if (newOverride != null) { - await preferences.setDouble( - PrefKeys.gridDpiOverride, newOverride); - } else { - await preferences.remove(PrefKeys.gridDpiOverride); - } - }, + final logLevelDropdown = + find.widgetWithText(DialogDropdownChooser, 'Trace'); + + expect(logLevelDropdown, findsOneWidget); + + await widgetTester.tap(logLevelDropdown); + await widgetTester.pumpAndSettle(); + + expect(find.text('Trace'), findsNWidgets(2)); + expect(find.text('Automatic'), findsOneWidget); + + await widgetTester.tap(find.text('Automatic')); + await widgetTester.pumpAndSettle(); + + expect(preferences.getString(PrefKeys.logLevel), isNull); + + verify(fakeSettings.changeLogLevel()).called(1); + + final newLogLevelDropdown = + find.widgetWithText(DialogDropdownChooser, 'Automatic'); + + expect(newLogLevelDropdown, findsOneWidget); + + await widgetTester.tap(newLogLevelDropdown); + await widgetTester.pumpAndSettle(); + + expect(find.text('Automatic'), findsNWidgets(2)); + }); + + testWidgets('Change Grid DPI Override', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + preferences: preferences, + ntConnection: createMockOfflineNT4(), + onGridDPIChanged: (value) async { + fakeSettings.changeGridDPIOverride(); + + if (value == null) { + return; + } + + double? newOverride = double.tryParse(value); + + if (newOverride != null) { + await preferences.setDouble( + PrefKeys.gridDpiOverride, newOverride); + } else { + await preferences.remove(PrefKeys.gridDpiOverride); + } + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle(); @@ -762,17 +855,19 @@ void main() { testWidgets('Open assets', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - await widgetTester.pumpWidget(MaterialApp( - home: Scaffold( - body: SettingsDialog( - preferences: preferences, - ntConnection: createMockOfflineNT4(), - onOpenAssetsFolderPressed: () { - fakeSettings.openAssetsFolder(); - }, + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SettingsDialog( + preferences: preferences, + ntConnection: createMockOfflineNT4(), + onOpenAssetsFolderPressed: () { + fakeSettings.openAssetsFolder(); + }, + ), ), ), - )); + ); await widgetTester.pumpAndSettle();