feat: erweiterte Chat-Funktionalität, UI-Verbesserungen und Lokalisierungsupdates
- Chat: Nachrichten-Status (read/unread), WebSocket-Verbesserungen - App: Login-Optimierung, Job-Übersicht verbessert, neue Übersetzungen - Backend: Dialog-Styling, Invoice-Generator, Job-Verwaltung erweitert - Mehrsprachigkeit: Neue Übersetzungen für DE, EN, ES, ET, FR, LT, LV, PL, RU, TR
This commit is contained in:
@@ -15,6 +15,8 @@ class ChatService {
|
||||
static const _jobIdPrefix = 'job:';
|
||||
static const _jobNumberPrefix = 'job_number:';
|
||||
static const _generalPrefix = 'general:';
|
||||
static const _defaultGeneralConversationKey =
|
||||
'general:allgemeine-nachrichten';
|
||||
|
||||
final DatabaseService _databaseService = DatabaseService();
|
||||
final AppState _appState = AppState();
|
||||
@@ -103,9 +105,11 @@ class ChatService {
|
||||
|
||||
_chats.removeWhere((chat) {
|
||||
final matchesKey = conversationKeys.contains(chat.id);
|
||||
final matchesId = trimmedJobId.isNotEmpty &&
|
||||
final matchesId =
|
||||
trimmedJobId.isNotEmpty &&
|
||||
(chat.jobId?.trim().toLowerCase() == lowerJobId);
|
||||
final matchesNumber = trimmedJobNumber.isNotEmpty &&
|
||||
final matchesNumber =
|
||||
trimmedJobNumber.isNotEmpty &&
|
||||
(chat.jobNumber?.trim().toLowerCase() == lowerJobNumber);
|
||||
return matchesKey || matchesId || matchesNumber;
|
||||
});
|
||||
@@ -129,18 +133,11 @@ class ChatService {
|
||||
|
||||
// Messages with GENERAL messageType should always go to the default general chat
|
||||
if (message.messageType == ChatMessageType.general) {
|
||||
final localId = _primaryLocalIdentifier();
|
||||
if (localId != null && localId.isNotEmpty) {
|
||||
final key = _conversationKeyForParticipants(
|
||||
localId,
|
||||
_appState.loggedInEmail!,
|
||||
);
|
||||
developer.log(
|
||||
'[DEBUG_LOG] GENERAL message detected, routing to conversation key: $key (localId=$localId, receiver=${_appState.loggedInEmail})',
|
||||
name: 'ChatService',
|
||||
);
|
||||
return key;
|
||||
}
|
||||
developer.log(
|
||||
'[DEBUG_LOG] GENERAL message detected, routing to conversation key: $_defaultGeneralConversationKey',
|
||||
name: 'ChatService',
|
||||
);
|
||||
return _defaultGeneralConversationKey;
|
||||
}
|
||||
|
||||
// Job-related messages go to job-specific chats
|
||||
@@ -165,30 +162,11 @@ class ChatService {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Fallback: create conversation based on userId
|
||||
final localId = _primaryLocalIdentifier();
|
||||
if (localId != null && localId.isNotEmpty) {
|
||||
final key = _conversationKeyForParticipants(
|
||||
localId,
|
||||
_appState.loggedInEmail!,
|
||||
);
|
||||
developer.log(
|
||||
'[DEBUG_LOG] Using fallback routing, conversation key: $key',
|
||||
name: 'ChatService',
|
||||
);
|
||||
return key;
|
||||
}
|
||||
|
||||
developer.log(
|
||||
'[DEBUG_LOG] No local identifier available for fallback routing',
|
||||
'[DEBUG_LOG] No job context available, routing to default general chat',
|
||||
name: 'ChatService',
|
||||
);
|
||||
return '$_generalPrefix${_appState.loggedInEmail!}';
|
||||
}
|
||||
|
||||
String _conversationKeyForParticipants(String a, String b) {
|
||||
final participants = <String>[a.toLowerCase(), b.toLowerCase()]..sort();
|
||||
return '$_generalPrefix${participants.join('|')}';
|
||||
return _defaultGeneralConversationKey;
|
||||
}
|
||||
|
||||
Future<void> saveIncomingMessage(ChatMessage message) async {
|
||||
@@ -205,6 +183,20 @@ class ChatService {
|
||||
await _persistMessage(message);
|
||||
}
|
||||
|
||||
Future<void> markOutgoingMessageSynced(String messageId) async {
|
||||
if (!_initialized) {
|
||||
await initialize();
|
||||
}
|
||||
|
||||
final conversationKey = await _databaseService.updateChatMessagePendingSync(
|
||||
messageId,
|
||||
false,
|
||||
);
|
||||
if (conversationKey != null && conversationKey.isNotEmpty) {
|
||||
await _refreshConversation(conversationKey);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _persistMessage(ChatMessage message) async {
|
||||
final conversationKey = conversationKeyForMessage(message);
|
||||
|
||||
@@ -239,7 +231,7 @@ class ChatService {
|
||||
|
||||
Future<void> _loadChatsFromDatabase() async {
|
||||
await _databaseService.ensureInitialized();
|
||||
final grouped = await _databaseService.loadAllChatMessagesGrouped();
|
||||
final grouped = await _loadNormalizedChatGroups();
|
||||
_chats.clear();
|
||||
grouped.forEach((conversationKey, messages) {
|
||||
final chat = _buildChat(conversationKey, messages);
|
||||
@@ -254,6 +246,14 @@ class ChatService {
|
||||
}
|
||||
|
||||
Future<void> _refreshConversation(String conversationKey) async {
|
||||
if (_isLegacyGeneralConversationKey(conversationKey)) {
|
||||
await _databaseService.migrateConversationKey(
|
||||
conversationKey,
|
||||
_defaultGeneralConversationKey,
|
||||
);
|
||||
conversationKey = _defaultGeneralConversationKey;
|
||||
}
|
||||
|
||||
final messages = await _databaseService.loadChatMessages(
|
||||
conversationKey: conversationKey,
|
||||
);
|
||||
@@ -317,15 +317,13 @@ class ChatService {
|
||||
|
||||
final counterpartNormalized =
|
||||
counterpart != null &&
|
||||
counterpart.toLowerCase() == _appState.loggedInEmail!.toLowerCase()
|
||||
counterpart.toLowerCase() ==
|
||||
_appState.loggedInEmail!.toLowerCase()
|
||||
? _appState.loggedInEmail!
|
||||
: counterpart;
|
||||
|
||||
final bool isDefaultGeneral =
|
||||
!isJobChat &&
|
||||
conversationKey.startsWith(_generalPrefix) &&
|
||||
(counterpartNormalized?.toLowerCase() ==
|
||||
_appState.loggedInEmail!.toLowerCase());
|
||||
!isJobChat && conversationKey == _defaultGeneralConversationKey;
|
||||
|
||||
final title =
|
||||
isJobChat
|
||||
@@ -406,23 +404,46 @@ class ChatService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, List<ChatMessage>>> _loadNormalizedChatGroups() async {
|
||||
var grouped = await _databaseService.loadAllChatMessagesGrouped();
|
||||
final legacyGeneralKeys =
|
||||
grouped.keys.where(_isLegacyGeneralConversationKey).toList();
|
||||
if (legacyGeneralKeys.isEmpty) {
|
||||
return grouped;
|
||||
}
|
||||
|
||||
for (final key in legacyGeneralKeys) {
|
||||
await _databaseService.migrateConversationKey(
|
||||
key,
|
||||
_defaultGeneralConversationKey,
|
||||
);
|
||||
}
|
||||
|
||||
grouped = await _databaseService.loadAllChatMessagesGrouped();
|
||||
return grouped;
|
||||
}
|
||||
|
||||
bool _isLegacyGeneralConversationKey(String conversationKey) {
|
||||
return conversationKey != _defaultGeneralConversationKey &&
|
||||
conversationKey.startsWith(_generalPrefix) &&
|
||||
!conversationKey.startsWith(_jobIdPrefix) &&
|
||||
!conversationKey.startsWith(_jobNumberPrefix);
|
||||
}
|
||||
|
||||
void _ensureDefaultGeneralChat() {
|
||||
final localId = _primaryLocalIdentifier();
|
||||
if (localId == null || localId.isEmpty) {
|
||||
final receiver = _appState.loggedInEmail;
|
||||
if (receiver == null || receiver.isEmpty) {
|
||||
developer.log(
|
||||
'[DEBUG_LOG] _ensureDefaultGeneralChat: No local identifier available, skipping',
|
||||
'[DEBUG_LOG] _ensureDefaultGeneralChat: No receiver available, skipping',
|
||||
name: 'ChatService',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final conversationKey = _conversationKeyForParticipants(
|
||||
localId,
|
||||
_appState.loggedInEmail!,
|
||||
);
|
||||
const conversationKey = _defaultGeneralConversationKey;
|
||||
|
||||
developer.log(
|
||||
'[DEBUG_LOG] _ensureDefaultGeneralChat: Creating/ensuring default general chat with key: $conversationKey (localId=$localId, receiver=${_appState.loggedInEmail})',
|
||||
'[DEBUG_LOG] _ensureDefaultGeneralChat: Creating/ensuring default general chat with key: $conversationKey (receiver=$receiver)',
|
||||
name: 'ChatService',
|
||||
);
|
||||
|
||||
@@ -431,8 +452,7 @@ class ChatService {
|
||||
chat.id != conversationKey &&
|
||||
chat.type == ChatType.general &&
|
||||
chat.receiver != null &&
|
||||
chat.receiver!.toLowerCase() ==
|
||||
_appState.loggedInEmail!.toLowerCase() &&
|
||||
chat.receiver!.toLowerCase() == receiver.toLowerCase() &&
|
||||
chat.messages.isEmpty,
|
||||
);
|
||||
final index = _chats.indexWhere((chat) => chat.id == conversationKey);
|
||||
@@ -446,7 +466,7 @@ class ChatService {
|
||||
Chat(
|
||||
id: conversationKey,
|
||||
title: 'Allgemeine Nachrichten',
|
||||
receiver: _appState.loggedInEmail!,
|
||||
receiver: receiver,
|
||||
type: ChatType.general,
|
||||
jobId: null,
|
||||
jobNumber: null,
|
||||
@@ -463,8 +483,7 @@ class ChatService {
|
||||
final existing = _chats[index];
|
||||
if (existing.type != ChatType.general ||
|
||||
existing.receiver == null ||
|
||||
existing.receiver!.toLowerCase() !=
|
||||
_appState.loggedInEmail!.toLowerCase() ||
|
||||
existing.receiver!.toLowerCase() != receiver.toLowerCase() ||
|
||||
(existing.messages.isEmpty &&
|
||||
existing.title != 'Allgemeine Nachrichten')) {
|
||||
developer.log(
|
||||
@@ -477,7 +496,7 @@ class ChatService {
|
||||
existing.messages.isEmpty
|
||||
? 'Allgemeine Nachrichten'
|
||||
: existing.title,
|
||||
receiver: _appState.loggedInEmail!,
|
||||
receiver: receiver,
|
||||
type: ChatType.general,
|
||||
jobId: existing.jobId,
|
||||
jobNumber: existing.jobNumber,
|
||||
@@ -493,8 +512,4 @@ class ChatService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _primaryLocalIdentifier() {
|
||||
return _appState.loggedInEmail;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user