Merge branch 'dev' into sync-progressbar

This commit is contained in:
HDDen
2026-05-26 00:44:08 +03:00
45 changed files with 553 additions and 139 deletions
+145 -64
View File
@@ -1126,19 +1126,31 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
Future<void> _translateIncomingContactMessage(
Future<TranslationResult?> translateContactMessage(
String contactKeyHex,
Message message,
) async {
Message message, {
bool manualTranslation = false,
}) async {
try {
if (message.translatedText?.trim().isNotEmpty == true ||
(!manualTranslation &&
message.translationStatus != MessageTranslationStatus.none)) {
return null;
}
final service = _translationService;
if (service == null ||
!service.shouldTranslateIncoming(
text: message.text,
isCli: message.isCli,
isOutgoing: message.isOutgoing,
)) {
return;
!(manualTranslation
? service.canTranslateIncoming(
text: message.text,
isCli: message.isCli,
isOutgoing: message.isOutgoing,
)
: service.shouldAutoTranslateIncoming(
text: message.text,
isCli: message.isCli,
isOutgoing: message.isOutgoing,
))) {
return null;
}
final targetLanguageCode = service.resolvedIncomingLanguageCode(
_appSettingsService?.settings.languageOverride,
@@ -1148,7 +1160,7 @@ class MeshCoreConnector extends ChangeNotifier {
targetLanguageCode: targetLanguageCode,
);
if (result == null) {
return;
return null;
}
final translated = result.status == MessageTranslationStatus.completed
? result.translatedText
@@ -1163,24 +1175,38 @@ class MeshCoreConnector extends ChangeNotifier {
translationModelId: result.modelId,
),
);
return result;
} catch (error) {
appLogger.warn('Translation failed for contact message: $error');
return null;
}
}
Future<void> _translateIncomingChannelMessage(
Future<TranslationResult?> translateChannelMessage(
int channelIndex,
ChannelMessage message,
) async {
ChannelMessage message, {
bool manualTranslation = false,
}) async {
try {
if (message.translatedText?.trim().isNotEmpty == true ||
(!manualTranslation &&
message.translationStatus != MessageTranslationStatus.none)) {
return null;
}
final service = _translationService;
if (service == null ||
!service.shouldTranslateIncoming(
text: message.text,
isCli: false,
isOutgoing: message.isOutgoing,
)) {
return;
!(manualTranslation
? service.canTranslateIncoming(
text: message.text,
isCli: false,
isOutgoing: message.isOutgoing,
)
: service.shouldAutoTranslateIncoming(
text: message.text,
isCli: false,
isOutgoing: message.isOutgoing,
))) {
return null;
}
final targetLanguageCode = service.resolvedIncomingLanguageCode(
_appSettingsService?.settings.languageOverride,
@@ -1190,11 +1216,16 @@ class MeshCoreConnector extends ChangeNotifier {
targetLanguageCode: targetLanguageCode,
);
if (result == null) {
return;
return null;
}
final translated = result.status == MessageTranslationStatus.completed
var translated = result.status == MessageTranslationStatus.completed
? result.translatedText
: null;
// Strip replyInfo prefix from translated text to match stored message.text
if (translated != null) {
final regex = RegExp(r'^@\[[^\]]+\]\s+', dotAll: true);
translated = translated.replaceFirst(regex, '');
}
_updateStoredChannelMessage(
channelIndex,
message.messageId,
@@ -1205,8 +1236,10 @@ class MeshCoreConnector extends ChangeNotifier {
translationModelId: result.modelId,
),
);
return result;
} catch (error) {
appLogger.warn('Translation failed for channel message: $error');
return null;
}
}
@@ -4517,6 +4550,12 @@ class MeshCoreConnector extends ChangeNotifier {
Future<void> _handleIncomingMessage(Uint8List frame) async {
if (_selfPublicKey == null) return;
// If we're syncing the queued messages, advance the queue immediately
// before any potentially long async work (like translation/notifications).
if (_isSyncingQueuedMessages) {
_handleQueuedMessageReceived();
}
var message = _parseContactMessage(frame);
// If message parsing failed due to unknown contact, refresh contacts and retry
@@ -4582,35 +4621,52 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
_addMessage(message.senderKeyHex, message);
if (!message.isOutgoing) {
unawaited(
_translateIncomingContactMessage(message.senderKeyHex, message),
);
}
_maybeIncrementContactUnread(message);
notifyListeners();
// Show notification for new incoming message
// Show notification for new incoming message (run async with translation)
if (!message.isOutgoing &&
!message.isCli &&
_appSettingsService != null) {
final settings = _appSettingsService!.settings;
if (settings.notificationsEnabled && settings.notifyOnNewMessage) {
if (contact?.type == advTypeChat) {
_notificationService.showMessageNotification(
contactName: contact?.name ?? 'Unknown',
message: message.text,
contactId: message.senderKeyHex,
badgeCount: getTotalUnreadCount(),
final msg = message; // capture for closure
final c = contact; // capture contact reference
unawaited(() async {
final translationResult = await translateContactMessage(
msg.senderKeyHex,
msg,
);
} else if (contact?.type == advTypeRoom) {
_notificationService.showMessageNotification(
contactName: contact?.name ?? 'Unknown Room',
message: message.text,
contactId: message.senderKeyHex,
badgeCount: getTotalUnreadCount(),
);
}
if (c?.type == advTypeChat) {
final resolvedText =
(translationResult != null &&
translationResult.status ==
MessageTranslationStatus.completed &&
translationResult.translatedText.trim().isNotEmpty)
? translationResult.translatedText.trim()
: msg.text.trim();
await _notificationService.showMessageNotification(
contactName: c?.name ?? 'Unknown',
message: resolvedText,
contactId: msg.senderKeyHex,
badgeCount: getTotalUnreadCount(),
);
} else if (c?.type == advTypeRoom) {
final resolvedText =
(translationResult != null &&
translationResult.status ==
MessageTranslationStatus.completed &&
translationResult.translatedText.trim().isNotEmpty)
? translationResult.translatedText.trim()
: msg.text.trim();
await _notificationService.showMessageNotification(
contactName: c?.name ?? 'Unknown Room',
message: resolvedText,
contactId: msg.senderKeyHex,
badgeCount: getTotalUnreadCount(),
);
}
}());
}
}
_handleQueuedMessageReceived();
@@ -4884,6 +4940,7 @@ class MeshCoreConnector extends ChangeNotifier {
void _maybeNotifyChannelMessage(
ChannelMessage message, {
String? channelName,
TranslationResult? translationResult,
}) {
if (message.isOutgoing || _appSettingsService == null) return;
final channelIndex = message.channelIndex;
@@ -4897,16 +4954,30 @@ class MeshCoreConnector extends ChangeNotifier {
final label = channelName ?? _channelDisplayName(channelIndex);
if (_appSettingsService!.isChannelMuted(label)) return;
_notificationService.showChannelMessageNotification(
channelName: label,
senderName: message.senderName,
message: message.text,
channelIndex: channelIndex,
badgeCount: getTotalUnreadCount(),
);
// Reuse translation result only if completed and non-empty; else use original text
final resolvedText =
(translationResult != null &&
translationResult.status == MessageTranslationStatus.completed &&
translationResult.translatedText.trim().isNotEmpty)
? translationResult.translatedText.trim()
: message.text.trim();
unawaited(() async {
await _notificationService.showChannelMessageNotification(
channelName: label,
senderName: message.senderName,
message: resolvedText,
channelIndex: message.channelIndex,
badgeCount: getTotalUnreadCount(),
);
}());
}
void _handleIncomingChannelMessage(Uint8List frame) {
void _handleIncomingChannelMessage(Uint8List frame) async {
// If we're syncing the queued messages, advance the queue immediately
// before any potentially long async work (like translation/notifications).
if (_isSyncingQueuedMessages) {
_handleQueuedMessageReceived();
}
final parsed = ChannelMessage.fromFrame(frame);
if (parsed != null && parsed.channelIndex != null) {
if (_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) {
@@ -4925,15 +4996,17 @@ class MeshCoreConnector extends ChangeNotifier {
pathBytes: message.pathBytes,
);
final isNew = _addChannelMessage(message.channelIndex!, message);
if (isNew && !message.isOutgoing) {
unawaited(
_translateIncomingChannelMessage(message.channelIndex!, message),
);
}
_maybeIncrementChannelUnread(message, isNew: isNew);
notifyListeners();
if (isNew) {
_maybeNotifyChannelMessage(message);
if (isNew && !message.isOutgoing) {
final msg = message; // capture for closure
unawaited(() async {
final translationResult = await translateChannelMessage(
msg.channelIndex!,
msg,
);
_maybeNotifyChannelMessage(msg, translationResult: translationResult);
}());
}
_handleQueuedMessageReceived();
} else if (_isSyncingQueuedMessages) {
@@ -4941,7 +5014,7 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
void _handleLogRxData(Uint8List frame) {
void _handleLogRxData(Uint8List frame) async {
if (frame.length < 4) return;
try {
final reader = BufferReader(frame);
@@ -5009,16 +5082,24 @@ class MeshCoreConnector extends ChangeNotifier {
pathBytes: message.pathBytes,
);
final isNew = _addChannelMessage(channel.index, message);
if (isNew && !message.isOutgoing) {
unawaited(_translateIncomingChannelMessage(channel.index, message));
}
_maybeIncrementChannelUnread(message, isNew: isNew);
notifyListeners();
if (isNew) {
final label = channel.name.isEmpty
? 'Channel ${channel.index}'
: channel.name;
_maybeNotifyChannelMessage(message, channelName: label);
// Run translation + notification asynchronously to avoid blocking
unawaited(() async {
final translationResult = await translateChannelMessage(
channel.index,
message,
);
final label = channel.name.isEmpty
? 'Channel ${channel.index}'
: channel.name;
_maybeNotifyChannelMessage(
message,
channelName: label,
translationResult: translationResult,
);
}());
}
return;
} catch (e) {