198 lines
5.1 KiB
Dart
198 lines
5.1 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:sales_chat/models/group.dart';
|
|||
|
|
import 'package:sales_chat/models/message.dart';
|
|||
|
|
import 'package:sales_chat/services/api_service.dart';
|
|||
|
|
|
|||
|
|
class ChatProvider with ChangeNotifier {
|
|||
|
|
final ApiService _apiService;
|
|||
|
|
|
|||
|
|
List<Group> _groups = [];
|
|||
|
|
Map<String, List<Message>> _messagesByConverse = {};
|
|||
|
|
Group? _currentGroup;
|
|||
|
|
bool _isLoading = false;
|
|||
|
|
bool _isLoadingMessages = false;
|
|||
|
|
String? _error;
|
|||
|
|
|
|||
|
|
ChatProvider(this._apiService);
|
|||
|
|
|
|||
|
|
List<Group> get groups => _groups;
|
|||
|
|
Group? get currentGroup => _currentGroup;
|
|||
|
|
bool get isLoading => _isLoading;
|
|||
|
|
bool get isLoadingMessages => _isLoadingMessages;
|
|||
|
|
String? get error => _error;
|
|||
|
|
|
|||
|
|
List<Message> get currentMessages {
|
|||
|
|
if (_currentGroup == null) return [];
|
|||
|
|
// 使用第一个文本面板的 ID 作为 converseId
|
|||
|
|
final converseId = _currentGroup!.firstTextPanel?.id ?? _currentGroup!.id;
|
|||
|
|
return _messagesByConverse[converseId] ?? [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取群组的 converseId(第一个文本面板,或以群组 ID 作为备选)
|
|||
|
|
String _getConverseIdForGroup(Group group) {
|
|||
|
|
return group.firstTextPanel?.id ?? group.id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> loadGroups() async {
|
|||
|
|
_isLoading = true;
|
|||
|
|
_error = null;
|
|||
|
|
notifyListeners();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
_groups = await _apiService.getGroups();
|
|||
|
|
_isLoading = false;
|
|||
|
|
notifyListeners();
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = 'Failed to load groups';
|
|||
|
|
_isLoading = false;
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void selectGroup(Group group) {
|
|||
|
|
_currentGroup = group;
|
|||
|
|
notifyListeners();
|
|||
|
|
|
|||
|
|
final converseId = _getConverseIdForGroup(group);
|
|||
|
|
if (_messagesByConverse[converseId] == null) {
|
|||
|
|
loadMessages(converseId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void deselectGroup() {
|
|||
|
|
_currentGroup = null;
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> loadMessages(String converseId, {bool refresh = false}) async {
|
|||
|
|
if (_isLoadingMessages) return;
|
|||
|
|
|
|||
|
|
_isLoadingMessages = true;
|
|||
|
|
notifyListeners();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
final messages = await _apiService.getMessages(
|
|||
|
|
converseId,
|
|||
|
|
limit: 50,
|
|||
|
|
before: refresh ? null : (_messagesByConverse[converseId]?.first.id),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (refresh) {
|
|||
|
|
_messagesByConverse[converseId] = messages;
|
|||
|
|
} else {
|
|||
|
|
_messagesByConverse[converseId] = [
|
|||
|
|
...messages,
|
|||
|
|
...(_messagesByConverse[converseId] ?? [])
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_isLoadingMessages = false;
|
|||
|
|
notifyListeners();
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = 'Failed to load messages';
|
|||
|
|
_isLoadingMessages = false;
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> sendMessage(String content) async {
|
|||
|
|
if (_currentGroup == null || content.isEmpty) return;
|
|||
|
|
|
|||
|
|
final converseId = _getConverseIdForGroup(_currentGroup!);
|
|||
|
|
final groupId = _currentGroup!.id;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
final message = await _apiService.sendMessage(
|
|||
|
|
converseId,
|
|||
|
|
content,
|
|||
|
|
groupId: groupId,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
_messagesByConverse[converseId] = [
|
|||
|
|
...(_messagesByConverse[converseId] ?? []),
|
|||
|
|
message,
|
|||
|
|
];
|
|||
|
|
notifyListeners();
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = 'Failed to send message';
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 撤回消息
|
|||
|
|
Future<bool> recallMessage(String messageId) async {
|
|||
|
|
try {
|
|||
|
|
await _apiService.recallMessage(messageId);
|
|||
|
|
|
|||
|
|
// 更新本地消息列表
|
|||
|
|
if (_currentGroup != null) {
|
|||
|
|
final converseId = _getConverseIdForGroup(_currentGroup!);
|
|||
|
|
final messages = _messagesByConverse[converseId];
|
|||
|
|
if (messages != null) {
|
|||
|
|
final idx = messages.indexWhere((m) => m.id == messageId);
|
|||
|
|
if (idx != -1) {
|
|||
|
|
// 标记为已撤回而非删除
|
|||
|
|
final old = messages[idx];
|
|||
|
|
messages[idx] = Message(
|
|||
|
|
id: old.id,
|
|||
|
|
content: 'Message recalled',
|
|||
|
|
author: old.author,
|
|||
|
|
groupId: old.groupId,
|
|||
|
|
converseId: old.converseId,
|
|||
|
|
hasRecall: true,
|
|||
|
|
meta: old.meta,
|
|||
|
|
createdAt: old.createdAt,
|
|||
|
|
updatedAt: old.updatedAt,
|
|||
|
|
);
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = 'Failed to recall message: $e';
|
|||
|
|
notifyListeners();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 删除消息(管理员)
|
|||
|
|
Future<bool> deleteMessage(String messageId) async {
|
|||
|
|
try {
|
|||
|
|
await _apiService.deleteMessage(messageId);
|
|||
|
|
|
|||
|
|
// 从本地消息列表中移除
|
|||
|
|
if (_currentGroup != null) {
|
|||
|
|
final converseId = _getConverseIdForGroup(_currentGroup!);
|
|||
|
|
final messages = _messagesByConverse[converseId];
|
|||
|
|
if (messages != null) {
|
|||
|
|
messages.removeWhere((m) => m.id == messageId);
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (e) {
|
|||
|
|
_error = 'Failed to delete message: $e';
|
|||
|
|
notifyListeners();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 添加通过 socket.io 接收的消息
|
|||
|
|
void addMessage(Message message) {
|
|||
|
|
final converseId = message.converseId;
|
|||
|
|
_messagesByConverse[converseId] = [
|
|||
|
|
...(_messagesByConverse[converseId] ?? []),
|
|||
|
|
message,
|
|||
|
|
];
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void clearError() {
|
|||
|
|
_error = null;
|
|||
|
|
notifyListeners();
|
|||
|
|
}
|
|||
|
|
}
|