fix(chat): fix ChatBloc event callback and test reliability

- Fix onEvent callback initialization in ChatBloc constructor
- Add MockAgUiService to isolate test from mock API behavior
- Remove unnecessary non-null assertions in tests
This commit is contained in:
qzl
2026-02-28 14:41:21 +08:00
parent 92781ddbbe
commit d37677c533
9 changed files with 254 additions and 217 deletions
@@ -10,6 +10,25 @@ import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import 'home_sheet.dart';
/// 布局常量
const _headerHeight = 60.0;
const _defaultPadding = 20.0;
const _itemSpacing = 16.0;
const _inputPadding = 16.0;
const _iconSize = 24.0;
const _avatarSize = 32.0;
const _botIconSize = 18.0;
const _messagePaddingH = 13.0;
const _messagePaddingV = 9.0;
const _cornerRadius = 12.0;
const _inputMinHeight = 48.0;
const _inputRadius = 24.0;
const _scrollDurationMs = 300;
/// 颜色常量
const _chatBgColor = Color(0xFFF8FAFC);
const _userBubbleColor = Color(0xFFEAF1FB);
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@@ -53,7 +72,7 @@ class _HomeScreenState extends State<HomeScreen> {
},
builder: (context, state) {
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
backgroundColor: _chatBgColor,
body: SafeArea(
child: Column(
children: [
@@ -71,16 +90,16 @@ class _HomeScreenState extends State<HomeScreen> {
Widget _buildHeader(BuildContext context) {
return SizedBox(
height: 60,
height: _headerHeight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.symmetric(horizontal: _defaultPadding),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(
LucideIcons.settings,
size: 24,
size: _iconSize,
color: AppColors.slate900,
),
onPressed: () => context.push('/settings'),
@@ -90,16 +109,16 @@ class _HomeScreenState extends State<HomeScreen> {
IconButton(
icon: const Icon(
LucideIcons.calendar,
size: 24,
size: _iconSize,
color: AppColors.slate900,
),
onPressed: () => context.push('/calendar/dayweek?from=home'),
),
const SizedBox(width: 16),
const SizedBox(width: _itemSpacing),
IconButton(
icon: const Icon(
LucideIcons.messageSquare,
size: 24,
size: _iconSize,
color: AppColors.slate900,
),
onPressed: () => context.push('/messages/invites'),
@@ -126,7 +145,7 @@ class _HomeScreenState extends State<HomeScreen> {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
duration: const Duration(milliseconds: _scrollDurationMs),
curve: Curves.easeOut,
);
}
@@ -134,12 +153,12 @@ class _HomeScreenState extends State<HomeScreen> {
return ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(_defaultPadding),
itemCount: state.items.length,
itemBuilder: (context, index) {
final item = state.items[index];
return Padding(
padding: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.only(bottom: _itemSpacing),
child: _buildChatItem(item),
);
},
@@ -167,15 +186,15 @@ class _HomeScreenState extends State<HomeScreen> {
children: [
if (!isUser) ...[
Container(
width: 32,
height: 32,
width: _avatarSize,
height: _avatarSize,
decoration: BoxDecoration(
color: AppColors.blue100,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.bot,
size: 18,
size: _botIconSize,
color: AppColors.blue600,
),
),
@@ -183,14 +202,17 @@ class _HomeScreenState extends State<HomeScreen> {
],
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 9),
padding: const EdgeInsets.symmetric(
horizontal: _messagePaddingH,
vertical: _messagePaddingV,
),
decoration: BoxDecoration(
color: isUser ? const Color(0xFFEAF1FB) : AppColors.white,
color: isUser ? _userBubbleColor : AppColors.white,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(12),
topRight: const Radius.circular(12),
bottomLeft: Radius.circular(isUser ? 12 : 0),
bottomRight: Radius.circular(isUser ? 0 : 12),
topLeft: const Radius.circular(_cornerRadius),
topRight: const Radius.circular(_cornerRadius),
bottomLeft: Radius.circular(isUser ? _cornerRadius : 0),
bottomRight: Radius.circular(isUser ? 0 : _cornerRadius),
),
border: isUser ? null : Border.all(color: AppColors.slate300),
),
@@ -207,32 +229,28 @@ class _HomeScreenState extends State<HomeScreen> {
}
Widget _buildToolCallItem(ToolCallItem item) {
String statusText;
Color statusColor;
IconData statusIcon;
switch (item.status) {
case ToolCallStatus.pending:
statusText = '准备中...';
statusColor = AppColors.slate500;
statusIcon = LucideIcons.clock;
break;
case ToolCallStatus.executing:
statusText = '执行中...';
statusColor = AppColors.blue600;
statusIcon = LucideIcons.loader;
break;
case ToolCallStatus.error:
statusText = item.errorMessage ?? '执行失败';
statusColor = AppColors.red600;
statusIcon = LucideIcons.alertCircle;
break;
case ToolCallStatus.completed:
statusText = '已完成';
statusColor = AppColors.emerald600;
statusIcon = LucideIcons.checkCircle;
break;
}
final (statusText, statusColor, statusIcon) = switch (item.status) {
ToolCallStatus.pending => (
'准备中...',
AppColors.slate500,
LucideIcons.clock,
),
ToolCallStatus.executing => (
'执行中...',
AppColors.blue600,
LucideIcons.loader,
),
ToolCallStatus.error => (
item.errorMessage ?? '执行失败',
AppColors.red600,
LucideIcons.alertCircle,
),
ToolCallStatus.completed => (
'已完成',
AppColors.emerald600,
LucideIcons.checkCircle,
),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
@@ -267,8 +285,8 @@ class _HomeScreenState extends State<HomeScreen> {
Widget _buildInputContainer(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
color: const Color(0xFFF8FAFC),
padding: const EdgeInsets.all(_inputPadding),
color: _chatBgColor,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -292,11 +310,11 @@ class _HomeScreenState extends State<HomeScreen> {
const SizedBox(width: 8),
Expanded(
child: Container(
constraints: const BoxConstraints(minHeight: 48),
constraints: const BoxConstraints(minHeight: _inputMinHeight),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(24),
borderRadius: BorderRadius.circular(_inputRadius),
border: Border.all(color: AppColors.slate300),
),
child: Row(
@@ -327,7 +345,7 @@ class _HomeScreenState extends State<HomeScreen> {
onTap: _hasMessage ? () => _sendMessage(context) : null,
child: Icon(
_hasMessage ? LucideIcons.send : LucideIcons.mic,
size: 24,
size: _iconSize,
color: _hasMessage
? AppColors.blue600
: AppColors.slate500,