From 9c2c07b700d6132dc74fb68e4764e29f9aad6786 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Tue, 26 May 2026 07:17:16 +0800 Subject: [PATCH] feat(mobile): move configuration chips to + menu and add left drawer --- .../mobile/mobile_assistant_list_page.dart | 82 +++++++-- .../mobile_assistant_page_composer.dart | 171 +++++++++++------- .../mobile/mobile_assistant_page_test.dart | 17 ++ 3 files changed, 196 insertions(+), 74 deletions(-) diff --git a/lib/features/mobile/mobile_assistant_list_page.dart b/lib/features/mobile/mobile_assistant_list_page.dart index de055cf6..3bb7e421 100644 --- a/lib/features/mobile/mobile_assistant_list_page.dart +++ b/lib/features/mobile/mobile_assistant_list_page.dart @@ -82,6 +82,21 @@ class _MobileAssistantListPageState extends State { backgroundColor: palette.canvas.withValues(alpha: 0.8), largeTitle: const Text('XWorkmate'), border: null, + leading: Builder( + builder: (context) { + return CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + Scaffold.of(context).openDrawer(); + }, + child: CircleAvatar( + radius: 14, + backgroundColor: palette.accent, + child: const Text('X', style: TextStyle(color: Colors.white, fontSize: 12)), + ), + ); + } + ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -102,18 +117,6 @@ class _MobileAssistantListPageState extends State { size: 24, ), ), - const SizedBox(width: 8), - CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () { - // TODO: Open settings or profile - }, - child: CircleAvatar( - radius: 14, - backgroundColor: palette.accent, - child: const Text('X', style: TextStyle(color: Colors.white, fontSize: 12)), - ), - ), ], ), ), @@ -231,6 +234,61 @@ class _MobileAssistantListPageState extends State { ), ], ), + drawer: Drawer( + backgroundColor: palette.surfacePrimary, + child: SafeArea( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: palette.accent, + child: const Text('X', style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)), + ), + const SizedBox(width: 16), + Text( + 'XWorkmate', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: palette.textPrimary, + ), + ), + ], + ), + ), + const Divider(), + ListTile( + leading: Icon(CupertinoIcons.settings, color: palette.textPrimary), + title: Text(appText('设置', 'Settings'), style: TextStyle(color: palette.textPrimary)), + onTap: () { + Navigator.pop(context); + // TODO: Navigate to settings + }, + ), + ListTile( + leading: Icon(CupertinoIcons.person, color: palette.textPrimary), + title: Text(appText('个人中心', 'Profile'), style: TextStyle(color: palette.textPrimary)), + onTap: () { + Navigator.pop(context); + // TODO: Navigate to profile + }, + ), + const Spacer(), + const Divider(), + ListTile( + leading: Icon(CupertinoIcons.info_circle, color: palette.textPrimary), + title: Text(appText('关于', 'About'), style: TextStyle(color: palette.textPrimary)), + onTap: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ), floatingActionButton: FloatingActionButton.extended( key: const Key('mobile-assistant-fab-create'), onPressed: _handleCreateTask, diff --git a/lib/features/mobile/mobile_assistant_page_composer.dart b/lib/features/mobile/mobile_assistant_page_composer.dart index 1e95e627..b5ea48d7 100644 --- a/lib/features/mobile/mobile_assistant_page_composer.dart +++ b/lib/features/mobile/mobile_assistant_page_composer.dart @@ -47,18 +47,32 @@ class MobileAssistantComposer extends StatelessWidget { final hasPendingRun = controller.hasAssistantPendingRun || controller.activeRunId != null; - return Padding( - key: const Key('mobile-assistant-composer'), - padding: EdgeInsets.fromLTRB(12, 8, 12, bottomPadding == 0 ? 12 : bottomPadding), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( + void showConfigurationMenu() { + showModalBottomSheet( + context: context, + backgroundColor: palette.surfacePrimary, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (sheetContext) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appText('会话配置', 'Configuration'), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: palette.textPrimary, + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 12, children: [ MobileAssistantActionChip( key: const Key('mobile-assistant-target-button'), @@ -66,82 +80,112 @@ class MobileAssistantComposer extends StatelessWidget { ? Icons.cloud_queue_rounded : Icons.smart_toy_outlined, label: target.compactLabel, - onTap: () => showMobileAssistantTargetSheet( - context, - controller: controller, - onSelected: onSetExecutionTarget, - ), + onTap: () { + Navigator.pop(sheetContext); + showMobileAssistantTargetSheet( + context, + controller: controller, + onSelected: onSetExecutionTarget, + ); + }, ), - const SizedBox(width: 6), MobileAssistantActionChip( key: const Key('mobile-assistant-provider-button'), icon: Icons.hub_outlined, label: providerLabel, - onTap: () => showMobileAssistantProviderSheet( - context, - controller: controller, - target: target, - selectedProvider: provider, - onSelected: onSetProvider, - ), + onTap: () { + Navigator.pop(sheetContext); + showMobileAssistantProviderSheet( + context, + controller: controller, + target: target, + selectedProvider: provider, + onSelected: onSetProvider, + ); + }, ), - const SizedBox(width: 6), MobileAssistantActionChip( key: const Key('mobile-assistant-permission-button'), icon: mobilePermissionIcon( controller.assistantPermissionLevel, ), label: controller.assistantPermissionLevel.label, - onTap: () => showMobileAssistantPermissionSheet( - context, - controller: controller, - ), + onTap: () { + Navigator.pop(sheetContext); + showMobileAssistantPermissionSheet( + context, + controller: controller, + ); + }, ), - const SizedBox(width: 6), MobileAssistantActionChip( key: const Key('mobile-assistant-thinking-button'), icon: Icons.psychology_alt_outlined, label: mobileThinkingLabel(thinking), - onTap: () => showMobileAssistantThinkingSheet( - context, - value: thinking, - onSelected: onThinkingChanged, - ), + onTap: () { + Navigator.pop(sheetContext); + showMobileAssistantThinkingSheet( + context, + value: thinking, + onSelected: onThinkingChanged, + ); + }, ), ], ), - ), + ], ), - if (hasPendingRun) ...[ - const SizedBox(width: 8), - IconButton.filledTonal( + ), + ); + }, + ); + } + + return Padding( + key: const Key('mobile-assistant-composer'), + padding: EdgeInsets.fromLTRB(12, 8, 12, bottomPadding == 0 ? 12 : bottomPadding), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (hasPendingRun) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: IconButton.filledTonal( key: const Key('mobile-assistant-stop-button'), onPressed: () => unawaited(controller.abortRun()), icon: const Icon(Icons.stop_rounded), tooltip: appText('停止运行', 'Stop Run'), ), - ], - ], - ), - const SizedBox(height: 12), - DecoratedBox( - decoration: BoxDecoration( - color: palette.surfaceSecondary, - borderRadius: BorderRadius.circular(26), + ), ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6, bottom: 4), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8, bottom: 4), + child: CircleAvatar( + radius: 20, + backgroundColor: palette.surfaceSecondary, child: IconButton( - icon: Icon(Icons.add, color: palette.textSecondary), - onPressed: () { - // 预留功能位 - }, + key: const Key('mobile-assistant-composer-add-button'), + padding: EdgeInsets.zero, + icon: Icon(Icons.add, color: palette.textPrimary, size: 24), + onPressed: showConfigurationMenu, ), ), - Expanded( + ), + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + color: palette.surfaceSecondary, + borderRadius: BorderRadius.circular(26), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( child: ConstrainedBox( constraints: const BoxConstraints( minHeight: 46, @@ -184,10 +228,13 @@ class MobileAssistantComposer extends StatelessWidget { ], ), ), - ], - ), - ); - } + ), + ], + ), + ], +), +); +} } class MobileAssistantActionChip extends StatelessWidget { diff --git a/test/features/mobile/mobile_assistant_page_test.dart b/test/features/mobile/mobile_assistant_page_test.dart index fab66885..41f8f2e2 100644 --- a/test/features/mobile/mobile_assistant_page_test.dart +++ b/test/features/mobile/mobile_assistant_page_test.dart @@ -74,6 +74,10 @@ void main() { await tester.pumpWidget(_buildTestApp(controller: controller)); await tester.pumpAndSettle(); + await tester.tap( + find.byKey(const Key('mobile-assistant-composer-add-button')), + ); + await tester.pumpAndSettle(); await tester.tap( find.byKey(const Key('mobile-assistant-provider-button')), ); @@ -118,6 +122,10 @@ void main() { await tester.pumpWidget(_buildTestApp(controller: controller)); await tester.pumpAndSettle(); + await tester.tap( + find.byKey(const Key('mobile-assistant-composer-add-button')), + ); + await tester.pumpAndSettle(); await tester.tap( find.byKey(const Key('mobile-assistant-provider-button')), ); @@ -134,6 +142,11 @@ void main() { await tester.tap(find.byKey(const Key('mobile-assistant-sheet-close'))); await tester.pumpAndSettle(); + + await tester.tap( + find.byKey(const Key('mobile-assistant-composer-add-button')), + ); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('mobile-assistant-target-button'))); await tester.pumpAndSettle(); await tester.tap( @@ -141,6 +154,10 @@ void main() { ); await tester.pumpAndSettle(); + await tester.tap( + find.byKey(const Key('mobile-assistant-composer-add-button')), + ); + await tester.pumpAndSettle(); await tester.tap( find.byKey(const Key('mobile-assistant-provider-button')), );