Merge remote-tracking branch 'origin/main' into advert-intervals

This commit is contained in:
Zach
2026-02-01 17:03:53 -07:00
41 changed files with 2784 additions and 1864 deletions
+26 -2
View File
@@ -6,6 +6,10 @@ Open-source Flutter client for MeshCore LoRa mesh networking devices.
MeshCore Open is a cross-platform mobile application for communicating with MeshCore LoRa mesh network devices via Bluetooth Low Energy (BLE). The app enables long-range, off-grid communication through peer-to-peer messaging, public channels, and mesh networking capabilities. MeshCore Open is a cross-platform mobile application for communicating with MeshCore LoRa mesh network devices via Bluetooth Low Energy (BLE). The app enables long-range, off-grid communication through peer-to-peer messaging, public channels, and mesh networking capabilities.
<a href="http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/zjs81/meshcore-open">
<img src="assets/badges/badge_obtainium.png" height="80" align="center" alt="Get it on Obtainium"/>
</a>
## Screenshots ## Screenshots
<table> <table>
@@ -21,6 +25,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Features ## Features
### Core Functionality ### Core Functionality
- **Direct Messaging**: Private encrypted conversations with individual contacts - **Direct Messaging**: Private encrypted conversations with individual contacts
- **Public Channels**: Broadcast messages to channel subscribers on the mesh network - **Public Channels**: Broadcast messages to channel subscribers on the mesh network
- **Contact Management**: Organize contacts, track last seen times, and manage conversation history - **Contact Management**: Organize contacts, track last seen times, and manage conversation history
@@ -29,6 +34,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Message Replies**: Thread conversations with inline reply functionality - **Message Replies**: Thread conversations with inline reply functionality
### Mesh Network ### Mesh Network
- **Path Visualization**: View routing paths and signal quality for each contact - **Path Visualization**: View routing paths and signal quality for each contact
- **Route Management**: Manual path overriding and automatic route rotation - **Route Management**: Manual path overriding and automatic route rotation
- **Signal Metrics**: Real-time SNR (Signal-to-Noise Ratio) tracking - **Signal Metrics**: Real-time SNR (Signal-to-Noise Ratio) tracking
@@ -36,6 +42,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Repeater Support**: Connect to and manage repeater nodes for extended range - **Repeater Support**: Connect to and manage repeater nodes for extended range
### Map & Location ### Map & Location
- **Live Map View**: Real-time visualization of mesh network nodes on an interactive map - **Live Map View**: Real-time visualization of mesh network nodes on an interactive map
- **Node Filtering**: Filter by node type (chat, repeater, sensor) and time range - **Node Filtering**: Filter by node type (chat, repeater, sensor) and time range
- **Location Sharing**: Share GPS coordinates and custom markers with contacts - **Location Sharing**: Share GPS coordinates and custom markers with contacts
@@ -43,12 +50,14 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **MGRS Coordinates**: Support for Military Grid Reference System coordinate format - **MGRS Coordinates**: Support for Military Grid Reference System coordinate format
### Device Management ### Device Management
- **BLE Connection**: Scan and connect to MeshCore devices via Bluetooth - **BLE Connection**: Scan and connect to MeshCore devices via Bluetooth
- **Device Settings**: Configure radio parameters, power settings, and network options - **Device Settings**: Configure radio parameters, power settings, and network options
- **Battery Monitoring**: Real-time battery status with chemistry-specific voltage curves - **Battery Monitoring**: Real-time battery status with chemistry-specific voltage curves
- **Firmware Updates**: Over-the-air firmware updates via BLE (coming soon) - **Firmware Updates**: Over-the-air firmware updates via BLE (coming soon)
### Repeater Hub ### Repeater Hub
- **CLI Access**: Full command-line interface to repeater nodes - **CLI Access**: Full command-line interface to repeater nodes
- **Settings Management**: Configure repeater behavior, power limits, and network settings - **Settings Management**: Configure repeater behavior, power limits, and network settings
- **Statistics Dashboard**: View repeater traffic, connected clients, and system health - **Statistics Dashboard**: View repeater traffic, connected clients, and system health
@@ -57,6 +66,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Technical Details ## Technical Details
### Architecture ### Architecture
- **Framework**: Flutter 3.38.5 / Dart 3.10.4 - **Framework**: Flutter 3.38.5 / Dart 3.10.4
- **State Management**: Provider pattern with ChangeNotifier - **State Management**: Provider pattern with ChangeNotifier
- **BLE Protocol**: Nordic UART Service (NUS) over Bluetooth Low Energy - **BLE Protocol**: Nordic UART Service (NUS) over Bluetooth Low Energy
@@ -64,11 +74,13 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Encryption**: End-to-end encryption for private messages using the MeshCore protocol - **Encryption**: End-to-end encryption for private messages using the MeshCore protocol
### Platform Support ### Platform Support
-**Android**: Full support (API 21+) -**Android**: Full support (API 21+)
-**iOS**: Full support (iOS 12+) -**iOS**: Full support (iOS 12+)
- 🚧 **Desktop**: Limited support (macOS/Linux/Windows) - 🚧 **Desktop**: Limited support (macOS/Linux/Windows)
### Dependencies ### Dependencies
| Package | Purpose | | Package | Purpose |
|---------|---------| |---------|---------|
| flutter_blue_plus | Bluetooth Low Energy communication | | flutter_blue_plus | Bluetooth Low Energy communication |
@@ -84,6 +96,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Getting Started ## Getting Started
### Prerequisites ### Prerequisites
- Flutter SDK 3.38.5 or later - Flutter SDK 3.38.5 or later
- Android Studio / Xcode (for mobile development) - Android Studio / Xcode (for mobile development)
- A MeshCore-compatible LoRa device - A MeshCore-compatible LoRa device
@@ -91,17 +104,20 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
### Installation ### Installation
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone https://github.com/zjs81/meshcore-open.git git clone https://github.com/zjs81/meshcore-open.git
cd meshcore-open cd meshcore-open
``` ```
2. **Install dependencies** 2. **Install dependencies**
```bash ```bash
flutter pub get flutter pub get
``` ```
3. **Run the app** 3. **Run the app**
```bash ```bash
flutter run flutter run
``` ```
@@ -109,11 +125,13 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
### Building for Release ### Building for Release
**Android APK:** **Android APK:**
```bash ```bash
flutter build apk --release flutter build apk --release
``` ```
**iOS:** **iOS:**
```bash ```bash
flutter build ios --release flutter build ios --release
``` ```
@@ -152,25 +170,30 @@ lib/
## BLE Protocol ## BLE Protocol
### Nordic UART Service (NUS) ### Nordic UART Service (NUS)
- **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e` - **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e`
- **RX Characteristic**: `6e400002-b5a3-f393-e0a9-e50e24dcca9e` (Write to device) - **RX Characteristic**: `6e400002-b5a3-f393-e0a9-e50e24dcca9e` (Write to device)
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device) - **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
### Device Discovery ### Device Discovery
Devices are discovered by scanning for BLE advertisements with the name prefix `MeshCore-` Devices are discovered by scanning for BLE advertisements with the name prefix `MeshCore-`
### Message Format ### Message Format
Messages are transmitted as binary frames using a custom protocol optimized for LoRa transmission. See `meshcore_protocol.dart` for frame structure definitions. Messages are transmitted as binary frames using a custom protocol optimized for LoRa transmission. See `meshcore_protocol.dart` for frame structure definitions.
## Configuration ## Configuration
### App Settings ### App Settings
- **Theme**: System default, light, or dark mode - **Theme**: System default, light, or dark mode
- **Notifications**: Configurable for messages, channels, and node advertisements - **Notifications**: Configurable for messages, channels, and node advertisements
- **Battery Chemistry**: Support for NMC, LiFePO4, and LiPo battery types - **Battery Chemistry**: Support for NMC, LiFePO4, and LiPo battery types
- **Message Retry**: Automatic retry with configurable path clearing - **Message Retry**: Automatic retry with configurable path clearing
### Device Settings ### Device Settings
- **Radio Power**: Transmit power adjustment (10-30 dBm) - **Radio Power**: Transmit power adjustment (10-30 dBm)
- **Frequency**: LoRa frequency configuration - **Frequency**: LoRa frequency configuration
- **Bandwidth**: Channel bandwidth selection - **Bandwidth**: Channel bandwidth selection
@@ -182,22 +205,23 @@ Messages are transmitted as binary frames using a custom protocol optimized for
This is an open-source project. Contributions are welcome! This is an open-source project. Contributions are welcome!
### Development Guidelines ### Development Guidelines
- Follow the Flutter style guide - Follow the Flutter style guide
- Use Material 3 design components - Use Material 3 design components
- Write clear commit messages - Write clear commit messages
- Test on both Android and iOS before submitting PRs - Test on both Android and iOS before submitting PRs
### Code Style ### Code Style
- Prefer `StatelessWidget` with `Consumer` for reactive UI - Prefer `StatelessWidget` with `Consumer` for reactive UI
- Use `const` constructors where possible - Use `const` constructors where possible
- Keep functions small and focused - Keep functions small and focused
- Avoid premature abstractions - Avoid premature abstractions
## Support ## Support
For issues, questions, or feature requests, please open an issue on GitHub: For issues, questions, or feature requests, please open an issue on GitHub:
https://github.com/zjs81/meshcore-open/issues <https://github.com/zjs81/meshcore-open/issues>
## Donate ## Donate
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

-7
View File
@@ -57,9 +57,6 @@ PODS:
- nanopb/encode (3.30910.0) - nanopb/encode (3.30910.0)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0) - PromisesObjC (2.4.0)
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
@@ -79,7 +76,6 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -112,8 +108,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/mobile_scanner/ios" :path: ".symlinks/plugins/mobile_scanner/ios"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin: sqflite_darwin:
@@ -140,7 +134,6 @@ SPEC CHECKSUMS:
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
+3 -7
View File
@@ -706,7 +706,7 @@ class MeshCoreConnector extends ChangeNotifier {
try { try {
_connectionSubscription = device.connectionState.listen((state) { _connectionSubscription = device.connectionState.listen((state) {
if (state == BluetoothConnectionState.disconnected) { if (state == BluetoothConnectionState.disconnected && isConnected) {
_handleDisconnection(); _handleDisconnection();
} }
}); });
@@ -959,12 +959,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (!isConnected) return; if (!isConnected) return;
if (_batteryRequested && !force) return; if (_batteryRequested && !force) return;
_batteryRequested = true; _batteryRequested = true;
try { await sendFrame(buildGetBattAndStorageFrame());
await sendFrame(buildGetBattAndStorageFrame());
} catch (e) {
// Connection likely lost - trigger disconnection handling
_handleDisconnection();
}
} }
void _startBatteryPolling() { void _startBatteryPolling() {
@@ -995,6 +990,7 @@ class MeshCoreConnector extends ChangeNotifier {
} }
Future<void> _requestDeviceInfo() async { Future<void> _requestDeviceInfo() async {
if (!isConnected || _awaitingSelfInfo) return;
_awaitingSelfInfo = true; _awaitingSelfInfo = true;
await sendFrame(buildDeviceQueryFrame()); await sendFrame(buildDeviceQueryFrame());
await sendFrame(buildAppStartFrame()); await sendFrame(buildAppStartFrame());
+45
View File
@@ -102,6 +102,23 @@ class BufferWriter {
} }
writeBytes(bytes); writeBytes(bytes);
} }
void writeHex(String hex) {
// Validate hex string length is even and not empty
if (hex.isEmpty || hex.length % 2 != 0) {
throw FormatException('Invalid hex string length: ${hex.length}');
}
List<int> result = [];
for (int i = 0; i < hex.length ~/ 2; i++) {
final hexByte = hex.substring(i * 2, i * 2 + 2);
final byte = int.tryParse(hexByte, radix: 16);
if (byte == null) {
throw FormatException('Invalid hex characters at position $i: $hexByte');
}
result.add(byte);
}
writeBytes(Uint8List.fromList(result));
}
} }
// Command codes (to device) // Command codes (to device)
@@ -164,6 +181,7 @@ const int respCodeContactMsgRecv = 7;
const int respCodeChannelMsgRecv = 8; const int respCodeChannelMsgRecv = 8;
const int respCodeCurrTime = 9; const int respCodeCurrTime = 9;
const int respCodeNoMoreMessages = 10; const int respCodeNoMoreMessages = 10;
const int respCodeExportContact = 11;
const int respCodeBattAndStorage = 12; const int respCodeBattAndStorage = 12;
const int respCodeDeviceInfo = 13; const int respCodeDeviceInfo = 13;
const int respCodeContactMsgRecvV3 = 16; const int respCodeContactMsgRecvV3 = 16;
@@ -729,3 +747,30 @@ Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload})
} }
return writer.toBytes(); return writer.toBytes();
} }
// Build a export contact frame
// [cmd][pub_key x32 / if empty exports your contact info]
Uint8List buildExportContactFrame(Uint8List pubKey) {
final writer = BufferWriter();
writer.writeByte(cmdExportContact);
writer.writeBytes(pubKey);
return writer.toBytes();
}
// Build a import contact frame
// [cmd][contact_frame x98+]
Uint8List buildImportContactFrame(String contactFrame) {
final writer = BufferWriter();
writer.writeByte(cmdImportContact);
writer.writeHex(contactFrame);
return writer.toBytes();
}
// Build a export contact frame
// [cmd][pub_key x32]
Uint8List buildZeroHopContact(Uint8List pubKey) {
final writer = BufferWriter();
writer.writeByte(cmdShareContact);
writer.writeBytes(pubKey);
return writer.toBytes();
}
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_chatTraceRoute": "Трасиране на път", "contacts_chatTraceRoute": "Трасиране на път",
"contacts_roomPathTrace": "Трасиране на път до съ", "contacts_roomPathTrace": "Трасиране на път до съ",
"contacts_roomPing": "Ping на сървъра на стаята", "contacts_roomPing": "Ping на сървъра на стаята",
"contacts_pathTraceTo": "Проследи маршрут към {name}" "contacts_pathTraceTo": "Проследи маршрут към {name}",
"appSettings_languageUk": "Украински",
"contacts_clipboardEmpty": "Клипборда е празна.",
"contacts_invalidAdvertFormat": "Невалидни данни за контакт",
"appSettings_languageRu": "Руски",
"contacts_contactImported": "Контактът е импортиран.",
"contacts_zeroHopAdvert": "Реклама без скок",
"contacts_contactImportFailed": "Контактът не е успешно импортиран.",
"contacts_floodAdvert": "Потопна реклама",
"contacts_addContactFromClipboard": "Добави контакт от клипборда",
"contacts_copyAdvertToClipboard": "Копирай обявата в клипборда",
"contacts_ShareContact": "Копирай контакт в клипборда",
"contacts_ShareContactZeroHop": "Сподели контакт чрез обява",
"contacts_contactAdvertCopied": "Рекламата е копирана в клипборда.",
"contacts_zeroHopContactAdvertFailed": "Неуспешно изпращане на контакт.",
"contacts_zeroHopContactAdvertSent": "Изпратен контакт по обява.",
"contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Pfadverfolgung zum Raumserver", "contacts_roomPathTrace": "Pfadverfolgung zum Raumserver",
"contacts_roomPing": "Raumserver anpingen", "contacts_roomPing": "Raumserver anpingen",
"contacts_pathTraceTo": "Route nach {name} verfolgen", "contacts_pathTraceTo": "Route nach {name} verfolgen",
"contacts_chatTraceRoute": "Pfadverfolgungsroute" "contacts_chatTraceRoute": "Pfadverfolgungsroute",
"appSettings_languageRu": "Russisch",
"contacts_invalidAdvertFormat": "Ungültige Kontaktdaten",
"contacts_clipboardEmpty": "Die Zwischenablage ist leer.",
"appSettings_languageUk": "Ukrainisch",
"contacts_contactImported": "Kontakt wurde importiert.",
"contacts_contactImportFailed": "Kontakt konnte nicht importiert werden",
"contacts_zeroHopAdvert": "Zero-Hop-Anzeige",
"contacts_floodAdvert": "Überflutungsanzeige",
"contacts_addContactFromClipboard": "Kontakt aus Zwischenablage hinzufügen",
"contacts_ShareContactZeroHop": "Kontakt über Anzeige teilen",
"contacts_copyAdvertToClipboard": "Werbung in die Zwischenablage kopieren",
"contacts_ShareContact": "Kontakt in die Zwischenablage kopieren",
"contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.",
"contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
"contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen."
} }
+15 -1
View File
@@ -1328,6 +1328,20 @@
"placeholders": { "placeholders": {
"name": {"type": "String"} "name": {"type": "String"}
} }
} },
"contacts_clipboardEmpty": "Clipboard is empty.",
"contacts_invalidAdvertFormat": "Invalid contact data",
"contacts_contactImported": "Contact has been imported.",
"contacts_contactImportFailed": "Failed to import contact.",
"contacts_zeroHopAdvert":"Zero Hop Advert",
"contacts_floodAdvert":"Flood Advert",
"contacts_copyAdvertToClipboard":"Copy Advert to Clipboard",
"contacts_addContactFromClipboard":"Add Contact from Clipboard",
"contacts_ShareContact": "Copy contact to Clipboard",
"contacts_ShareContactZeroHop": "Share contact by advert",
"contacts_zeroHopContactAdvertSent": "Sent contact by advert.",
"contacts_zeroHopContactAdvertFailed": "Failed to send contact.",
"contacts_contactAdvertCopied": "Advert copied to Clipboard.",
"contacts_contactAdvertCopyFailed": "Copying advert to Clipboard failed."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPing": "Pingar servidor de sala", "contacts_roomPing": "Pingar servidor de sala",
"contacts_roomPathTrace": "Rastreo de ruta al servidor de la habitación", "contacts_roomPathTrace": "Rastreo de ruta al servidor de la habitación",
"contacts_pathTraceTo": "Rastrear ruta a {name}", "contacts_pathTraceTo": "Rastrear ruta a {name}",
"contacts_chatTraceRoute": "Ruta de trazado" "contacts_chatTraceRoute": "Ruta de trazado",
"appSettings_languageUk": "Ucraniano",
"contacts_clipboardEmpty": "El portapapeles está vacío.",
"appSettings_languageRu": "Ruso",
"contacts_invalidAdvertFormat": "Datos de contacto no válidos",
"contacts_floodAdvert": "Anuncio de inundación",
"contacts_contactImported": "El contacto ha sido importado.",
"contacts_contactImportFailed": "Contacto no se importó correctamente.",
"contacts_zeroHopAdvert": "Anuncio de Zero Hop",
"contacts_ShareContactZeroHop": "Compartir contacto por anuncio",
"contacts_ShareContact": "Copiar contacto al Portapapeles",
"contacts_copyAdvertToClipboard": "Copiar anuncio al portapapeles",
"contacts_addContactFromClipboard": "Agregar contacto desde el portapapeles",
"contacts_zeroHopContactAdvertFailed": "No se pudo enviar el contacto.",
"contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.",
"contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.",
"contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_chatTraceRoute": "Tracer le chemin", "contacts_chatTraceRoute": "Tracer le chemin",
"contacts_pathTraceTo": "Tracer l'itinéraire vers {name}", "contacts_pathTraceTo": "Tracer l'itinéraire vers {name}",
"contacts_ping": "Ping", "contacts_ping": "Ping",
"contacts_roomPing": "Pinguer le serveur de la salle" "contacts_roomPing": "Pinguer le serveur de la salle",
"contacts_invalidAdvertFormat": "Données de contact non valides",
"appSettings_languageUk": "Ukrainien",
"appSettings_languageRu": "Russe",
"contacts_clipboardEmpty": "Le presse-papiers est vide.",
"contacts_contactImported": "Le contact a été importé.",
"contacts_floodAdvert": "Annonce de crue",
"contacts_contactImportFailed": "Échec de l'importation du contact.",
"contacts_zeroHopAdvert": "Annonce Zero Hop",
"contacts_copyAdvertToClipboard": "Copier l'annonce dans le presse-papiers",
"contacts_addContactFromClipboard": "Ajouter un contact depuis le presse-papiers",
"contacts_ShareContact": "Copier le contact dans le presse-papiers",
"contacts_ShareContactZeroHop": "Partager un contact par annonce",
"contacts_contactAdvertCopied": "Annonce copiée dans le presse-papiers.",
"contacts_contactAdvertCopyFailed": "La copie de l'annonce vers le presse-papiers a échoué.",
"contacts_zeroHopContactAdvertSent": "Envoyer un contact par annonce.",
"contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_repeaterPing": "Ripetitore ping", "contacts_repeaterPing": "Ripetitore ping",
"contacts_pathTraceTo": "Traccia percorso verso {name}", "contacts_pathTraceTo": "Traccia percorso verso {name}",
"contacts_roomPing": "Ping al server della stanza", "contacts_roomPing": "Ping al server della stanza",
"contacts_chatTraceRoute": "Traccia percorso path" "contacts_chatTraceRoute": "Traccia percorso path",
"appSettings_languageRu": "Russo",
"contacts_invalidAdvertFormat": "Dati di contatto non validi",
"appSettings_languageUk": "Ucraino",
"contacts_zeroHopAdvert": "Annuncio Zero Hop",
"contacts_floodAdvert": "Annuncio alluvionale",
"contacts_copyAdvertToClipboard": "Copia Annuncio negli Appunti",
"contacts_addContactFromClipboard": "Aggiungere contatto dalla clipboard",
"contacts_clipboardEmpty": "La clipboard è vuota.",
"contacts_ShareContact": "Copia contatto negli Appunti",
"contacts_contactImported": "Il contatto è stato importato.",
"contacts_contactImportFailed": "Contatto non importato con successo.",
"contacts_zeroHopContactAdvertSent": "Inviato contatto tramite annuncio.",
"contacts_contactAdvertCopyFailed": "Copia dell'annuncio nella Clipboard non riuscita.",
"contacts_ShareContactZeroHop": "Condividi contatto tramite annuncio",
"contacts_zeroHopContactAdvertFailed": "Invio del contatto non riuscito.",
"contacts_contactAdvertCopied": "Annuncio copiato negli Appunti."
} }
+84
View File
@@ -4771,6 +4771,90 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Trace route to {name}'** /// **'Trace route to {name}'**
String contacts_pathTraceTo(String name); String contacts_pathTraceTo(String name);
/// No description provided for @contacts_clipboardEmpty.
///
/// In en, this message translates to:
/// **'Clipboard is empty.'**
String get contacts_clipboardEmpty;
/// No description provided for @contacts_invalidAdvertFormat.
///
/// In en, this message translates to:
/// **'Invalid contact data'**
String get contacts_invalidAdvertFormat;
/// No description provided for @contacts_contactImported.
///
/// In en, this message translates to:
/// **'Contact has been imported.'**
String get contacts_contactImported;
/// No description provided for @contacts_contactImportFailed.
///
/// In en, this message translates to:
/// **'Failed to import contact.'**
String get contacts_contactImportFailed;
/// No description provided for @contacts_zeroHopAdvert.
///
/// In en, this message translates to:
/// **'Zero Hop Advert'**
String get contacts_zeroHopAdvert;
/// No description provided for @contacts_floodAdvert.
///
/// In en, this message translates to:
/// **'Flood Advert'**
String get contacts_floodAdvert;
/// No description provided for @contacts_copyAdvertToClipboard.
///
/// In en, this message translates to:
/// **'Copy Advert to Clipboard'**
String get contacts_copyAdvertToClipboard;
/// No description provided for @contacts_addContactFromClipboard.
///
/// In en, this message translates to:
/// **'Add Contact from Clipboard'**
String get contacts_addContactFromClipboard;
/// No description provided for @contacts_ShareContact.
///
/// In en, this message translates to:
/// **'Copy contact to Clipboard'**
String get contacts_ShareContact;
/// No description provided for @contacts_ShareContactZeroHop.
///
/// In en, this message translates to:
/// **'Share contact by advert'**
String get contacts_ShareContactZeroHop;
/// No description provided for @contacts_zeroHopContactAdvertSent.
///
/// In en, this message translates to:
/// **'Sent contact by advert.'**
String get contacts_zeroHopContactAdvertSent;
/// No description provided for @contacts_zeroHopContactAdvertFailed.
///
/// In en, this message translates to:
/// **'Failed to send contact.'**
String get contacts_zeroHopContactAdvertFailed;
/// No description provided for @contacts_contactAdvertCopied.
///
/// In en, this message translates to:
/// **'Advert copied to Clipboard.'**
String get contacts_contactAdvertCopied;
/// No description provided for @contacts_contactAdvertCopyFailed.
///
/// In en, this message translates to:
/// **'Copying advert to Clipboard failed.'**
String get contacts_contactAdvertCopyFailed;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate
+48 -2
View File
@@ -451,10 +451,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Руски';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Украински';
@override @override
String get appSettings_notifications => 'Уведомления'; String get appSettings_notifications => 'Уведомления';
@@ -2720,4 +2720,50 @@ class AppLocalizationsBg extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Проследи маршрут към $name'; return 'Проследи маршрут към $name';
} }
@override
String get contacts_clipboardEmpty => 'Клипборда е празна.';
@override
String get contacts_invalidAdvertFormat => 'Невалидни данни за контакт';
@override
String get contacts_contactImported => 'Контактът е импортиран.';
@override
String get contacts_contactImportFailed =>
'Контактът не е успешно импортиран.';
@override
String get contacts_zeroHopAdvert => 'Реклама без скок';
@override
String get contacts_floodAdvert => 'Потопна реклама';
@override
String get contacts_copyAdvertToClipboard => 'Копирай обявата в клипборда';
@override
String get contacts_addContactFromClipboard => 'Добави контакт от клипборда';
@override
String get contacts_ShareContact => 'Копирай контакт в клипборда';
@override
String get contacts_ShareContactZeroHop => 'Сподели контакт чрез обява';
@override
String get contacts_zeroHopContactAdvertSent => 'Изпратен контакт по обява.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Неуспешно изпращане на контакт.';
@override
String get contacts_contactAdvertCopied =>
'Рекламата е копирана в клипборда.';
@override
String get contacts_contactAdvertCopyFailed =>
'Копирането на обявата в клипборда не успя.';
} }
+51 -2
View File
@@ -445,10 +445,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Russisch';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukrainisch';
@override @override
String get appSettings_notifications => 'Benachrichtigungen'; String get appSettings_notifications => 'Benachrichtigungen';
@@ -2724,4 +2724,53 @@ class AppLocalizationsDe extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Route nach $name verfolgen'; return 'Route nach $name verfolgen';
} }
@override
String get contacts_clipboardEmpty => 'Die Zwischenablage ist leer.';
@override
String get contacts_invalidAdvertFormat => 'Ungültige Kontaktdaten';
@override
String get contacts_contactImported => 'Kontakt wurde importiert.';
@override
String get contacts_contactImportFailed =>
'Kontakt konnte nicht importiert werden';
@override
String get contacts_zeroHopAdvert => 'Zero-Hop-Anzeige';
@override
String get contacts_floodAdvert => 'Überflutungsanzeige';
@override
String get contacts_copyAdvertToClipboard =>
'Werbung in die Zwischenablage kopieren';
@override
String get contacts_addContactFromClipboard =>
'Kontakt aus Zwischenablage hinzufügen';
@override
String get contacts_ShareContact => 'Kontakt in die Zwischenablage kopieren';
@override
String get contacts_ShareContactZeroHop => 'Kontakt über Anzeige teilen';
@override
String get contacts_zeroHopContactAdvertSent =>
'Kontakt über Anzeige gesendet';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Kontakt konnte nicht gesendet werden.';
@override
String get contacts_contactAdvertCopied =>
'Anzeige in die Zwischenablage kopiert.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.';
} }
+43
View File
@@ -2680,4 +2680,47 @@ class AppLocalizationsEn extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Trace route to $name'; return 'Trace route to $name';
} }
@override
String get contacts_clipboardEmpty => 'Clipboard is empty.';
@override
String get contacts_invalidAdvertFormat => 'Invalid contact data';
@override
String get contacts_contactImported => 'Contact has been imported.';
@override
String get contacts_contactImportFailed => 'Failed to import contact.';
@override
String get contacts_zeroHopAdvert => 'Zero Hop Advert';
@override
String get contacts_floodAdvert => 'Flood Advert';
@override
String get contacts_copyAdvertToClipboard => 'Copy Advert to Clipboard';
@override
String get contacts_addContactFromClipboard => 'Add Contact from Clipboard';
@override
String get contacts_ShareContact => 'Copy contact to Clipboard';
@override
String get contacts_ShareContactZeroHop => 'Share contact by advert';
@override
String get contacts_zeroHopContactAdvertSent => 'Sent contact by advert.';
@override
String get contacts_zeroHopContactAdvertFailed => 'Failed to send contact.';
@override
String get contacts_contactAdvertCopied => 'Advert copied to Clipboard.';
@override
String get contacts_contactAdvertCopyFailed =>
'Copying advert to Clipboard failed.';
} }
+48 -2
View File
@@ -448,10 +448,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Ruso';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ucraniano';
@override @override
String get appSettings_notifications => 'Notificaciones'; String get appSettings_notifications => 'Notificaciones';
@@ -2720,4 +2720,50 @@ class AppLocalizationsEs extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Rastrear ruta a $name'; return 'Rastrear ruta a $name';
} }
@override
String get contacts_clipboardEmpty => 'El portapapeles está vacío.';
@override
String get contacts_invalidAdvertFormat => 'Datos de contacto no válidos';
@override
String get contacts_contactImported => 'El contacto ha sido importado.';
@override
String get contacts_contactImportFailed =>
'Contacto no se importó correctamente.';
@override
String get contacts_zeroHopAdvert => 'Anuncio de Zero Hop';
@override
String get contacts_floodAdvert => 'Anuncio de inundación';
@override
String get contacts_copyAdvertToClipboard => 'Copiar anuncio al portapapeles';
@override
String get contacts_addContactFromClipboard =>
'Agregar contacto desde el portapapeles';
@override
String get contacts_ShareContact => 'Copiar contacto al Portapapeles';
@override
String get contacts_ShareContactZeroHop => 'Compartir contacto por anuncio';
@override
String get contacts_zeroHopContactAdvertSent => 'Envió contacto por anuncio.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'No se pudo enviar el contacto.';
@override
String get contacts_contactAdvertCopied => 'Anuncio copiado al Portapapeles.';
@override
String get contacts_contactAdvertCopyFailed =>
'Copiar anuncio al Portapapeles ha fallado.';
} }
+52 -2
View File
@@ -449,10 +449,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Russe';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukrainien';
@override @override
String get appSettings_notifications => 'Notifications'; String get appSettings_notifications => 'Notifications';
@@ -2737,4 +2737,54 @@ class AppLocalizationsFr extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Tracer l\'itinéraire vers $name'; return 'Tracer l\'itinéraire vers $name';
} }
@override
String get contacts_clipboardEmpty => 'Le presse-papiers est vide.';
@override
String get contacts_invalidAdvertFormat => 'Données de contact non valides';
@override
String get contacts_contactImported => 'Le contact a été importé.';
@override
String get contacts_contactImportFailed =>
'Échec de l\'importation du contact.';
@override
String get contacts_zeroHopAdvert => 'Annonce Zero Hop';
@override
String get contacts_floodAdvert => 'Annonce de crue';
@override
String get contacts_copyAdvertToClipboard =>
'Copier l\'annonce dans le presse-papiers';
@override
String get contacts_addContactFromClipboard =>
'Ajouter un contact depuis le presse-papiers';
@override
String get contacts_ShareContact =>
'Copier le contact dans le presse-papiers';
@override
String get contacts_ShareContactZeroHop => 'Partager un contact par annonce';
@override
String get contacts_zeroHopContactAdvertSent =>
'Envoyer un contact par annonce.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Échec de l\'envoi du contact.';
@override
String get contacts_contactAdvertCopied =>
'Annonce copiée dans le presse-papiers.';
@override
String get contacts_contactAdvertCopyFailed =>
'La copie de l\'annonce vers le presse-papiers a échoué.';
} }
+50 -2
View File
@@ -447,10 +447,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Russo';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ucraino';
@override @override
String get appSettings_notifications => 'Notifiche'; String get appSettings_notifications => 'Notifiche';
@@ -2721,4 +2721,52 @@ class AppLocalizationsIt extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Traccia percorso verso $name'; return 'Traccia percorso verso $name';
} }
@override
String get contacts_clipboardEmpty => 'La clipboard è vuota.';
@override
String get contacts_invalidAdvertFormat => 'Dati di contatto non validi';
@override
String get contacts_contactImported => 'Il contatto è stato importato.';
@override
String get contacts_contactImportFailed =>
'Contatto non importato con successo.';
@override
String get contacts_zeroHopAdvert => 'Annuncio Zero Hop';
@override
String get contacts_floodAdvert => 'Annuncio alluvionale';
@override
String get contacts_copyAdvertToClipboard => 'Copia Annuncio negli Appunti';
@override
String get contacts_addContactFromClipboard =>
'Aggiungere contatto dalla clipboard';
@override
String get contacts_ShareContact => 'Copia contatto negli Appunti';
@override
String get contacts_ShareContactZeroHop =>
'Condividi contatto tramite annuncio';
@override
String get contacts_zeroHopContactAdvertSent =>
'Inviato contatto tramite annuncio.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Invio del contatto non riuscito.';
@override
String get contacts_contactAdvertCopied => 'Annuncio copiato negli Appunti.';
@override
String get contacts_contactAdvertCopyFailed =>
'Copia dell\'annuncio nella Clipboard non riuscita.';
} }
+50 -2
View File
@@ -445,10 +445,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Russisch';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Oekraïens';
@override @override
String get appSettings_notifications => 'Notificaties'; String get appSettings_notifications => 'Notificaties';
@@ -2710,4 +2710,52 @@ class AppLocalizationsNl extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Trace route to $name'; return 'Trace route to $name';
} }
@override
String get contacts_clipboardEmpty => 'Knipbord is leeg.';
@override
String get contacts_invalidAdvertFormat => 'Ongeldige contactgegevens';
@override
String get contacts_contactImported => 'Contact is geïmporteerd.';
@override
String get contacts_contactImportFailed =>
'Contact kon niet geïmporteerd worden.';
@override
String get contacts_zeroHopAdvert => 'Zero Hop Reclame';
@override
String get contacts_floodAdvert => 'Overstromingsadvertentie';
@override
String get contacts_copyAdvertToClipboard => 'Advert naar klembord kopiëren';
@override
String get contacts_addContactFromClipboard =>
'Contact uit klembord toevoegen';
@override
String get contacts_ShareContact => 'Kontakt naar Klembord kopiëren';
@override
String get contacts_ShareContactZeroHop => 'Contact delen via advertentie';
@override
String get contacts_zeroHopContactAdvertSent =>
'Contact verzonden via advertentie';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Mislukt om contact te verzenden';
@override
String get contacts_contactAdvertCopied =>
'Reclame gekopieerd naar Klembord.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopiëren van advertentie naar Clipboard is mislukt.';
} }
+49 -2
View File
@@ -449,10 +449,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Rosyjski';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukraińska';
@override @override
String get appSettings_notifications => 'Powiadomienia'; String get appSettings_notifications => 'Powiadomienia';
@@ -2719,4 +2719,51 @@ class AppLocalizationsPl extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Śledź trasę do $name'; return 'Śledź trasę do $name';
} }
@override
String get contacts_clipboardEmpty => 'Schowek jest pusty.';
@override
String get contacts_invalidAdvertFormat => 'Nieprawidłowe dane kontaktowe';
@override
String get contacts_contactImported => 'Kontakt został zaimportowany.';
@override
String get contacts_contactImportFailed =>
'Kontakt nie został zaimportowany.';
@override
String get contacts_zeroHopAdvert => 'Reklama Zero Hop';
@override
String get contacts_floodAdvert => 'Reklama powodziowa';
@override
String get contacts_copyAdvertToClipboard => 'Kopiuj ogłoszenie do schowka';
@override
String get contacts_addContactFromClipboard => 'Dodaj kontakt z schowka';
@override
String get contacts_ShareContact => 'Kopiuj kontakt do schowka';
@override
String get contacts_ShareContactZeroHop =>
'Udostępnij kontakt przez ogłoszenie';
@override
String get contacts_zeroHopContactAdvertSent =>
'Wysłano kontakt przez ogłoszenie.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Nie udało się wysłać kontaktu.';
@override
String get contacts_contactAdvertCopied => 'Reklama skopiowana do schowka.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopiowanie ogłoszenia do schowka nie powiodło się.';
} }
+49 -2
View File
@@ -449,10 +449,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Russo';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ucraniano';
@override @override
String get appSettings_notifications => 'Notificações'; String get appSettings_notifications => 'Notificações';
@@ -2721,4 +2721,51 @@ class AppLocalizationsPt extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Rastrear rota para $name'; return 'Rastrear rota para $name';
} }
@override
String get contacts_clipboardEmpty => 'Área de Transferência Está Vazia.';
@override
String get contacts_invalidAdvertFormat => 'Dados de Contato Inválidos';
@override
String get contacts_contactImported => 'Contato foi importado.';
@override
String get contacts_contactImportFailed => 'Contato falhou ao ser importado.';
@override
String get contacts_zeroHopAdvert => 'Anúncio Zero Hop';
@override
String get contacts_floodAdvert => 'Anúncio de Inundação';
@override
String get contacts_copyAdvertToClipboard =>
'Copiar Anúncio para Área de Transferência';
@override
String get contacts_addContactFromClipboard =>
'Adicionar Contato da Área de Transferência';
@override
String get contacts_ShareContact =>
'Copiar contato para Área de Transferência';
@override
String get contacts_ShareContactZeroHop => 'Compartilhar contato por anúncio';
@override
String get contacts_zeroHopContactAdvertSent => 'Enviou contato por anúncio.';
@override
String get contacts_zeroHopContactAdvertFailed => 'Falha ao enviar contato.';
@override
String get contacts_contactAdvertCopied =>
'Anúncio copiado para a Área de Transferência.';
@override
String get contacts_contactAdvertCopyFailed =>
'Cópia do anúncio para a Área de Transferência falhou.';
} }
+50
View File
@@ -2723,4 +2723,54 @@ class AppLocalizationsRu extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Показать маршрут к $name'; return 'Показать маршрут к $name';
} }
@override
String get contacts_clipboardEmpty => 'Буфер обмена пуст.';
@override
String get contacts_invalidAdvertFormat =>
'Недействительные контактные данные';
@override
String get contacts_contactImported => 'Контакт был импортирован';
@override
String get contacts_contactImportFailed => 'Контакт не удалось импортировать';
@override
String get contacts_zeroHopAdvert => 'Реклама Zero Hop';
@override
String get contacts_floodAdvert => 'Рекламный поток';
@override
String get contacts_copyAdvertToClipboard =>
'Копировать рекламу в буфер обмена';
@override
String get contacts_addContactFromClipboard =>
'Добавить контакт из буфера обмена';
@override
String get contacts_ShareContact => 'Копировать контакт в буфер обмена';
@override
String get contacts_ShareContactZeroHop =>
'Поделиться контактом по объявлению';
@override
String get contacts_zeroHopContactAdvertSent =>
'Отправлено сообщение по объявлению.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Не удалось отправить контакт.';
@override
String get contacts_contactAdvertCopied =>
'Реклама скопирована в буфер обмена.';
@override
String get contacts_contactAdvertCopyFailed =>
'Копирование рекламы в буфер обмена не удалось.';
} }
+48 -2
View File
@@ -445,10 +445,10 @@ class AppLocalizationsSk extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Ruština';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukrajinská';
@override @override
String get appSettings_notifications => 'Upozornenia'; String get appSettings_notifications => 'Upozornenia';
@@ -2706,4 +2706,50 @@ class AppLocalizationsSk extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Sledovať trasu k $name'; return 'Sledovať trasu k $name';
} }
@override
String get contacts_clipboardEmpty => 'Schránka je prázdna.';
@override
String get contacts_invalidAdvertFormat => 'Neplatné kontaktné údaje';
@override
String get contacts_contactImported => 'Kontakt bol importovaný.';
@override
String get contacts_contactImportFailed =>
'Kontakt sa nepodarilo importovať.';
@override
String get contacts_zeroHopAdvert => 'Inzerát Zero Hop';
@override
String get contacts_floodAdvert => 'Inzerát povodní';
@override
String get contacts_copyAdvertToClipboard => 'Kopírovať reklamu do schránky';
@override
String get contacts_addContactFromClipboard => 'Pridať kontakt z schránky';
@override
String get contacts_ShareContact => 'Kopírovať kontakt do schránky';
@override
String get contacts_ShareContactZeroHop => 'Zdieľať kontakt cez inzerát';
@override
String get contacts_zeroHopContactAdvertSent => 'Poslal kontakt cez inzerát.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Zlyhalo odoslanie kontaktu.';
@override
String get contacts_contactAdvertCopied =>
'Inzerát bol skopírovaný do schránky.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopírovanie inzerátu do schránky zlyhalo.';
} }
+47 -2
View File
@@ -444,10 +444,10 @@ class AppLocalizationsSl extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Ruščina';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukrajinsko';
@override @override
String get appSettings_notifications => 'Obvestila'; String get appSettings_notifications => 'Obvestila';
@@ -2709,4 +2709,49 @@ class AppLocalizationsSl extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Trace route to $name'; return 'Trace route to $name';
} }
@override
String get contacts_clipboardEmpty => 'Odložišče je prazno.';
@override
String get contacts_invalidAdvertFormat => 'Neveljavni kontaktne podatke';
@override
String get contacts_contactImported => 'Kontakt je bil uvožen.';
@override
String get contacts_contactImportFailed => 'Kontakt ni bil uspešno uvožen.';
@override
String get contacts_zeroHopAdvert => 'Reklama brez posrednikov';
@override
String get contacts_floodAdvert => 'Poplavna oglás';
@override
String get contacts_copyAdvertToClipboard => 'Kopiraj oglas v odložišče';
@override
String get contacts_addContactFromClipboard => 'Dodaj stik iz odložišča';
@override
String get contacts_ShareContact => 'Kopiraj stik v Odložišče';
@override
String get contacts_ShareContactZeroHop => 'Deliti kontakt prek oglasa';
@override
String get contacts_zeroHopContactAdvertSent => 'Poslano po oglasu.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Pošiljanje kontakta ni uspelo.';
@override
String get contacts_contactAdvertCopied =>
'Oglas je bil kopiran v odložišče.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopiranje oglasa v odložišče je spodletelo.';
} }
+47 -2
View File
@@ -442,10 +442,10 @@ class AppLocalizationsSv extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Ryska';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Ukrainska';
@override @override
String get appSettings_notifications => 'Meddelanden'; String get appSettings_notifications => 'Meddelanden';
@@ -2694,4 +2694,49 @@ class AppLocalizationsSv extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Spåra rutt till $name'; return 'Spåra rutt till $name';
} }
@override
String get contacts_clipboardEmpty => 'Urklipp är tomt.';
@override
String get contacts_invalidAdvertFormat => 'Ogiltiga kontaktuppgifter';
@override
String get contacts_contactImported => 'Kontakt har importerats.';
@override
String get contacts_contactImportFailed => 'Kontakt kunde inte importeras.';
@override
String get contacts_zeroHopAdvert => 'Reklam med nollhopp';
@override
String get contacts_floodAdvert => 'Översvämningsannons';
@override
String get contacts_copyAdvertToClipboard => 'Kopiera annons till urklipp';
@override
String get contacts_addContactFromClipboard =>
'Lägg till kontakt från urklipp';
@override
String get contacts_ShareContact => 'Kopiera kontakt till Urklipp';
@override
String get contacts_ShareContactZeroHop => 'Dela kontakt via annons';
@override
String get contacts_zeroHopContactAdvertSent => 'Skickat kontakt via annons.';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Misslyckades med att skicka kontakt.';
@override
String get contacts_contactAdvertCopied => 'Annons kopierad till Urklipp.';
@override
String get contacts_contactAdvertCopyFailed =>
'Kopiering av annons till Urklipp misslyckades.';
} }
+50 -1
View File
@@ -447,7 +447,7 @@ class AppLocalizationsUk extends AppLocalizations {
String get appSettings_languageBg => 'Български'; String get appSettings_languageBg => 'Български';
@override @override
String get appSettings_languageRu => 'Русский'; String get appSettings_languageRu => 'Російська';
@override @override
String get appSettings_languageUk => 'Українська'; String get appSettings_languageUk => 'Українська';
@@ -2730,4 +2730,53 @@ class AppLocalizationsUk extends AppLocalizations {
String contacts_pathTraceTo(String name) { String contacts_pathTraceTo(String name) {
return 'Відстежити маршрут до $name'; return 'Відстежити маршрут до $name';
} }
@override
String get contacts_clipboardEmpty => 'Буфер обміну порожній';
@override
String get contacts_invalidAdvertFormat => 'Недійсні контактні дані';
@override
String get contacts_contactImported => 'Контакт було імпортовано.';
@override
String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати';
@override
String get contacts_zeroHopAdvert => 'Реклама без перехоплення';
@override
String get contacts_floodAdvert => 'Залив реклами';
@override
String get contacts_copyAdvertToClipboard =>
'Копіювати оголошення в буфер обміну';
@override
String get contacts_addContactFromClipboard =>
'Додати контакт з буфера обміну';
@override
String get contacts_ShareContact => 'Копіювати контакт у буфер обміну';
@override
String get contacts_ShareContactZeroHop =>
'Поділитися контактом за оголошенням';
@override
String get contacts_zeroHopContactAdvertSent =>
'Відправлено контакт за оголошенням';
@override
String get contacts_zeroHopContactAdvertFailed =>
'Не вдалося надіслати контакт.';
@override
String get contacts_contactAdvertCopied =>
'Рекламу скопійовано до буфера обміну.';
@override
String get contacts_contactAdvertCopyFailed =>
'Копіювання оголошення в буфер обміну завершилося невдало';
} }
File diff suppressed because it is too large Load Diff
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Padtrace naar room server", "contacts_roomPathTrace": "Padtrace naar room server",
"contacts_roomPing": "Ping kamer server", "contacts_roomPing": "Ping kamer server",
"contacts_chatTraceRoute": "Route traceren", "contacts_chatTraceRoute": "Route traceren",
"contacts_pathTraceTo": "Trace route to {name}" "contacts_pathTraceTo": "Trace route to {name}",
"appSettings_languageUk": "Oekraïens",
"contacts_invalidAdvertFormat": "Ongeldige contactgegevens",
"contacts_contactImportFailed": "Contact kon niet geïmporteerd worden.",
"contacts_zeroHopAdvert": "Zero Hop Reclame",
"contacts_floodAdvert": "Overstromingsadvertentie",
"contacts_copyAdvertToClipboard": "Advert naar klembord kopiëren",
"appSettings_languageRu": "Russisch",
"contacts_clipboardEmpty": "Knipbord is leeg.",
"contacts_addContactFromClipboard": "Contact uit klembord toevoegen",
"contacts_contactImported": "Contact is geïmporteerd.",
"contacts_zeroHopContactAdvertSent": "Contact verzonden via advertentie",
"contacts_contactAdvertCopied": "Reclame gekopieerd naar Klembord.",
"contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
"contacts_ShareContact": "Kontakt naar Klembord kopiëren",
"contacts_ShareContactZeroHop": "Contact delen via advertentie",
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden"
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"pathTrace_refreshTooltip": "Odśwież ścieżkę.", "pathTrace_refreshTooltip": "Odśwież ścieżkę.",
"contacts_repeaterPing": "Repeater pingowy", "contacts_repeaterPing": "Repeater pingowy",
"contacts_pathTraceTo": "Śledź trasę do {name}", "contacts_pathTraceTo": "Śledź trasę do {name}",
"contacts_chatTraceRoute": "Śledź trasę promienia" "contacts_chatTraceRoute": "Śledź trasę promienia",
"appSettings_languageRu": "Rosyjski",
"appSettings_languageUk": "Ukraińska",
"contacts_contactImportFailed": "Kontakt nie został zaimportowany.",
"contacts_zeroHopAdvert": "Reklama Zero Hop",
"contacts_floodAdvert": "Reklama powodziowa",
"contacts_copyAdvertToClipboard": "Kopiuj ogłoszenie do schowka",
"contacts_clipboardEmpty": "Schowek jest pusty.",
"contacts_invalidAdvertFormat": "Nieprawidłowe dane kontaktowe",
"contacts_addContactFromClipboard": "Dodaj kontakt z schowka",
"contacts_contactImported": "Kontakt został zaimportowany.",
"contacts_zeroHopContactAdvertSent": "Wysłano kontakt przez ogłoszenie.",
"contacts_contactAdvertCopied": "Reklama skopiowana do schowka.",
"contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.",
"contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie",
"contacts_ShareContact": "Kopiuj kontakt do schowka",
"contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Traçar caminho para o servidor da sala", "contacts_roomPathTrace": "Traçar caminho para o servidor da sala",
"contacts_roomPing": "Pingar servidor da sala", "contacts_roomPing": "Pingar servidor da sala",
"contacts_chatTraceRoute": "Rastrear rota do caminho", "contacts_chatTraceRoute": "Rastrear rota do caminho",
"contacts_pathTraceTo": "Rastrear rota para {name}" "contacts_pathTraceTo": "Rastrear rota para {name}",
"contacts_invalidAdvertFormat": "Dados de Contato Inválidos",
"contacts_clipboardEmpty": "Área de Transferência Está Vazia.",
"appSettings_languageUk": "Ucraniano",
"contacts_contactImported": "Contato foi importado.",
"contacts_zeroHopAdvert": "Anúncio Zero Hop",
"contacts_copyAdvertToClipboard": "Copiar Anúncio para Área de Transferência",
"contacts_addContactFromClipboard": "Adicionar Contato da Área de Transferência",
"appSettings_languageRu": "Russo",
"contacts_ShareContact": "Copiar contato para Área de Transferência",
"contacts_contactImportFailed": "Contato falhou ao ser importado.",
"contacts_zeroHopContactAdvertSent": "Enviou contato por anúncio.",
"contacts_contactAdvertCopied": "Anúncio copiado para a Área de Transferência.",
"contacts_floodAdvert": "Anúncio de Inundação",
"contacts_contactAdvertCopyFailed": "Cópia do anúncio para a Área de Transferência falhou.",
"contacts_ShareContactZeroHop": "Compartilhar contato por anúncio",
"contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato."
} }
+16 -1
View File
@@ -793,5 +793,20 @@
"contacts_roomPathTrace": "Трассировка пути к серверу комнаты", "contacts_roomPathTrace": "Трассировка пути к серверу комнаты",
"contacts_roomPing": "Пинговать сервер комнаты", "contacts_roomPing": "Пинговать сервер комнаты",
"contacts_chatTraceRoute": "Трассировка маршрута", "contacts_chatTraceRoute": "Трассировка маршрута",
"contacts_pathTraceTo": "Показать маршрут к {name}" "contacts_pathTraceTo": "Показать маршрут к {name}",
"contacts_contactImported": "Контакт был импортирован",
"contacts_contactImportFailed": "Контакт не удалось импортировать",
"contacts_invalidAdvertFormat": "Недействительные контактные данные",
"contacts_zeroHopAdvert": "Реклама Zero Hop",
"appSettings_languageUk": "Українська",
"contacts_floodAdvert": "Рекламный поток",
"contacts_clipboardEmpty": "Буфер обмена пуст.",
"contacts_copyAdvertToClipboard": "Копировать рекламу в буфер обмена",
"contacts_ShareContact": "Копировать контакт в буфер обмена",
"contacts_zeroHopContactAdvertFailed": "Не удалось отправить контакт.",
"contacts_contactAdvertCopied": "Реклама скопирована в буфер обмена.",
"contacts_contactAdvertCopyFailed": "Копирование рекламы в буфер обмена не удалось.",
"contacts_addContactFromClipboard": "Добавить контакт из буфера обмена",
"contacts_ShareContactZeroHop": "Поделиться контактом по объявлению",
"contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению."
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Sledovanie cesty k serveru miestnosti", "contacts_roomPathTrace": "Sledovanie cesty k serveru miestnosti",
"contacts_roomPing": "Ping server miestnosti", "contacts_roomPing": "Ping server miestnosti",
"contacts_chatTraceRoute": "Sledovať trasu lúča", "contacts_chatTraceRoute": "Sledovať trasu lúča",
"contacts_pathTraceTo": "Sledovať trasu k {name}" "contacts_pathTraceTo": "Sledovať trasu k {name}",
"contacts_clipboardEmpty": "Schránka je prázdna.",
"appSettings_languageUk": "Ukrajinská",
"contacts_contactImportFailed": "Kontakt sa nepodarilo importovať.",
"contacts_zeroHopAdvert": "Inzerát Zero Hop",
"contacts_floodAdvert": "Inzerát povodní",
"contacts_copyAdvertToClipboard": "Kopírovať reklamu do schránky",
"contacts_invalidAdvertFormat": "Neplatné kontaktné údaje",
"appSettings_languageRu": "Ruština",
"contacts_addContactFromClipboard": "Pridať kontakt z schránky",
"contacts_contactImported": "Kontakt bol importovaný.",
"contacts_zeroHopContactAdvertSent": "Poslal kontakt cez inzerát.",
"contacts_contactAdvertCopied": "Inzerát bol skopírovaný do schránky.",
"contacts_contactAdvertCopyFailed": "Kopírovanie inzerátu do schránky zlyhalo.",
"contacts_zeroHopContactAdvertFailed": "Zlyhalo odoslanie kontaktu.",
"contacts_ShareContactZeroHop": "Zdieľať kontakt cez inzerát",
"contacts_ShareContact": "Kopírovať kontakt do schránky"
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Sledenje poti do strežnika sobe", "contacts_roomPathTrace": "Sledenje poti do strežnika sobe",
"contacts_roomPing": "Ping strežnik sobe", "contacts_roomPing": "Ping strežnik sobe",
"contacts_chatTraceRoute": "Slediti poti žarkov", "contacts_chatTraceRoute": "Slediti poti žarkov",
"contacts_pathTraceTo": "Trace route to {name}" "contacts_pathTraceTo": "Trace route to {name}",
"appSettings_languageRu": "Ruščina",
"appSettings_languageUk": "Ukrajinsko",
"contacts_contactImported": "Kontakt je bil uvožen.",
"contacts_contactImportFailed": "Kontakt ni bil uspešno uvožen.",
"contacts_zeroHopAdvert": "Reklama brez posrednikov",
"contacts_floodAdvert": "Poplavna oglás",
"contacts_invalidAdvertFormat": "Neveljavni kontaktne podatke",
"contacts_clipboardEmpty": "Odložišče je prazno.",
"contacts_copyAdvertToClipboard": "Kopiraj oglas v odložišče",
"contacts_addContactFromClipboard": "Dodaj stik iz odložišča",
"contacts_zeroHopContactAdvertSent": "Poslano po oglasu.",
"contacts_zeroHopContactAdvertFailed": "Pošiljanje kontakta ni uspelo.",
"contacts_contactAdvertCopied": "Oglas je bil kopiran v odložišče.",
"contacts_contactAdvertCopyFailed": "Kopiranje oglasa v odložišče je spodletelo.",
"contacts_ShareContactZeroHop": "Deliti kontakt prek oglasa",
"contacts_ShareContact": "Kopiraj stik v Odložišče"
} }
+17 -1
View File
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Vägspårning till rumserver", "contacts_roomPathTrace": "Vägspårning till rumserver",
"contacts_roomPing": "Ping rumsserver", "contacts_roomPing": "Ping rumsserver",
"contacts_chatTraceRoute": "Spåra rutt", "contacts_chatTraceRoute": "Spåra rutt",
"contacts_pathTraceTo": "Spåra rutt till {name}" "contacts_pathTraceTo": "Spåra rutt till {name}",
"contacts_clipboardEmpty": "Urklipp är tomt.",
"appSettings_languageRu": "Ryska",
"contacts_contactImportFailed": "Kontakt kunde inte importeras.",
"contacts_zeroHopAdvert": "Reklam med nollhopp",
"contacts_floodAdvert": "Översvämningsannons",
"contacts_copyAdvertToClipboard": "Kopiera annons till urklipp",
"contacts_invalidAdvertFormat": "Ogiltiga kontaktuppgifter",
"appSettings_languageUk": "Ukrainska",
"contacts_addContactFromClipboard": "Lägg till kontakt från urklipp",
"contacts_contactImported": "Kontakt har importerats.",
"contacts_zeroHopContactAdvertSent": "Skickat kontakt via annons.",
"contacts_contactAdvertCopied": "Annons kopierad till Urklipp.",
"contacts_contactAdvertCopyFailed": "Kopiering av annons till Urklipp misslyckades.",
"contacts_ShareContact": "Kopiera kontakt till Urklipp",
"contacts_zeroHopContactAdvertFailed": "Misslyckades med att skicka kontakt.",
"contacts_ShareContactZeroHop": "Dela kontakt via annons"
} }
+16 -1
View File
@@ -1553,5 +1553,20 @@
"contacts_roomPathTrace": "Трасування шляху до серверу кімнати", "contacts_roomPathTrace": "Трасування шляху до серверу кімнати",
"contacts_roomPing": "Пінг сервера кімнати", "contacts_roomPing": "Пінг сервера кімнати",
"contacts_chatTraceRoute": "Трасування шляху", "contacts_chatTraceRoute": "Трасування шляху",
"contacts_pathTraceTo": "Відстежити маршрут до {name}" "contacts_pathTraceTo": "Відстежити маршрут до {name}",
"contacts_invalidAdvertFormat": "Недійсні контактні дані",
"contacts_contactImported": "Контакт було імпортовано.",
"contacts_contactImportFailed": "Контакт не вдалося імпортувати",
"contacts_zeroHopAdvert": "Реклама без перехоплення",
"contacts_floodAdvert": "Залив реклами",
"contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну",
"contacts_clipboardEmpty": "Буфер обміну порожній",
"appSettings_languageRu": "Російська",
"contacts_ShareContact": "Копіювати контакт у буфер обміну",
"contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.",
"contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.",
"contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало",
"contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
"contacts_addContactFromClipboard": "Додати контакт з буфера обміну",
"contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням"
} }
+540 -524
View File
File diff suppressed because it is too large Load Diff
+38 -18
View File
@@ -121,24 +121,44 @@ class _ChannelsScreenState extends State<ChannelsScreen>
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
if (_communities.isNotEmpty) PopupMenuButton(
IconButton( itemBuilder: (context) => [
icon: const Icon(Icons.groups), PopupMenuItem(
tooltip: context.l10n.community_manageCommunities, child: Row(
onPressed: () => _showManageCommunitiesDialog(context), children: [
), const Icon(Icons.logout, color: Colors.red),
IconButton( const SizedBox(width: 8),
icon: const Icon(Icons.bluetooth_disabled), Text(context.l10n.common_disconnect),
tooltip: context.l10n.common_disconnect, ],
onPressed: () => _disconnect(context), ),
), onTap: () => _disconnect(context),
IconButton( ),
icon: const Icon(Icons.tune), if (_communities.isNotEmpty)
tooltip: context.l10n.common_settings, PopupMenuItem(
onPressed: () => Navigator.push( child: Row(
context, children: [
MaterialPageRoute(builder: (context) => const SettingsScreen()), const Icon(Icons.groups),
), const SizedBox(width: 8),
Text(context.l10n.community_manageCommunities),
],
),
onTap: () => _showManageCommunitiesDialog(context),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.settings),
const SizedBox(width: 8),
Text(context.l10n.settings_title),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
),
),
],
icon: const Icon(Icons.more_vert),
), ),
], ],
), ),
+245 -18
View File
@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/widgets/path_trace_dialog.dart'; import 'package:meshcore_open/widgets/path_trace_dialog.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -34,6 +34,12 @@ enum RoomLoginDestination {
management, management,
} }
enum ContactOperationType {
import,
export,
zeroHopShare,
}
class ContactsScreen extends StatefulWidget { class ContactsScreen extends StatefulWidget {
final bool hideBackButton; final bool hideBackButton;
@@ -54,16 +60,22 @@ class _ContactsScreenState extends State<ContactsScreen>
List<ContactGroup> _groups = []; List<ContactGroup> _groups = [];
Timer? _searchDebounce; Timer? _searchDebounce;
final Set<ContactOperationType> _pendingOperations = {};
StreamSubscription<Uint8List>? _frameSubscription;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadGroups(); _loadGroups();
_setupFrameListener();
} }
@override @override
void dispose() { void dispose() {
_searchDebounce?.cancel(); _searchDebounce?.cancel();
_searchController.dispose(); _searchController.dispose();
_frameSubscription?.cancel();
super.dispose(); super.dispose();
} }
@@ -79,6 +91,130 @@ class _ContactsScreenState extends State<ContactsScreen>
await _groupStore.saveGroups(_groups); await _groupStore.saveGroups(_groups);
} }
void _setupFrameListener() {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
// Listen for incoming text messages from the repeater
_frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return;
final frameBuffer = BufferReader(frame);
final code = frameBuffer.readUInt8();
if (code == respCodeExportContact) {
final advertPacket = frameBuffer.readRemainingBytes();
// Validate packet has expected minimum size (98+ bytes per protocol)
if (advertPacket.length < 98) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
);
}
_pendingOperations.remove(ContactOperationType.export);
return;
}
final hexString = pubKeyToHex(advertPacket);
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
}
if(code == respCodeOk) {
// Show a snackbar indicating success
if(!mounted) return;
if(_pendingOperations.contains(ContactOperationType.import)){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImported)),
);
}
if(_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertSent)),
);
}
if(_pendingOperations.contains(ContactOperationType.export)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
);
}
_pendingOperations.clear();
}
if(code == respCodeErr) {
// Show a snackbar indicating failure
if(!mounted) return;
if(_pendingOperations.contains(ContactOperationType.import)){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
);
}
if(_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertFailed)),
);
}
if(_pendingOperations.contains(ContactOperationType.export)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopyFailed)),
);
}
_pendingOperations.clear();
}
});
}
Future<void> _contactExport(Uint8List pubKey) async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final exportContactFrame = buildExportContactFrame(pubKey);
_pendingOperations.add(ContactOperationType.export);
await connector.sendFrame(exportContactFrame);
}
Future<void> _contactZeroHop(Uint8List pubKey) async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final exportContactZeroHopFrame = buildZeroHopContact(pubKey);
_pendingOperations.add(ContactOperationType.zeroHopShare);
await connector.sendFrame(exportContactZeroHopFrame);
}
Future<void> _contactImport() async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final clipboardData = await Clipboard.getData('text/plain');
if (clipboardData == null || clipboardData.text == null) {
if(mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)),
);
}
return;
}
final text = clipboardData.text!.trim();
if (!text.startsWith('meshcore://')) {
if(mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
);
}
return;
}
final hexString = text.substring('meshcore://'.length);
try {
final importContactFrame = buildImportContactFrame(hexString);
_pendingOperations.add(ContactOperationType.import);
await connector.sendFrame(importContactFrame);
} catch (e) {
if(mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final connector = context.watch<MeshCoreConnector>(); final connector = context.watch<MeshCoreConnector>();
@@ -98,18 +234,87 @@ class _ContactsScreenState extends State<ContactsScreen>
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
IconButton( PopupMenuButton(itemBuilder: (context) => [
icon: const Icon(Icons.bluetooth_disabled), PopupMenuItem(
tooltip: context.l10n.common_disconnect, child: Row(
onPressed: () => _disconnect(context, connector), children: [
), const Icon(Icons.connect_without_contact),
IconButton( const SizedBox(width: 8),
icon: const Icon(Icons.tune), Text(context.l10n.contacts_zeroHopAdvert),
tooltip: context.l10n.common_settings, ],
onPressed: () => Navigator.push( ),
context, onTap: () => {
MaterialPageRoute(builder: (context) => const SettingsScreen()), connector.sendSelfAdvert(flood: false),
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(context.l10n.settings_advertisementSent))),
},
), ),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.cell_tower),
const SizedBox(width: 8),
Text(context.l10n.contacts_floodAdvert),
],
),
onTap: () => {
connector.sendSelfAdvert(flood: true),
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(context.l10n.settings_advertisementSent))),
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.copy),
const SizedBox(width: 8),
Text(context.l10n.contacts_copyAdvertToClipboard),
],
),
onTap: () => _contactExport(Uint8List.fromList([])),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.paste),
const SizedBox(width: 8),
Text(context.l10n.contacts_addContactFromClipboard),
],
),
onTap: () => _contactImport(),
),
],
icon: const Icon(Icons.connect_without_contact),
),
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.logout, color: Colors.red),
const SizedBox(width: 8),
Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context, connector),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.settings),
const SizedBox(width: 8),
Text(context.l10n.settings_title),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
),
),
],
icon: const Icon(Icons.more_vert),
), ),
], ],
), ),
@@ -834,6 +1039,23 @@ class _ContactsScreenState extends State<ContactsScreen>
_openChat(context, contact); _openChat(context, contact);
}, },
), ),
],
ListTile(
leading: const Icon(Icons.copy),
title: Text(context.l10n.contacts_ShareContact),
onTap: () {
Navigator.pop(sheetContext);
_contactExport(contact.publicKey);
},
),
ListTile(
leading: const Icon(Icons.connect_without_contact),
title: Text(context.l10n.contacts_ShareContactZeroHop),
onTap: () {
Navigator.pop(sheetContext);
_contactZeroHop(contact.publicKey);
},
),
ListTile( ListTile(
leading: const Icon(Icons.delete, color: Colors.red), leading: const Icon(Icons.delete, color: Colors.red),
title: Text( title: Text(
@@ -845,7 +1067,6 @@ class _ContactsScreenState extends State<ContactsScreen>
_confirmDelete(context, connector, contact); _confirmDelete(context, connector, contact);
}, },
), ),
],
], ],
), ),
), ),
@@ -906,9 +1127,10 @@ class _ContactTile extends StatelessWidget {
child: _buildContactAvatar(contact), child: _buildContactAvatar(contact),
), ),
title: Text(contact.name), title: Text(contact.name),
subtitle: Text( subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
'${contact.typeLabel}${contact.pathLabel} ${contact.shortPubKeyHex}', Text(contact.pathLabel),
), Text(contact.shortPubKeyHex, style: TextStyle(fontSize: 12))
],),
// Clamp text scaling in trailing section to prevent overflow while // Clamp text scaling in trailing section to prevent overflow while
// maintaining accessibility. Primary content (title/subtitle) scales normally. // maintaining accessibility. Primary content (title/subtitle) scales normally.
trailing: MediaQuery( trailing: MediaQuery(
@@ -929,8 +1151,13 @@ class _ContactTile extends StatelessWidget {
_formatLastSeen(context, lastSeen), _formatLastSeen(context, lastSeen),
style: TextStyle(fontSize: 12, color: Colors.grey[600]), style: TextStyle(fontSize: 12, color: Colors.grey[600]),
), ),
if (contact.hasLocation) Row(
Icon(Icons.location_on, size: 14, color: Colors.grey[400]), mainAxisSize: MainAxisSize.min,
children: [
if (contact.hasLocation)
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
],
),
], ],
), ),
), ),
+26 -13
View File
@@ -245,20 +245,33 @@ class _MapScreenState extends State<MapScreen> {
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
IconButton( PopupMenuButton(
icon: const Icon(Icons.bluetooth_disabled), itemBuilder: (context) => [
tooltip: context.l10n.common_disconnect, PopupMenuItem(
onPressed: () => _disconnect(context, connector), child: Row(
), children: [
IconButton( const Icon(Icons.logout, color: Colors.red),
icon: const Icon(Icons.tune), const SizedBox(width: 8),
tooltip: context.l10n.common_settings, Text(context.l10n.common_disconnect),
onPressed: () => Navigator.push( ],
context, ),
MaterialPageRoute( onTap: () => _disconnect(context, connector),
builder: (context) => const SettingsScreen(),
), ),
), PopupMenuItem(
child: Row(
children: [
const Icon(Icons.settings),
const SizedBox(width: 8),
Text(context.l10n.settings_title),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
),
),
],
icon: const Icon(Icons.more_vert),
), ),
], ],
), ),
+342 -697
View File
File diff suppressed because it is too large Load Diff
+1 -69
View File
@@ -1,69 +1 @@
{ {}
"bg": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"de": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"es": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"fr": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"it": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"nl": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"pl": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"pt": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"ru": [
"appSettings_languageUk"
],
"sk": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"sl": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"sv": [
"appSettings_languageRu",
"appSettings_languageUk"
],
"uk": [
"appSettings_languageRu"
],
"zh": [
"appSettings_languageRu",
"appSettings_languageUk"
]
}