From 37ee2729e000bc47ab1c1f54afa431ccd077a1c1 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Tue, 24 Mar 2026 18:26:39 +0800 Subject: [PATCH] refactor(web): align shell and focused entry chrome --- lib/app/app_controller_web.dart | 35 +- lib/app/app_shell_web.dart | 176 +--------- lib/web/web_assistant_page.dart | 546 +++++++++++++++++++----------- test/web/web_ui_browser_test.dart | 50 ++- 4 files changed, 427 insertions(+), 380 deletions(-) diff --git a/lib/app/app_controller_web.dart b/lib/app/app_controller_web.dart index fc8e525b..79cd241f 100644 --- a/lib/app/app_controller_web.dart +++ b/lib/app/app_controller_web.dart @@ -96,6 +96,12 @@ class AppController extends ChangeNotifier { bool get hasPendingSettingsApply => _pendingSettingsApply; String get settingsDraftStatusMessage => _settingsDraftStatusMessage; AppLanguage get appLanguage => _settings.appLanguage; + AssistantPermissionLevel get assistantPermissionLevel => + _settings.assistantPermissionLevel; + List get assistantNavigationDestinations => + _settings.assistantNavigationDestinations + .where(capabilities.supportsDestination) + .toList(growable: false); GatewayConnectionSnapshot get connection => _relayClient.snapshot; bool get relayBusy => _relayBusy; bool get aiGatewayBusy => _aiGatewayBusy; @@ -643,6 +649,27 @@ class AppController extends ChangeNotifier { notifyListeners(); } + Future toggleAssistantNavigationDestination( + WorkspaceDestination destination, + ) async { + if (!kAssistantNavigationDestinationCandidates.contains(destination) || + !capabilities.supportsDestination(destination)) { + return; + } + final current = assistantNavigationDestinations; + final next = current.contains(destination) + ? current.where((item) => item != destination).toList(growable: false) + : [...current, destination]; + _settings = _settings.copyWith(assistantNavigationDestinations: next); + if (_settingsDraftInitialized) { + _settingsDraft = settingsDraft.copyWith( + assistantNavigationDestinations: next, + ); + } + notifyListeners(); + await _persistSettings(); + } + Future setThemeMode(ThemeMode mode) async { if (_themeMode == mode) { return; @@ -1829,9 +1856,15 @@ class AppController extends ChangeNotifier { } SettingsSnapshot _sanitizeSettings(SettingsSnapshot snapshot) { + final allowedDestinations = featuresFor(UiFeaturePlatform.web) + .allowedDestinations; final target = featuresFor(UiFeaturePlatform.web).sanitizeExecutionTarget( _sanitizeTarget(snapshot.assistantExecutionTarget), ); + final assistantNavigationDestinations = + normalizeAssistantNavigationDestinations( + snapshot.assistantNavigationDestinations, + ).where(allowedDestinations.contains).toList(growable: false); final normalizedSessionBaseUrl = RemoteWebSessionRepository.normalizeBaseUrl( snapshot.webSessionPersistence.remoteBaseUrl, @@ -1862,7 +1895,7 @@ class AppController extends ChangeNotifier { webSessionPersistence: snapshot.webSessionPersistence.copyWith( remoteBaseUrl: normalizedSessionBaseUrl, ), - assistantNavigationDestinations: const [], + assistantNavigationDestinations: assistantNavigationDestinations, ); } diff --git a/lib/app/app_shell_web.dart b/lib/app/app_shell_web.dart index 82a11226..27ce5160 100644 --- a/lib/app/app_shell_web.dart +++ b/lib/app/app_shell_web.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import '../i18n/app_language.dart'; import '../models/app_models.dart'; -import '../theme/app_palette.dart'; import '../web/web_assistant_page.dart'; import '../web/web_settings_page.dart'; -import '../widgets/app_brand_logo.dart'; import 'app_controller_web.dart'; class AppShell extends StatelessWidget { @@ -75,87 +72,9 @@ class AppShell extends StatelessWidget { ); } - final palette = context.palette; - return Row( - children: [ - Container( - width: 76, - margin: const EdgeInsets.fromLTRB(4, 4, 0, 4), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - palette.chromeHighlight.withValues(alpha: 0.94), - palette.chromeSurface.withValues(alpha: 0.92), - ], - ), - borderRadius: BorderRadius.circular(24), - border: Border.all(color: palette.chromeStroke), - boxShadow: [palette.chromeShadowAmbient], - ), - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 12, 10, 10), - child: Column( - children: [ - Container( - width: 52, - height: 52, - decoration: BoxDecoration( - color: palette.surfacePrimary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: palette.strokeSoft), - ), - child: const Center( - child: AppBrandLogo(size: 28, borderRadius: 8), - ), - ), - const SizedBox(height: 18), - ...availableDestinations.map( - (destination) => Padding( - padding: const EdgeInsets.only(bottom: 8), - child: _WebNavRailButton( - key: Key('web-shell-nav-${destination.name}'), - destination: destination, - selected: currentDestination == destination, - onTap: () => - controller.navigateTo(destination), - ), - ), - ), - const Spacer(), - _WebUtilityButton( - key: const Key('web-shell-language-toggle'), - tooltip: controller.appLanguage == AppLanguage.zh - ? '中文' - : 'English', - icon: Icons.translate_rounded, - onTap: controller.toggleAppLanguage, - ), - const SizedBox(height: 8), - _WebUtilityButton( - key: const Key('web-shell-theme-toggle'), - tooltip: _themeLabel(controller.themeMode), - icon: controller.themeMode == ThemeMode.dark - ? Icons.dark_mode_rounded - : Icons.light_mode_rounded, - onTap: () => controller.setThemeMode( - controller.themeMode == ThemeMode.dark - ? ThemeMode.light - : ThemeMode.dark, - ), - ), - ], - ), - ), - ), - Expanded( - child: _buildPage( - controller, - destination: currentDestination, - ), - ), - ], + return _buildPage( + controller, + destination: currentDestination, ); }, ), @@ -175,92 +94,3 @@ class AppShell extends StatelessWidget { }; } } - -class _WebNavRailButton extends StatelessWidget { - const _WebNavRailButton({ - super.key, - required this.destination, - required this.selected, - required this.onTap, - }); - - final WorkspaceDestination destination; - final bool selected; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final palette = context.palette; - return Tooltip( - message: destination.label, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 180), - curve: Curves.easeOutCubic, - width: 52, - height: 52, - decoration: BoxDecoration( - color: selected ? palette.accentMuted : palette.surfacePrimary, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: selected - ? palette.accent.withValues(alpha: 0.28) - : palette.strokeSoft, - ), - boxShadow: selected ? [palette.chromeShadowLift] : null, - ), - child: Icon( - destination.icon, - size: 22, - color: selected ? palette.accent : palette.textSecondary, - ), - ), - ), - ); - } -} - -class _WebUtilityButton extends StatelessWidget { - const _WebUtilityButton({ - super.key, - required this.tooltip, - required this.icon, - required this.onTap, - }); - - final String tooltip; - final IconData icon; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final palette = context.palette; - return Tooltip( - message: tooltip, - child: InkWell( - borderRadius: BorderRadius.circular(14), - onTap: onTap, - child: Container( - width: 52, - height: 44, - decoration: BoxDecoration( - color: palette.surfacePrimary, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: palette.strokeSoft), - ), - child: Icon(icon, size: 20, color: palette.textSecondary), - ), - ), - ); - } -} - -String _themeLabel(ThemeMode mode) { - return switch (mode) { - ThemeMode.dark => appText('深色', 'Dark'), - ThemeMode.system => appText('跟随系统', 'System'), - ThemeMode.light => appText('浅色', 'Light'), - }; -} diff --git a/lib/web/web_assistant_page.dart b/lib/web/web_assistant_page.dart index 852a3cda..568795af 100644 --- a/lib/web/web_assistant_page.dart +++ b/lib/web/web_assistant_page.dart @@ -807,109 +807,359 @@ class _AssistantQuickPane extends StatelessWidget { @override Widget build(BuildContext context) { + final favorites = controller.assistantNavigationDestinations + .where((item) => item == WorkspaceDestination.settings) + .toList(growable: false); + final canAddSettings = + controller.capabilities.supportsDestination( + WorkspaceDestination.settings, + ) && + !favorites.contains(WorkspaceDestination.settings); + final palette = context.palette; + return SurfaceCard( borderRadius: 10, tone: SurfaceCardTone.chrome, + padding: EdgeInsets.zero, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 10, 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appText('关注入口', 'Focused navigation'), + key: const Key('assistant-focus-panel-title'), + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 6), + Text( + appText( + '添加后的入口会直接出现在最左侧侧板。这里负责管理关注项和查看摘要,需要完整页面时再单独打开。', + 'Added entries appear directly in the far-left rail. Manage focused destinations and review summaries here, then open the full page only when needed.', + ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: palette.textSecondary, + height: 1.35, + ), + ), + ], + ), + ), + if (canAddSettings) + Tooltip( + message: appText('添加关注入口', 'Add focused destination'), + child: InkWell( + key: const Key('assistant-focus-add-menu'), + borderRadius: BorderRadius.circular(12), + onTap: () { + controller.toggleAssistantNavigationDestination( + WorkspaceDestination.settings, + ); + }, + child: Container( + width: 38, + height: 38, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + palette.chromeHighlight.withValues(alpha: 0.94), + palette.chromeSurfacePressed, + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: palette.chromeStroke), + boxShadow: [palette.chromeShadowLift], + ), + child: Icon( + Icons.add_rounded, + size: 18, + color: palette.textSecondary, + ), + ), + ), + ), + ], + ), + ), + Divider(height: 1, color: palette.strokeSoft), + Expanded( + child: favorites.isEmpty + ? _FocusedNavigationEmptyState( + canAddSettings: canAddSettings, + onAddSettings: () { + controller.toggleAssistantNavigationDestination( + WorkspaceDestination.settings, + ); + }, + ) + : ListView( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 12), + children: [ + _FocusedSettingsCard( + controller: controller, + onOpenPage: () => + controller.openSettings(tab: SettingsTab.general), + onRemoveFavorite: () { + controller.toggleAssistantNavigationDestination( + WorkspaceDestination.settings, + ); + }, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _FocusedNavigationEmptyState extends StatelessWidget { + const _FocusedNavigationEmptyState({ + required this.canAddSettings, + required this.onAddSettings, + }); + + final bool canAddSettings; + final VoidCallback onAddSettings; + + @override + Widget build(BuildContext context) { + final palette = context.palette; + final theme = Theme.of(context); + + return ListView( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 12), + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: palette.surfaceSecondary, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: palette.strokeSoft), + ), + child: Text( + appText( + '还没有关注入口。给功能菜单点星标,或从右上角添加一个入口,加入最左侧侧板。', + 'No focused entries yet. Star a destination or add one from the top-right menu to place it in the far-left rail.', + ), + style: theme.textTheme.bodySmall?.copyWith( + color: palette.textSecondary, + height: 1.35, + ), + ), + ), + if (canAddSettings) ...[ + const SizedBox(height: 12), + Align( + alignment: Alignment.centerLeft, + child: ActionChip( + key: const ValueKey('assistant-focus-add-settings'), + avatar: const Icon(Icons.tune_rounded, size: 16), + label: Text(WorkspaceDestination.settings.label), + onPressed: onAddSettings, + ), + ), + ], + ], + ); + } +} + +class _FocusedSettingsCard extends StatelessWidget { + const _FocusedSettingsCard({ + required this.controller, + required this.onOpenPage, + required this.onRemoveFavorite, + }); + + final AppController controller; + final VoidCallback onOpenPage; + final VoidCallback onRemoveFavorite; + + @override + Widget build(BuildContext context) { + final palette = context.palette; + final theme = Theme.of(context); + final languageLabel = controller.appLanguage == AppLanguage.zh + ? appText('中文', 'Chinese') + : 'English'; + final themeLabel = switch (controller.themeMode) { + ThemeMode.dark => appText('深色', 'Dark'), + ThemeMode.light => appText('浅色', 'Light'), + ThemeMode.system => appText('跟随系统', 'System'), + }; + + return Container( + decoration: BoxDecoration( + color: palette.surfacePrimary, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: palette.strokeSoft), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 10, 10), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: palette.surfaceSecondary, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + WorkspaceDestination.settings.icon, + size: 18, + color: palette.accent, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + WorkspaceDestination.settings.label, + key: const ValueKey( + 'assistant-focus-active-title-settings', + ), + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 3), + Text( + WorkspaceDestination.settings.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodySmall?.copyWith( + color: palette.textSecondary, + height: 1.3, + ), + ), + ], + ), + ), + IconButton( + key: const ValueKey('assistant-focus-open-page-settings'), + tooltip: appText('打开全页', 'Open full page'), + onPressed: onOpenPage, + icon: const Icon(Icons.open_in_new_rounded, size: 18), + ), + IconButton( + key: const ValueKey('assistant-focus-remove-settings'), + tooltip: appText('取消关注', 'Remove from focused panel'), + onPressed: onRemoveFavorite, + icon: Icon(Icons.star_rounded, color: palette.accent), + ), + ], + ), + ), + Divider(height: 1, color: palette.strokeSoft), + Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 14), + child: Column( + children: [ + _FocusedPreviewTile( + title: appText('语言', 'Language'), + subtitle: appText('当前界面语言', 'Current interface language'), + trailing: languageLabel, + ), + const SizedBox(height: 8), + _FocusedPreviewTile( + title: appText('主题', 'Theme'), + subtitle: appText('当前显示模式', 'Current display mode'), + trailing: themeLabel, + ), + const SizedBox(height: 8), + _FocusedPreviewTile( + title: appText('执行目标', 'Execution target'), + subtitle: appText( + 'Assistant 默认运行位置', + 'Default assistant execution target', + ), + trailing: controller.assistantExecutionTarget.label, + ), + const SizedBox(height: 8), + _FocusedPreviewTile( + title: appText('权限', 'Permissions'), + subtitle: appText( + 'Assistant 默认权限级别', + 'Default assistant permission level', + ), + trailing: controller.assistantPermissionLevel.label, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _FocusedPreviewTile extends StatelessWidget { + const _FocusedPreviewTile({ + required this.title, + required this.subtitle, + required this.trailing, + }); + + final String title; + final String subtitle; + final String trailing; + + @override + Widget build(BuildContext context) { + final palette = context.palette; + final theme = Theme.of(context); + + return Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(12, 12, 12, 12), + decoration: BoxDecoration( + color: palette.surfaceSecondary, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: palette.strokeSoft), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - appText('关注入口', 'Focused navigation'), - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), + title, + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), ), - const SizedBox(height: 6), + const SizedBox(height: 4), Text( - appText( - '这里放首页常用设置与运行态摘要;需要完整配置时再进入 Settings。', - 'Use this rail for homepage settings and runtime summaries, then open full Settings when needed.', - ), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: context.palette.textSecondary, + subtitle, + style: theme.textTheme.bodySmall?.copyWith( + color: palette.textSecondary, + height: 1.3, ), ), - const SizedBox(height: 12), - _QuickInfoCard( - icon: WorkspaceDestination.settings.icon, - title: WorkspaceDestination.settings.label, - description: WorkspaceDestination.settings.description, - trailing: IconButton( - tooltip: appText('打开全页', 'Open full page'), - onPressed: () => - controller.openSettings(tab: SettingsTab.general), - icon: const Icon(Icons.open_in_new_rounded), - ), - ), - const SizedBox(height: 10), - _QuickValueCard( - title: appText('语言', 'Language'), - subtitle: appText('当前界面语言', 'Current interface language'), - value: controller.appLanguage == AppLanguage.zh ? '中文' : 'English', - ), - const SizedBox(height: 10), - _QuickValueCard( - title: appText('主题', 'Theme'), - subtitle: appText('当前显示模式', 'Current display mode'), - value: switch (controller.themeMode) { - ThemeMode.dark => appText('深色', 'Dark'), - ThemeMode.system => appText('跟随系统', 'System'), - ThemeMode.light => appText('浅色', 'Light'), - }, - ), - const SizedBox(height: 10), - _QuickValueCard( - title: appText('执行目标', 'Execution target'), - subtitle: appText('Assistant 默认运行位置', 'Assistant default target'), - value: _targetLabel(controller.assistantExecutionTarget), - ), - const SizedBox(height: 10), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: context.palette.surfacePrimary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: context.palette.strokeSoft), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - appText('权限', 'Permissions'), - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 4), - Text( - appText( - 'Assistant 默认权限级别', - 'Assistant default permission level', - ), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: context.palette.textSecondary, - ), - ), - const SizedBox(height: 10), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - color: context.palette.surfaceSecondary, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: context.palette.strokeSoft), - ), - child: _CompactDropdown( - value: permissionLevel, - items: AssistantPermissionLevel.values, - labelBuilder: (item) => item.label, - onChanged: (value) { - if (value != null) { - onPermissionChanged(value); - } - }, - ), - ), - ], + const SizedBox(height: 8), + Text( + trailing, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelLarge?.copyWith( + color: palette.textPrimary, ), ), ], @@ -1709,118 +1959,6 @@ class _MetaChip extends StatelessWidget { } } -class _QuickInfoCard extends StatelessWidget { - const _QuickInfoCard({ - required this.icon, - required this.title, - required this.description, - this.trailing = const SizedBox.shrink(), - }); - - final IconData icon; - final String title; - final String description; - final Widget trailing; - - @override - Widget build(BuildContext context) { - final palette = context.palette; - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: palette.surfacePrimary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: palette.strokeSoft), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: palette.surfaceSecondary, - borderRadius: BorderRadius.circular(14), - ), - child: Icon(icon, size: 18, color: palette.accent), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 4), - Text( - description, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: palette.textSecondary), - ), - ], - ), - ), - trailing, - ], - ), - ); - } -} - -class _QuickValueCard extends StatelessWidget { - const _QuickValueCard({ - required this.title, - required this.subtitle, - required this.value, - }); - - final String title; - final String subtitle; - final String value; - - @override - Widget build(BuildContext context) { - final palette = context.palette; - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: palette.surfacePrimary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: palette.strokeSoft), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 4), - Text( - subtitle, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: palette.textSecondary), - ), - const SizedBox(height: 10), - Text( - value, - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700), - ), - ], - ), - ); - } -} class _CompactDropdown extends StatelessWidget { const _CompactDropdown({ diff --git a/test/web/web_ui_browser_test.dart b/test/web/web_ui_browser_test.dart index df5977d1..37338f3f 100644 --- a/test/web/web_ui_browser_test.dart +++ b/test/web/web_ui_browser_test.dart @@ -21,14 +21,60 @@ void main() { await tester.pumpAndSettle(); expect(find.text('助手'), findsWidgets); - expect(find.byKey(const Key('web-shell-nav-assistant')), findsOneWidget); - expect(find.byKey(const Key('web-shell-nav-settings')), findsOneWidget); + expect(find.byKey(const Key('web-shell-nav-assistant')), findsNothing); + expect(find.byKey(const Key('web-shell-nav-settings')), findsNothing); + expect(find.byKey(const Key('web-shell-language-toggle')), findsNothing); + expect(find.byKey(const Key('web-shell-theme-toggle')), findsNothing); expect(find.text('Tasks'), findsNothing); expect(find.byKey(const Key('assistant-task-rail')), findsOneWidget); expect( find.byKey(const Key('assistant-attachment-menu-button')), findsOneWidget, ); + expect(find.byKey(const Key('assistant-focus-panel-title')), findsNothing); + + await tester.tap(find.byKey(const Key('assistant-side-pane-tab-quick'))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('assistant-focus-panel-title')), findsOneWidget); + expect( + find.byKey(const ValueKey('assistant-focus-add-settings')), + findsOneWidget, + ); + expect( + find.byKey(const ValueKey('assistant-focus-add-tasks')), + findsNothing, + ); + expect( + find.byKey(const ValueKey('assistant-focus-add-skills')), + findsNothing, + ); + expect( + find.byKey(const ValueKey('assistant-focus-add-nodes')), + findsNothing, + ); + expect( + find.byKey(const ValueKey('assistant-focus-add-secrets')), + findsNothing, + ); + expect( + find.byKey(const ValueKey('assistant-focus-add-aiGateway')), + findsNothing, + ); + + await tester.tap( + find.byKey(const ValueKey('assistant-focus-add-settings')), + ); + await tester.pumpAndSettle(); + + expect( + find.byKey(const ValueKey('assistant-focus-open-page-settings')), + findsOneWidget, + ); + expect( + find.byKey(const ValueKey('assistant-focus-remove-settings')), + findsOneWidget, + ); await tester.tap(find.text('连接设置')); await tester.pumpAndSettle();