feat(mobile): move configuration chips to + menu and add left drawer
This commit is contained in:
parent
f8facadca8
commit
9c2c07b700
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user