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:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user