feat(mobile): move configuration chips to + menu and add left drawer

This commit is contained in:
Haitao Pan 2026-05-26 07:17:16 +08:00
parent f8facadca8
commit 9c2c07b700
3 changed files with 196 additions and 74 deletions

View File

@ -82,6 +82,21 @@ class _MobileAssistantListPageState extends State<MobileAssistantListPage> {
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<MobileAssistantListPage> {
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<MobileAssistantListPage> {
),
],
),
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,

View File

@ -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 {

View File

@ -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')),
);