diff --git a/lib/main.dart b/lib/main.dart index 164ce51c..0fa6efbc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; @@ -66,18 +65,17 @@ void main() async { await FieldImages.loadFields('assets/fields/'); Display primaryDisplay = await screenRetriever.getPrimaryDisplay(); - double scaleFactor = (primaryDisplay.scaleFactor?.toDouble() ?? 1.0); - Size screenSize = - (primaryDisplay.visibleSize ?? primaryDisplay.size) * scaleFactor; + Size screenSize = primaryDisplay.visibleSize ?? primaryDisplay.size; - double minimumWidth = min(screenSize.width * 0.77 / scaleFactor, 1280.0); - double minimumHeight = min(screenSize.height * 0.7 / scaleFactor, 720.0); + logger.debug('Display Information: - Screen Size: $screenSize'); - Size minimumSize = Size(minimumWidth, minimumHeight); + const Size minimumSize = Size(436.5, 320.0); await windowManager.setMinimumSize(minimumSize); - await windowManager.setTitleBarStyle(TitleBarStyle.hidden, - windowButtonVisibility: false); + await windowManager.setTitleBarStyle( + TitleBarStyle.hidden, + windowButtonVisibility: false, + ); if (preferences.getBool(PrefKeys.rememberWindowPosition) ?? false) { await _restoreWindowPosition(preferences, primaryDisplay, minimumSize); diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index ed127e9e..0be892f4 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -447,6 +447,7 @@ class _DashboardPageState extends State with WindowListener { progressIndicatorBackground: colorScheme.surface, progressIndicatorColor: const Color(0xffFE355C), width: 350, + height: 100, position: Alignment.bottomRight, toastDuration: const Duration(seconds: 3, milliseconds: 500), icon: const Icon(Icons.error, color: Color(0xffFE355C)), @@ -473,6 +474,7 @@ class _DashboardPageState extends State with WindowListener { showProgressIndicator: false, background: colorScheme.surface, width: 350, + height: 100, position: Alignment.bottomRight, title: Text( 'Version ${updateResponse.latestVersion!} Available', @@ -508,6 +510,7 @@ class _DashboardPageState extends State with WindowListener { title: 'No Updates Available', message: 'You are running on the latest version of Elastic', width: 350, + height: 75, ); } } @@ -1859,196 +1862,209 @@ class _DashboardPageState extends State with WindowListener { @override Widget build(BuildContext context) { + final double windowWidth = MediaQuery.of(context).size.width; + TextStyle? menuTextStyle = Theme.of(context).textTheme.bodySmall; TextStyle? footerStyle = Theme.of(context).textTheme.bodyMedium; ButtonStyle menuButtonStyle = ButtonStyle( alignment: Alignment.center, - textStyle: WidgetStateProperty.all(menuTextStyle), + textStyle: WidgetStatePropertyAll(menuTextStyle), backgroundColor: const WidgetStatePropertyAll(Color.fromARGB(255, 25, 25, 25)), + minimumSize: const WidgetStatePropertyAll(Size(48, 48)), iconSize: const WidgetStatePropertyAll(20.0), ); - MenuBar menuBar = MenuBar( - style: const MenuStyle( - backgroundColor: - WidgetStatePropertyAll(Color.fromARGB(255, 25, 25, 25)), - elevation: WidgetStatePropertyAll(0), - ), - children: [ - Center( - child: Image.asset( - logoPath, - width: 24.0, - height: 24.0, + final bool layoutLocked = + preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked; + + final double minWindowWidth = layoutLocked ? 500 : 460; + final bool consolidateMenu = windowWidth < minWindowWidth; + + List menuChildren = [ + // File + SubmenuButton( + style: menuButtonStyle, + menuChildren: [ + // Open Layout + MenuItemButton( + style: menuButtonStyle, + onPressed: !layoutLocked ? _importLayout : null, + shortcut: + const SingleActivator(LogicalKeyboardKey.keyO, control: true), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.folder_open_outlined), + SizedBox(width: 8), + Text('Open Layout'), + ], + ), ), - ), - const SizedBox(width: 10), - // File - SubmenuButton( - style: menuButtonStyle, - menuChildren: [ - // Open Layout - MenuItemButton( - style: menuButtonStyle, - onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) - ? _importLayout - : null, - shortcut: - const SingleActivator(LogicalKeyboardKey.keyO, control: true), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.folder_open_outlined), - SizedBox(width: 8), - Text('Open Layout'), - ], - ), + // Save + MenuItemButton( + style: menuButtonStyle, + onPressed: _saveLayout, + shortcut: + const SingleActivator(LogicalKeyboardKey.keyS, control: true), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.save_outlined), + SizedBox(width: 8), + Text('Save'), + ], ), - // Save - MenuItemButton( - style: menuButtonStyle, - onPressed: _saveLayout, - shortcut: - const SingleActivator(LogicalKeyboardKey.keyS, control: true), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.save_outlined), - SizedBox(width: 8), - Text('Save'), - ], - ), + ), + // Export layout + MenuItemButton( + style: menuButtonStyle, + onPressed: _exportLayout, + shortcut: const SingleActivator( + LogicalKeyboardKey.keyS, + shift: true, + control: true, ), - // Export layout - MenuItemButton( - style: menuButtonStyle, - onPressed: _exportLayout, - shortcut: const SingleActivator( - LogicalKeyboardKey.keyS, - shift: true, - control: true, - ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.save_as_outlined), - SizedBox(width: 8), - Text('Save As'), - ], - ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.save_as_outlined), + SizedBox(width: 8), + Text('Save As'), + ], ), - // Download layout - MenuItemButton( - style: menuButtonStyle, - onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) - ? _loadLayoutFromRobot - : null, - shortcut: const SingleActivator( - LogicalKeyboardKey.keyD, - control: true, - ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.download), - SizedBox(width: 8), - Text('Download From Robot'), - ], - ), + ), + // Download layout + MenuItemButton( + style: menuButtonStyle, + onPressed: !layoutLocked ? _loadLayoutFromRobot : null, + shortcut: const SingleActivator( + LogicalKeyboardKey.keyD, + control: true, + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.download), + SizedBox(width: 8), + Text('Download From Robot'), + ], ), - ], - child: const Text( - 'File', ), + ], + child: const Text( + 'File', ), - // Edit - SubmenuButton( + ), + // Edit + SubmenuButton( + style: menuButtonStyle, + menuChildren: [ + // Clear layout + MenuItemButton( style: menuButtonStyle, - menuChildren: [ - // Clear layout - MenuItemButton( - style: menuButtonStyle, - onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) - ? () { - setState(() { - _tabData[_currentTabIndex] - .tabGrid - .confirmClearWidgets(context); - }); - } - : null, - leadingIcon: const Icon(Icons.clear), - child: const Text('Clear Layout'), - ), - // Lock/Unlock Layout - MenuItemButton( - style: menuButtonStyle, - onPressed: () { - if (preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) { - _unlockLayout(); - } else { - _lockLayout(); + onPressed: !layoutLocked + ? () { + setState(() { + _tabData[_currentTabIndex] + .tabGrid + .confirmClearWidgets(context); + }); } + : null, + leadingIcon: const Icon(Icons.clear), + child: const Text('Clear Layout'), + ), + // Lock/Unlock Layout + MenuItemButton( + style: menuButtonStyle, + onPressed: () { + if (layoutLocked) { + _unlockLayout(); + } else { + _lockLayout(); + } - setState(() {}); - }, - leadingIcon: (preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) - ? const Icon(Icons.lock_open) - : const Icon(Icons.lock_outline), - child: Text( - '${(preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) ? 'Unlock' : 'Lock'} Layout'), - ) - ], - child: const Text( - 'Edit', - )), - // Help - SubmenuButton( - style: menuButtonStyle, - menuChildren: [ - // About + setState(() {}); + }, + leadingIcon: layoutLocked + ? const Icon(Icons.lock_open) + : const Icon(Icons.lock_outline), + child: Text('${layoutLocked ? 'Unlock' : 'Lock'} Layout'), + ) + ], + child: const Text( + 'Edit', + ), + ), + // Help + SubmenuButton( + style: menuButtonStyle, + menuChildren: [ + // About + MenuItemButton( + style: menuButtonStyle, + onPressed: () { + _displayAboutDialog(context); + }, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.info_outline), + SizedBox(width: 8), + Text('About'), + ], + ), + ), + // Check for Updates (not for WPILib distribution) + if (!isWPILib) MenuItemButton( style: menuButtonStyle, onPressed: () { - _displayAboutDialog(context); + _checkForUpdates(); }, child: const Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.info_outline), + Icon(Icons.update_outlined), SizedBox(width: 8), - Text('About'), + Text('Check for Updates'), ], ), ), - // Check for Updates (not for WPILib distribution) - if (!isWPILib) - MenuItemButton( - style: menuButtonStyle, - onPressed: () { - _checkForUpdates(); - }, - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.update_outlined), - SizedBox(width: 8), - Text('Check for Updates'), - ], - ), - ), - ], - child: const Text( - 'Help', + ], + child: const Text( + 'Help', + ), + ), + ]; + + MenuBar menuBar = MenuBar( + style: const MenuStyle( + backgroundColor: + WidgetStatePropertyAll(Color.fromARGB(255, 25, 25, 25)), + elevation: WidgetStatePropertyAll(0), + ), + children: [ + Center( + child: Image.asset( + logoPath, + width: 24, + height: 24, ), ), - const VerticalDivider(), + const SizedBox(width: 5), + if (!consolidateMenu) + ...menuChildren + else + SubmenuButton( + style: menuButtonStyle.copyWith( + iconSize: const WidgetStatePropertyAll(24), + ), + menuChildren: menuChildren, + child: const Icon(Icons.menu), + ), + const VerticalDivider(width: 4), // Settings MenuItemButton( style: menuButtonStyle, @@ -2058,20 +2074,16 @@ class _DashboardPageState extends State with WindowListener { }, child: const Text('Settings'), ), - const VerticalDivider(), + const VerticalDivider(width: 4), // Add Widget MenuItemButton( style: menuButtonStyle, leadingIcon: const Icon(Icons.add), - onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked) - ? () => _displayAddWidgetDialog() - : null, + onPressed: !layoutLocked ? () => _displayAddWidgetDialog() : null, child: const Text('Add Widget'), ), - if ((preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked)) ...[ - const VerticalDivider(), + if (layoutLocked) ...[ + const VerticalDivider(width: 4), // Unlock Layout Tooltip( message: 'Unlock Layout', @@ -2093,38 +2105,39 @@ class _DashboardPageState extends State with WindowListener { ], ); - final List trailing = [ - if (lastUpdateResponse.updateAvailable) ...[ - const VerticalDivider(), - Tooltip( - message: 'Download version ${lastUpdateResponse.latestVersion}', - child: MenuItemButton( - style: menuButtonStyle.copyWith( - minimumSize: - const WidgetStatePropertyAll(Size(36.0, double.infinity)), - maximumSize: - const WidgetStatePropertyAll(Size(36.0, double.infinity)), - ), - onPressed: () async { - Uri url = Uri.parse(Settings.releasesLink); - - if (await canLaunchUrl(url)) { - await launchUrl(url); - } - }, - child: const Icon(Icons.update, color: Colors.orange), - ), + Widget? updateButton; + if (lastUpdateResponse.updateAvailable) { + updateButton = IconButton( + style: const ButtonStyle( + shape: WidgetStatePropertyAll(RoundedRectangleBorder()), + maximumSize: WidgetStatePropertyAll(Size.square(34.0)), + minimumSize: WidgetStatePropertyAll(Size.zero), + padding: WidgetStatePropertyAll(EdgeInsets.all(4.0)), + iconSize: WidgetStatePropertyAll(24.0), ), - const VerticalDivider(), - ], - ]; + tooltip: 'Download version ${lastUpdateResponse.latestVersion}', + onPressed: () async { + Uri url = Uri.parse(Settings.releasesLink); + + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + }, + icon: const Icon(Icons.update, color: Colors.orange), + ); + } + + final double nonConolidatedLeadingWidth = (layoutLocked) ? 409 : 369; + final double consolidatedLeadingWidth = (layoutLocked) ? 330 : 290; return Scaffold( appBar: CustomAppBar( titleText: appTitle, onWindowClose: onWindowClose, leading: menuBar, - trailing: trailing, + leadingWidth: consolidateMenu + ? consolidatedLeadingWidth + : nonConolidatedLeadingWidth, ), body: Focus( autofocus: true, @@ -2141,6 +2154,7 @@ class _DashboardPageState extends State with WindowListener { preferences: preferences, gridDpiOverride: preferences.getDouble(PrefKeys.gridDpiOverride), + updateButton: updateButton, currentIndex: _currentTabIndex, onTabMoveLeft: () { _moveTabLeft(); @@ -2256,47 +2270,62 @@ class _DashboardPageState extends State with WindowListener { height: 32, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Row( - children: [ - Expanded( - child: StreamBuilder( - stream: widget.ntConnection.connectionStatus(), - builder: (context, snapshot) { - bool connected = snapshot.data ?? false; - - String connectedText = (connected) - ? 'Network Tables: Connected (${preferences.getString(PrefKeys.ipAddress) ?? Defaults.ipAddress})' - : 'Network Tables: Disconnected'; - - return Text( + child: ValueListenableBuilder( + valueListenable: widget.ntConnection.ntConnected, + builder: (context, connected, child) { + String connectedText = (connected) + ? 'Network Tables: Connected (${preferences.getString(PrefKeys.ipAddress) ?? Defaults.ipAddress})' + : 'Network Tables: Disconnected'; + + double connectedWidth = (TextPainter( + text: TextSpan( + text: connectedText, + style: footerStyle, + ), + maxLines: 1, + textDirection: TextDirection.ltr) + ..layout(minWidth: 0, maxWidth: double.infinity)) + .size + .width; + + double availableSpace = windowWidth - 20 - connectedWidth; + + return Stack( + alignment: Alignment.center, + children: [ + if (availableSpace >= windowWidth / 2 + 30) + Text( + 'Team ${preferences.getInt(PrefKeys.teamNumber)?.toString() ?? 'Unknown'}', + textAlign: TextAlign.center, + ), + if (availableSpace >= 115) + Align( + alignment: Alignment.centerRight, + child: StreamBuilder( + stream: widget.ntConnection.latencyStream(), + builder: (context, snapshot) { + double latency = snapshot.data ?? 0.0; + + return Text( + 'Latency: ${latency.toStringAsFixed(2).padLeft(5)} ms', + textAlign: TextAlign.right, + ); + }, + ), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( connectedText, style: footerStyle?.copyWith( color: (connected) ? Colors.green : Colors.red, ), textAlign: TextAlign.left, - ); - }), - ), - Expanded( - child: Text( - 'Team ${preferences.getInt(PrefKeys.teamNumber)?.toString() ?? 'Unknown'}', - textAlign: TextAlign.center, - ), - ), - Expanded( - child: StreamBuilder( - stream: widget.ntConnection.latencyStream(), - builder: (context, snapshot) { - double latency = snapshot.data ?? 0.0; - - return Text( - 'Latency: ${latency.toStringAsFixed(2).padLeft(5)} ms', - textAlign: TextAlign.right, - ); - }), - ), - ], - ), + ), + ), + ], + ); + }), ), ), ], diff --git a/lib/services/app_distributor.dart b/lib/services/app_distributor.dart index 65a331e9..0cc468ba 100644 --- a/lib/services/app_distributor.dart +++ b/lib/services/app_distributor.dart @@ -1,5 +1,5 @@ -bool get isWPILib => const bool.fromEnvironment('ELASTIC_WPILIB'); +const bool isWPILib = bool.fromEnvironment('ELASTIC_WPILIB'); -String logoPath = 'assets/logos/logo.png'; +const String logoPath = 'assets/logos/logo.png'; -String get appTitle => (!isWPILib) ? 'Elastic' : 'Elastic (WPILib)'; +const String appTitle = !isWPILib ? 'Elastic' : 'Elastic (WPILib)'; diff --git a/lib/widgets/custom_appbar.dart b/lib/widgets/custom_appbar.dart index 4709e0ee..48f39e62 100644 --- a/lib/widgets/custom_appbar.dart +++ b/lib/widgets/custom_appbar.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:titlebar_buttons/titlebar_buttons.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:elastic_dashboard/services/app_distributor.dart'; import 'package:elastic_dashboard/services/settings.dart'; class CustomAppBar extends AppBar { @@ -10,100 +11,92 @@ class CustomAppBar extends AppBar { final Color? appBarColor; final VoidCallback? onWindowClose; - final List trailing; - - static const double _leadingSize = 500; static const ThemeType buttonType = ThemeType.materia; + static const double windowButtonSize = 24; + + static const double titleSize = !isWPILib ? 60.0 : 140.0; + CustomAppBar({ super.key, this.titleText = 'Elastic', this.appBarColor, this.onWindowClose, - this.trailing = const [], + required super.leadingWidth, required super.leading, }) : super( toolbarHeight: 36, backgroundColor: appBarColor ?? const Color.fromARGB(255, 25, 25, 25), elevation: 0.0, scrolledUnderElevation: 0.0, - leadingWidth: _leadingSize, centerTitle: true, + flexibleSpace: const _WindowDragArea(), notificationPredicate: (_) => false, actions: [ - SizedBox( - width: _leadingSize, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Expanded( - child: _WindowDragArea(), - ), - ...trailing.map((e) => ExcludeFocus(child: e)), - InkWell( - canRequestFocus: false, - onTap: () async => await windowManager.minimize(), - child: const AbsorbPointer( - child: DecoratedMinimizeButton( - type: buttonType, - onPressed: null, - ), - ), - ), - InkWell( - canRequestFocus: false, - onTap: () async { - if (!Settings.isWindowMaximizable) { - return; - } + InkWell( + canRequestFocus: false, + onTap: () async => await windowManager.minimize(), + child: const AbsorbPointer( + child: DecoratedMinimizeButton( + width: windowButtonSize, + height: windowButtonSize, + type: buttonType, + onPressed: null, + ), + ), + ), + InkWell( + canRequestFocus: false, + onTap: () async { + if (!Settings.isWindowMaximizable) { + return; + } - if (await windowManager.isMaximized()) { - windowManager.unmaximize(); - } else { - windowManager.maximize(); - } - }, - child: const AbsorbPointer( - child: DecoratedMaximizeButton( - type: buttonType, - onPressed: null, - ), - ), - ), - InkWell( - canRequestFocus: false, - hoverColor: Colors.red, - onTap: () async { - if (onWindowClose == null) { - await windowManager.close(); - } else { - onWindowClose.call(); - } - }, - child: const AbsorbPointer( - child: DecoratedCloseButton( - type: buttonType, - onPressed: null, - ), - ), - ), - ], + if (await windowManager.isMaximized()) { + windowManager.unmaximize(); + } else { + windowManager.maximize(); + } + }, + child: const AbsorbPointer( + child: DecoratedMaximizeButton( + width: windowButtonSize, + height: windowButtonSize, + type: buttonType, + onPressed: null, + ), + ), + ), + InkWell( + canRequestFocus: false, + hoverColor: Colors.red, + onTap: () async { + if (onWindowClose == null) { + await windowManager.close(); + } else { + onWindowClose.call(); + } + }, + child: const AbsorbPointer( + child: DecoratedCloseButton( + width: windowButtonSize, + height: windowButtonSize, + type: buttonType, + onPressed: null, + ), ), ), ], title: _WindowDragArea( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Text( - titleText, - textAlign: TextAlign.center, - overflow: TextOverflow.visible, - ), - ), - ], + child: LayoutBuilder( + builder: (context, constraints) => + (constraints.maxWidth >= titleSize) + ? Text( + titleText, + textAlign: TextAlign.center, + overflow: TextOverflow.visible, + ) + : const SizedBox(), ), ), ); diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 0843494b..659ab1cb 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -27,6 +27,8 @@ class EditableTabBar extends StatelessWidget { final int currentIndex; + final Widget? updateButton; + final double? gridDpiOverride; const EditableTabBar({ @@ -41,6 +43,7 @@ class EditableTabBar extends StatelessWidget { required this.onTabRename, required this.onTabChanged, required this.onTabDuplicate, + this.updateButton, this.gridDpiOverride, }); @@ -245,6 +248,7 @@ class EditableTabBar extends StatelessWidget { // Tab movement buttons (move left, close, move right) Row( children: [ + if (updateButton != null) updateButton!, IconButton( style: endButtonStyle, onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index a1c81d27..819c0ef5 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -109,13 +109,18 @@ class _SettingsDialogState extends State { // Network Tab Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ..._ipAddressSettings(), - const Divider(), - ..._networkTablesSettings(), - ], + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 250), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ..._ipAddressSettings(), + const Divider(), + ..._networkTablesSettings(), + ], + ), + ), ), ), // Style Preferences Tab @@ -123,7 +128,7 @@ class _SettingsDialogState extends State { padding: const EdgeInsets.symmetric(horizontal: 4.0), child: SingleChildScrollView( child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 350), + constraints: const BoxConstraints(maxHeight: 355), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -138,10 +143,15 @@ class _SettingsDialogState extends State { // Advanced Settings Tab Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Column( - children: [ - ..._advancedSettings(), - ], + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 140), + child: Column( + children: [ + ..._advancedSettings(), + ], + ), + ), ), ), ], @@ -237,8 +247,8 @@ class _SettingsDialogState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Flexible( - flex: 2, + SizedBox( + width: 140, child: UnconstrainedBox( constrainedAxis: Axis.horizontal, child: DialogColorPicker( @@ -252,7 +262,6 @@ class _SettingsDialogState extends State { ), const VerticalDivider(), Flexible( - flex: 4, child: Column( children: [ const Text('Theme Variant'), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d76db182..6cf6d508 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,7 +7,7 @@ #include "generated_plugin_registrant.h" #include -#include +#include #include #include @@ -15,9 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) screen_retriever_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); - screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7432170a..74bbc59d 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,7 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux - screen_retriever + screen_retriever_linux url_launcher_linux window_manager ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ed842a3f..24b61b4f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,7 @@ import Foundation import file_selector_macos import package_info_plus import path_provider_foundation -import screen_retriever +import screen_retriever_macos import shared_preferences_foundation import url_launcher_macos import window_manager @@ -17,7 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 3d92364e..31e9534c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -253,10 +253,10 @@ packages: dependency: "direct main" description: name: elegant_notification - sha256: f6ad40163a06ac5c41fe698d39c4e45b5d6c1617d6cd17062e11e1ecd39b3a97 + sha256: "4439f045ba21c931316bc1dce731f59d59e0ec4167971639e040ad7e5d96ba9f" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.1" equatable: dependency: transitive description: @@ -853,10 +853,42 @@ packages: dependency: "direct main" description: name: screen_retriever - sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" url: "https://pub.dev" source: hosted - version: "0.1.9" + version: "0.2.0" searchable_listview: dependency: "direct main" description: @@ -1210,10 +1242,10 @@ packages: dependency: "direct main" description: name: window_manager - sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494 + sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059" url: "https://pub.dev" source: hosted - version: "0.3.8" + version: "0.4.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0a6ed036..a7d48bf5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: decimal: ^2.3.3 dot_cast: ^1.2.0 dropdown_button2: ^2.3.9 - elegant_notification: ^2.2.0 + elegant_notification: ^2.4.1 file_selector: ^1.0.1 flex_seed_scheme: ^2.0.0 flutter: @@ -39,7 +39,7 @@ dependencies: patterns_canvas: ^0.4.0 popover: ^0.3.0 provider: ^6.0.5 - screen_retriever: ^0.1.9 + screen_retriever: ^0.2.0 searchable_listview: ^2.7.0 shared_preferences: ^2.1.2 syncfusion_flutter_charts: ^23.1.44 @@ -51,7 +51,7 @@ dependencies: version: ^3.0.2 visibility_detector: ^0.4.0+2 web_socket_channel: ^2.4.3 - window_manager: ^0.3.7 + window_manager: ^0.4.3 dev_dependencies: flutter_test: diff --git a/test/pages/dashboard_page_test.dart b/test/pages/dashboard_page_test.dart index 24767655..a2f5fd0e 100644 --- a/test/pages/dashboard_page_test.dart +++ b/test/pages/dashboard_page_test.dart @@ -45,8 +45,13 @@ Future pumpDashboardPage( NTConnection? ntConnection, ElasticLayoutDownloader? layoutDownloader, UpdateChecker? updateChecker, + Size? size = const Size(1920, 1080), }) async { FlutterError.onError = ignoreOverflowErrors; + if (size != null) { + widgetTester.view.physicalSize = size; + widgetTester.view.devicePixelRatio = 1.0; + } await widgetTester.pumpWidget( MaterialApp( @@ -93,6 +98,73 @@ void main() { hotKeyManager.tearDown(); }); + group('[Responsive Layout]:', () { + final fileButton = find.widgetWithText(SubmenuButton, 'File'); + final collapsedMenu = find.widgetWithIcon(SubmenuButton, Icons.menu); + final title = find.text('Elastic'); + final teamNumber = find.text('Team 353'); + final latency = find.textContaining('Latency:'); + + testWidgets('Shows everything on normal screen', (widgetTester) async { + await pumpDashboardPage(widgetTester, preferences); + + expect(fileButton, findsOneWidget); + expect(collapsedMenu, findsNothing); + expect(title, findsOneWidget); + expect(teamNumber, findsOneWidget); + expect(latency, findsOneWidget); + }); + + testWidgets('Hides title when width = 525', (widgetTester) async { + await pumpDashboardPage( + widgetTester, + preferences, + size: const Size(525, 525), + ); + + expect(title, findsNothing); + }); + + testWidgets('Hides team number when width = 500', (widgetTester) async { + await pumpDashboardPage( + widgetTester, + preferences, + size: const Size(500, 500), + ); + + expect(fileButton, findsOneWidget); + expect(collapsedMenu, findsNothing); + + expect(teamNumber, findsNothing); + }); + + testWidgets('Collapses menu when width < 460', (widgetTester) async { + await pumpDashboardPage( + widgetTester, + preferences, + size: const Size(459.99, 459.99), + ); + + expect(fileButton, findsNothing); + expect(collapsedMenu, findsOneWidget); + expect(title, findsNothing); + }); + + testWidgets('Hides latency when width = 300', (widgetTester) async { + await pumpDashboardPage( + widgetTester, + preferences, + size: const Size(300, 300), + ); + + expect(fileButton, findsNothing); + expect(collapsedMenu, findsOneWidget); + expect(title, findsNothing); + expect(teamNumber, findsNothing); + expect(latency, findsNothing); + }); + }); + group('[Loading and Saving]:', () { testWidgets('offline loading', (widgetTester) async { await pumpDashboardPage(widgetTester, preferences); @@ -483,6 +555,7 @@ void main() { widgetTester, preferences, ntConnection: createMockOnlineNT4(), + size: null, ); final addWidget = find.widgetWithText(MenuItemButton, 'Add Widget'); diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0d3b5b1f..d1a47a99 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,15 +7,15 @@ #include "generated_plugin_registrant.h" #include -#include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - ScreenRetrieverPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index c0677f7b..39617810 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows - screen_retriever + screen_retriever_windows url_launcher_windows window_manager )