|
|
|
|
@ -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(),
|
|
|
|
|
);
|
|
|
|
|
} else if (contact?.type == advTypeRoom) {
|
|
|
|
|
_notificationService.showMessageNotification(
|
|
|
|
|
contactName: contact?.name ?? 'Unknown Room',
|
|
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
|
|