respect smaz encoding in message byte length calculation.

forgot sending button check, for contacts.
This commit is contained in:
ericz 2026-04-11 18:48:43 +02:00 committed by Enot (ded) Skelly
parent 00cf9cab76
commit 09ebba321d
No known key found for this signature in database
GPG key ID: 2FE5B19B03656304
5 changed files with 62 additions and 14 deletions

View file

@ -2994,13 +2994,7 @@ class MeshCoreConnector extends ChangeNotifier {
_pendingChannelSentQueue.add(message.messageId);
notifyListeners();
final trimmed = text.trim();
final isStructuredPayload =
trimmed.startsWith('g:') || trimmed.startsWith('m:');
final outboundText =
(isChannelSmazEnabled(channel.index) && !isStructuredPayload)
? Smaz.encodeIfSmaller(text)
: text;
final outboundText = prepareChannelOutboundText(channel.index, text);
await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime);
await sendFrame(
buildSendChannelTextMsgFrame(channel.index, outboundText),
@ -4452,6 +4446,16 @@ class MeshCoreConnector extends ChangeNotifier {
return text;
}
String prepareChannelOutboundText(int channelIndex, String text) {
final trimmed = text.trim();
final isStructuredPayload =
trimmed.startsWith('g:') || trimmed.startsWith('m:');
if (!isStructuredPayload && isChannelSmazEnabled(channelIndex)) {
return Smaz.encodeIfSmaller(text);
}
return text;
}
String _channelDisplayName(int channelIndex) {
for (final channel in _channels) {
if (channel.index != channelIndex) continue;

View file

@ -4,8 +4,14 @@ import 'package:flutter/services.dart';
class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
final int maxBytes;
final String Function(String)? encoder;
const Utf8LengthLimitingTextInputFormatter(this.maxBytes);
const Utf8LengthLimitingTextInputFormatter(this.maxBytes, {this.encoder});
int _effectiveByteLength(String text) {
final effective = encoder != null ? encoder!(text) : text;
return utf8.encode(effective).length;
}
@override
TextEditingValue formatEditUpdate(
@ -13,8 +19,7 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
TextEditingValue newValue,
) {
if (maxBytes <= 0) return oldValue;
final bytes = utf8.encode(newValue.text);
if (bytes.length <= maxBytes) return newValue;
if (_effectiveByteLength(newValue.text) <= maxBytes) return newValue;
final truncated = _truncateToMaxBytes(newValue.text, maxBytes);
return TextEditingValue(
@ -25,6 +30,14 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
}
String _truncateToMaxBytes(String text, int limit) {
if (encoder != null) {
final runes = text.runes.toList();
while (runes.isNotEmpty &&
_effectiveByteLength(String.fromCharCodes(runes)) > maxBytes) {
runes.removeLast();
}
return String.fromCharCodes(runes);
}
final buffer = StringBuffer();
var used = 0;
for (final rune in text.runes) {

View file

@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../utils/platform_info.dart';
import '../helpers/chat_scroll_controller.dart';
import '../helpers/smaz.dart';
import '../connector/meshcore_protocol.dart';
import '../helpers/gif_helper.dart';
import '../helpers/reaction_helper.dart';
@ -1099,6 +1100,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
focusNode: _textFieldFocusNode,
hintText: context.l10n.chat_typeMessage,
onSubmitted: (_) => _sendMessage(),
encoder:
connector.isChannelSmazEnabled(widget.channel.index)
? Smaz.encodeIfSmaller
: null,
decoration: InputDecoration(
hintText: context.l10n.chat_typeMessage,
border: OutlineInputBorder(
@ -1193,7 +1198,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
}
final maxBytes = maxChannelMessageBytes(connector.selfName);
if (utf8.encode(messageText).length > maxBytes) {
final outboundText = connector.prepareChannelOutboundText(
widget.channel.index,
messageText,
);
if (utf8.encode(outboundText).length > maxBytes) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))),
);

View file

@ -14,6 +14,7 @@ import 'package:latlong2/latlong.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../helpers/reaction_helper.dart';
import '../helpers/smaz.dart';
import '../widgets/message_status_icon.dart';
import '../helpers/chat_scroll_controller.dart';
import '../helpers/gif_helper.dart';
@ -572,6 +573,12 @@ class _ChatScreenState extends State<ChatScreen> {
focusNode: _textFieldFocusNode,
hintText: context.l10n.chat_typeMessage,
onSubmitted: (_) => _sendMessage(connector),
encoder:
connector.isContactSmazEnabled(
widget.contact.publicKeyHex,
)
? Smaz.encodeIfSmaller
: null,
decoration: InputDecoration(
hintText: context.l10n.chat_typeMessage,
border: OutlineInputBorder(
@ -673,7 +680,11 @@ class _ChatScreenState extends State<ChatScreen> {
}
}
final maxBytes = maxContactMessageBytes();
if (utf8.encode(outgoingText).length > maxBytes) {
final outboundText = connector.prepareContactOutboundText(
_resolveContact(connector),
outgoingText,
);
if (utf8.encode(outboundText).length > maxBytes) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))),
);

View file

@ -50,6 +50,10 @@ class ByteCountedTextField extends StatelessWidget {
/// Whether to hide the counter when the field is empty (default `true`).
final bool hideCounterWhenEmpty;
/// Optional encoder function to transform text before byte counting/limiting.
/// If provided, byte limits and counters will use the encoded text length.
final String Function(String)? encoder;
const ByteCountedTextField({
super.key,
required this.maxBytes,
@ -64,6 +68,7 @@ class ByteCountedTextField extends StatelessWidget {
this.warningThreshold = 0.7,
this.errorThreshold = 0.9,
this.hideCounterWhenEmpty = true,
this.encoder,
});
@override
@ -71,7 +76,10 @@ class ByteCountedTextField extends StatelessWidget {
return ValueListenableBuilder<TextEditingValue>(
valueListenable: controller,
builder: (context, value, _) {
final usedBytes = utf8.encode(value.text).length;
final effectiveText = encoder != null
? encoder!(value.text)
: value.text;
final usedBytes = utf8.encode(effectiveText).length;
final ratio = maxBytes > 0 ? usedBytes / maxBytes : 0.0;
final showCounter = !(hideCounterWhenEmpty && value.text.isEmpty);
@ -90,7 +98,10 @@ class ByteCountedTextField extends StatelessWidget {
focusNode: focusNode,
inputFormatters: [
...extraFormatters,
Utf8LengthLimitingTextInputFormatter(maxBytes),
Utf8LengthLimitingTextInputFormatter(
maxBytes,
encoder: encoder,
),
],
textCapitalization: textCapitalization,
decoration: