feat: 通知推送/免打扰偏好 + 饮食记录侧滑删除修复 + 聊天交互优化
- 通知: 新增推送开关/免打扰时段偏好持久化 + EF 迁移; 通知管线支持免打扰过滤 - 饮食: 侧滑删除 confirmDismiss 按方向放行(修复删不掉); 新增 diet_nutrition_widgets; 饮食录入页重构 - 聊天: 智能体胶囊点击锁防误触; 流式状态 select 优化; 卡片入场动画 - 主页: 消息列表提取 _HomeMessages + 侧滑手势打开抽屉 - 其他: api_client IP 适配; 通知/用药/饮食端点微调; 测试更新
This commit is contained in:
@@ -76,13 +76,13 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
StreamSubscription<Map<String, dynamic>>? _subscription;
|
||||
Completer<void>? _streamDone;
|
||||
ActiveAgent? _lastTriggeredAgent;
|
||||
Timer? _agentWelcomeTimer;
|
||||
String? _pendingAgentTriggerMessageId;
|
||||
Timer? _agentTapLockTimer;
|
||||
|
||||
/// 重置整个会话:取消正在进行的 SSE,清空消息和会话 ID。
|
||||
/// 历史记录页一键清空 / 删除当前会话时调用。
|
||||
Future<void> resetSession() async {
|
||||
await _cancelActiveStream();
|
||||
_cancelPendingAgentWelcome();
|
||||
_lastTriggeredAgent = null;
|
||||
state = const ChatState();
|
||||
}
|
||||
@@ -133,7 +133,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
ChatState build() {
|
||||
ref.onDispose(() {
|
||||
_subscription?.cancel();
|
||||
_agentWelcomeTimer?.cancel();
|
||||
_agentTapLockTimer?.cancel();
|
||||
_subscription = null;
|
||||
if (_streamDone != null && !_streamDone!.isCompleted) {
|
||||
_streamDone!.complete();
|
||||
@@ -229,61 +229,41 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
}
|
||||
}
|
||||
|
||||
void insertAgentWelcome(ActiveAgent agent) {
|
||||
_pendingAgentTriggerMessageId = null;
|
||||
state = state.copyWith(
|
||||
messages: [
|
||||
...state.messages,
|
||||
ChatMessage(
|
||||
id: 'welcome_${agent.name}_${DateTime.now().millisecondsSinceEpoch}',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: DateTime.now(),
|
||||
type: MessageType.agentWelcome,
|
||||
metadata: {'agent': agent.name},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 点击胶囊:先出用户标签 → 0.4 秒后出欢迎卡片,不走 AI
|
||||
/// 点击胶囊:用户标签和欢迎卡片立即出现,不走 AI。
|
||||
/// 300ms 点击锁只防止误触,不延迟界面反馈。
|
||||
/// 重复点击同一胶囊不重复弹卡片
|
||||
void triggerAgent(ActiveAgent agent, String label) {
|
||||
if (_pendingAgentTriggerMessageId != null) {
|
||||
return;
|
||||
}
|
||||
if (_lastTriggeredAgent == agent && _pendingAgentTriggerMessageId == null) {
|
||||
return;
|
||||
}
|
||||
if (_agentTapLockTimer != null || _lastTriggeredAgent == agent) return;
|
||||
_lastTriggeredAgent = agent;
|
||||
|
||||
final now = DateTime.now();
|
||||
final userMsg = ChatMessage(
|
||||
id: 'agent_trigger_${DateTime.now().millisecondsSinceEpoch}',
|
||||
id: 'agent_trigger_${now.microsecondsSinceEpoch}',
|
||||
role: 'user',
|
||||
content: label,
|
||||
createdAt: DateTime.now(),
|
||||
createdAt: now,
|
||||
);
|
||||
final welcomeMsg = ChatMessage(
|
||||
id: 'welcome_${agent.name}_${now.microsecondsSinceEpoch}',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: now,
|
||||
type: MessageType.agentWelcome,
|
||||
metadata: {'agent': agent.name},
|
||||
);
|
||||
state = state.copyWith(
|
||||
messages: [...state.messages, userMsg, welcomeMsg],
|
||||
activeAgent: agent,
|
||||
);
|
||||
final messages = state.messages.toList();
|
||||
messages.add(userMsg);
|
||||
_pendingAgentTriggerMessageId = userMsg.id;
|
||||
state = state.copyWith(messages: messages, activeAgent: agent);
|
||||
final expectedConversationId = state.conversationId;
|
||||
|
||||
_agentWelcomeTimer = Timer(Duration.zero, () {
|
||||
if (state.conversationId != expectedConversationId ||
|
||||
state.activeAgent != agent ||
|
||||
_lastTriggeredAgent != agent) {
|
||||
return;
|
||||
}
|
||||
_agentWelcomeTimer = null;
|
||||
insertAgentWelcome(agent);
|
||||
_agentTapLockTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
_agentTapLockTimer = null;
|
||||
});
|
||||
}
|
||||
|
||||
void _cancelPendingAgentWelcome() {
|
||||
_agentWelcomeTimer?.cancel();
|
||||
_agentWelcomeTimer = null;
|
||||
_pendingAgentTriggerMessageId = null;
|
||||
_agentTapLockTimer?.cancel();
|
||||
_agentTapLockTimer = null;
|
||||
}
|
||||
|
||||
Future<void> sendImage(String imagePath, String text) async {
|
||||
|
||||
Reference in New Issue
Block a user