背景
完成状态管理重构后,我在实际使用中发现了多个 UI 细节问题,同时需要实现”选择网盘文件发送”功能,该功能与文件移动的选择器可以复用。
一、气泡颜色修复
问题
浅色模式下,聊天气泡背景色与页面背景对比度太低,几乎看不清。
分析
之前使用 primaryContainer,但 Material 3 的 ColorScheme.fromSeed 生成的 primaryContainer 是浅粉色,与我们主题的 primary(橙色)不匹配。
我这边指出:气泡应该使用设置页面 Switch 开关开启时的填充色。
在 Material 3 中,Switch 开启时的轨道颜色是 primary,所以气泡也应该使用 primary 带透明度。
解决方案
// chat_widgets.dart - MessageBubble
final bubbleColor = isSent
? theme.colorScheme.primary.withValues(alpha: 0.25) // 与 Switch 轨道一致
: theme.colorScheme.surfaceContainerHighest;
final textColor = theme.colorScheme.onSurface;二、垃圾桶图标透出问题
问题
由于气泡使用了透明度,滑动删除时底层的垃圾桶图标会透过气泡显示出来。
解决方案
按需渲染:只有在拖动时才渲染垃圾桶图标,未拖动时完全不渲染(不是设为透明)。
// SwipeToDeleteMessage
final isDragging = _dragExtent != 0 || _animController.isAnimating;
// 只在拖动时显示,性能更好
if (isDragging)
Positioned.fill(
child: Icon(Icons.delete_outline, color: iconColor),
),优点:
- 解决透出问题
- 性能更好(不渲染 > 设透明)
三、纯黑白默认主题
需求
我这边认为 Nord 配色作为”默认”不够”默认”,需要一个纯粹的黑白主题作为系统默认。
实现
新增 defaultDark 和 defaultLight 纯黑白配色,Nord 保留为可选项:
// DarkThemeType
enum DarkThemeType {
defaultDark, // 纯黑白(新默认)
nord, // Nord 风格
inkSmoke,
// ...
}
// 纯黑白暗色
static const defaultDark = AppColorScheme(
name: '默认',
description: '纯黑白,经典简洁',
primary: Color(0xFFFFFFFF),
background: Color(0xFF000000),
surface: Color(0xFF1A1A1A),
// ...
);
// 纯黑白浅色
static const defaultLight = AppColorScheme(
name: '默认',
description: '纯黑白,经典简洁',
primary: Color(0xFF000000),
background: Color(0xFFFFFFFF),
surface: Color(0xFFFFFFFF),
// ...
);四、通用文件选择器组件
需求
- 选择网盘文件发送(聊天中引用文件)
- 文件移动时选择目标文件夹
两个功能共用一个选择器组件,区别:
- 移动文件夹:只显示文件夹
- 发送文件:显示所有文件和文件夹
设计
创建 FilePickerDialog 组件:
/// 文件选择器模式
enum FilePickerMode {
folderOnly, // 只选择文件夹(移动/复制)
fileOnly, // 选择文件(发送)
all, // 选择所有
}
/// 使用示例
// 选择文件夹
final path = await FilePickerDialog.pickFolder(context, title: '选择目标');
// 选择文件
final file = await FilePickerDialog.pickFile(context, title: '选择文件');特点
- 复用现有风格:使用与文件列表一致的图标和颜色
- 导航支持:可以进入子文件夹,有面包屑导航
- 美观的弹窗:底部弹出,占屏幕 70% 高度
- 排除选项:可以排除已选中的文件(防止自己移动到自己)
文件结构
client/lib/ui/widgets/├── file_picker_dialog.dart // 新增├── chat_widgets.dart└── app_bar_widgets.dart五、聊天发送网盘文件
需求
我更希望在聊天中可以直接引用已有的网盘文件发送,而不是每次都重新上传。
实现
在附件面板添加”网盘文件”选项:
// _buildAttachmentPanel
Row(
children: [
_buildAttachmentOption(icon: Icons.camera_alt_outlined, label: '拍照', ...),
_buildAttachmentOption(icon: Icons.photo_library_outlined, label: '照片', ...),
_buildAttachmentOption(icon: Icons.upload_file_outlined, label: '上传文件', ...),
],
),
const SizedBox(height: 12),
Row(
children: [
_buildAttachmentOption(icon: Icons.cloud_outlined, label: '网盘文件', onTap: _pickCloudFile),
// 占位
],
),发送逻辑
Future<void> _sendCloudFileMessage(FileMetadata file) async {
// 直接引用已有的 fileId,不需要上传
final result = await appState.api.addSendMessage(
sessionId: widget.session.id,
type: 'file',
fileId: file.id,
fileName: file.name,
fileSize: file.size,
isLocalFile: false, // 标记为网盘文件
);
// ...
}优点:
- 无需重复上传,节省流量和时间
- 复用已有的预览逻辑
六、替换文件移动选择器
之前
使用简陋的 SimpleDialog,所有文件夹平铺显示,没有层级导航:
SimpleDialog(
title: const Text('选择目标文件夹'),
children: [
SimpleDialogOption(child: Text('/'), ...),
SimpleDialogOption(child: Text('/folder1/'), ...),
// ...
],
)之后
使用新的 FilePickerDialog,支持导航进入子文件夹:
Future<String?> _pickTargetPath(AppState state) async {
return await FilePickerDialog.pickFolder(
context,
title: '选择目标文件夹',
allowRoot: true,
excludeIds: _selectedIds,
);
}修改的文件
| 文件 | 修改内容 |
|---|---|
chat_widgets.dart | 气泡颜色使用 primary.withOpacity;垃圾桶按需渲染 |
app_theme.dart | 新增纯黑白默认主题;Nord 改为可选 |
file_picker_dialog.dart | 新建通用文件选择器组件 |
chat_page.dart | 添加”网盘文件”入口和发送逻辑 |
files_page.dart | 使用新选择器替换 SimpleDialog |
七、文件卡片上传进度显示
需求
我更希望在发送文件时,文件卡片能够显示上传进度,而不是只显示发送中的外部指示器。
实现
- 扩展
FileMessageContent组件,添加进度显示功能 - 在
chat_page.dart中跟踪上传进度 - 当文件正在上传时,文件卡片内部显示进度环
// FileMessageContent 新增参数
FileMessageContent(
fileName: message.fileName ?? '未知文件',
fileSize: message.fileSize,
icon: getFileIcon(getFileType(message.fileName ?? '')),
isSent: true,
uploadProgress: progress, // 新增进度参数
)
// 在聊天页面中跟踪上传进度
final Map<String, double> _uploadProgress = {};
// 构建消息气泡时检查进度
final progress = _uploadProgress[message.id];
bubble = MessageBubble(
// ... 其他参数
onTap: progress == null ? () => _openFile(message) : null, // 上传中时不可点击
customContent: FileMessageContent(
// ... 参数
uploadProgress: progress,
),
);进度环设计
- 上传中显示圆形进度指示器
- 进度环中心显示暂停图标
- 点击进度环可以暂停上传
- 进度文字显示”上传中 XX%“
八、修复状态栏颜色问题
问题
聊天页面进入后会改变状态栏字体颜色,导致浅色模式下状态栏字体变成白色,与背景融为一体。
解决方案
在 AppBar 中设置 systemOverlayStyle 属性:
AppBar(
// ... 其他参数
systemOverlayStyle: isDark
? SystemUiOverlayStyle.light // 深色背景使用白色图标
: SystemUiOverlayStyle.dark, // 浅色背景使用黑色图标
)效果
- 状态栏图标颜色与页面背景协调
- 与其他页面保持一致的视觉效果
总结
本次优化解决了几个我这边用的时候发现的 UI 细节问题:
- 气泡颜色 - 使用与 Switch 一致的 primary 色带透明度
- 垃圾桶透出 - 按需渲染,性能更好
- 默认主题 - 纯黑白更符合”默认”定位
- 文件上传进度 - 文件卡片显示内部进度环
- 状态栏颜色 - 修复状态栏图标颜色问题
同时实现了文件选择器复用架构,一个组件支持两种使用场景(选文件夹/选文件),提升了代码复用性和使用体验。