mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
respect smaz encoding in message byte length calculation.
forgot sending button check, for contacts.
This commit is contained in:
parent
00cf9cab76
commit
09ebba321d
5 changed files with 62 additions and 14 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue