diff --git a/AGENTS.md b/AGENTS.md index bac981d..273bb96 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/CLAUDE.md b/CLAUDE.md index 08ef342..55af890 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ac727ba --- /dev/null +++ b/CONTRIBUTING.md @@ -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. diff --git a/README.md b/README.md index 2f87e91..ac188f6 100644 --- a/README.md +++ b/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 diff --git a/docs/BLE_PROTOCOL.md b/docs/BLE_PROTOCOL.md index 993c3ea..c17c3e7 100644 --- a/docs/BLE_PROTOCOL.md +++ b/docs/BLE_PROTOCOL.md @@ -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 diff --git a/documentation/ble-protocol.md b/documentation/ble-protocol.md index 9f4c1d7..ec24094 100644 --- a/documentation/ble-protocol.md +++ b/documentation/ble-protocol.md @@ -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 diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index d21f015..b432277 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -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( diff --git a/lib/connector/meshcore_uuids.dart b/lib/connector/meshcore_uuids.dart index da7f6b5..ae6697b 100644 --- a/lib/connector/meshcore_uuids.dart +++ b/lib/connector/meshcore_uuids.dart @@ -7,6 +7,9 @@ class MeshCoreUuids { "MeshCore-", "Whisper-", "WisCore-", + "Seeed", + "Lilygo", "HT-", + "LowMesh_MC_", ]; } diff --git a/lib/helpers/gif_helper.dart b/lib/helpers/gif_helper.dart new file mode 100644 index 0000000..5b68e90 --- /dev/null +++ b/lib/helpers/gif_helper.dart @@ -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'; + } +} diff --git a/lib/helpers/reaction_helper.dart b/lib/helpers/reaction_helper.dart index 90733c3..36118ca 100644 --- a/lib/helpers/reaction_helper.dart +++ b/lib/helpers/reaction_helper.dart @@ -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'; + } } diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index c4061a1..567ed24 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingShowPin": "Покажи PIN", "repeater_cliQuickClockSync": "Синхронизация на часовника", "repeater_cliQuickDiscovery": "Открий Съседи" -} \ No newline at end of file +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 2f7cd33..2503372 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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" -} \ No newline at end of file +} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 169f28e..5d98e4e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -2090,4 +2090,4 @@ "translation_systemLanguage": "Idioma del sistema", "repeater_cliQuickDiscovery": "Descubrir Vecinos", "repeater_cliQuickClockSync": "Sincronización del reloj" -} \ No newline at end of file +} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 59a6306..00b861e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingShowPin": "Afficher le code PIN", "repeater_cliQuickClockSync": "Synchronisation de l'horloge", "repeater_cliQuickDiscovery": "Découvrir les voisins" -} \ No newline at end of file +} diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 2be4039..e7671a4 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -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" -} \ No newline at end of file +} diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 4d08edb..6ebe4f9 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingHidePin": "Nascondi il PIN", "repeater_cliQuickClockSync": "Sincronizzazione dell'orologio", "repeater_cliQuickDiscovery": "Scopri i Vicini" -} \ No newline at end of file +} diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 92965f8..bc38720 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2100,4 +2100,4 @@ "scanner_linuxPairingPinPrompt": "{deviceName}のPINを入力してください(なしの場合は空欄のまま)。", "repeater_cliQuickClockSync": "クロック同期", "repeater_cliQuickDiscovery": "近隣を発見する" -} \ No newline at end of file +} diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 5221dd7..f63badc 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2100,4 +2100,4 @@ "translation_systemLanguage": "시스템 언어", "repeater_cliQuickClockSync": "시계 동기화", "repeater_cliQuickDiscovery": "이웃 발견하기" -} \ No newline at end of file +} diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index bbf989e..5d305ee 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -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 diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 5ecb330..490c269 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN", "repeater_cliQuickDiscovery": "Ontdek Buren", "repeater_cliQuickClockSync": "Kloksynchronisatie" -} \ No newline at end of file +} diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 42c8586..8693a28 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth", "repeater_cliQuickClockSync": "Sincronização do Relógio", "repeater_cliQuickDiscovery": "Descobrir Vizinhos" -} \ No newline at end of file +} diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 06f277e..50d42d2 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -2062,4 +2062,4 @@ "translation_systemLanguage": "Jazyk systému", "repeater_cliQuickClockSync": "Synchronizácia hodin", "repeater_cliQuickDiscovery": "Objaviť susedov" -} \ No newline at end of file +} diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 7a5c00c..6fc76a8 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -2062,4 +2062,4 @@ "scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje", "repeater_cliQuickDiscovery": "Odkrijte sosede", "repeater_cliQuickClockSync": "Usklajevanje ure" -} \ No newline at end of file +} diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index bc3a2a8..f054f79 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2067,4 +2067,4 @@ "translation_systemLanguage": "系统语言", "repeater_cliQuickDiscovery": "发现邻居", "repeater_cliQuickClockSync": "同步时钟" -} \ No newline at end of file +} diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 1ca0ee9..64da058 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -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 { final settingsService = context.watch(); 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 { 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 { ); } - 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 { isScrollControlled: true, builder: (context) => GifPicker( onGifSelected: (gifId) { - _textController.text = 'g:$gifId'; + _textController.text = GifHelper.encodeGif(gifId); }, ), ); @@ -1053,7 +1048,7 @@ class _ChannelChatScreenState extends State { child: ValueListenableBuilder( 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 { message.senderName, message.text, ); - final reactionText = 'r:$hash:$emojiIndex'; + final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex); connector.sendChannelMessage(widget.channel, reactionText); } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index b0f0c0c..a4ebc76 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -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 { child: ValueListenableBuilder( 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 { ); } - 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 { 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( diff --git a/lib/services/translation_service.dart b/lib/services/translation_service.dart index f8147a1..7d76efa 100644 --- a/lib/services/translation_service.dart +++ b/lib/services/translation_service.dart @@ -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:')); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2428a77..ffc8c59 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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"))