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:
2026-04-04 10:30:36 +02:00
parent d6132fabe1
commit bba5733783
55 changed files with 2708 additions and 697 deletions

View File

@@ -38,7 +38,10 @@ class DatabaseService {
final completer = Completer<void>();
_initializingCompleter = completer;
try {
developer.log('Initializing ObjectBox database...', name: 'DatabaseService');
developer.log(
'Initializing ObjectBox database...',
name: 'DatabaseService',
);
// Get database path
final docsDir = await getApplicationDocumentsDirectory();
@@ -75,8 +78,6 @@ class DatabaseService {
await initialize();
}
/// Log database statistics
Future<void> _logDatabaseStats() async {
try {
@@ -164,7 +165,10 @@ class DatabaseService {
return;
}
developer.log('Deleting job $jobId from database...', name: 'DatabaseService');
developer.log(
'Deleting job $jobId from database...',
name: 'DatabaseService',
);
final jobBox = _store!.box<JobEntity>();
final query = jobBox.query(JobEntity_.jobId.equals(jobId)).build();
@@ -173,9 +177,15 @@ class DatabaseService {
if (entities.isNotEmpty) {
jobBox.remove(entities.first.id);
developer.log('Job $jobId deleted successfully', name: 'DatabaseService');
developer.log(
'Job $jobId deleted successfully',
name: 'DatabaseService',
);
} else {
developer.log('Job $jobId not found in database', name: 'DatabaseService');
developer.log(
'Job $jobId not found in database',
name: 'DatabaseService',
);
}
} catch (e, stackTrace) {
developer.log('Error deleting job: $e', name: 'DatabaseService');
@@ -220,9 +230,13 @@ class DatabaseService {
if (jobs.isNotEmpty) {
try {
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(
(ChatMessageEntity_.jobId.notNull() | ChatMessageEntity_.jobNumber.notNull())
).build();
final query =
chatBox
.query(
(ChatMessageEntity_.jobId.notNull() |
ChatMessageEntity_.jobNumber.notNull()),
)
.build();
final messagesWithJobs = query.find();
query.close();
@@ -282,7 +296,8 @@ class DatabaseService {
final taskStatusBox = _store!.box<TaskStatusEntity>();
// Find existing entity by taskId
final query = taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final query =
taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final existing = query.findFirst();
query.close();
@@ -321,7 +336,8 @@ class DatabaseService {
}
final taskStatusBox = _store!.box<TaskStatusEntity>();
final query = taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final query =
taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final entity = query.findFirst();
query.close();
@@ -449,7 +465,8 @@ class DatabaseService {
final keys = jobIds.map((id) => 'job_seen:$id').toList();
for (final key in keys) {
final query = userDataBox.query(UserDataEntity_.key.equals(key)).build();
final query =
userDataBox.query(UserDataEntity_.key.equals(key)).build();
final entity = query.findFirst();
query.close();
@@ -545,7 +562,8 @@ class DatabaseService {
final taskStatusBox = _store!.box<TaskStatusEntity>();
// Find existing job entity by jobId
final jobQuery = jobBox.query(JobEntity_.jobId.equals(normalized.id)).build();
final jobQuery =
jobBox.query(JobEntity_.jobId.equals(normalized.id)).build();
final existingJob = jobQuery.findFirst();
jobQuery.close();
@@ -568,7 +586,10 @@ class DatabaseService {
final taskIds = normalized.tasks.map((t) => t.id).toList();
if (taskIds.isNotEmpty) {
for (final taskId in taskIds) {
final query = taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final query =
taskStatusBox
.query(TaskStatusEntity_.taskId.equals(taskId))
.build();
final entities = query.find();
query.close();
for (final entity in entities) {
@@ -617,7 +638,8 @@ class DatabaseService {
if (trimmedJobId.isNotEmpty) {
// Delete job
final jobQuery = jobBox.query(JobEntity_.jobId.equals(trimmedJobId)).build();
final jobQuery =
jobBox.query(JobEntity_.jobId.equals(trimmedJobId)).build();
final jobEntities = jobQuery.find();
jobQuery.close();
for (final entity in jobEntities) {
@@ -625,7 +647,10 @@ class DatabaseService {
}
// Delete job_seen flag
final seenQuery = userDataBox.query(UserDataEntity_.key.equals('job_seen:$trimmedJobId')).build();
final seenQuery =
userDataBox
.query(UserDataEntity_.key.equals('job_seen:$trimmedJobId'))
.build();
final seenEntities = seenQuery.find();
seenQuery.close();
for (final entity in seenEntities) {
@@ -633,15 +658,19 @@ class DatabaseService {
}
}
final taskIds = job.tasks
.map((task) => task.id.trim())
.where((id) => id.isNotEmpty)
.toList();
final taskIds =
job.tasks
.map((task) => task.id.trim())
.where((id) => id.isNotEmpty)
.toList();
if (taskIds.isNotEmpty) {
for (final taskId in taskIds) {
// Delete task status
final taskQuery = taskStatusBox.query(TaskStatusEntity_.taskId.equals(taskId)).build();
final taskQuery =
taskStatusBox
.query(TaskStatusEntity_.taskId.equals(taskId))
.build();
final taskEntities = taskQuery.find();
taskQuery.close();
for (final entity in taskEntities) {
@@ -649,7 +678,8 @@ class DatabaseService {
}
// Delete photos
final photoQuery = photoBox.query(PhotoEntity_.taskId.equals(taskId)).build();
final photoQuery =
photoBox.query(PhotoEntity_.taskId.equals(taskId)).build();
final photoEntities = photoQuery.find();
photoQuery.close();
for (final entity in photoEntities) {
@@ -960,9 +990,21 @@ class DatabaseService {
/// Save login credentials for auto-login on app restart
Future<void> saveCredentials(String email, String password) async {
await saveKeyValue('auth_email', email);
final normalizedEmail = email.trim();
if (normalizedEmail.isEmpty || password.isEmpty) {
developer.log(
'Skipping credential save because email or password is empty',
name: 'DatabaseService',
);
return;
}
await saveKeyValue('auth_email', normalizedEmail);
await saveKeyValue('auth_password', password);
developer.log('Credentials saved for $email', name: 'DatabaseService');
developer.log(
'Credentials saved for $normalizedEmail',
name: 'DatabaseService',
);
}
/// Load saved login credentials
@@ -970,11 +1012,29 @@ class DatabaseService {
Future<({String email, String password})?> loadCredentials() async {
final email = await loadKeyValue('auth_email');
final password = await loadKeyValue('auth_password');
if (email != null && password != null) {
developer.log('Credentials loaded for $email', name: 'DatabaseService');
return (email: email, password: password);
final normalizedEmail = email?.trim();
if (normalizedEmail != null &&
normalizedEmail.isNotEmpty &&
password != null &&
password.isNotEmpty) {
developer.log(
'Credentials loaded for $normalizedEmail',
name: 'DatabaseService',
);
return (email: normalizedEmail, password: password);
}
developer.log('No credentials found', name: 'DatabaseService');
if ((email != null && email.isNotEmpty) ||
(password != null && password.isNotEmpty)) {
developer.log(
'Stored credentials are incomplete or empty - removing them',
name: 'DatabaseService',
);
await deleteCredentials();
}
developer.log('No valid credentials found', name: 'DatabaseService');
return null;
}
@@ -1008,7 +1068,10 @@ class DatabaseService {
final chatBox = _store!.box<ChatMessageEntity>();
// Find existing entity by messageId
final query = chatBox.query(ChatMessageEntity_.messageId.equals(message.id)).build();
final query =
chatBox
.query(ChatMessageEntity_.messageId.equals(message.id))
.build();
final existing = query.findFirst();
query.close();
@@ -1060,7 +1123,10 @@ class DatabaseService {
return;
}
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(ChatMessageEntity_.conversationKey.equals(fromKey)).build();
final query =
chatBox
.query(ChatMessageEntity_.conversationKey.equals(fromKey))
.build();
final entities = query.find();
query.close();
@@ -1089,13 +1155,18 @@ class DatabaseService {
}
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(
ChatMessageEntity_.conversationKey.equals(conversationKey) &
ChatMessageEntity_.pendingSync.equals(true) &
ChatMessageEntity_.content.equals(message.content) &
ChatMessageEntity_.contentType.equals(chatContentTypeToString(message.contentType)) &
ChatMessageEntity_.messageId.notEquals(message.id)
).build();
final query =
chatBox
.query(
ChatMessageEntity_.conversationKey.equals(conversationKey) &
ChatMessageEntity_.pendingSync.equals(true) &
ChatMessageEntity_.content.equals(message.content) &
ChatMessageEntity_.contentType.equals(
chatContentTypeToString(message.contentType),
) &
ChatMessageEntity_.messageId.notEquals(message.id),
)
.build();
final entities = query.find();
query.close();
@@ -1123,9 +1194,13 @@ class DatabaseService {
List<ChatMessageEntity> entities;
if (conversationKey != null) {
final query = chatBox.query(ChatMessageEntity_.conversationKey.equals(conversationKey))
.order(ChatMessageEntity_.createdAt)
.build();
final query =
chatBox
.query(
ChatMessageEntity_.conversationKey.equals(conversationKey),
)
.order(ChatMessageEntity_.createdAt)
.build();
entities = query.find();
query.close();
} else {
@@ -1186,7 +1261,10 @@ class DatabaseService {
}
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(ChatMessageEntity_.conversationKey.equals(conversationKey)).build();
final query =
chatBox
.query(ChatMessageEntity_.conversationKey.equals(conversationKey))
.build();
final entities = query.find();
query.close();
@@ -1211,7 +1289,8 @@ class DatabaseService {
}
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(ChatMessageEntity_.messageId.equals(messageId)).build();
final query =
chatBox.query(ChatMessageEntity_.messageId.equals(messageId)).build();
final entities = query.find();
query.close();
@@ -1241,15 +1320,18 @@ class DatabaseService {
final trimmedJobId = jobId?.trim() ?? '';
final trimmedJobNumber = jobNumber?.trim() ?? '';
final keysList = conversationKeys == null
? <String>[]
: conversationKeys
.map((key) => key.trim())
.where((key) => key.isNotEmpty)
.toSet()
.toList();
final keysList =
conversationKeys == null
? <String>[]
: conversationKeys
.map((key) => key.trim())
.where((key) => key.isNotEmpty)
.toSet()
.toList();
if (trimmedJobId.isEmpty && trimmedJobNumber.isEmpty && keysList.isEmpty) {
if (trimmedJobId.isEmpty &&
trimmedJobNumber.isEmpty &&
keysList.isEmpty) {
developer.log(
'No chat messages matched deletion criteria for jobId=$jobId jobNumber=$jobNumber',
name: 'DatabaseService',
@@ -1261,20 +1343,29 @@ class DatabaseService {
final entitiesToDelete = <ChatMessageEntity>[];
if (trimmedJobId.isNotEmpty) {
final query = chatBox.query(ChatMessageEntity_.jobId.equals(trimmedJobId)).build();
final query =
chatBox
.query(ChatMessageEntity_.jobId.equals(trimmedJobId))
.build();
entitiesToDelete.addAll(query.find());
query.close();
}
if (trimmedJobNumber.isNotEmpty) {
final query = chatBox.query(ChatMessageEntity_.jobNumber.equals(trimmedJobNumber)).build();
final query =
chatBox
.query(ChatMessageEntity_.jobNumber.equals(trimmedJobNumber))
.build();
entitiesToDelete.addAll(query.find());
query.close();
}
if (keysList.isNotEmpty) {
for (final key in keysList) {
final query = chatBox.query(ChatMessageEntity_.conversationKey.equals(key)).build();
final query =
chatBox
.query(ChatMessageEntity_.conversationKey.equals(key))
.build();
entitiesToDelete.addAll(query.find());
query.close();
}
@@ -1309,7 +1400,8 @@ class DatabaseService {
}
final chatBox = _store!.box<ChatMessageEntity>();
final query = chatBox.query(ChatMessageEntity_.read.equals(false)).build();
final query =
chatBox.query(ChatMessageEntity_.read.equals(false)).build();
final count = query.count();
query.close();
@@ -1349,6 +1441,7 @@ class DatabaseService {
/// Save a failed message to the queue
Future<void> queueMessage(QueuedMessage message) async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return;
@@ -1357,7 +1450,8 @@ class DatabaseService {
final box = _store!.box<QueuedMessageEntity>();
// Find existing entity by messageId
final query = box.query(QueuedMessageEntity_.messageId.equals(message.id)).build();
final query =
box.query(QueuedMessageEntity_.messageId.equals(message.id)).build();
final existing = query.findFirst();
query.close();
@@ -1391,6 +1485,7 @@ class DatabaseService {
/// Get all queued messages
Future<List<QueuedMessage>> getQueuedMessages() async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return [];
@@ -1424,13 +1519,15 @@ class DatabaseService {
/// Remove a successfully sent message from the queue
Future<void> removeQueuedMessage(String messageId) async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return;
}
final box = _store!.box<QueuedMessageEntity>();
final query = box.query(QueuedMessageEntity_.messageId.equals(messageId)).build();
final query =
box.query(QueuedMessageEntity_.messageId.equals(messageId)).build();
final entities = query.find();
query.close();
@@ -1452,18 +1549,17 @@ class DatabaseService {
}
/// Update retry count for a message
Future<void> updateMessageRetryCount(
String messageId,
int retryCount,
) async {
Future<void> updateMessageRetryCount(String messageId, int retryCount) async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return;
}
final box = _store!.box<QueuedMessageEntity>();
final query = box.query(QueuedMessageEntity_.messageId.equals(messageId)).build();
final query =
box.query(QueuedMessageEntity_.messageId.equals(messageId)).build();
final entity = query.findFirst();
query.close();
@@ -1488,16 +1584,14 @@ class DatabaseService {
/// Clear all queued messages (for cleanup)
Future<void> clearQueuedMessages() async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return;
}
_store!.box<QueuedMessageEntity>().removeAll();
developer.log(
'Cleared all queued messages',
name: 'DatabaseService',
);
developer.log('Cleared all queued messages', name: 'DatabaseService');
} catch (e, st) {
developer.log(
'Error clearing queued messages: $e',
@@ -1507,12 +1601,49 @@ class DatabaseService {
}
}
Future<String?> updateChatMessagePendingSync(
String messageId,
bool pendingSync,
) async {
try {
await ensureInitialized();
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return null;
}
final chatBox = _store!.box<ChatMessageEntity>();
final query =
chatBox.query(ChatMessageEntity_.messageId.equals(messageId)).build();
final entity = query.findFirst();
query.close();
if (entity == null) {
return null;
}
entity.pendingSync = pendingSync;
chatBox.put(entity);
return entity.conversationKey;
} catch (e, st) {
developer.log(
'Error updating pendingSync for message $messageId: $e',
name: 'DatabaseService',
);
developer.log('Stack trace: $st', name: 'DatabaseService');
return null;
}
}
// Language preference persistence ----------------------------------------------------
/// Save language preference
Future<void> saveLanguagePreference(String languageCode) async {
await saveKeyValue('language_preference', languageCode);
developer.log('Language preference saved: $languageCode', name: 'DatabaseService');
developer.log(
'Language preference saved: $languageCode',
name: 'DatabaseService',
);
}
/// Load saved language preference
@@ -1520,7 +1651,10 @@ class DatabaseService {
Future<String?> loadLanguagePreference() async {
final languageCode = await loadKeyValue('language_preference');
if (languageCode != null) {
developer.log('Language preference loaded: $languageCode', name: 'DatabaseService');
developer.log(
'Language preference loaded: $languageCode',
name: 'DatabaseService',
);
return languageCode;
}
developer.log('No language preference found', name: 'DatabaseService');