mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
Fix dev
rebase dev over main and resolve merge conflicts
This commit is contained in:
commit
cac6abfef1
28 changed files with 177 additions and 52 deletions
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## BLE Frames & Protocol Notes
|
||||
- Nordic UART Service (NUS) UUIDs: Service `6e400001-b5a3-f393-e0a9-e50e24dcca9e`, RX `6e400002-b5a3-f393-e0a9-e50e24dcca9e`, TX `6e400003-b5a3-f393-e0a9-e50e24dcca9e`.
|
||||
- Discovery: scans for device name prefix `MeshCore-` and filters by `platformName`/`advertisementData.advName`.
|
||||
- Discovery: scans for device names matching known prefixes and filters by `platformName`/`advertisementData.advName`.
|
||||
- Frames are capped at `maxFrameSize = 172` bytes; byte 0 is the command/response/push code. I/O is `MeshCoreConnector.sendFrame` and `MeshCoreConnector.receivedFrames`.
|
||||
- Command codes (to device): `cmdAppStart`=1, `cmdSendTxtMsg`=2, `cmdSendChannelTxtMsg`=3, `cmdGetContacts`=4, `cmdGetDeviceTime`=5, `cmdSetDeviceTime`=6, `cmdSendSelfAdvert`=7, `cmdSetAdvertName`=8, `cmdAddUpdateContact`=9, `cmdSyncNextMessage`=10, `cmdSetRadioParams`=11, `cmdSetRadioTxPower`=12, `cmdResetPath`=13, `cmdSetAdvertLatLon`=14, `cmdRemoveContact`=15, `cmdShareContact`=16, `cmdExportContact`=17, `cmdImportContact`=18, `cmdReboot`=19, `cmdSendLogin`=26, `cmdGetChannel`=31, `cmdSetChannel`=32, `cmdGetRadioSettings`=57.
|
||||
- Response codes (from device): `respCodeOk`=0, `respCodeErr`=1, `respCodeContactsStart`=2, `respCodeContact`=3, `respCodeEndOfContacts`=4, `respCodeSelfInfo`=5, `respCodeSent`=6, `respCodeContactMsgRecv`=7, `respCodeChannelMsgRecv`=8, `respCodeCurrTime`=9, `respCodeNoMoreMessages`=10, `respCodeContactMsgRecvV3`=16, `respCodeChannelMsgRecvV3`=17, `respCodeChannelInfo`=18, `respCodeRadioSettings`=25.
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ lib/
|
|||
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
|
||||
|
||||
### Device Discovery
|
||||
- Scans for devices with name prefix `MeshCore-`
|
||||
- Scans for devices with known name prefixes
|
||||
- Filters by `platformName` or `advertisementData.advName`
|
||||
|
||||
### Connection States
|
||||
|
|
|
|||
71
CONTRIBUTING.md
Normal file
71
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# How to contribute to Meshcore Open
|
||||
|
||||
Before submitting any pull requests (PR), please review the following information.
|
||||
|
||||
Unsolicited PRs without previous discussion or open issues may be
|
||||
rejected. As may changes that are too broad (i.e. 100 files changed) or that
|
||||
cover too many separate changes. If the changes are clearly AI generated they
|
||||
may also be rejected. [See more](#ai-use)
|
||||
|
||||
## First Step Checklist
|
||||
|
||||
### **Did you find a bug?**
|
||||
|
||||
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/zjs81/meshcore-open/issues).
|
||||
|
||||
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/zjs81/meshcore-open/issues/new).
|
||||
Be sure to include a **title and clear description**, as much relevant
|
||||
information as possible, and a **code sample** or an **executable test case**
|
||||
demonstrating the expected behavior that is not occurring. You can also include
|
||||
screenshots or video.
|
||||
|
||||
* DO NOT start work and submit a PR at this time, please discuss the issue and
|
||||
your implementation plan first.
|
||||
|
||||
### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
|
||||
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the
|
||||
stability, functionality, or testability of the application will generally not
|
||||
be accepted.
|
||||
|
||||
### **Do you intend to add a new feature or change an existing one?**
|
||||
|
||||
* Suggest your change in a new issue as a feature request.
|
||||
|
||||
* DO NOT start work and submit a PR at this time, please discuss the change and
|
||||
your implementation plan first.
|
||||
|
||||
* After it is generally decided that the feature or change fits the goals of the
|
||||
project you can start work or open a PR if you have already started.
|
||||
|
||||
## Submitting your patch
|
||||
|
||||
* All changes should be based on the `dev` branch. When creating your PR please
|
||||
be sure to change the target to merge into dev, and when starting work on a new
|
||||
branch be sure to start on latest `dev`.
|
||||
|
||||
* Ensure the PR description clearly describes the problem and solution. Include
|
||||
the relevant issue number if applicable.
|
||||
|
||||
* The PR should contain **one commit** only, the commit message should have a
|
||||
clear title followed by a new line and then brief description if needed. PR with
|
||||
multiple commits will be squashed into one before merging if required. See
|
||||
[Git Mastery](https://git-mastery.org/lessons/commitMessage/) for more
|
||||
information on good commit messages.
|
||||
|
||||
* **Before committing changes** on your branch, be sure to run both
|
||||
`dart format .` and `flutter analyze`. The continuous development checks will
|
||||
fail if issues here are not addressed before hand.
|
||||
|
||||
## AI-use
|
||||
|
||||
Everyone loves some help, AI agents are a tool in many of our belts. The project
|
||||
is not anti-AI.
|
||||
|
||||
There are some limits to acceptable use however. Generally:
|
||||
|
||||
* All code generated by AI should be thoroughly reviewed by the contributor.
|
||||
* The changes should be tightly controlled to not change anything out of scope
|
||||
for the patch, bug fix, etc.
|
||||
* The contributor should have a good understanding of what the code does and how
|
||||
the application works in order to effectively be able to manage the agent.
|
||||
13
README.md
13
README.md
|
|
@ -150,7 +150,8 @@ lib/
|
|||
├── main.dart # App entry point
|
||||
├── connector/
|
||||
│ ├── meshcore_connector.dart # BLE communication & state management
|
||||
│ └── meshcore_protocol.dart # Protocol definitions & frame parsing
|
||||
│ ├── meshcore_protocol.dart # Protocol definitions & frame parsing
|
||||
│ └── meshcore_uuids.dart # Device names and IDs (add prefixes here!)
|
||||
├── screens/
|
||||
│ ├── scanner_screen.dart # Device scanning (home screen)
|
||||
│ ├── contacts_screen.dart # Contact list
|
||||
|
|
@ -184,7 +185,15 @@ lib/
|
|||
|
||||
### Device Discovery
|
||||
|
||||
Devices are discovered by scanning for BLE advertisements with the name prefix `MeshCore-`
|
||||
Devices are discovered by scanning for BLE advertisements with known MeshCore device name prefixes. These are currently:
|
||||
- `MeshCore-`
|
||||
- `Whisper-`
|
||||
- `WisCore-`
|
||||
- `HT-`
|
||||
- `LowMesh_MC_`
|
||||
|
||||
New device prefixes can be added in `lib/connector/meshcore_uuids.dart`.
|
||||
|
||||
|
||||
### Message Format
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ The MeshCore BLE protocol implements a binary frame-based communication system u
|
|||
|
||||
### Connection Flow
|
||||
|
||||
1. **Scan** for devices with name prefix `MeshCore-`
|
||||
1. **Scan** for devices with known name prefixes (defined in `MeshCoreUuids.deviceNamePrefixes`):
|
||||
- `MeshCore-`
|
||||
- `Whisper-`
|
||||
- `WisCore-`
|
||||
- `HT-`
|
||||
- `LowMesh_MC_`
|
||||
2. **Connect** with 15-second timeout
|
||||
3. **Request MTU** of 185 bytes (falls back to default if unsupported)
|
||||
4. **Discover services** and locate NUS characteristics
|
||||
|
|
|
|||
|
|
@ -49,7 +49,12 @@ enum MeshCoreConnectionState {
|
|||
|
||||
## BLE Connection Lifecycle
|
||||
|
||||
1. **Scan** with keyword filters `["MeshCore-", "Whisper-"]`
|
||||
1. **Scan** with known name prefixes (defined in `MeshCoreUuids.deviceNamePrefixes`):
|
||||
- `MeshCore-`
|
||||
- `Whisper-`
|
||||
- `WisCore-`
|
||||
- `HT-`
|
||||
- `LowMesh_MC_`
|
||||
2. **Connect** with 15-second timeout
|
||||
3. **Request MTU** 185 bytes (non-web only)
|
||||
4. **Discover services** and locate NUS
|
||||
|
|
|
|||
|
|
@ -3976,11 +3976,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
tag: 'Connector',
|
||||
);
|
||||
|
||||
// CRITICAL: Preserve user's path override when contact is refreshed from device
|
||||
// Preserve user-selected path settings and previously known GPS when
|
||||
// refreshed frames omit coordinates (lat/lon encoded as 0,0).
|
||||
_contacts[existingIndex] = contact.copyWith(
|
||||
lastMessageAt: mergedLastMessageAt,
|
||||
pathOverride: existing.pathOverride, // Preserve user's path choice
|
||||
pathOverrideBytes: existing.pathOverrideBytes,
|
||||
latitude: contact.latitude ?? existing.latitude,
|
||||
longitude: contact.longitude ?? existing.longitude,
|
||||
);
|
||||
|
||||
appLogger.info(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ class MeshCoreUuids {
|
|||
"MeshCore-",
|
||||
"Whisper-",
|
||||
"WisCore-",
|
||||
"Seeed",
|
||||
"Lilygo",
|
||||
"HT-",
|
||||
"LowMesh_MC_",
|
||||
];
|
||||
}
|
||||
|
|
|
|||
38
lib/helpers/gif_helper.dart
Normal file
38
lib/helpers/gif_helper.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
class GifHelper {
|
||||
/// Parse a known GIF format, which can be any of:
|
||||
/// g:GIFID
|
||||
/// https://media.giphy.com/media/GIFID/giphy.gif
|
||||
/// https://giphy.com/gifs/Optional-title-with-dashes-GIFID
|
||||
///
|
||||
/// GIFID is a Giphy GIF ID. The https:// is optional (and
|
||||
/// can also be http://). The giphy.com/gifs form can also
|
||||
/// include a trailing slash.
|
||||
///
|
||||
/// Returns null if text is not a valid GIF format
|
||||
static String? parseGif(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
||||
if (match != null) {
|
||||
return match.group(1);
|
||||
}
|
||||
final directUrlMatch = RegExp(
|
||||
r'^(?:https?:\/\/)?media\.giphy\.com\/media\/([A-Za-z0-9_-]+)\/giphy\.gif$',
|
||||
).firstMatch(trimmed);
|
||||
if (directUrlMatch != null) {
|
||||
return directUrlMatch.group(1);
|
||||
}
|
||||
// Giphy understands page URLs with just the ID, or any string and a
|
||||
// dash before the ID, and redirects to a page with a dash-separated
|
||||
// title, a dash, and the ID. IDs in this form *probably* can't
|
||||
// contain dashes.
|
||||
final pageMatch = RegExp(
|
||||
r'^(?:https?:\/\/)?giphy\.com\/gifs\/(?:[^/?]*-)?([A-Za-z0-9_]+)\/?$',
|
||||
).firstMatch(trimmed);
|
||||
return pageMatch?.group(1);
|
||||
}
|
||||
|
||||
/// Encode a GIF in a format that parseGif() can parse.
|
||||
static String encodeGif(String gifId) {
|
||||
return 'g:$gifId';
|
||||
}
|
||||
}
|
||||
|
|
@ -109,4 +109,9 @@ class ReactionHelper {
|
|||
|
||||
return ReactionInfo(targetHash: match.group(1)!, emoji: emoji);
|
||||
}
|
||||
|
||||
/// Encode a reaction message that parseReaction() can parse.
|
||||
static String encodeReaction(String hash, String emojiIndex) {
|
||||
return 'r:$hash:$emojiIndex';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingShowPin": "Покажи PIN",
|
||||
"repeater_cliQuickClockSync": "Синхронизация на часовника",
|
||||
"repeater_cliQuickDiscovery": "Открий Съседи"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2090,4 +2090,4 @@
|
|||
"scanner_linuxPairingPinPrompt": "Geben Sie die PIN für {deviceName} ein (leer lassen, falls keine).",
|
||||
"repeater_cliQuickClockSync": "Uhr Synchronisieren",
|
||||
"repeater_cliQuickDiscovery": "Entdecke Nachbarn"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2090,4 +2090,4 @@
|
|||
"translation_systemLanguage": "Idioma del sistema",
|
||||
"repeater_cliQuickDiscovery": "Descubrir Vecinos",
|
||||
"repeater_cliQuickClockSync": "Sincronización del reloj"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingShowPin": "Afficher le code PIN",
|
||||
"repeater_cliQuickClockSync": "Synchronisation de l'horloge",
|
||||
"repeater_cliQuickDiscovery": "Découvrir les voisins"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2098,6 +2098,7 @@
|
|||
"translation_translateTo": "Fordítás {language}-ra",
|
||||
"translation_translationOptions": "Fordítási lehetőségek",
|
||||
"translation_systemLanguage": "Rendszer nyelvé",
|
||||
"scanner_linuxPairingPinPrompt": "Adja meg a(z) {deviceName} PIN-kódját (hagyja üresen, ha nincs).",
|
||||
"repeater_cliQuickClockSync": "Óra szinkronizálás",
|
||||
"repeater_cliQuickDiscovery": "Fedezd fel a szomszédokat"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingHidePin": "Nascondi il PIN",
|
||||
"repeater_cliQuickClockSync": "Sincronizzazione dell'orologio",
|
||||
"repeater_cliQuickDiscovery": "Scopri i Vicini"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2100,4 +2100,4 @@
|
|||
"scanner_linuxPairingPinPrompt": "{deviceName}のPINを入力してください(なしの場合は空欄のまま)。",
|
||||
"repeater_cliQuickClockSync": "クロック同期",
|
||||
"repeater_cliQuickDiscovery": "近隣を発見する"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2100,4 +2100,4 @@
|
|||
"translation_systemLanguage": "시스템 언어",
|
||||
"repeater_cliQuickClockSync": "시계 동기화",
|
||||
"repeater_cliQuickDiscovery": "이웃 발견하기"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3677,7 +3677,7 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String scanner_linuxPairingPinPrompt(String deviceName) {
|
||||
return 'Adja meg a PIN kódot a $deviceName számára (hagyja üresen, ha nincs).';
|
||||
return 'Adja meg a(z) $deviceName PIN-kódját (hagyja üresen, ha nincs).';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN",
|
||||
"repeater_cliQuickDiscovery": "Ontdek Buren",
|
||||
"repeater_cliQuickClockSync": "Kloksynchronisatie"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth",
|
||||
"repeater_cliQuickClockSync": "Sincronização do Relógio",
|
||||
"repeater_cliQuickDiscovery": "Descobrir Vizinhos"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"translation_systemLanguage": "Jazyk systému",
|
||||
"repeater_cliQuickClockSync": "Synchronizácia hodin",
|
||||
"repeater_cliQuickDiscovery": "Objaviť susedov"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2062,4 +2062,4 @@
|
|||
"scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje",
|
||||
"repeater_cliQuickDiscovery": "Odkrijte sosede",
|
||||
"repeater_cliQuickClockSync": "Usklajevanje ure"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2067,4 +2067,4 @@
|
|||
"translation_systemLanguage": "系统语言",
|
||||
"repeater_cliQuickDiscovery": "发现邻居",
|
||||
"repeater_cliQuickClockSync": "同步时钟"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import '../connector/meshcore_connector.dart';
|
|||
import '../utils/platform_info.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/gif_helper.dart';
|
||||
import '../helpers/reaction_helper.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
|
|
@ -355,7 +356,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
final settingsService = context.watch<AppSettingsService>();
|
||||
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||
final isOutgoing = message.isOutgoing;
|
||||
final gifId = _parseGifId(message.text);
|
||||
final gifId = GifHelper.parseGif(message.text);
|
||||
final poi = _parsePoiMessage(message.text);
|
||||
final translatedDisplayText =
|
||||
message.translatedText != null &&
|
||||
|
|
@ -699,7 +700,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
||||
|
||||
final gifId = _parseGifId(replyText);
|
||||
final gifId = GifHelper.parseGif(replyText);
|
||||
final poi = _parsePoiMessage(replyText);
|
||||
|
||||
Widget contentPreview;
|
||||
|
|
@ -811,12 +812,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
String? _parseGifId(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
||||
return match?.group(1);
|
||||
}
|
||||
|
||||
_PoiInfo? _parsePoiMessage(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(
|
||||
|
|
@ -897,7 +892,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
isScrollControlled: true,
|
||||
builder: (context) => GifPicker(
|
||||
onGifSelected: (gifId) {
|
||||
_textController.text = 'g:$gifId';
|
||||
_textController.text = GifHelper.encodeGif(gifId);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -1053,7 +1048,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
child: ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _textController,
|
||||
builder: (context, value, child) {
|
||||
final gifId = _parseGifId(value.text);
|
||||
final gifId = GifHelper.parseGif(value.text);
|
||||
if (gifId != null) {
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
|
|
@ -1322,7 +1317,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||
message.senderName,
|
||||
message.text,
|
||||
);
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex);
|
||||
connector.sendChannelMessage(widget.channel, reactionText);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import '../connector/meshcore_protocol.dart';
|
|||
import '../helpers/reaction_helper.dart';
|
||||
import '../widgets/message_status_icon.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../helpers/gif_helper.dart';
|
||||
import '../helpers/path_helper.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../models/channel_message.dart';
|
||||
|
|
@ -523,7 +524,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
child: ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _textController,
|
||||
builder: (context, value, child) {
|
||||
final gifId = _parseGifId(value.text);
|
||||
final gifId = GifHelper.parseGif(value.text);
|
||||
if (gifId != null) {
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
|
|
@ -601,19 +602,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
String? _parseGifId(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
||||
return match?.group(1);
|
||||
}
|
||||
|
||||
void _showGifPicker(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => GifPicker(
|
||||
onGifSelected: (gifId) {
|
||||
_textController.text = 'g:$gifId';
|
||||
_textController.text = GifHelper.encodeGif(gifId);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -1546,7 +1541,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
senderName,
|
||||
message.text,
|
||||
);
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex);
|
||||
connector.sendMessage(_resolveContact(connector), reactionText);
|
||||
}
|
||||
}
|
||||
|
|
@ -1576,7 +1571,7 @@ class _MessageBubble extends StatelessWidget {
|
|||
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||
final isOutgoing = message.isOutgoing;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final gifId = _parseGifId(message.text);
|
||||
final gifId = GifHelper.parseGif(message.text);
|
||||
final poi = _parsePoiMessage(message.text);
|
||||
final isFailed = message.status == MessageStatus.failed;
|
||||
final bubbleColor = isFailed
|
||||
|
|
@ -1850,12 +1845,6 @@ class _MessageBubble extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
String? _parseGifId(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
||||
return match?.group(1);
|
||||
}
|
||||
|
||||
_PoiInfo? _parsePoiMessage(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:llamadart/llamadart.dart';
|
|||
|
||||
import '../models/app_settings.dart';
|
||||
import '../models/translation_support.dart';
|
||||
import '../helpers/gif_helper.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import 'app_settings_service.dart';
|
||||
import 'translation_file_store.dart';
|
||||
|
|
@ -509,8 +510,10 @@ class TranslationService extends ChangeNotifier {
|
|||
if (trimmed.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return !(trimmed.startsWith('g:') ||
|
||||
trimmed.startsWith('m:') ||
|
||||
if (GifHelper.parseGif(trimmed) != null) {
|
||||
return false;
|
||||
}
|
||||
return !(trimmed.startsWith('m:') ||
|
||||
trimmed.startsWith('V1|') ||
|
||||
trimmed.startsWith('r:'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import flutter_blue_plus_darwin
|
|||
import flutter_local_notifications
|
||||
import mobile_scanner
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
|
|
@ -20,7 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue