feat(mobile): refine composer ui with minimalist modern aesthetic

This commit is contained in:
Haitao Pan 2026-05-26 09:34:27 +08:00
parent b18550b6ce
commit 6f9f0c75c9
3 changed files with 96 additions and 12 deletions

View File

@ -164,14 +164,18 @@ class MobileAssistantComposer extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 8, bottom: 5),
child: CircleAvatar(
radius: 18,
backgroundColor: palette.surfaceSecondary,
padding: const EdgeInsets.only(right: 8, bottom: 7),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.light ? const Color(0xFFF4F6F8) : palette.surfaceSecondary,
shape: BoxShape.circle,
),
child: IconButton(
key: const Key('mobile-assistant-composer-add-button'),
padding: EdgeInsets.zero,
icon: Icon(Icons.add, color: palette.textPrimary, size: 22),
icon: Icon(Icons.add, color: palette.textPrimary, size: 20),
onPressed: showConfigurationMenu,
),
),
@ -179,7 +183,7 @@ class MobileAssistantComposer extends StatelessWidget {
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: palette.surfaceSecondary,
color: Theme.of(context).brightness == Brightness.light ? const Color(0xFFF4F6F8) : palette.surfaceSecondary,
borderRadius: BorderRadius.circular(26),
),
child: Row(
@ -214,21 +218,32 @@ class MobileAssistantComposer extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.only(right: 12, bottom: 12),
child: Icon(Icons.mic_none, color: palette.textMuted, size: 24),
child: Icon(Icons.mic_none, color: Theme.of(context).brightness == Brightness.light ? const Color(0xFF9CA3AF) : palette.textMuted, size: 22),
),
],
),
),
),
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 5),
child: CircleAvatar(
radius: 18,
backgroundColor: palette.accent,
padding: const EdgeInsets.only(left: 8, bottom: 7),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.light ? const Color(0xFF6EA8FF) : palette.accent,
shape: BoxShape.circle,
boxShadow: Theme.of(context).brightness == Brightness.light ? [
BoxShadow(
color: const Color(0xFF6EA8FF).withValues(alpha: 0.15),
blurRadius: 4,
offset: const Offset(0, 2),
)
] : null,
),
child: IconButton(
key: const Key('mobile-assistant-send-button'),
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_upward_rounded, color: Colors.white, size: 24),
icon: const Icon(Icons.arrow_upward_rounded, color: Colors.white, size: 20),
onPressed: onSend,
),
),

View File

@ -388,6 +388,50 @@ void main() {
);
});
test(
'mobile target selection keeps a complete remote workspace when local home is unavailable',
() async {
final controller = AppController(
environmentOverride: const <String, String>{},
initialGatewayProviderCatalog: <SingleAgentProvider>[
SingleAgentProvider.openclaw.copyWith(
supportedTargets: const <AssistantExecutionTarget>[
AssistantExecutionTarget.gateway,
],
),
],
initialAvailableExecutionTargets: const <AssistantExecutionTarget>[
AssistantExecutionTarget.agent,
AssistantExecutionTarget.gateway,
],
);
addTearDown(controller.dispose);
controller.resolvedUserHomeDirectoryInternal = '';
await controller.ensureActiveAssistantThreadInternal();
await controller.setAssistantExecutionTarget(
AssistantExecutionTarget.gateway,
);
final record = controller.taskThreadForSessionInternal(
controller.currentSessionKey,
);
expect(controller.currentAssistantExecutionTarget.isGateway, isTrue);
expect(record?.workspaceBinding.isComplete, isTrue);
expect(record?.workspaceBinding.workspaceKind, WorkspaceKind.remoteFs);
expect(
record?.workspaceBinding.workspacePath,
contains('/threads/${controller.currentSessionKey}'),
);
expect(
controller.assistantWorkingDirectoryForSessionInternal(
controller.currentSessionKey,
),
record?.workspaceBinding.workspacePath,
);
},
);
test('writes inline ACP artifacts into the local thread workspace', () async {
final controller = AppController(
environmentOverride: const <String, String>{},

View File

@ -1132,6 +1132,31 @@ void main() {
);
});
test(
'sendChatMessage runs Gateway task with remote workspace when local workspace is unavailable',
() async {
final fakeGoTaskService = _RecordingGoTaskServiceClient();
final controller = _connectedGatewayController(fakeGoTaskService);
addTearDown(controller.dispose);
controller.resolvedUserHomeDirectoryInternal = '';
await controller.ensureActiveAssistantThreadInternal();
await controller.setAssistantExecutionTarget(
AssistantExecutionTarget.gateway,
);
await controller.sendChatMessage('mobile gateway task');
expect(fakeGoTaskService.requests, hasLength(1));
final request = fakeGoTaskService.requests.single;
expect(request.target, AssistantExecutionTarget.gateway);
expect(request.provider.providerId, 'openclaw');
expect(request.workingDirectory, startsWith('/owners/'));
expect(request.workingDirectory, contains('/threads/'));
expect(request.remoteWorkingDirectoryHint, request.workingDirectory);
expect(request.prompt, contains(request.workingDirectory));
},
);
test(
'sendChatMessage forwards inline attachment content and size',
() async {