feat: 重构 Home Screen 视觉设计与消息输入组件
- 新增 Home Screen 视觉设计 token (背景、工具栏、对话区、输入框等) - 重构首页布局为浮动式底部输入栈结构 - 新增 HomeBackgroundField、HomeFloatingHeader、HomeAttachmentStrip 组件 - 优化 MessageComposer 视觉样式为悬浮 shell 设计 - 添加相关测试用例
This commit is contained in:
@@ -9,6 +9,8 @@ enum MessageComposerMode { text, holdToSpeak }
|
||||
enum MessageComposerProcess { idle, recording, transcribing }
|
||||
|
||||
const messageComposerContainerKey = ValueKey('message_composer_container');
|
||||
const messageComposerShellKey = ValueKey('message_composer_shell');
|
||||
const messageComposerInnerKey = ValueKey('message_composer_inner');
|
||||
const messageComposerPlusButtonKey = ValueKey('message_composer_plus_button');
|
||||
const messageComposerRightButtonKey = ValueKey('message_composer_right_button');
|
||||
const messageComposerHoldAreaKey = ValueKey('message_composer_hold_area');
|
||||
@@ -35,7 +37,7 @@ class MessageComposer extends StatelessWidget {
|
||||
required this.textInputChild,
|
||||
required this.recordingAnimation,
|
||||
this.holdToSpeakText = '按住说话',
|
||||
this.recordingText = '松手发送',
|
||||
this.recordingText = '松开发送',
|
||||
this.transcribingText = '语音识别中...',
|
||||
this.recordingHintText = '松开发送,上滑取消',
|
||||
this.showRecordingInlineFeedback = true,
|
||||
@@ -69,71 +71,77 @@ class MessageComposer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return KeyedSubtree(
|
||||
key: messageComposerContainerKey,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
border: Border.all(color: AppColors.slate200),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: AppColors.slate200,
|
||||
blurRadius: AppRadius.lg,
|
||||
offset: Offset(AppSpacing.none, AppSpacing.xs),
|
||||
),
|
||||
BoxShadow(
|
||||
color: AppColors.white,
|
||||
blurRadius: AppRadius.md,
|
||||
offset: Offset(AppSpacing.none, -AppSpacing.xs),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
ignoring: _isRecording && _isHoldMode,
|
||||
child: Opacity(
|
||||
opacity: _isRecording && _isHoldMode ? AppSpacing.none : 1,
|
||||
child: IconButton(
|
||||
key: messageComposerPlusButtonKey,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: onTapPlus,
|
||||
icon: Icon(
|
||||
LucideIcons.plus,
|
||||
size: iconSize,
|
||||
color: AppColors.slate500,
|
||||
child: Container(
|
||||
key: messageComposerShellKey,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.homeComposerShell,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
border: Border.all(color: AppColors.homeComposerBorder),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: AppColors.slate200,
|
||||
blurRadius: AppRadius.lg,
|
||||
offset: Offset(AppSpacing.none, AppSpacing.sm),
|
||||
),
|
||||
BoxShadow(
|
||||
color: AppColors.white,
|
||||
blurRadius: AppRadius.md,
|
||||
offset: Offset(AppSpacing.none, -AppSpacing.xs),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: KeyedSubtree(
|
||||
key: messageComposerInnerKey,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
ignoring: _isRecording && _isHoldMode,
|
||||
child: Opacity(
|
||||
opacity: _isRecording && _isHoldMode ? AppSpacing.none : 1,
|
||||
child: IconButton(
|
||||
key: messageComposerPlusButtonKey,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: onTapPlus,
|
||||
icon: Icon(
|
||||
LucideIcons.plus,
|
||||
size: iconSize,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(child: _buildCenterArea()),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
IconButton(
|
||||
key: messageComposerRightButtonKey,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: onTapRightAction,
|
||||
icon: _isTranscribing
|
||||
? const SizedBox(
|
||||
width: AppSpacing.lg,
|
||||
height: AppSpacing.lg,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: AppSpacing.xs / 2,
|
||||
color: AppColors.blue600,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
_resolveRightIcon(),
|
||||
size: iconSize,
|
||||
color: _resolveRightIconColor(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(child: _buildCenterArea()),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
IconButton(
|
||||
key: messageComposerRightButtonKey,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: onTapRightAction,
|
||||
icon: _isTranscribing
|
||||
? const SizedBox(
|
||||
width: AppSpacing.lg,
|
||||
height: AppSpacing.lg,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: AppSpacing.xs / 2,
|
||||
color: AppColors.blue600,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
_resolveRightIcon(),
|
||||
size: iconSize,
|
||||
color: _resolveRightIconColor(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -201,6 +209,11 @@ class MessageComposer extends StatelessWidget {
|
||||
children: [
|
||||
recordingAnimation,
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
recordingText,
|
||||
style: const TextStyle(color: AppColors.slate700),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
recordingHintText,
|
||||
key: messageComposerRecordingHintKey,
|
||||
|
||||
Reference in New Issue
Block a user