mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-15 23:24:29 +10:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c9c089a53 | |||
| cb3b5a84eb | |||
| a4bbeffddc | |||
| 37ec8f2f05 | |||
| 39cd6d5514 | |||
| 44eb4fad58 | |||
| 1a209cbcfc | |||
| 33a8f34463 | |||
| ce8e8f0d5b | |||
| aa2d0f1927 | |||
| 0757c8e53a | |||
| add4731d05 | |||
| 7dc162d968 | |||
| 8ba4bbfbc5 | |||
| cac6abfef1 | |||
| 5354acb1d3 | |||
| fae416fb34 | |||
| 69433b6d89 | |||
| ea3b9609fc | |||
| 20a9939314 | |||
| c7b7deb0f6 | |||
| 82e04e8090 | |||
| f299608296 | |||
| 7dcec5b4ee | |||
| e4684b585a | |||
| 8386f262e1 | |||
| 45cd8a56a3 | |||
| 754f8a6c62 | |||
| c4f54efd77 | |||
| 637e08d22c | |||
| 32dc0fca22 | |||
| b5aa294fc1 | |||
| 26516baf67 | |||
| 4879b136f8 | |||
| 89a14c2719 | |||
| 4ad01ed43c | |||
| ffaa4033ae | |||
| 1a4fd1b477 | |||
| e1555ce380 | |||
| c7933d363b | |||
| 08ffb978cf | |||
| c5ec60638c | |||
| 75ec3b6116 | |||
| 45c9823c6f | |||
| 45658a7612 | |||
| 7633327f45 | |||
| 6b4b2d7ce6 | |||
| e4e8bfa4ef |
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## BLE Frames & Protocol Notes
|
## 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`.
|
- 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`.
|
- 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.
|
- 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.
|
- Response codes (from device): `respCodeOk`=0, `respCodeErr`=1, `respCodeContactsStart`=2, `respCodeContact`=3, `respCodeEndOfContacts`=4, `respCodeSelfInfo`=5, `respCodeSent`=6, `respCodeContactMsgRecv`=7, `respCodeChannelMsgRecv`=8, `respCodeCurrTime`=9, `respCodeNoMoreMessages`=10, `respCodeContactMsgRecvV3`=16, `respCodeChannelMsgRecvV3`=17, `respCodeChannelInfo`=18, `respCodeRadioSettings`=25.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ lib/
|
|||||||
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
|
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
|
||||||
|
|
||||||
### Device Discovery
|
### Device Discovery
|
||||||
- Scans for devices with name prefix `MeshCore-`
|
- Scans for devices with known name prefixes
|
||||||
- Filters by `platformName` or `advertisementData.advName`
|
- Filters by `platformName` or `advertisementData.advName`
|
||||||
|
|
||||||
### Connection States
|
### Connection States
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -150,7 +150,8 @@ lib/
|
|||||||
├── main.dart # App entry point
|
├── main.dart # App entry point
|
||||||
├── connector/
|
├── connector/
|
||||||
│ ├── meshcore_connector.dart # BLE communication & state management
|
│ ├── 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/
|
├── screens/
|
||||||
│ ├── scanner_screen.dart # Device scanning (home screen)
|
│ ├── scanner_screen.dart # Device scanning (home screen)
|
||||||
│ ├── contacts_screen.dart # Contact list
|
│ ├── contacts_screen.dart # Contact list
|
||||||
@@ -184,7 +185,15 @@ lib/
|
|||||||
|
|
||||||
### 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 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
|
### Message Format
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ The MeshCore BLE protocol implements a binary frame-based communication system u
|
|||||||
|
|
||||||
### Connection Flow
|
### 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
|
2. **Connect** with 15-second timeout
|
||||||
3. **Request MTU** of 185 bytes (falls back to default if unsupported)
|
3. **Request MTU** of 185 bytes (falls back to default if unsupported)
|
||||||
4. **Discover services** and locate NUS characteristics
|
4. **Discover services** and locate NUS characteristics
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ enum MeshCoreConnectionState {
|
|||||||
|
|
||||||
## BLE Connection Lifecycle
|
## 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
|
2. **Connect** with 15-second timeout
|
||||||
3. **Request MTU** 185 bytes (non-web only)
|
3. **Request MTU** 185 bytes (non-web only)
|
||||||
4. **Discover services** and locate NUS
|
4. **Discover services** and locate NUS
|
||||||
|
|||||||
@@ -3976,11 +3976,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
tag: 'Connector',
|
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(
|
_contacts[existingIndex] = contact.copyWith(
|
||||||
lastMessageAt: mergedLastMessageAt,
|
lastMessageAt: mergedLastMessageAt,
|
||||||
pathOverride: existing.pathOverride, // Preserve user's path choice
|
pathOverride: existing.pathOverride, // Preserve user's path choice
|
||||||
pathOverrideBytes: existing.pathOverrideBytes,
|
pathOverrideBytes: existing.pathOverrideBytes,
|
||||||
|
latitude: contact.latitude ?? existing.latitude,
|
||||||
|
longitude: contact.longitude ?? existing.longitude,
|
||||||
);
|
);
|
||||||
|
|
||||||
appLogger.info(
|
appLogger.info(
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ class MeshCoreUuids {
|
|||||||
"MeshCore-",
|
"MeshCore-",
|
||||||
"Whisper-",
|
"Whisper-",
|
||||||
"WisCore-",
|
"WisCore-",
|
||||||
|
"Seeed",
|
||||||
|
"Lilygo",
|
||||||
"HT-",
|
"HT-",
|
||||||
|
"LowMesh_MC_",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:flutter_linkify/flutter_linkify.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class LinkHandler {
|
class LinkHandler {
|
||||||
static TextStyle defaultLinkStyle(BuildContext context, TextStyle base) {
|
static TextStyle defaultLinkStyle(BuildContext context, TextStyle base) {
|
||||||
@@ -93,21 +94,19 @@ class LinkHandler {
|
|||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_invalidLink),
|
content: Text(context.l10n.chat_invalidLink),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,4 +109,9 @@ class ReactionHelper {
|
|||||||
|
|
||||||
return ReactionInfo(targetHash: match.group(1)!, emoji: emoji);
|
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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// showDismissibleSnackBar shows a [SnackBar] with tap to dismiss
|
||||||
|
// all other properties are default and optional
|
||||||
|
void showDismissibleSnackBar(
|
||||||
|
BuildContext context, {
|
||||||
|
Key? key,
|
||||||
|
required Widget content,
|
||||||
|
Color? backgroundColor,
|
||||||
|
double? elevation,
|
||||||
|
EdgeInsetsGeometry? margin,
|
||||||
|
EdgeInsetsGeometry? padding,
|
||||||
|
double? width,
|
||||||
|
ShapeBorder? shape,
|
||||||
|
HitTestBehavior? hitTestBehavior,
|
||||||
|
SnackBarBehavior? behavior,
|
||||||
|
SnackBarAction? action,
|
||||||
|
double? actionOverflowThreshold,
|
||||||
|
bool? showCloseIcon,
|
||||||
|
Color? closeIconColor,
|
||||||
|
Duration? duration,
|
||||||
|
bool? persist,
|
||||||
|
Animation<double>? animation,
|
||||||
|
void Function()? onVisible,
|
||||||
|
DismissDirection? dismissDirection,
|
||||||
|
Clip? clipBehavior,
|
||||||
|
}) {
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
key: key,
|
||||||
|
content: GestureDetector(
|
||||||
|
onTap: () => messenger.hideCurrentSnackBar(),
|
||||||
|
child: content,
|
||||||
|
),
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
elevation: elevation,
|
||||||
|
margin: margin,
|
||||||
|
padding: padding,
|
||||||
|
width: width,
|
||||||
|
shape: shape,
|
||||||
|
hitTestBehavior: hitTestBehavior,
|
||||||
|
behavior: behavior,
|
||||||
|
action: action,
|
||||||
|
actionOverflowThreshold: actionOverflowThreshold,
|
||||||
|
showCloseIcon: showCloseIcon,
|
||||||
|
closeIconColor: closeIconColor,
|
||||||
|
duration: duration ?? const Duration(seconds: 4),
|
||||||
|
persist: persist,
|
||||||
|
animation: animation,
|
||||||
|
onVisible: onVisible,
|
||||||
|
dismissDirection: dismissDirection ?? DismissDirection.down,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.hardEdge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingHidePin": "Скриване на PIN кода",
|
"scanner_linuxPairingHidePin": "Скриване на PIN кода",
|
||||||
"scanner_linuxPairingShowPin": "Покажи PIN",
|
"scanner_linuxPairingShowPin": "Покажи PIN",
|
||||||
"repeater_cliQuickClockSync": "Синхронизация на часовника",
|
"repeater_cliQuickClockSync": "Синхронизация на часовника",
|
||||||
"repeater_cliQuickDiscovery": "Открий Съседи"
|
"repeater_cliQuickDiscovery": "Открий Съседи",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Автоматично изпращайте съобщение \"синхронизиране на часовника\" след успешно влизане.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Синхронизиране на часовника след влизане",
|
||||||
|
"chat_sendMessage": "Изпратете съобщение",
|
||||||
|
"room_guest": "Информация за сървъра на стаята",
|
||||||
|
"repeater_guest": "Информация за ретранслаторите",
|
||||||
|
"repeater_guestTools": "Инструменти за гости"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2089,5 +2089,17 @@
|
|||||||
"scanner_linuxPairingPinTitle": "Bluetooth-Paarungs-PIN",
|
"scanner_linuxPairingPinTitle": "Bluetooth-Paarungs-PIN",
|
||||||
"scanner_linuxPairingPinPrompt": "Geben Sie die PIN für {deviceName} ein (leer lassen, falls keine).",
|
"scanner_linuxPairingPinPrompt": "Geben Sie die PIN für {deviceName} ein (leer lassen, falls keine).",
|
||||||
"repeater_cliQuickClockSync": "Uhr Synchronisieren",
|
"repeater_cliQuickClockSync": "Uhr Synchronisieren",
|
||||||
"repeater_cliQuickDiscovery": "Entdecke Nachbarn"
|
"repeater_cliQuickDiscovery": "Entdecke Nachbarn",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Uhrzeit-Synchronisation nach dem Anmelden",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatisch \"Uhrzeit-Synchronisierung\" nach erfolgreicher Anmeldung senden.",
|
||||||
|
"repeater_guest": "Informationen zu Repeatern",
|
||||||
|
"repeater_guestTools": "Gastwerkzeuge",
|
||||||
|
"chat_sendMessage": "Nachricht senden",
|
||||||
|
"room_guest": "Informationen zum Room Server"
|
||||||
|
}
|
||||||
|
|||||||
+13
-2
@@ -1038,8 +1038,8 @@
|
|||||||
"login_enterPassword": "Enter password",
|
"login_enterPassword": "Enter password",
|
||||||
"login_savePassword": "Save password",
|
"login_savePassword": "Save password",
|
||||||
"login_savePasswordSubtitle": "Password will be stored securely on this device",
|
"login_savePasswordSubtitle": "Password will be stored securely on this device",
|
||||||
"login_repeaterDescription": "Enter the repeater password to access settings and status.",
|
"login_repeaterDescription": "Enter the repeater password for guest or admin access.",
|
||||||
"login_roomDescription": "Enter the room password to access settings and status.",
|
"login_roomDescription": "Enter the room password for guest or admin access.",
|
||||||
"login_routing": "Routing",
|
"login_routing": "Routing",
|
||||||
"login_routingMode": "Routing mode",
|
"login_routingMode": "Routing mode",
|
||||||
"login_autoUseSavedPath": "Auto (use saved path)",
|
"login_autoUseSavedPath": "Auto (use saved path)",
|
||||||
@@ -1105,7 +1105,10 @@
|
|||||||
"path_setPath": "Set Path",
|
"path_setPath": "Set Path",
|
||||||
"repeater_management": "Repeater Management",
|
"repeater_management": "Repeater Management",
|
||||||
"room_management": "Room Server Management",
|
"room_management": "Room Server Management",
|
||||||
|
"repeater_guest": "Repeater Information",
|
||||||
|
"room_guest": "Room Server Information",
|
||||||
"repeater_managementTools": "Management Tools",
|
"repeater_managementTools": "Management Tools",
|
||||||
|
"repeater_guestTools": "Guest Tools",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Status",
|
||||||
"repeater_statusSubtitle": "View repeater status, stats, and neighbors",
|
"repeater_statusSubtitle": "View repeater status, stats, and neighbors",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetry",
|
||||||
@@ -1116,6 +1119,14 @@
|
|||||||
"repeater_neighborsSubtitle": "View zero hop neighbors.",
|
"repeater_neighborsSubtitle": "View zero hop neighbors.",
|
||||||
"repeater_settings": "Settings",
|
"repeater_settings": "Settings",
|
||||||
"repeater_settingsSubtitle": "Configure repeater parameters",
|
"repeater_settingsSubtitle": "Configure repeater parameters",
|
||||||
|
"repeater_clockSyncAfterLogin": "Clock sync after login",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatically send \"clock sync\" after a successful login",
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
"repeater_statusTitle": "Repeater Status",
|
"repeater_statusTitle": "Repeater Status",
|
||||||
"repeater_routingMode": "Routing mode",
|
"repeater_routingMode": "Routing mode",
|
||||||
"repeater_autoUseSavedPath": "Auto (use saved path)",
|
"repeater_autoUseSavedPath": "Auto (use saved path)",
|
||||||
|
|||||||
+14
-2
@@ -2089,5 +2089,17 @@
|
|||||||
"translation_translationOptions": "Opciones de traducción",
|
"translation_translationOptions": "Opciones de traducción",
|
||||||
"translation_systemLanguage": "Idioma del sistema",
|
"translation_systemLanguage": "Idioma del sistema",
|
||||||
"repeater_cliQuickDiscovery": "Descubrir Vecinos",
|
"repeater_cliQuickDiscovery": "Descubrir Vecinos",
|
||||||
"repeater_cliQuickClockSync": "Sincronización del reloj"
|
"repeater_cliQuickClockSync": "Sincronización del reloj",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Enviar automáticamente la función de \"sincronización de reloj\" después de un inicio de sesión exitoso.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronización del reloj después de iniciar sesión",
|
||||||
|
"repeater_guest": "Información sobre repetidores",
|
||||||
|
"chat_sendMessage": "Enviar mensaje",
|
||||||
|
"repeater_guestTools": "Herramientas para invitados",
|
||||||
|
"room_guest": "Información del servidor"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Entrez le code PIN pour {deviceName} (laissez vide si nécessaire).",
|
"scanner_linuxPairingPinPrompt": "Entrez le code PIN pour {deviceName} (laissez vide si nécessaire).",
|
||||||
"scanner_linuxPairingShowPin": "Afficher le code PIN",
|
"scanner_linuxPairingShowPin": "Afficher le code PIN",
|
||||||
"repeater_cliQuickClockSync": "Synchronisation de l'horloge",
|
"repeater_cliQuickClockSync": "Synchronisation de l'horloge",
|
||||||
"repeater_cliQuickDiscovery": "Découvrir les voisins"
|
"repeater_cliQuickDiscovery": "Découvrir les voisins",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Envoyer automatiquement une notification \"synchronisation de l'heure\" après une connexion réussie.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Synchronisation de l'horloge après la connexion",
|
||||||
|
"repeater_guestTools": "Outils pour les invités",
|
||||||
|
"chat_sendMessage": "Envoyer un message",
|
||||||
|
"room_guest": "Informations sur le serveur",
|
||||||
|
"repeater_guest": "Informations sur les répéteurs"
|
||||||
|
}
|
||||||
|
|||||||
+15
-3
@@ -2081,7 +2081,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_linuxPairingShowPin": "Megjelenítse a PIN-kódot",
|
"scanner_linuxPairingShowPin": "Megjelenítse a PIN-kódot",
|
||||||
"scanner_linuxPairingPinPrompt": "Adja meg a PIN kódot a {deviceName} számára (hagyja üresen, ha nincs).",
|
"scanner_linuxPairingPinPrompt": "Adja meg a(z) {deviceName} PIN-kódját (hagyja üresen, ha nincs).",
|
||||||
"scanner_linuxPairingHidePin": "Rejtse el a PIN-kódot",
|
"scanner_linuxPairingHidePin": "Rejtse el a PIN-kódot",
|
||||||
"scanner_linuxPairingPinTitle": "Bluetooth párosítási PIN",
|
"scanner_linuxPairingPinTitle": "Bluetooth párosítási PIN",
|
||||||
"@translation_translateTo": {
|
"@translation_translateTo": {
|
||||||
@@ -2099,5 +2099,17 @@
|
|||||||
"translation_translationOptions": "Fordítási lehetőségek",
|
"translation_translationOptions": "Fordítási lehetőségek",
|
||||||
"translation_systemLanguage": "Rendszer nyelvé",
|
"translation_systemLanguage": "Rendszer nyelvé",
|
||||||
"repeater_cliQuickClockSync": "Óra szinkronizálás",
|
"repeater_cliQuickClockSync": "Óra szinkronizálás",
|
||||||
"repeater_cliQuickDiscovery": "Fedezd fel a szomszédokat"
|
"repeater_cliQuickDiscovery": "Fedezd fel a szomszédokat",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatikusan küldje el a \"óra szinkronizálás\" üzenetet a sikeres bejelentkezés után.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Óra szinkronizálás bejelentkezés után",
|
||||||
|
"repeater_guestTools": "Vendégek számára elérhető eszközök",
|
||||||
|
"room_guest": "Szoba szerver információk",
|
||||||
|
"chat_sendMessage": "Üzenet küldése",
|
||||||
|
"repeater_guest": "Adatok a repeaterről"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinTitle": "PIN per l'accoppiamento Bluetooth",
|
"scanner_linuxPairingPinTitle": "PIN per l'accoppiamento Bluetooth",
|
||||||
"scanner_linuxPairingHidePin": "Nascondi il PIN",
|
"scanner_linuxPairingHidePin": "Nascondi il PIN",
|
||||||
"repeater_cliQuickClockSync": "Sincronizzazione dell'orologio",
|
"repeater_cliQuickClockSync": "Sincronizzazione dell'orologio",
|
||||||
"repeater_cliQuickDiscovery": "Scopri i Vicini"
|
"repeater_cliQuickDiscovery": "Scopri i Vicini",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Invia automaticamente il comando \"sincronizzazione dell'orologio\" dopo un login riuscito.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronizzazione dell'orologio dopo il login",
|
||||||
|
"repeater_guest": "Informazioni sul ripetitore",
|
||||||
|
"repeater_guestTools": "Strumenti per gli ospiti",
|
||||||
|
"chat_sendMessage": "Invia messaggio",
|
||||||
|
"room_guest": "Informazioni sul server"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2099,5 +2099,17 @@
|
|||||||
"scanner_linuxPairingPinTitle": "Bluetooth ペアリング PIN",
|
"scanner_linuxPairingPinTitle": "Bluetooth ペアリング PIN",
|
||||||
"scanner_linuxPairingPinPrompt": "{deviceName}のPINを入力してください(なしの場合は空欄のまま)。",
|
"scanner_linuxPairingPinPrompt": "{deviceName}のPINを入力してください(なしの場合は空欄のまま)。",
|
||||||
"repeater_cliQuickClockSync": "クロック同期",
|
"repeater_cliQuickClockSync": "クロック同期",
|
||||||
"repeater_cliQuickDiscovery": "近隣を発見する"
|
"repeater_cliQuickDiscovery": "近隣を発見する",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "ログイン後、時計の時刻を同期する",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "ログインが成功した場合、自動的に「時刻同期」を送信する。",
|
||||||
|
"room_guest": "ルームサーバーに関する情報",
|
||||||
|
"chat_sendMessage": "メッセージを送信する",
|
||||||
|
"repeater_guest": "繰り返し送信に関する情報",
|
||||||
|
"repeater_guestTools": "ゲスト向けツール"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2099,5 +2099,17 @@
|
|||||||
"translation_translationOptions": "번역 옵션",
|
"translation_translationOptions": "번역 옵션",
|
||||||
"translation_systemLanguage": "시스템 언어",
|
"translation_systemLanguage": "시스템 언어",
|
||||||
"repeater_cliQuickClockSync": "시계 동기화",
|
"repeater_cliQuickClockSync": "시계 동기화",
|
||||||
"repeater_cliQuickDiscovery": "이웃 발견하기"
|
"repeater_cliQuickDiscovery": "이웃 발견하기",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "로그인 후 시계 동기화",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "성공적인 로그인 후, 자동으로 \"시간 동기화\"를 전송합니다.",
|
||||||
|
"repeater_guestTools": "손님용 도구",
|
||||||
|
"chat_sendMessage": "메시지를 보내기",
|
||||||
|
"repeater_guest": "반복 장비 정보",
|
||||||
|
"room_guest": "서버 정보"
|
||||||
|
}
|
||||||
|
|||||||
@@ -3438,13 +3438,13 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @login_repeaterDescription.
|
/// No description provided for @login_repeaterDescription.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Enter the repeater password to access settings and status.'**
|
/// **'Enter the repeater password for guest or admin access.'**
|
||||||
String get login_repeaterDescription;
|
String get login_repeaterDescription;
|
||||||
|
|
||||||
/// No description provided for @login_roomDescription.
|
/// No description provided for @login_roomDescription.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Enter the room password to access settings and status.'**
|
/// **'Enter the room password for guest or admin access.'**
|
||||||
String get login_roomDescription;
|
String get login_roomDescription;
|
||||||
|
|
||||||
/// No description provided for @login_routing.
|
/// No description provided for @login_routing.
|
||||||
@@ -3609,12 +3609,30 @@ abstract class AppLocalizations {
|
|||||||
/// **'Room Server Management'**
|
/// **'Room Server Management'**
|
||||||
String get room_management;
|
String get room_management;
|
||||||
|
|
||||||
|
/// No description provided for @repeater_guest.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Repeater Information'**
|
||||||
|
String get repeater_guest;
|
||||||
|
|
||||||
|
/// No description provided for @room_guest.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Room Server Information'**
|
||||||
|
String get room_guest;
|
||||||
|
|
||||||
/// No description provided for @repeater_managementTools.
|
/// No description provided for @repeater_managementTools.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Management Tools'**
|
/// **'Management Tools'**
|
||||||
String get repeater_managementTools;
|
String get repeater_managementTools;
|
||||||
|
|
||||||
|
/// No description provided for @repeater_guestTools.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Guest Tools'**
|
||||||
|
String get repeater_guestTools;
|
||||||
|
|
||||||
/// No description provided for @repeater_status.
|
/// No description provided for @repeater_status.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -3675,6 +3693,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Configure repeater parameters'**
|
/// **'Configure repeater parameters'**
|
||||||
String get repeater_settingsSubtitle;
|
String get repeater_settingsSubtitle;
|
||||||
|
|
||||||
|
/// Repeater setting: auto sync device clock after successful login
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Clock sync after login'**
|
||||||
|
String get repeater_clockSyncAfterLogin;
|
||||||
|
|
||||||
|
/// Repeater setting subtitle: describes the clock sync after login behavior
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Automatically send \"clock sync\" after a successful login'**
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle;
|
||||||
|
|
||||||
/// No description provided for @repeater_statusTitle.
|
/// No description provided for @repeater_statusTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -1240,7 +1240,7 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Няма съобщения.';
|
String get chat_noMessages => 'Няма съобщения.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Изпратете съобщение';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2019,9 +2019,18 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Управление на сървъра за стая';
|
String get room_management => 'Управление на сървъра за стая';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Информация за ретранслаторите';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Информация за сървъра на стаята';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Инструменти за управление';
|
String get repeater_managementTools => 'Инструменти за управление';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Инструменти за гости';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Статус';
|
String get repeater_status => 'Статус';
|
||||||
|
|
||||||
@@ -2056,6 +2065,14 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
String get repeater_settingsSubtitle =>
|
String get repeater_settingsSubtitle =>
|
||||||
'Конфигурирайте параметрите на репитера';
|
'Конфигурирайте параметрите на репитера';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Синхронизиране на часовника след влизане';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Автоматично изпращайте съобщение \"синхронизиране на часовника\" след успешно влизане.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Статус на повтарянето';
|
String get repeater_statusTitle => 'Статус на повтарянето';
|
||||||
|
|
||||||
|
|||||||
@@ -1239,7 +1239,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Noch keine Nachrichten.';
|
String get chat_noMessages => 'Noch keine Nachrichten.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Nachricht senden';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2017,9 +2017,18 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Raum-Server-Verwaltung';
|
String get room_management => 'Raum-Server-Verwaltung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informationen zu Repeatern';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informationen zum Room Server';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Verwaltungs-Tools';
|
String get repeater_managementTools => 'Verwaltungs-Tools';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Gastwerkzeuge';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2052,6 +2061,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Repeater-parameter konfigurieren';
|
String get repeater_settingsSubtitle => 'Repeater-parameter konfigurieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Uhrzeit-Synchronisation nach dem Anmelden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatisch \"Uhrzeit-Synchronisierung\" nach erfolgreicher Anmeldung senden.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Repeaterstatus';
|
String get repeater_statusTitle => 'Repeaterstatus';
|
||||||
|
|
||||||
|
|||||||
@@ -1871,11 +1871,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterDescription =>
|
String get login_repeaterDescription =>
|
||||||
'Enter the repeater password to access settings and status.';
|
'Enter the repeater password for guest or admin access.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_roomDescription =>
|
String get login_roomDescription =>
|
||||||
'Enter the room password to access settings and status.';
|
'Enter the room password for guest or admin access.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_routing => 'Routing';
|
String get login_routing => 'Routing';
|
||||||
@@ -1979,9 +1979,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Room Server Management';
|
String get room_management => 'Room Server Management';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Repeater Information';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Room Server Information';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Management Tools';
|
String get repeater_managementTools => 'Management Tools';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Guest Tools';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2014,6 +2023,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Configure repeater parameters';
|
String get repeater_settingsSubtitle => 'Configure repeater parameters';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => 'Clock sync after login';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatically send \"clock sync\" after a successful login';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Repeater Status';
|
String get repeater_statusTitle => 'Repeater Status';
|
||||||
|
|
||||||
|
|||||||
@@ -1239,7 +1239,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Aún no hay mensajes';
|
String get chat_noMessages => 'Aún no hay mensajes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Enviar mensaje';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2015,9 +2015,18 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Administración del Servidor de Habitación';
|
String get room_management => 'Administración del Servidor de Habitación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Información sobre repetidores';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Información del servidor';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Herramientas de Gestión';
|
String get repeater_managementTools => 'Herramientas de Gestión';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Herramientas para invitados';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Estado';
|
String get repeater_status => 'Estado';
|
||||||
|
|
||||||
@@ -2050,6 +2059,14 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Configurar parámetros del repetidor';
|
String get repeater_settingsSubtitle => 'Configurar parámetros del repetidor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Sincronización del reloj después de iniciar sesión';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Enviar automáticamente la función de \"sincronización de reloj\" después de un inicio de sesión exitoso.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Estado del Repetidor';
|
String get repeater_statusTitle => 'Estado del Repetidor';
|
||||||
|
|
||||||
|
|||||||
@@ -1244,7 +1244,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Aucun message pour le moment.';
|
String get chat_noMessages => 'Aucun message pour le moment.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Envoyer un message';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2026,9 +2026,18 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Administrattion Room Server';
|
String get room_management => 'Administrattion Room Server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informations sur les répéteurs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informations sur le serveur';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Outils de Gestion';
|
String get repeater_managementTools => 'Outils de Gestion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Outils pour les invités';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'État';
|
String get repeater_status => 'État';
|
||||||
|
|
||||||
@@ -2062,6 +2071,14 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get repeater_settingsSubtitle =>
|
String get repeater_settingsSubtitle =>
|
||||||
'Configurer les paramètres du répéteur';
|
'Configurer les paramètres du répéteur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Synchronisation de l\'horloge après la connexion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Envoyer automatiquement une notification \"synchronisation de l\'heure\" après une connexion réussie.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'État du répéteur';
|
String get repeater_statusTitle => 'État du répéteur';
|
||||||
|
|
||||||
|
|||||||
@@ -1247,7 +1247,7 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Még nincs üzenet.';
|
String get chat_noMessages => 'Még nincs üzenet.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Üzenet küldése';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2030,9 +2030,18 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Szoba-szerver kezelés';
|
String get room_management => 'Szoba-szerver kezelés';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Adatok a repeaterről';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Szoba szerver információk';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Menedzsmentes eszközök';
|
String get repeater_managementTools => 'Menedzsmentes eszközök';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Vendégek számára elérhető eszközök';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Állapot';
|
String get repeater_status => 'Állapot';
|
||||||
|
|
||||||
@@ -2066,6 +2075,14 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Állítsa be a repeater paramétereket';
|
String get repeater_settingsSubtitle => 'Állítsa be a repeater paramétereket';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Óra szinkronizálás bejelentkezés után';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatikusan küldje el a \"óra szinkronizálás\" üzenetet a sikeres bejelentkezés után.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Adatkapcsolódás állapot';
|
String get repeater_statusTitle => 'Adatkapcsolódás állapot';
|
||||||
|
|
||||||
@@ -3677,7 +3694,7 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String scanner_linuxPairingPinPrompt(String deviceName) {
|
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
|
@override
|
||||||
|
|||||||
@@ -1240,7 +1240,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Nessun messaggio ancora';
|
String get chat_noMessages => 'Nessun messaggio ancora';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Invia messaggio';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2016,9 +2016,18 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Gestione del Server di Camera';
|
String get room_management => 'Gestione del Server di Camera';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informazioni sul ripetitore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informazioni sul server';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Strumenti di Gestione';
|
String get repeater_managementTools => 'Strumenti di Gestione';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Strumenti per gli ospiti';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Stato';
|
String get repeater_status => 'Stato';
|
||||||
|
|
||||||
@@ -2053,6 +2062,14 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get repeater_settingsSubtitle =>
|
String get repeater_settingsSubtitle =>
|
||||||
'Configura i parametri del ripetitore';
|
'Configura i parametri del ripetitore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Sincronizzazione dell\'orologio dopo il login';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Invia automaticamente il comando \"sincronizzazione dell\'orologio\" dopo un login riuscito.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Stato del Ripetitore';
|
String get repeater_statusTitle => 'Stato del Ripetitore';
|
||||||
|
|
||||||
|
|||||||
@@ -1180,7 +1180,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'まだメッセージは届いていません';
|
String get chat_noMessages => 'まだメッセージは届いていません';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'メッセージを送信する';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -1932,9 +1932,18 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'ルームサーバーの管理';
|
String get room_management => 'ルームサーバーの管理';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => '繰り返し送信に関する情報';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'ルームサーバーに関する情報';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => '管理ツール';
|
String get repeater_managementTools => '管理ツール';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'ゲスト向けツール';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'ステータス';
|
String get repeater_status => 'ステータス';
|
||||||
|
|
||||||
@@ -1965,6 +1974,13 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'リピーターのパラメータを設定する';
|
String get repeater_settingsSubtitle => 'リピーターのパラメータを設定する';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => 'ログイン後、時計の時刻を同期する';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'ログインが成功した場合、自動的に「時刻同期」を送信する。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => '再送ステータス';
|
String get repeater_statusTitle => '再送ステータス';
|
||||||
|
|
||||||
|
|||||||
@@ -1175,7 +1175,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get chat_noMessages => '아직 메시지가 없습니다.';
|
String get chat_noMessages => '아직 메시지가 없습니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => '메시지를 보내기';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -1929,9 +1929,18 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => '방 서버 관리';
|
String get room_management => '방 서버 관리';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => '반복 장비 정보';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => '서버 정보';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => '관리 도구';
|
String get repeater_managementTools => '관리 도구';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => '손님용 도구';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => '상태';
|
String get repeater_status => '상태';
|
||||||
|
|
||||||
@@ -1962,6 +1971,13 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => '리피터 파라미터 설정';
|
String get repeater_settingsSubtitle => '리피터 파라미터 설정';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => '로그인 후 시계 동기화';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'성공적인 로그인 후, 자동으로 \"시간 동기화\"를 전송합니다.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => '반복 장치 상태';
|
String get repeater_statusTitle => '반복 장치 상태';
|
||||||
|
|
||||||
|
|||||||
@@ -1228,7 +1228,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Nog geen berichten.';
|
String get chat_noMessages => 'Nog geen berichten.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Verzend bericht';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2003,9 +2003,18 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Beheer Server Kamer';
|
String get room_management => 'Beheer Server Kamer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informatie over herhalingsapparatuur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informatie over de server';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Beheerfuncties';
|
String get repeater_managementTools => 'Beheerfuncties';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Gastenfuncties';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2038,6 +2047,14 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Configureer repeaterparameters';
|
String get repeater_settingsSubtitle => 'Configureer repeaterparameters';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Na het inloggen, klok synchroniseren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatisch een \"klok synchroniseren\" bericht versturen na een succesvolle inlog.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Status repeater';
|
String get repeater_statusTitle => 'Status repeater';
|
||||||
|
|
||||||
|
|||||||
@@ -1248,7 +1248,7 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Brak jeszcze wiadomości';
|
String get chat_noMessages => 'Brak jeszcze wiadomości';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Wyślij wiadomość';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2031,9 +2031,18 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Zarządzanie Serwerem Pokoju';
|
String get room_management => 'Zarządzanie Serwerem Pokoju';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informacje dotyczące urządzenia powtarzającego';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informacje o serwerze';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Narzędzia Zarządzania';
|
String get repeater_managementTools => 'Narzędzia Zarządzania';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Narzędzia dla gości';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2066,6 +2075,14 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Skonfiguruj parametry przekaźnika';
|
String get repeater_settingsSubtitle => 'Skonfiguruj parametry przekaźnika';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Synchronizacja zegara po zalogowaniu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatycznie wysyłaj powiadomienie \"synchronizacja zegara\" po pomyślnym zalogowaniu.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Status przekaźnika';
|
String get repeater_statusTitle => 'Status przekaźnika';
|
||||||
|
|
||||||
|
|||||||
@@ -1239,7 +1239,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Ainda não existem mensagens.';
|
String get chat_noMessages => 'Ainda não existem mensagens.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Enviar mensagem';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2015,9 +2015,18 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Gerenciamento de Servidor de Sala';
|
String get room_management => 'Gerenciamento de Servidor de Sala';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informações sobre repetidores';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informações do Servidor';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Ferramentas de Gerenciamento';
|
String get repeater_managementTools => 'Ferramentas de Gerenciamento';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Ferramentas para hóspedes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2050,6 +2059,14 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Configurar parâmetros do repetidor';
|
String get repeater_settingsSubtitle => 'Configurar parâmetros do repetidor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Sincronização do relógio após o login';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Enviar automaticamente a sincronização do \"relógio\" após um login bem-sucedido.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Status do Repetidor';
|
String get repeater_statusTitle => 'Status do Repetidor';
|
||||||
|
|
||||||
|
|||||||
@@ -1239,7 +1239,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Сообщений пока нет';
|
String get chat_noMessages => 'Сообщений пока нет';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Отправить сообщение';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2019,9 +2019,18 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Управление сервером комнат';
|
String get room_management => 'Управление сервером комнат';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Информация о ретрансляторе';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Информация о сервере';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Инструменты управления';
|
String get repeater_managementTools => 'Инструменты управления';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Инструменты для гостей';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Статус';
|
String get repeater_status => 'Статус';
|
||||||
|
|
||||||
@@ -2054,6 +2063,14 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Настройка параметров репитера';
|
String get repeater_settingsSubtitle => 'Настройка параметров репитера';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Синхронизация часов после входа в систему';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Автоматически отправлять сообщение \"синхронизация времени\" после успешной авторизации.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Статус репитера';
|
String get repeater_statusTitle => 'Статус репитера';
|
||||||
|
|
||||||
|
|||||||
@@ -1227,7 +1227,7 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Zatiaľ žiadne správy.';
|
String get chat_noMessages => 'Zatiaľ žiadne správy.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Odoslať správu';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2004,9 +2004,18 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Správa servera miestnosti';
|
String get room_management => 'Správa servera miestnosti';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informácie o opakovači';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informácie o serveri';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Nástroje na správu';
|
String get repeater_managementTools => 'Nástroje na správu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Nástroje pre hostí';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2039,6 +2048,14 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Konfigurujte parametre opakovača';
|
String get repeater_settingsSubtitle => 'Konfigurujte parametre opakovača';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Synchronizácia hodiniek po prihlávení';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automaticky posielajte notifikáciu \"synchronizácia času\" po úspešnom prihládení.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Status opakého zboru';
|
String get repeater_statusTitle => 'Status opakého zboru';
|
||||||
|
|
||||||
|
|||||||
@@ -1225,7 +1225,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Še ni sporočil.';
|
String get chat_noMessages => 'Še ni sporočil.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Pošlji sporočilo';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2001,9 +2001,18 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Upravljanje stremlišča';
|
String get room_management => 'Upravljanje stremlišča';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Informacije o ponovljalniku';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Informacije o strežniku';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Upravne orodje';
|
String get repeater_managementTools => 'Upravne orodje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Naložila za goste';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2038,6 +2047,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
String get repeater_settingsSubtitle =>
|
String get repeater_settingsSubtitle =>
|
||||||
'Konfigurirajte parametre ponovitelja';
|
'Konfigurirajte parametre ponovitelja';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => 'Sinhronizacija ure po prijavi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Samodejno po uspešnem vstopu pošljite obvestilo o sinhronizaciji časa.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Status ponovitelja';
|
String get repeater_statusTitle => 'Status ponovitelja';
|
||||||
|
|
||||||
|
|||||||
@@ -1218,7 +1218,7 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Inga meddelanden ännu';
|
String get chat_noMessages => 'Inga meddelanden ännu';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Skicka meddelande';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -1990,9 +1990,18 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Rumserverhantering';
|
String get room_management => 'Rumserverhantering';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Information om repetorer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Information om servern';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Administrationsverktyg';
|
String get repeater_managementTools => 'Administrationsverktyg';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Gästverktyg';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Status';
|
String get repeater_status => 'Status';
|
||||||
|
|
||||||
@@ -2025,6 +2034,14 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Konfigurera återspolarparametrar';
|
String get repeater_settingsSubtitle => 'Konfigurera återspolarparametrar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin =>
|
||||||
|
'Synkronisera klockan efter inloggning';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Automatiskt skicka \"klocksynkronisering\" efter en lyckad inloggning.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Återspelsstatus';
|
String get repeater_statusTitle => 'Återspelsstatus';
|
||||||
|
|
||||||
|
|||||||
@@ -1231,7 +1231,7 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get chat_noMessages => 'Поки немає повідомлень.';
|
String get chat_noMessages => 'Поки немає повідомлень.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => 'Надіслати повідомлення';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -2014,9 +2014,18 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => 'Адміністрування сервера кімнати';
|
String get room_management => 'Адміністрування сервера кімнати';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => 'Інформація про ретранслятор';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => 'Інформація про сервер кімнати';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => 'Інструменти керування';
|
String get repeater_managementTools => 'Інструменти керування';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => 'Інструменти для гостей';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => 'Статус';
|
String get repeater_status => 'Статус';
|
||||||
|
|
||||||
@@ -2050,6 +2059,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => 'Налаштувати параметри ретранслятора';
|
String get repeater_settingsSubtitle => 'Налаштувати параметри ретранслятора';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => 'Синхронізація годин після входу';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle =>
|
||||||
|
'Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => 'Статус ретранслятора';
|
String get repeater_statusTitle => 'Статус ретранслятора';
|
||||||
|
|
||||||
|
|||||||
@@ -1162,7 +1162,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get chat_noMessages => '暂无消息';
|
String get chat_noMessages => '暂无消息';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chat_sendMessage => 'Send message';
|
String get chat_sendMessage => '发送消息';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String chat_sendMessageTo(String contactName) {
|
String chat_sendMessageTo(String contactName) {
|
||||||
@@ -1890,9 +1890,18 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get room_management => '房间服务器管理';
|
String get room_management => '房间服务器管理';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guest => '重复器信息';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get room_guest => '服务器信息';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_managementTools => '管理工具';
|
String get repeater_managementTools => '管理工具';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_guestTools => '访客工具';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_status => '状态';
|
String get repeater_status => '状态';
|
||||||
|
|
||||||
@@ -1923,6 +1932,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get repeater_settingsSubtitle => '配置转发节点参数';
|
String get repeater_settingsSubtitle => '配置转发节点参数';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLogin => '登录后,自动同步时钟';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get repeater_clockSyncAfterLoginSubtitle => '在成功登录后,自动发送“时钟同步”指令。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get repeater_statusTitle => '转发节点状态';
|
String get repeater_statusTitle => '转发节点状态';
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Voer PIN in voor {deviceName} (laat leeg als er geen is).",
|
"scanner_linuxPairingPinPrompt": "Voer PIN in voor {deviceName} (laat leeg als er geen is).",
|
||||||
"scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN",
|
"scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN",
|
||||||
"repeater_cliQuickDiscovery": "Ontdek Buren",
|
"repeater_cliQuickDiscovery": "Ontdek Buren",
|
||||||
"repeater_cliQuickClockSync": "Kloksynchronisatie"
|
"repeater_cliQuickClockSync": "Kloksynchronisatie",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatisch een \"klok synchroniseren\" bericht versturen na een succesvolle inlog.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Na het inloggen, klok synchroniseren",
|
||||||
|
"repeater_guestTools": "Gastenfuncties",
|
||||||
|
"room_guest": "Informatie over de server",
|
||||||
|
"chat_sendMessage": "Verzend bericht",
|
||||||
|
"repeater_guest": "Informatie over herhalingsapparatuur"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2099,5 +2099,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Wprowadź kod PIN dla {deviceName} (pozostaw puste, jeśli brak).",
|
"scanner_linuxPairingPinPrompt": "Wprowadź kod PIN dla {deviceName} (pozostaw puste, jeśli brak).",
|
||||||
"scanner_linuxPairingPinTitle": "Kod PIN parowania Bluetooth",
|
"scanner_linuxPairingPinTitle": "Kod PIN parowania Bluetooth",
|
||||||
"repeater_cliQuickClockSync": "Synchronizacja zegara",
|
"repeater_cliQuickClockSync": "Synchronizacja zegara",
|
||||||
"repeater_cliQuickDiscovery": "Odkryj Sąsiadów"
|
"repeater_cliQuickDiscovery": "Odkryj Sąsiadów",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Synchronizacja zegara po zalogowaniu",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatycznie wysyłaj powiadomienie \"synchronizacja zegara\" po pomyślnym zalogowaniu.",
|
||||||
|
"chat_sendMessage": "Wyślij wiadomość",
|
||||||
|
"repeater_guestTools": "Narzędzia dla gości",
|
||||||
|
"repeater_guest": "Informacje dotyczące urządzenia powtarzającego",
|
||||||
|
"room_guest": "Informacje o serwerze"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Insira o PIN para {deviceName} (deixe em branco se não houver).",
|
"scanner_linuxPairingPinPrompt": "Insira o PIN para {deviceName} (deixe em branco se não houver).",
|
||||||
"scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth",
|
"scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth",
|
||||||
"repeater_cliQuickClockSync": "Sincronização do Relógio",
|
"repeater_cliQuickClockSync": "Sincronização do Relógio",
|
||||||
"repeater_cliQuickDiscovery": "Descobrir Vizinhos"
|
"repeater_cliQuickDiscovery": "Descobrir Vizinhos",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Enviar automaticamente a sincronização do \"relógio\" após um login bem-sucedido.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronização do relógio após o login",
|
||||||
|
"room_guest": "Informações do Servidor",
|
||||||
|
"chat_sendMessage": "Enviar mensagem",
|
||||||
|
"repeater_guest": "Informações sobre repetidores",
|
||||||
|
"repeater_guestTools": "Ferramentas para hóspedes"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -1301,5 +1301,17 @@
|
|||||||
"scanner_linuxPairingHidePin": "Скрыть PIN",
|
"scanner_linuxPairingHidePin": "Скрыть PIN",
|
||||||
"scanner_linuxPairingPinTitle": "PIN‑код сопряжения Bluetooth",
|
"scanner_linuxPairingPinTitle": "PIN‑код сопряжения Bluetooth",
|
||||||
"repeater_cliQuickDiscovery": "Обнаружить Соседей",
|
"repeater_cliQuickDiscovery": "Обнаружить Соседей",
|
||||||
"repeater_cliQuickClockSync": "Синхронизация часов"
|
"repeater_cliQuickClockSync": "Синхронизация часов",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Синхронизация часов после входа в систему",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Автоматически отправлять сообщение \"синхронизация времени\" после успешной авторизации.",
|
||||||
|
"chat_sendMessage": "Отправить сообщение",
|
||||||
|
"repeater_guest": "Информация о ретрансляторе",
|
||||||
|
"room_guest": "Информация о сервере",
|
||||||
|
"repeater_guestTools": "Инструменты для гостей"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"translation_translationOptions": "Možnosti prekladania",
|
"translation_translationOptions": "Možnosti prekladania",
|
||||||
"translation_systemLanguage": "Jazyk systému",
|
"translation_systemLanguage": "Jazyk systému",
|
||||||
"repeater_cliQuickClockSync": "Synchronizácia hodin",
|
"repeater_cliQuickClockSync": "Synchronizácia hodin",
|
||||||
"repeater_cliQuickDiscovery": "Objaviť susedov"
|
"repeater_cliQuickDiscovery": "Objaviť susedov",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Synchronizácia hodiniek po prihlávení",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automaticky posielajte notifikáciu \"synchronizácia času\" po úspešnom prihládení.",
|
||||||
|
"chat_sendMessage": "Odoslať správu",
|
||||||
|
"repeater_guest": "Informácie o opakovači",
|
||||||
|
"room_guest": "Informácie o serveri",
|
||||||
|
"repeater_guestTools": "Nástroje pre hostí"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Vnesite PIN za {deviceName} (pustite prazno, če ga ni).",
|
"scanner_linuxPairingPinPrompt": "Vnesite PIN za {deviceName} (pustite prazno, če ga ni).",
|
||||||
"scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje",
|
"scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje",
|
||||||
"repeater_cliQuickDiscovery": "Odkrijte sosede",
|
"repeater_cliQuickDiscovery": "Odkrijte sosede",
|
||||||
"repeater_cliQuickClockSync": "Usklajevanje ure"
|
"repeater_cliQuickClockSync": "Usklajevanje ure",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Samodejno po uspešnem vstopu pošljite obvestilo o sinhronizaciji časa.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sinhronizacija ure po prijavi",
|
||||||
|
"repeater_guest": "Informacije o ponovljalniku",
|
||||||
|
"chat_sendMessage": "Pošlji sporočilo",
|
||||||
|
"room_guest": "Informacije o strežniku",
|
||||||
|
"repeater_guestTools": "Naložila za goste"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Ange PIN för {deviceName} (lämna tomt om ingen).",
|
"scanner_linuxPairingPinPrompt": "Ange PIN för {deviceName} (lämna tomt om ingen).",
|
||||||
"scanner_linuxPairingHidePin": "Dölj PIN",
|
"scanner_linuxPairingHidePin": "Dölj PIN",
|
||||||
"repeater_cliQuickDiscovery": "Upptäck grannar",
|
"repeater_cliQuickDiscovery": "Upptäck grannar",
|
||||||
"repeater_cliQuickClockSync": "Synkronisera klocka"
|
"repeater_cliQuickClockSync": "Synkronisera klocka",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatiskt skicka \"klocksynkronisering\" efter en lyckad inloggning.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Synkronisera klockan efter inloggning",
|
||||||
|
"repeater_guest": "Information om repetorer",
|
||||||
|
"chat_sendMessage": "Skicka meddelande",
|
||||||
|
"repeater_guestTools": "Gästverktyg",
|
||||||
|
"room_guest": "Information om servern"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2061,5 +2061,17 @@
|
|||||||
"scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).",
|
"scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).",
|
||||||
"scanner_linuxPairingHidePin": "Приховати PIN",
|
"scanner_linuxPairingHidePin": "Приховати PIN",
|
||||||
"repeater_cliQuickClockSync": "Синхронізація годинника",
|
"repeater_cliQuickClockSync": "Синхронізація годинника",
|
||||||
"repeater_cliQuickDiscovery": "Відкрити сусідів"
|
"repeater_cliQuickDiscovery": "Відкрити сусідів",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Синхронізація годин після входу",
|
||||||
|
"repeater_guestTools": "Інструменти для гостей",
|
||||||
|
"repeater_guest": "Інформація про ретранслятор",
|
||||||
|
"room_guest": "Інформація про сервер кімнати",
|
||||||
|
"chat_sendMessage": "Надіслати повідомлення"
|
||||||
|
}
|
||||||
|
|||||||
+14
-2
@@ -2066,5 +2066,17 @@
|
|||||||
"translation_translationOptions": "翻译选项",
|
"translation_translationOptions": "翻译选项",
|
||||||
"translation_systemLanguage": "系统语言",
|
"translation_systemLanguage": "系统语言",
|
||||||
"repeater_cliQuickDiscovery": "发现邻居",
|
"repeater_cliQuickDiscovery": "发现邻居",
|
||||||
"repeater_cliQuickClockSync": "同步时钟"
|
"repeater_cliQuickClockSync": "同步时钟",
|
||||||
}
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "登录后,自动同步时钟",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "在成功登录后,自动发送“时钟同步”指令。",
|
||||||
|
"repeater_guestTools": "访客工具",
|
||||||
|
"repeater_guest": "重复器信息",
|
||||||
|
"chat_sendMessage": "发送消息",
|
||||||
|
"room_guest": "服务器信息"
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../services/app_debug_log_service.dart';
|
import '../services/app_debug_log_service.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class AppDebugLogScreen extends StatelessWidget {
|
class AppDebugLogScreen extends StatelessWidget {
|
||||||
const AppDebugLogScreen({super.key});
|
const AppDebugLogScreen({super.key});
|
||||||
@@ -34,8 +35,9 @@ class AppDebugLogScreen extends StatelessWidget {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.debugLog_copied)),
|
context,
|
||||||
|
content: Text(context.l10n.debugLog_copied),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import '../services/app_settings_service.dart';
|
|||||||
import '../services/notification_service.dart';
|
import '../services/notification_service.dart';
|
||||||
import '../services/translation_service.dart';
|
import '../services/translation_service.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'map_cache_screen.dart';
|
import 'map_cache_screen.dart';
|
||||||
|
|
||||||
class AppSettingsScreen extends StatelessWidget {
|
class AppSettingsScreen extends StatelessWidget {
|
||||||
@@ -151,13 +152,12 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
.requestPermissions();
|
.requestPermissions();
|
||||||
if (!granted) {
|
if (!granted) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.appSettings_notificationPermissionDenied,
|
context.l10n.appSettings_notificationPermissionDenied,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -166,15 +166,14 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
|
|
||||||
await settingsService.setNotificationsEnabled(value);
|
await settingsService.setNotificationsEnabled(value);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
value
|
value
|
||||||
? context.l10n.appSettings_notificationsEnabled
|
? context.l10n.appSettings_notificationsEnabled
|
||||||
: context.l10n.appSettings_notificationsDisabled,
|
: context.l10n.appSettings_notificationsDisabled,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -301,15 +300,14 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
value: settingsService.settings.clearPathOnMaxRetry,
|
value: settingsService.settings.clearPathOnMaxRetry,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
settingsService.setClearPathOnMaxRetry(value);
|
settingsService.setClearPathOnMaxRetry(value);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
value
|
value
|
||||||
? context.l10n.appSettings_pathsWillBeCleared
|
? context.l10n.appSettings_pathsWillBeCleared
|
||||||
: context.l10n.appSettings_pathsWillNotBeCleared,
|
: context.l10n.appSettings_pathsWillNotBeCleared,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -329,15 +327,14 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
value: settingsService.settings.autoRouteRotationEnabled,
|
value: settingsService.settings.autoRouteRotationEnabled,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
settingsService.setAutoRouteRotationEnabled(value);
|
settingsService.setAutoRouteRotationEnabled(value);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
value
|
value
|
||||||
? context.l10n.appSettings_autoRouteRotationEnabled
|
? context.l10n.appSettings_autoRouteRotationEnabled
|
||||||
: context.l10n.appSettings_autoRouteRotationDisabled,
|
: context.l10n.appSettings_autoRouteRotationDisabled,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1065,25 +1062,25 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(context.l10n.appSettings_showNodesDiscoveredWithin),
|
Text(context.l10n.appSettings_showNodesDiscoveredWithin),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ListTile(
|
RadioListTile<double>(
|
||||||
title: Text(context.l10n.appSettings_allTime),
|
title: Text(context.l10n.appSettings_allTime),
|
||||||
leading: Radio<double>(value: 0),
|
value: 0,
|
||||||
),
|
),
|
||||||
ListTile(
|
RadioListTile<double>(
|
||||||
title: Text(context.l10n.appSettings_lastHour),
|
title: Text(context.l10n.appSettings_lastHour),
|
||||||
leading: Radio<double>(value: 1),
|
value: 1,
|
||||||
),
|
),
|
||||||
ListTile(
|
RadioListTile<double>(
|
||||||
title: Text(context.l10n.appSettings_last6Hours),
|
title: Text(context.l10n.appSettings_last6Hours),
|
||||||
leading: Radio<double>(value: 6),
|
value: 6,
|
||||||
),
|
),
|
||||||
ListTile(
|
RadioListTile<double>(
|
||||||
title: Text(context.l10n.appSettings_last24Hours),
|
title: Text(context.l10n.appSettings_last24Hours),
|
||||||
leading: Radio<double>(value: 24),
|
value: 24,
|
||||||
),
|
),
|
||||||
ListTile(
|
RadioListTile<double>(
|
||||||
title: Text(context.l10n.appSettings_lastWeek),
|
title: Text(context.l10n.appSettings_lastWeek),
|
||||||
leading: Radio<double>(value: 168),
|
value: 168,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1117,13 +1114,13 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
RadioListTile<UnitSystem>(
|
||||||
title: Text(context.l10n.appSettings_unitsMetric),
|
title: Text(context.l10n.appSettings_unitsMetric),
|
||||||
leading: const Radio<UnitSystem>(value: UnitSystem.metric),
|
value: UnitSystem.metric,
|
||||||
),
|
),
|
||||||
ListTile(
|
RadioListTile<UnitSystem>(
|
||||||
title: Text(context.l10n.appSettings_unitsImperial),
|
title: Text(context.l10n.appSettings_unitsImperial),
|
||||||
leading: const Radio<UnitSystem>(value: UnitSystem.imperial),
|
value: UnitSystem.imperial,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1164,8 +1161,9 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
String? id,
|
String? id,
|
||||||
}) async {
|
}) async {
|
||||||
if (sourceUrl.isEmpty) {
|
if (sourceUrl.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.translation_enterUrlFirst)),
|
context,
|
||||||
|
content: Text(context.l10n.translation_enterUrlFirst),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1176,22 +1174,23 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
id: id,
|
id: id,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.translation_modelDownloaded)),
|
context,
|
||||||
|
content: Text(context.l10n.translation_modelDownloaded),
|
||||||
);
|
);
|
||||||
await settingsService.setTranslationEnabled(true);
|
await settingsService.setTranslationEnabled(true);
|
||||||
} on TranslationDownloadCancelled {
|
} on TranslationDownloadCancelled {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.translation_downloadStopped)),
|
context,
|
||||||
|
content: Text(context.l10n.translation_downloadStopped),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.translation_downloadFailed(error.toString()),
|
context.l10n.translation_downloadFailed(error.toString()),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1236,16 +1235,16 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
try {
|
try {
|
||||||
await translationService.removeModel(model);
|
await translationService.removeModel(model);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
// TODO: l10n
|
// TODO: l10n
|
||||||
content: Text('Deleted ${translationModelFriendlyName(model)}.'),
|
content: Text('Deleted ${translationModelFriendlyName(model)}.'),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text('Delete failed: $error')),
|
context,
|
||||||
|
content: Text('Delete failed: $error'),
|
||||||
); // TODO: l10n
|
); // TODO: l10n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1279,15 +1278,14 @@ class AppSettingsScreen extends StatelessWidget {
|
|||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await settingsService.setAppDebugLogEnabled(value);
|
await settingsService.setAppDebugLogEnabled(value);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
value
|
value
|
||||||
? context.l10n.appSettings_appDebugLoggingEnabled
|
? context.l10n.appSettings_appDebugLoggingEnabled
|
||||||
: context.l10n.appSettings_appDebugLoggingDisabled,
|
: context.l10n.appSettings_appDebugLoggingDisabled,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../services/ble_debug_log_service.dart';
|
import '../services/ble_debug_log_service.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
enum _BleLogView { frames, rawLogRx }
|
enum _BleLogView { frames, rawLogRx }
|
||||||
|
|
||||||
@@ -52,10 +53,9 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.debugLog_bleCopied),
|
content: Text(context.l10n.debugLog_bleCopied),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import '../connector/meshcore_connector.dart';
|
|||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
import '../helpers/chat_scroll_controller.dart';
|
import '../helpers/chat_scroll_controller.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
import '../helpers/gif_helper.dart';
|
||||||
import '../helpers/reaction_helper.dart';
|
import '../helpers/reaction_helper.dart';
|
||||||
import '../helpers/utf8_length_limiter.dart';
|
import '../helpers/utf8_length_limiter.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../models/channel.dart';
|
import '../models/channel.dart';
|
||||||
import '../models/channel_message.dart';
|
import '../models/channel_message.dart';
|
||||||
@@ -143,11 +145,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
Future<void> _scrollToMessage(String messageId) async {
|
Future<void> _scrollToMessage(String messageId) async {
|
||||||
final key = _messageKeys[messageId];
|
final key = _messageKeys[messageId];
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_originalMessageNotFound),
|
content: Text(context.l10n.chat_originalMessageNotFound),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -355,7 +356,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final settingsService = context.watch<AppSettingsService>();
|
final settingsService = context.watch<AppSettingsService>();
|
||||||
final enableTracing = settingsService.settings.enableMessageTracing;
|
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||||
final isOutgoing = message.isOutgoing;
|
final isOutgoing = message.isOutgoing;
|
||||||
final gifId = _parseGifId(message.text);
|
final gifId = GifHelper.parseGif(message.text);
|
||||||
final poi = _parsePoiMessage(message.text);
|
final poi = _parsePoiMessage(message.text);
|
||||||
final translatedDisplayText =
|
final translatedDisplayText =
|
||||||
message.translatedText != null &&
|
message.translatedText != null &&
|
||||||
@@ -699,7 +700,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
||||||
|
|
||||||
final gifId = _parseGifId(replyText);
|
final gifId = GifHelper.parseGif(replyText);
|
||||||
final poi = _parsePoiMessage(replyText);
|
final poi = _parsePoiMessage(replyText);
|
||||||
|
|
||||||
Widget contentPreview;
|
Widget contentPreview;
|
||||||
@@ -811,12 +812,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _parseGifId(String text) {
|
|
||||||
final trimmed = text.trim();
|
|
||||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
|
||||||
return match?.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_PoiInfo? _parsePoiMessage(String text) {
|
_PoiInfo? _parsePoiMessage(String text) {
|
||||||
final trimmed = text.trim();
|
final trimmed = text.trim();
|
||||||
final match = RegExp(
|
final match = RegExp(
|
||||||
@@ -897,7 +892,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => GifPicker(
|
builder: (context) => GifPicker(
|
||||||
onGifSelected: (gifId) {
|
onGifSelected: (gifId) {
|
||||||
_textController.text = 'g:$gifId';
|
_textController.text = GifHelper.encodeGif(gifId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1053,7 +1048,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
child: ValueListenableBuilder<TextEditingValue>(
|
child: ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: _textController,
|
valueListenable: _textController,
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
final gifId = _parseGifId(value.text);
|
final gifId = GifHelper.parseGif(value.text);
|
||||||
if (gifId != null) {
|
if (gifId != null) {
|
||||||
return Focus(
|
return Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -1156,9 +1151,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastChannelSendAt != null &&
|
if (_lastChannelSendAt != null &&
|
||||||
now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) {
|
now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown)));
|
content: Text(context.l10n.chat_sendCooldown),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_lastChannelSendAt = now;
|
_lastChannelSendAt = now;
|
||||||
@@ -1200,8 +1196,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
|
|
||||||
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
||||||
if (utf8.encode(messageText).length > maxBytes) {
|
if (utf8.encode(messageText).length > maxBytes) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))),
|
context,
|
||||||
|
content: Text(context.l10n.chat_messageTooLong(maxBytes)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1322,23 +1319,25 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
message.senderName,
|
message.senderName,
|
||||||
message.text,
|
message.text,
|
||||||
);
|
);
|
||||||
final reactionText = 'r:$hash:$emojiIndex';
|
final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex);
|
||||||
connector.sendChannelMessage(widget.channel, reactionText);
|
connector.sendChannelMessage(widget.channel, reactionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _copyMessageText(String text) {
|
void _copyMessageText(String text) {
|
||||||
Clipboard.setData(ClipboardData(text: text));
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageCopied)));
|
content: Text(context.l10n.chat_messageCopied),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteMessage(ChannelMessage message) async {
|
Future<void> _deleteMessage(ChannelMessage message) async {
|
||||||
await context.read<MeshCoreConnector>().deleteChannelMessage(message);
|
await context.read<MeshCoreConnector>().deleteChannelMessage(message);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageDeleted)));
|
content: Text(context.l10n.chat_messageDeleted),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatPathPrefixes(Uint8List pathBytes) {
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import '../widgets/empty_state.dart';
|
|||||||
import '../widgets/qr_code_display.dart';
|
import '../widgets/qr_code_display.dart';
|
||||||
import '../widgets/quick_switch_bar.dart';
|
import '../widgets/quick_switch_bar.dart';
|
||||||
import '../widgets/unread_badge.dart';
|
import '../widgets/unread_badge.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'channel_chat_screen.dart';
|
import 'channel_chat_screen.dart';
|
||||||
import 'community_qr_scanner_screen.dart';
|
import 'community_qr_scanner_screen.dart';
|
||||||
import 'contacts_screen.dart';
|
import 'contacts_screen.dart';
|
||||||
@@ -809,15 +810,12 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final name = nameController.text.trim();
|
final name = nameController.text.trim();
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
context,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext
|
||||||
content: Text(
|
.l10n
|
||||||
dialogContext
|
.channels_enterChannelName,
|
||||||
.l10n
|
|
||||||
.channels_enterChannelName,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -837,13 +835,10 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
nextIndex,
|
nextIndex,
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_channelAdded(
|
context.l10n.channels_channelAdded(name),
|
||||||
name,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -897,15 +892,12 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
final name = nameController.text.trim();
|
final name = nameController.text.trim();
|
||||||
final pskHex = pskController.text.trim();
|
final pskHex = pskController.text.trim();
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
context,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext
|
||||||
content: Text(
|
.l10n
|
||||||
dialogContext
|
.channels_enterChannelName,
|
||||||
.l10n
|
|
||||||
.channels_enterChannelName,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -914,15 +906,12 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
try {
|
try {
|
||||||
psk = Channel.parsePskHex(pskHex);
|
psk = Channel.parsePskHex(pskHex);
|
||||||
} on FormatException {
|
} on FormatException {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
context,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext
|
||||||
content: Text(
|
.l10n
|
||||||
dialogContext
|
.channels_pskMustBe32Hex,
|
||||||
.l10n
|
|
||||||
.channels_pskMustBe32Hex,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -930,13 +919,10 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
connector.setChannel(nextIndex, name, psk);
|
connector.setChannel(nextIndex, name, psk);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_channelAdded(
|
context.l10n.channels_channelAdded(name),
|
||||||
name,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -967,11 +953,10 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
connector.setChannel(nextIndex, 'Public', psk);
|
connector.setChannel(nextIndex, 'Public', psk);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_publicChannelAdded,
|
context.l10n.channels_publicChannelAdded,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1097,15 +1082,12 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var hashtag = hashtagController.text.trim();
|
var hashtag = hashtagController.text.trim();
|
||||||
if (hashtag.isEmpty) {
|
if (hashtag.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
context,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext
|
||||||
content: Text(
|
.l10n
|
||||||
dialogContext
|
.channels_enterChannelName,
|
||||||
.l10n
|
|
||||||
.channels_enterChannelName,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -1125,15 +1107,12 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
} else {
|
} else {
|
||||||
// Community hashtag - HMAC derivation from community secret
|
// Community hashtag - HMAC derivation from community secret
|
||||||
if (selectedCommunity == null) {
|
if (selectedCommunity == null) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
dialogContext,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext
|
||||||
content: Text(
|
.l10n
|
||||||
dialogContext
|
.community_selectCommunity,
|
||||||
.l10n
|
|
||||||
.community_selectCommunity,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -1159,12 +1138,11 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
psk,
|
psk,
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_channelAdded(
|
context.l10n.channels_channelAdded(
|
||||||
channelName,
|
channelName,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1259,13 +1237,10 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final name = nameController.text.trim();
|
final name = nameController.text.trim();
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
dialogContext,
|
context,
|
||||||
).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
dialogContext.l10n.community_enterName,
|
||||||
content: Text(
|
|
||||||
dialogContext.l10n.community_enterName,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -1301,11 +1276,10 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
_loadCommunities();
|
_loadCommunities();
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.community_created(name),
|
context.l10n.community_created(name),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1494,10 +1468,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
try {
|
try {
|
||||||
psk = Channel.parsePskHex(pskHex);
|
psk = Channel.parsePskHex(pskHex);
|
||||||
} on FormatException {
|
} on FormatException {
|
||||||
ScaffoldMessenger.of(dialogContext).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
dialogContext,
|
||||||
content: Text(dialogContext.l10n.channels_pskMustBe32Hex),
|
content: Text(dialogContext.l10n.channels_pskMustBe32Hex),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1510,16 +1483,16 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
smazEnabled,
|
smazEnabled,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.channels_channelUpdated(name)),
|
content: Text(context.l10n.channels_channelUpdated(name)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
debugPrint(st.toString());
|
debugPrint(st.toString());
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text('Failed to update channel: $e')),
|
context,
|
||||||
|
content: Text('Failed to update channel: $e'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1559,21 +1532,19 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_channelDeleted(channel.name),
|
context.l10n.channels_channelDeleted(channel.name),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.channels_channelDeleteFailed(channel.name),
|
context.l10n.channels_channelDeleteFailed(channel.name),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1594,8 +1565,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
void _addPublicChannel(BuildContext context, MeshCoreConnector connector) {
|
void _addPublicChannel(BuildContext context, MeshCoreConnector connector) {
|
||||||
final psk = Channel.parsePskHex(Channel.publicChannelPsk);
|
final psk = Channel.parsePskHex(Channel.publicChannelPsk);
|
||||||
connector.setChannel(0, 'Public', psk);
|
connector.setChannel(0, 'Public', psk);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.channels_publicChannelAdded)),
|
context,
|
||||||
|
content: Text(context.l10n.channels_publicChannelAdded),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1810,12 +1782,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
_loadCommunities();
|
_loadCommunities();
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(context.l10n.community_deleted(community.name)),
|
||||||
context.l10n.community_deleted(community.name),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import '../connector/meshcore_protocol.dart';
|
|||||||
import '../helpers/reaction_helper.dart';
|
import '../helpers/reaction_helper.dart';
|
||||||
import '../widgets/message_status_icon.dart';
|
import '../widgets/message_status_icon.dart';
|
||||||
import '../helpers/chat_scroll_controller.dart';
|
import '../helpers/chat_scroll_controller.dart';
|
||||||
|
import '../helpers/gif_helper.dart';
|
||||||
import '../helpers/path_helper.dart';
|
import '../helpers/path_helper.dart';
|
||||||
import '../helpers/utf8_length_limiter.dart';
|
import '../helpers/utf8_length_limiter.dart';
|
||||||
import '../models/channel_message.dart';
|
import '../models/channel_message.dart';
|
||||||
@@ -42,6 +43,7 @@ import '../widgets/radio_stats_entry.dart';
|
|||||||
import '../widgets/translated_message_content.dart';
|
import '../widgets/translated_message_content.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'telemetry_screen.dart';
|
import 'telemetry_screen.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
@@ -523,7 +525,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
child: ValueListenableBuilder<TextEditingValue>(
|
child: ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: _textController,
|
valueListenable: _textController,
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
final gifId = _parseGifId(value.text);
|
final gifId = GifHelper.parseGif(value.text);
|
||||||
if (gifId != null) {
|
if (gifId != null) {
|
||||||
return Focus(
|
return Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -601,19 +603,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _parseGifId(String text) {
|
|
||||||
final trimmed = text.trim();
|
|
||||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
|
||||||
return match?.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showGifPicker(BuildContext context) {
|
void _showGifPicker(BuildContext context) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => GifPicker(
|
builder: (context) => GifPicker(
|
||||||
onGifSelected: (gifId) {
|
onGifSelected: (gifId) {
|
||||||
_textController.text = 'g:$gifId';
|
_textController.text = GifHelper.encodeGif(gifId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -638,9 +634,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastTextSendAt != null &&
|
if (_lastTextSendAt != null &&
|
||||||
now.difference(_lastTextSendAt!) < const Duration(seconds: 1)) {
|
now.difference(_lastTextSendAt!) < const Duration(seconds: 1)) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown)));
|
content: Text(context.l10n.chat_sendCooldown),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_lastTextSendAt = now;
|
_lastTextSendAt = now;
|
||||||
@@ -676,8 +673,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
}
|
}
|
||||||
final maxBytes = maxContactMessageBytes();
|
final maxBytes = maxContactMessageBytes();
|
||||||
if (utf8.encode(outgoingText).length > maxBytes) {
|
if (utf8.encode(outgoingText).length > maxBytes) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))),
|
context,
|
||||||
|
content: Text(context.l10n.chat_messageTooLong(maxBytes)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -865,15 +863,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
_showFullPathDialog(context, path.pathBytes),
|
_showFullPathDialog(context, path.pathBytes),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (path.pathBytes.isEmpty) {
|
if (path.pathBytes.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context
|
context.l10n.chat_pathDetailsNotAvailable,
|
||||||
.l10n
|
|
||||||
.chat_pathDetailsNotAvailable,
|
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -957,11 +952,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
_resolveContact(connector),
|
_resolveContact(connector),
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_pathCleared),
|
content: Text(context.l10n.chat_pathCleared),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
@@ -987,11 +981,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
pathLen: -1,
|
pathLen: -1,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_floodModeEnabled),
|
content: Text(context.l10n.chat_floodModeEnabled),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
@@ -1025,11 +1018,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
void _showFullPathDialog(BuildContext context, List<int> pathBytes) {
|
void _showFullPathDialog(BuildContext context, List<int> pathBytes) {
|
||||||
if (pathBytes.isEmpty) {
|
if (pathBytes.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_pathDetailsNotAvailable),
|
content: Text(context.l10n.chat_pathDetailsNotAvailable),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1142,11 +1134,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
: (verified
|
: (verified
|
||||||
? context.l10n.chat_pathDeviceConfirmed
|
? context.l10n.chat_pathDeviceConfirmed
|
||||||
: context.l10n.chat_pathDeviceNotConfirmed);
|
: context.l10n.chat_pathDeviceNotConfirmed);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_pathSetHops(hopCount, status)),
|
content: Text(context.l10n.chat_pathSetHops(hopCount, status)),
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1495,26 +1486,29 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
void _copyMessageText(String text) {
|
void _copyMessageText(String text) {
|
||||||
Clipboard.setData(ClipboardData(text: text));
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageCopied)));
|
content: Text(context.l10n.chat_messageCopied),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteMessage(Message message) async {
|
Future<void> _deleteMessage(Message message) async {
|
||||||
await context.read<MeshCoreConnector>().deleteMessage(message);
|
await context.read<MeshCoreConnector>().deleteMessage(message);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageDeleted)));
|
content: Text(context.l10n.chat_messageDeleted),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _retryMessage(Message message) {
|
void _retryMessage(Message message) {
|
||||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||||
// Retry using the contact's current path override setting
|
// Retry using the contact's current path override setting
|
||||||
connector.sendMessage(_resolveContact(connector), message.text);
|
connector.sendMessage(_resolveContact(connector), message.text);
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_retryingMessage)));
|
content: Text(context.l10n.chat_retryingMessage),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showEmojiPicker(Message message, Contact senderContact) {
|
void _showEmojiPicker(Message message, Contact senderContact) {
|
||||||
@@ -1546,7 +1540,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
senderName,
|
senderName,
|
||||||
message.text,
|
message.text,
|
||||||
);
|
);
|
||||||
final reactionText = 'r:$hash:$emojiIndex';
|
final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex);
|
||||||
connector.sendMessage(_resolveContact(connector), reactionText);
|
connector.sendMessage(_resolveContact(connector), reactionText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1576,7 +1570,7 @@ class _MessageBubble extends StatelessWidget {
|
|||||||
final enableTracing = settingsService.settings.enableMessageTracing;
|
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||||
final isOutgoing = message.isOutgoing;
|
final isOutgoing = message.isOutgoing;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final gifId = _parseGifId(message.text);
|
final gifId = GifHelper.parseGif(message.text);
|
||||||
final poi = _parsePoiMessage(message.text);
|
final poi = _parsePoiMessage(message.text);
|
||||||
final isFailed = message.status == MessageStatus.failed;
|
final isFailed = message.status == MessageStatus.failed;
|
||||||
final bubbleColor = isFailed
|
final bubbleColor = isFailed
|
||||||
@@ -1850,12 +1844,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) {
|
_PoiInfo? _parsePoiMessage(String text) {
|
||||||
final trimmed = text.trim();
|
final trimmed = text.trim();
|
||||||
final match = RegExp(
|
final match = RegExp(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../models/community.dart';
|
|||||||
import '../storage/community_store.dart';
|
import '../storage/community_store.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
import '../widgets/qr_scanner_widget.dart';
|
import '../widgets/qr_scanner_widget.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
/// Screen for scanning community QR codes to join communities.
|
/// Screen for scanning community QR codes to join communities.
|
||||||
///
|
///
|
||||||
@@ -76,11 +77,10 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_invalidQrCode),
|
content: Text(context.l10n.community_invalidQrCode),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -93,12 +93,11 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showInvalidQrError(BuildContext context) {
|
void _showInvalidQrError(BuildContext context) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_invalidQrCode),
|
content: Text(context.l10n.community_invalidQrCode),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,11 +228,10 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_joined(community.name)),
|
content: Text(context.l10n.community_joined(community.name)),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return to previous screen
|
// Return to previous screen
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import '../widgets/quick_switch_bar.dart';
|
|||||||
import '../widgets/repeater_login_dialog.dart';
|
import '../widgets/repeater_login_dialog.dart';
|
||||||
import '../widgets/room_login_dialog.dart';
|
import '../widgets/room_login_dialog.dart';
|
||||||
import '../widgets/unread_badge.dart';
|
import '../widgets/unread_badge.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'channels_screen.dart';
|
import 'channels_screen.dart';
|
||||||
import 'chat_screen.dart';
|
import 'chat_screen.dart';
|
||||||
import 'discovery_screen.dart';
|
import 'discovery_screen.dart';
|
||||||
@@ -150,9 +151,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showGroupsUnavailableMessage(BuildContext context) {
|
void _showGroupsUnavailableMessage(BuildContext context) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.common_loading)));
|
content: Text(context.l10n.common_loading),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setupFrameListener() {
|
void _setupFrameListener() {
|
||||||
@@ -169,10 +171,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
// Validate packet has expected minimum size (98+ bytes per protocol)
|
// Validate packet has expected minimum size (98+ bytes per protocol)
|
||||||
if (advertPacket.length < 98) {
|
if (advertPacket.length < 98) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_invalidAdvertFormat),
|
content: Text(context.l10n.contacts_invalidAdvertFormat),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_pendingOperations.remove(ContactOperationType.export);
|
_pendingOperations.remove(ContactOperationType.export);
|
||||||
@@ -187,24 +188,23 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (_pendingOperations.contains(ContactOperationType.import)) {
|
if (_pendingOperations.contains(ContactOperationType.import)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.contacts_contactImported)),
|
context,
|
||||||
|
content: Text(context.l10n.contacts_contactImported),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_zeroHopContactAdvertSent),
|
content: Text(context.l10n.contacts_zeroHopContactAdvertSent),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pendingOperations.contains(ContactOperationType.export)) {
|
if (_pendingOperations.contains(ContactOperationType.export)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_contactAdvertCopied),
|
content: Text(context.l10n.contacts_contactAdvertCopied),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,25 +216,22 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (_pendingOperations.contains(ContactOperationType.import)) {
|
if (_pendingOperations.contains(ContactOperationType.import)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_contactImportFailed),
|
content: Text(context.l10n.contacts_contactImportFailed),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_zeroHopContactAdvertFailed),
|
content: Text(context.l10n.contacts_zeroHopContactAdvertFailed),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_pendingOperations.contains(ContactOperationType.export)) {
|
if (_pendingOperations.contains(ContactOperationType.export)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_contactAdvertCopyFailed),
|
content: Text(context.l10n.contacts_contactAdvertCopyFailed),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +268,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
final clipboardData = await Clipboard.getData('text/plain');
|
final clipboardData = await Clipboard.getData('text/plain');
|
||||||
if (clipboardData == null || clipboardData.text == null) {
|
if (clipboardData == null || clipboardData.text == null) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)),
|
context,
|
||||||
|
content: Text(context.l10n.contacts_clipboardEmpty),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -280,8 +278,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
final text = clipboardData.text!.trim();
|
final text = clipboardData.text!.trim();
|
||||||
if (!text.startsWith('meshcore://')) {
|
if (!text.startsWith('meshcore://')) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
|
context,
|
||||||
|
content: Text(context.l10n.contacts_invalidAdvertFormat),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -294,8 +293,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
connector.importContact(importContactFrame);
|
connector.importContact(importContactFrame);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
|
context,
|
||||||
|
content: Text(context.l10n.contacts_invalidAdvertFormat),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,10 +330,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
),
|
),
|
||||||
onTap: () => {
|
onTap: () => {
|
||||||
connector.sendSelfAdvert(flood: false),
|
connector.sendSelfAdvert(flood: false),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.settings_advertisementSent),
|
content: Text(context.l10n.settings_advertisementSent),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -347,10 +346,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
),
|
),
|
||||||
onTap: () => {
|
onTap: () => {
|
||||||
connector.sendSelfAdvert(flood: true),
|
connector.sendSelfAdvert(flood: true),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.settings_advertisementSent),
|
content: Text(context.l10n.settings_advertisementSent),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -963,13 +961,16 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RepeaterLoginDialog(
|
builder: (context) => RepeaterLoginDialog(
|
||||||
repeater: repeater,
|
repeater: repeater,
|
||||||
onLogin: (password) {
|
onLogin: (password, isAdmin) {
|
||||||
// Navigate to repeater hub screen after successful login
|
// Navigate to repeater hub screen after successful login
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => RepeaterHubScreen(
|
||||||
RepeaterHubScreen(repeater: repeater, password: password),
|
repeater: repeater,
|
||||||
|
password: password,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -986,14 +987,18 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RoomLoginDialog(
|
builder: (context) => RoomLoginDialog(
|
||||||
room: room,
|
room: room,
|
||||||
onLogin: (password) {
|
onLogin: (password, isAdmin) {
|
||||||
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
destination == RoomLoginDestination.management
|
destination == RoomLoginDestination.management
|
||||||
? RepeaterHubScreen(repeater: room, password: password)
|
? RepeaterHubScreen(
|
||||||
|
repeater: room,
|
||||||
|
password: password,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
)
|
||||||
: ChatScreen(contact: room),
|
: ChatScreen(contact: room),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1146,19 +1151,17 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final name = nameController.text.trim();
|
final name = nameController.text.trim();
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_groupNameRequired),
|
content: Text(context.l10n.contacts_groupNameRequired),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (name.toLowerCase() ==
|
if (name.toLowerCase() ==
|
||||||
contactsAllGroupsValue.toLowerCase()) {
|
contactsAllGroupsValue.toLowerCase()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.contacts_groupNameReserved),
|
content: Text(context.l10n.contacts_groupNameReserved),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1167,11 +1170,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
return g.name.toLowerCase() == name.toLowerCase();
|
return g.name.toLowerCase() == name.toLowerCase();
|
||||||
});
|
});
|
||||||
if (exists) {
|
if (exists) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.contacts_groupAlreadyExists(name),
|
context.l10n.contacts_groupAlreadyExists(name),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import '../utils/contact_search.dart';
|
|||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
import '../widgets/app_bar.dart';
|
import '../widgets/app_bar.dart';
|
||||||
import '../widgets/list_filter_widget.dart';
|
import '../widgets/list_filter_widget.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
enum DiscoverySortOption { lastSeen, name, type }
|
enum DiscoverySortOption { lastSeen, name, type }
|
||||||
|
|
||||||
@@ -234,8 +235,9 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
|||||||
final hexString = pubKeyToHex(contact.rawPacket!);
|
final hexString = pubKeyToHex(contact.rawPacket!);
|
||||||
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
|
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
|
context,
|
||||||
|
content: Text(context.l10n.contacts_contactAdvertCopied),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'delete_contact':
|
case 'delete_contact':
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../services/app_settings_service.dart';
|
import '../services/app_settings_service.dart';
|
||||||
import '../services/map_tile_cache_service.dart';
|
import '../services/map_tile_cache_service.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class MapCacheScreen extends StatefulWidget {
|
class MapCacheScreen extends StatefulWidget {
|
||||||
const MapCacheScreen({super.key});
|
const MapCacheScreen({super.key});
|
||||||
@@ -112,15 +113,17 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
|||||||
Future<void> _startDownload() async {
|
Future<void> _startDownload() async {
|
||||||
final bounds = _selectedBounds;
|
final bounds = _selectedBounds;
|
||||||
if (bounds == null) {
|
if (bounds == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.mapCache_selectAreaFirst)),
|
context,
|
||||||
|
content: Text(context.l10n.mapCache_selectAreaFirst),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_estimatedTiles == 0) {
|
if (_estimatedTiles == 0) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.mapCache_noTilesToDownload)),
|
context,
|
||||||
|
content: Text(context.l10n.mapCache_noTilesToDownload),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -182,9 +185,7 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
|||||||
result.failed,
|
result.failed,
|
||||||
)
|
)
|
||||||
: context.l10n.mapCache_cachedTiles(result.downloaded);
|
: context.l10n.mapCache_cachedTiles(result.downloaded);
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(context, content: Text(message));
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(message)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _clearCache() async {
|
Future<void> _clearCache() async {
|
||||||
@@ -210,8 +211,9 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
|||||||
final cacheService = context.read<MapTileCacheService>();
|
final cacheService = context.read<MapTileCacheService>();
|
||||||
await cacheService.clearCache();
|
await cacheService.clearCache();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.mapCache_offlineCacheCleared)),
|
context,
|
||||||
|
content: Text(context.l10n.mapCache_offlineCacheCleared),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import 'chat_screen.dart';
|
|||||||
import 'contacts_screen.dart';
|
import 'contacts_screen.dart';
|
||||||
import '../widgets/repeater_login_dialog.dart';
|
import '../widgets/repeater_login_dialog.dart';
|
||||||
import '../widgets/room_login_dialog.dart';
|
import '../widgets/room_login_dialog.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'repeater_hub_screen.dart';
|
import 'repeater_hub_screen.dart';
|
||||||
import 'settings_screen.dart';
|
import 'settings_screen.dart';
|
||||||
import 'line_of_sight_map_screen.dart';
|
import 'line_of_sight_map_screen.dart';
|
||||||
@@ -1366,13 +1367,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RepeaterLoginDialog(
|
builder: (context) => RepeaterLoginDialog(
|
||||||
repeater: repeater,
|
repeater: repeater,
|
||||||
onLogin: (password) {
|
onLogin: (password, isAdmin) {
|
||||||
// Navigate to repeater hub screen after successful login
|
// Navigate to repeater hub screen after successful login
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => RepeaterHubScreen(
|
||||||
RepeaterHubScreen(repeater: repeater, password: password),
|
repeater: repeater,
|
||||||
|
password: password,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1385,7 +1389,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RoomLoginDialog(
|
builder: (context) => RoomLoginDialog(
|
||||||
room: room,
|
room: room,
|
||||||
onLogin: (password) {
|
// onLogin(password, isAdmin) isAdmin not used for room caht screen
|
||||||
|
onLogin: (password, _) {
|
||||||
// Navigate to chat screen after successful login
|
// Navigate to chat screen after successful login
|
||||||
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -1659,7 +1664,10 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
);
|
);
|
||||||
await connector.refreshDeviceInfo();
|
await connector.refreshDeviceInfo();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
messenger.showSnackBar(SnackBar(content: Text(successMsg)));
|
showDismissibleSnackBar(
|
||||||
|
messenger.context,
|
||||||
|
content: Text(successMsg),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -1681,8 +1689,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
required String flags,
|
required String flags,
|
||||||
}) async {
|
}) async {
|
||||||
if (!connector.isConnected) {
|
if (!connector.isConnected) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.map_connectToShareMarkers)),
|
context,
|
||||||
|
content: Text(context.l10n.map_connectToShareMarkers),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2271,8 +2280,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
_points.clear();
|
_points.clear();
|
||||||
_polylines.clear();
|
_polylines.clear();
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.map_pathTraceCancelled)),
|
context,
|
||||||
|
content: Text(l10n.map_pathTraceCancelled),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tooltip: l10n.common_cancel,
|
tooltip: l10n.common_cancel,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../connector/meshcore_protocol.dart';
|
|||||||
import '../services/repeater_command_service.dart';
|
import '../services/repeater_command_service.dart';
|
||||||
import '../widgets/path_management_dialog.dart';
|
import '../widgets/path_management_dialog.dart';
|
||||||
import '../widgets/snr_indicator.dart';
|
import '../widgets/snr_indicator.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class NeighborsScreen extends StatefulWidget {
|
class NeighborsScreen extends StatefulWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
@@ -163,11 +164,10 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
|||||||
_neighborCount = neighborCount;
|
_neighborCount = neighborCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.neighbors_receivedData),
|
content: Text(context.l10n.neighbors_receivedData),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_statusTimeout?.cancel();
|
_statusTimeout?.cancel();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -224,11 +224,10 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.neighbors_requestTimedOut),
|
content: Text(context.l10n.neighbors_requestTimedOut),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_recordStatusResult(false);
|
_recordStatusResult(false);
|
||||||
});
|
});
|
||||||
@@ -239,11 +238,10 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
|||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
|
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '../connector/meshcore_protocol.dart';
|
|||||||
import '../widgets/debug_frame_viewer.dart';
|
import '../widgets/debug_frame_viewer.dart';
|
||||||
import '../services/repeater_command_service.dart';
|
import '../services/repeater_command_service.dart';
|
||||||
import '../widgets/path_management_dialog.dart';
|
import '../widgets/path_management_dialog.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class RepeaterCliScreen extends StatefulWidget {
|
class RepeaterCliScreen extends StatefulWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
@@ -336,8 +337,9 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
|||||||
if (_commandController.text.trim().isNotEmpty) {
|
if (_commandController.text.trim().isNotEmpty) {
|
||||||
_sendCommand(showDebug: true);
|
_sendCommand(showDebug: true);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.repeater_enterCommandFirst)),
|
context,
|
||||||
|
content: Text(l10n.repeater_enterCommandFirst),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ import 'neighbors_screen.dart';
|
|||||||
class RepeaterHubScreen extends StatelessWidget {
|
class RepeaterHubScreen extends StatelessWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
final String password;
|
final String password;
|
||||||
|
final bool isAdmin;
|
||||||
|
|
||||||
const RepeaterHubScreen({
|
const RepeaterHubScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.repeater,
|
required this.repeater,
|
||||||
required this.password,
|
required this.password,
|
||||||
|
required this.isAdmin,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,11 +35,18 @@ class RepeaterHubScreen extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
if (isAdmin)
|
||||||
repeater.type == advTypeRepeater
|
Text(
|
||||||
? l10n.repeater_management
|
repeater.type == advTypeRepeater
|
||||||
: l10n.room_management,
|
? l10n.repeater_management
|
||||||
),
|
: l10n.room_management,
|
||||||
|
),
|
||||||
|
if (!isAdmin)
|
||||||
|
Text(
|
||||||
|
repeater.type == advTypeRepeater
|
||||||
|
? l10n.repeater_guest
|
||||||
|
: l10n.room_guest,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
repeater.name,
|
repeater.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -113,64 +122,67 @@ class RepeaterHubScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Card(
|
if (isAdmin)
|
||||||
child: Padding(
|
Card(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
const Icon(Icons.battery_full),
|
children: [
|
||||||
const SizedBox(width: 10),
|
const Icon(Icons.battery_full),
|
||||||
Expanded(
|
const SizedBox(width: 10),
|
||||||
child: Text(
|
Expanded(
|
||||||
l10n.appSettings_batteryChemistry,
|
child: Text(
|
||||||
style: const TextStyle(
|
l10n.appSettings_batteryChemistry,
|
||||||
fontSize: 16,
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
initialValue: chemistry,
|
|
||||||
isExpanded: true,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: UnderlineInputBorder(),
|
|
||||||
isDense: true,
|
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
const SizedBox(height: 12),
|
||||||
if (value == null) return;
|
DropdownButtonFormField<String>(
|
||||||
settingsService.setBatteryChemistryForRepeater(
|
initialValue: chemistry,
|
||||||
repeater.publicKeyHex,
|
isExpanded: true,
|
||||||
value,
|
decoration: const InputDecoration(
|
||||||
);
|
border: UnderlineInputBorder(),
|
||||||
},
|
isDense: true,
|
||||||
items: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: 'nmc',
|
|
||||||
child: Text(l10n.appSettings_batteryNmc),
|
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
onChanged: (value) {
|
||||||
value: 'lifepo4',
|
if (value == null) return;
|
||||||
child: Text(l10n.appSettings_batteryLifepo4),
|
settingsService.setBatteryChemistryForRepeater(
|
||||||
),
|
repeater.publicKeyHex,
|
||||||
DropdownMenuItem(
|
value,
|
||||||
value: 'lipo',
|
);
|
||||||
child: Text(l10n.appSettings_batteryLipo),
|
},
|
||||||
),
|
items: [
|
||||||
],
|
DropdownMenuItem(
|
||||||
),
|
value: 'nmc',
|
||||||
],
|
child: Text(l10n.appSettings_batteryNmc),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'lifepo4',
|
||||||
|
child: Text(l10n.appSettings_batteryLifepo4),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'lipo',
|
||||||
|
child: Text(l10n.appSettings_batteryLipo),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text(
|
||||||
l10n.repeater_managementTools,
|
isAdmin
|
||||||
|
? l10n.repeater_managementTools
|
||||||
|
: l10n.repeater_guestTools,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -210,26 +222,27 @@ class RepeaterHubScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
if (isAdmin) const SizedBox(height: 12),
|
||||||
// CLI button
|
// CLI button
|
||||||
_buildManagementCard(
|
if (isAdmin)
|
||||||
context,
|
_buildManagementCard(
|
||||||
icon: Icons.terminal,
|
context,
|
||||||
title: l10n.repeater_cli,
|
icon: Icons.terminal,
|
||||||
subtitle: l10n.repeater_cliSubtitle,
|
title: l10n.repeater_cli,
|
||||||
color: Colors.green,
|
subtitle: l10n.repeater_cliSubtitle,
|
||||||
onTap: () {
|
color: Colors.green,
|
||||||
Navigator.push(
|
onTap: () {
|
||||||
context,
|
Navigator.push(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (context) => RepeaterCliScreen(
|
MaterialPageRoute(
|
||||||
repeater: repeater,
|
builder: (context) => RepeaterCliScreen(
|
||||||
password: password,
|
repeater: repeater,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Neighbors button
|
// Neighbors button
|
||||||
_buildManagementCard(
|
_buildManagementCard(
|
||||||
@@ -248,26 +261,27 @@ class RepeaterHubScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
if (isAdmin) const SizedBox(height: 12),
|
||||||
// Settings button
|
// Settings button
|
||||||
_buildManagementCard(
|
if (isAdmin)
|
||||||
context,
|
_buildManagementCard(
|
||||||
icon: Icons.settings,
|
context,
|
||||||
title: l10n.repeater_settings,
|
icon: Icons.settings,
|
||||||
subtitle: l10n.repeater_settingsSubtitle,
|
title: l10n.repeater_settings,
|
||||||
color: Colors.deepOrange,
|
subtitle: l10n.repeater_settingsSubtitle,
|
||||||
onTap: () {
|
color: Colors.deepOrange,
|
||||||
Navigator.push(
|
onTap: () {
|
||||||
context,
|
Navigator.push(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (context) => RepeaterSettingsScreen(
|
MaterialPageRoute(
|
||||||
repeater: repeater,
|
builder: (context) => RepeaterSettingsScreen(
|
||||||
password: password,
|
repeater: repeater,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import '../connector/meshcore_connector.dart';
|
|||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../services/app_debug_log_service.dart';
|
import '../services/app_debug_log_service.dart';
|
||||||
import '../services/repeater_command_service.dart';
|
import '../services/repeater_command_service.dart';
|
||||||
|
import '../services/storage_service.dart';
|
||||||
import '../widgets/path_management_dialog.dart';
|
import '../widgets/path_management_dialog.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class RepeaterSettingsScreen extends StatefulWidget {
|
class RepeaterSettingsScreen extends StatefulWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
@@ -25,6 +27,8 @@ class RepeaterSettingsScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
||||||
|
final StorageService _storage = StorageService();
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _hasChanges = false;
|
bool _hasChanges = false;
|
||||||
bool _refreshingBasic = false;
|
bool _refreshingBasic = false;
|
||||||
@@ -59,6 +63,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
bool _repeatEnabled = true;
|
bool _repeatEnabled = true;
|
||||||
bool _allowReadOnly = true;
|
bool _allowReadOnly = true;
|
||||||
bool _privacyMode = false;
|
bool _privacyMode = false;
|
||||||
|
bool _autoClockSyncAfterLogin = false;
|
||||||
|
|
||||||
// Advertisement settings
|
// Advertisement settings
|
||||||
bool _advertEnable = true;
|
bool _advertEnable = true;
|
||||||
@@ -464,18 +469,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (successCount > 0) {
|
if (successCount > 0) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.repeater_refreshed(label)),
|
content: Text(l10n.repeater_refreshed(label)),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.repeater_errorRefreshing(label)),
|
content: Text(l10n.repeater_errorRefreshing(label)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +569,15 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
_lonController.text = widget.repeater.longitude?.toString() ?? '';
|
_lonController.text = widget.repeater.longitude?.toString() ?? '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final autoClockSync = await _storage
|
||||||
|
.getRepeaterAutoClockSyncAfterLoginEnabled(
|
||||||
|
widget.repeater.publicKeyHex,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_autoClockSyncAfterLogin = autoClockSync;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveSettings() async {
|
Future<void> _saveSettings() async {
|
||||||
@@ -653,11 +665,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.repeater_settingsSaved),
|
content: Text(context.l10n.repeater_settingsSaved),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -666,13 +677,12 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
context.l10n.repeater_errorSavingSettings(e.toString()),
|
context.l10n.repeater_errorSavingSettings(e.toString()),
|
||||||
),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1139,6 +1149,21 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
onRefresh: _refreshAllowReadOnly,
|
onRefresh: _refreshAllowReadOnly,
|
||||||
refreshTooltip: l10n.repeater_refreshGuestAccess,
|
refreshTooltip: l10n.repeater_refreshGuestAccess,
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(l10n.repeater_clockSyncAfterLogin),
|
||||||
|
subtitle: Text(l10n.repeater_clockSyncAfterLoginSubtitle),
|
||||||
|
value: _autoClockSyncAfterLogin,
|
||||||
|
onChanged: (value) async {
|
||||||
|
setState(() {
|
||||||
|
_autoClockSyncAfterLogin = value;
|
||||||
|
});
|
||||||
|
await _storage.setRepeaterAutoClockSyncAfterLoginEnabled(
|
||||||
|
widget.repeater.publicKeyHex,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
// Privacy mode - hidden until fully implemented
|
// Privacy mode - hidden until fully implemented
|
||||||
// _buildFeatureToggleRow(
|
// _buildFeatureToggleRow(
|
||||||
// title: l10n.repeater_privacyMode,
|
// title: l10n.repeater_privacyMode,
|
||||||
@@ -1401,9 +1426,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
|
|
||||||
if (command == 'erase') {
|
if (command == 'erase') {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.repeater_eraseSerialOnly)));
|
content: Text(l10n.repeater_eraseSerialOnly),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1425,17 +1451,17 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||||||
await connector.sendFrame(frame);
|
await connector.sendFrame(frame);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.repeater_commandSent(command))),
|
context,
|
||||||
|
content: Text(l10n.repeater_commandSent(command)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.repeater_errorSendingCommand(e.toString())),
|
content: Text(l10n.repeater_errorSendingCommand(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import '../services/app_settings_service.dart';
|
|||||||
import '../services/repeater_command_service.dart';
|
import '../services/repeater_command_service.dart';
|
||||||
import '../utils/battery_utils.dart';
|
import '../utils/battery_utils.dart';
|
||||||
import '../widgets/path_management_dialog.dart';
|
import '../widgets/path_management_dialog.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class RepeaterStatusScreen extends StatefulWidget {
|
class RepeaterStatusScreen extends StatefulWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
@@ -309,11 +310,10 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.repeater_statusRequestTimeout),
|
content: Text(context.l10n.repeater_statusRequestTimeout),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_recordStatusResult(false);
|
_recordStatusResult(false);
|
||||||
});
|
});
|
||||||
@@ -323,13 +323,10 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(context.l10n.repeater_errorLoadingStatus(e.toString())),
|
||||||
context.l10n.repeater_errorLoadingStatus(e.toString()),
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_recordStatusResult(false);
|
_recordStatusResult(false);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import '../services/linux_ble_error_classifier.dart';
|
|||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
import '../widgets/device_tile.dart';
|
import '../widgets/device_tile.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'contacts_screen.dart';
|
import 'contacts_screen.dart';
|
||||||
import 'tcp_screen.dart';
|
import 'tcp_screen.dart';
|
||||||
import 'usb_screen.dart';
|
import 'usb_screen.dart';
|
||||||
@@ -317,11 +318,10 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.scanner_connectionFailed(e.toString())),
|
content: Text(context.l10n.scanner_connectionFailed(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../models/radio_settings.dart';
|
import '../models/radio_settings.dart';
|
||||||
import '../services/app_debug_log_service.dart';
|
import '../services/app_debug_log_service.dart';
|
||||||
import '../widgets/app_bar.dart';
|
import '../widgets/app_bar.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'app_settings_screen.dart';
|
import 'app_settings_screen.dart';
|
||||||
import 'app_debug_log_screen.dart';
|
import 'app_debug_log_screen.dart';
|
||||||
import 'ble_debug_log_screen.dart';
|
import 'ble_debug_log_screen.dart';
|
||||||
@@ -513,8 +514,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
await connector.setNodeName(controller.text);
|
await connector.setNodeName(controller.text);
|
||||||
await connector.refreshDeviceInfo();
|
await connector.refreshDeviceInfo();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_nodeNameUpdated)),
|
context,
|
||||||
|
content: Text(l10n.settings_nodeNameUpdated),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(l10n.common_save),
|
child: Text(l10n.common_save),
|
||||||
@@ -628,10 +630,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
final interval = int.tryParse(intervalText);
|
final interval = int.tryParse(intervalText);
|
||||||
if (interval == null || interval < 60 || interval >= 86400) {
|
if (interval == null || interval < 60 || interval >= 86400) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.settings_locationIntervalInvalid),
|
content: Text(l10n.settings_locationIntervalInvalid),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -639,8 +640,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
await connector.setCustomVar("gps_interval:$interval");
|
await connector.setCustomVar("gps_interval:$interval");
|
||||||
await connector.refreshDeviceInfo();
|
await connector.refreshDeviceInfo();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
context,
|
||||||
|
content: Text(l10n.settings_locationUpdated),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,15 +662,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
: currentLon;
|
: currentLon;
|
||||||
if (lat == null || lon == null) {
|
if (lat == null || lon == null) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_locationBothRequired)),
|
context,
|
||||||
|
content: Text(l10n.settings_locationBothRequired),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_locationInvalid)),
|
context,
|
||||||
|
content: Text(l10n.settings_locationInvalid),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -676,8 +680,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
await connector.setNodeLocation(lat: lat, lon: lon);
|
await connector.setNodeLocation(lat: lat, lon: lon);
|
||||||
await connector.refreshDeviceInfo();
|
await connector.refreshDeviceInfo();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
context,
|
||||||
|
content: Text(l10n.settings_locationUpdated),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(l10n.common_save),
|
child: Text(l10n.common_save),
|
||||||
@@ -691,9 +696,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
connector.syncTime();
|
connector.syncTime();
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.settings_timeSynchronized)));
|
content: Text(l10n.settings_timeSynchronized),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _confirmReboot(BuildContext context, MeshCoreConnector connector) {
|
void _confirmReboot(BuildContext context, MeshCoreConnector connector) {
|
||||||
@@ -758,23 +764,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case gpxExportSuccess:
|
case gpxExportSuccess:
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportSuccess)));
|
content: Text(l10n.settings_gpxExportSuccess),
|
||||||
|
);
|
||||||
case gpxExportNoContacts:
|
case gpxExportNoContacts:
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_gpxExportNoContacts)),
|
context,
|
||||||
|
content: Text(l10n.settings_gpxExportNoContacts),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case gpxExportNotAvailable:
|
case gpxExportNotAvailable:
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_gpxExportNotAvailable)),
|
context,
|
||||||
|
content: Text(l10n.settings_gpxExportNotAvailable),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case gpxExportFailed:
|
case gpxExportFailed:
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.settings_gpxExportError)));
|
content: Text(l10n.settings_gpxExportError),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1077,8 +1087,9 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) {
|
|||||||
);
|
);
|
||||||
await connector.refreshDeviceInfo();
|
await connector.refreshDeviceInfo();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_telemetryModeUpdated)),
|
context,
|
||||||
|
content: Text(l10n.settings_telemetryModeUpdated),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(l10n.common_save),
|
child: Text(l10n.common_save),
|
||||||
@@ -1410,18 +1421,18 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
|||||||
final txPower = int.tryParse(_txPowerController.text);
|
final txPower = int.tryParse(_txPowerController.text);
|
||||||
|
|
||||||
if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) {
|
if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) {
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.settings_frequencyInvalid)));
|
content: Text(l10n.settings_frequencyInvalid),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final maxTxPower = widget.connector.maxTxPower ?? 22;
|
final maxTxPower = widget.connector.maxTxPower ?? 22;
|
||||||
if (txPower == null || txPower < 0 || txPower > maxTxPower) {
|
if (txPower == null || txPower < 0 || txPower > maxTxPower) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text('${l10n.settings_txPowerInvalid} (0-$maxTxPower dBm)'),
|
content: Text('${l10n.settings_txPowerInvalid} (0-$maxTxPower dBm)'),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1441,8 +1452,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
|||||||
if (knownRepeat) {
|
if (knownRepeat) {
|
||||||
const validRepeatFreqsKHz = {433000, 869000, 918000};
|
const validRepeatFreqsKHz = {433000, 869000, 918000};
|
||||||
if (_clientRepeat && !validRepeatFreqsKHz.contains(freqHz)) {
|
if (_clientRepeat && !validRepeatFreqsKHz.contains(freqHz)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_clientRepeatFreqWarning)),
|
context,
|
||||||
|
content: Text(l10n.settings_clientRepeatFreqWarning),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1472,14 +1484,16 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_logRadioSettingsState('Radio settings saved successfully');
|
_logRadioSettingsState('Radio settings saved successfully');
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_radioSettingsUpdated)),
|
context,
|
||||||
|
content: Text(l10n.settings_radioSettingsUpdated),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_appLog.warn('Radio settings save failed: $e', tag: 'RadioSettings');
|
_appLog.warn('Radio settings save failed: $e', tag: 'RadioSettings');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(l10n.settings_error(e.toString()))),
|
context,
|
||||||
|
content: Text(l10n.settings_error(e.toString())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../services/app_settings_service.dart';
|
import '../services/app_settings_service.dart';
|
||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'contacts_screen.dart';
|
import 'contacts_screen.dart';
|
||||||
import 'usb_screen.dart';
|
import 'usb_screen.dart';
|
||||||
|
|
||||||
@@ -270,8 +271,10 @@ class _TcpScreenState extends State<TcpScreen> {
|
|||||||
|
|
||||||
void _showError(String message) {
|
void _showError(String message) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
context,
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import '../utils/app_logger.dart';
|
|||||||
import '../widgets/path_management_dialog.dart';
|
import '../widgets/path_management_dialog.dart';
|
||||||
import '../helpers/cayenne_lpp.dart';
|
import '../helpers/cayenne_lpp.dart';
|
||||||
import '../utils/battery_utils.dart';
|
import '../utils/battery_utils.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class TelemetryScreen extends StatefulWidget {
|
class TelemetryScreen extends StatefulWidget {
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
@@ -86,11 +87,10 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.telemetry_requestTimeout),
|
content: Text(context.l10n.telemetry_requestTimeout),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_recordTelemetryResult(false);
|
_recordTelemetryResult(false);
|
||||||
});
|
});
|
||||||
@@ -137,11 +137,10 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||||||
_parsedTelemetry = parsedTelemetry;
|
_parsedTelemetry = parsedTelemetry;
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.telemetry_receivedData),
|
content: Text(context.l10n.telemetry_receivedData),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_statusTimeout?.cancel();
|
_statusTimeout?.cancel();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -182,11 +181,10 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
|
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import '../utils/app_logger.dart';
|
|||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
import '../utils/usb_port_labels.dart';
|
import '../utils/usb_port_labels.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'contacts_screen.dart';
|
import 'contacts_screen.dart';
|
||||||
import 'scanner_screen.dart';
|
import 'scanner_screen.dart';
|
||||||
import 'tcp_screen.dart';
|
import 'tcp_screen.dart';
|
||||||
@@ -383,11 +384,10 @@ class _UsbScreenState extends State<UsbScreen> {
|
|||||||
|
|
||||||
void _showError(Object error) {
|
void _showError(Object error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(_friendlyErrorMessage(error)),
|
content: Text(_friendlyErrorMessage(error)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,42 @@ class StorageService {
|
|||||||
static const String _pathHistoryPrefix = 'path_history_';
|
static const String _pathHistoryPrefix = 'path_history_';
|
||||||
static const String _pendingMessagesKey = 'pending_messages';
|
static const String _pendingMessagesKey = 'pending_messages';
|
||||||
static const String _repeaterPasswordsKey = 'repeater_passwords';
|
static const String _repeaterPasswordsKey = 'repeater_passwords';
|
||||||
|
static const String _repeaterAutoClockSyncAfterLoginKey =
|
||||||
|
'repeater_auto_clock_sync_after_login';
|
||||||
static const String _deliveryObservationsKey = 'delivery_observations';
|
static const String _deliveryObservationsKey = 'delivery_observations';
|
||||||
|
|
||||||
|
Future<Map<String, bool>> _loadRepeaterAutoClockSyncAfterLogin() async {
|
||||||
|
final prefs = PrefsManager.instance;
|
||||||
|
final jsonStr = prefs.getString(_repeaterAutoClockSyncAfterLoginKey);
|
||||||
|
|
||||||
|
if (jsonStr == null) return {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final json = jsonDecode(jsonStr) as Map<String, dynamic>;
|
||||||
|
return json.map((key, value) => MapEntry(key, value == true));
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> getRepeaterAutoClockSyncAfterLoginEnabled(
|
||||||
|
String repeaterPubKeyHex,
|
||||||
|
) async {
|
||||||
|
final settings = await _loadRepeaterAutoClockSyncAfterLogin();
|
||||||
|
return settings[repeaterPubKeyHex] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setRepeaterAutoClockSyncAfterLoginEnabled(
|
||||||
|
String repeaterPubKeyHex,
|
||||||
|
bool enabled,
|
||||||
|
) async {
|
||||||
|
final prefs = PrefsManager.instance;
|
||||||
|
final settings = await _loadRepeaterAutoClockSyncAfterLogin();
|
||||||
|
settings[repeaterPubKeyHex] = enabled;
|
||||||
|
final jsonStr = jsonEncode(settings);
|
||||||
|
await prefs.setString(_repeaterAutoClockSyncAfterLoginKey, jsonStr);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> savePathHistory(
|
Future<void> savePathHistory(
|
||||||
String contactPubKeyHex,
|
String contactPubKeyHex,
|
||||||
ContactPathHistory history,
|
ContactPathHistory history,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:llamadart/llamadart.dart';
|
|||||||
|
|
||||||
import '../models/app_settings.dart';
|
import '../models/app_settings.dart';
|
||||||
import '../models/translation_support.dart';
|
import '../models/translation_support.dart';
|
||||||
|
import '../helpers/gif_helper.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import 'app_settings_service.dart';
|
import 'app_settings_service.dart';
|
||||||
import 'translation_file_store.dart';
|
import 'translation_file_store.dart';
|
||||||
@@ -509,8 +510,10 @@ class TranslationService extends ChangeNotifier {
|
|||||||
if (trimmed.isEmpty) {
|
if (trimmed.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !(trimmed.startsWith('g:') ||
|
if (GifHelper.parseGif(trimmed) != null) {
|
||||||
trimmed.startsWith('m:') ||
|
return false;
|
||||||
|
}
|
||||||
|
return !(trimmed.startsWith('m:') ||
|
||||||
trimmed.startsWith('V1|') ||
|
trimmed.startsWith('V1|') ||
|
||||||
trimmed.startsWith('r:'));
|
trimmed.startsWith('r:'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
import '../helpers/path_helper.dart';
|
import '../helpers/path_helper.dart';
|
||||||
import '../services/path_history_service.dart';
|
import '../services/path_history_service.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'path_selection_dialog.dart';
|
import 'path_selection_dialog.dart';
|
||||||
|
|
||||||
class PathManagementDialog {
|
class PathManagementDialog {
|
||||||
@@ -65,11 +66,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
void _showFullPathDialog(BuildContext context, List<int> pathBytes) {
|
void _showFullPathDialog(BuildContext context, List<int> pathBytes) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
if (pathBytes.isEmpty) {
|
if (pathBytes.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.chat_pathDetailsNotAvailable),
|
content: Text(l10n.chat_pathDetailsNotAvailable),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -159,11 +159,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.chat_hopsCount(result.length)),
|
content: Text(l10n.chat_hopsCount(result.length)),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,13 +336,12 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
_showFullPathDialog(context, path.pathBytes),
|
_showFullPathDialog(context, path.pathBytes),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (path.pathBytes.isEmpty) {
|
if (path.pathBytes.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
l10n.chat_pathDetailsNotAvailable,
|
l10n.chat_pathDetailsNotAvailable,
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -361,13 +359,12 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(
|
||||||
l10n.path_usingHopsPath(path.hopCount),
|
l10n.path_usingHopsPath(path.hopCount),
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
),
|
),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -459,11 +456,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
await connector.clearContactPath(currentContact);
|
await connector.clearContactPath(currentContact);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.chat_pathCleared),
|
content: Text(l10n.chat_pathCleared),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
@@ -489,11 +485,10 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||||||
pathLen: -1,
|
pathLen: -1,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.chat_floodModeEnabled),
|
content: Text(l10n.chat_floodModeEnabled),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class PathSelectionDialog extends StatefulWidget {
|
class PathSelectionDialog extends StatefulWidget {
|
||||||
final List<Contact> availableContacts;
|
final List<Contact> availableContacts;
|
||||||
@@ -138,26 +139,22 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
|
|||||||
|
|
||||||
// Show error for invalid prefixes
|
// Show error for invalid prefixes
|
||||||
if (invalidPrefixes.isNotEmpty) {
|
if (invalidPrefixes.isNotEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
content: Text(l10n.path_invalidHexPrefixes(invalidPrefixes.join(", "))),
|
||||||
l10n.path_invalidHexPrefixes(invalidPrefixes.join(", ")),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
backgroundColor: Colors.red,
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check max path length (64 hops)
|
// Check max path length (64 hops)
|
||||||
if (pathBytesList.length > 64) {
|
if (pathBytesList.length > 64) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(l10n.path_tooLong),
|
content: Text(l10n.path_tooLong),
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import 'path_management_dialog.dart';
|
|||||||
|
|
||||||
class RepeaterLoginDialog extends StatefulWidget {
|
class RepeaterLoginDialog extends StatefulWidget {
|
||||||
final Contact repeater;
|
final Contact repeater;
|
||||||
final Function(String password) onLogin;
|
final Function(String password, bool isAdmin) onLogin;
|
||||||
|
|
||||||
const RepeaterLoginDialog({
|
const RepeaterLoginDialog({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -119,6 +119,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||||||
: '${selection.hopCount} hops';
|
: '${selection.hopCount} hops';
|
||||||
appLogger.info('Login routing: $selectionLabel', tag: 'RepeaterLogin');
|
appLogger.info('Login routing: $selectionLabel', tag: 'RepeaterLogin');
|
||||||
bool? loginResult;
|
bool? loginResult;
|
||||||
|
bool isAdmin = false;
|
||||||
for (int attempt = 0; attempt < _maxAttempts; attempt++) {
|
for (int attempt = 0; attempt < _maxAttempts; attempt++) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -131,7 +132,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||||||
);
|
);
|
||||||
await _connector.sendFrame(loginFrame);
|
await _connector.sendFrame(loginFrame);
|
||||||
|
|
||||||
loginResult = await _awaitLoginResponse(timeout);
|
(loginResult, isAdmin) = await _awaitLoginResponse(timeout);
|
||||||
if (loginResult == true) {
|
if (loginResult == true) {
|
||||||
appLogger.info(
|
appLogger.info(
|
||||||
'Login succeeded for ${repeater.name}',
|
'Login succeeded for ${repeater.name}',
|
||||||
@@ -187,9 +188,32 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||||||
await _storage.removeRepeaterPassword(widget.repeater.publicKeyHex);
|
await _storage.removeRepeaterPassword(widget.repeater.publicKeyHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final autoClockSync = await _storage
|
||||||
|
.getRepeaterAutoClockSyncAfterLoginEnabled(
|
||||||
|
widget.repeater.publicKeyHex,
|
||||||
|
);
|
||||||
|
if (autoClockSync) {
|
||||||
|
try {
|
||||||
|
final timestampSeconds =
|
||||||
|
DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
await _connector.sendFrame(
|
||||||
|
buildSendCliCommandFrame(
|
||||||
|
repeater.publicKey,
|
||||||
|
'clock sync',
|
||||||
|
timestampSeconds: timestampSeconds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Auto clock sync failed for ${repeater.name}: $e',
|
||||||
|
tag: 'RepeaterLogin',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.pop(context, password);
|
Navigator.pop(context, password);
|
||||||
Future.microtask(() => widget.onLogin(password));
|
Future.microtask(() => widget.onLogin(password, isAdmin));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final repeater = _resolveRepeater(_connector);
|
final repeater = _resolveRepeater(_connector);
|
||||||
@@ -206,17 +230,21 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool?> _awaitLoginResponse(Duration timeout) async {
|
// _awaitLoginResponse returns a record of bool, for success and if the client is an admin
|
||||||
|
Future<(bool?, bool)> _awaitLoginResponse(Duration timeout) async {
|
||||||
final completer = Completer<bool?>();
|
final completer = Completer<bool?>();
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
StreamSubscription<Uint8List>? subscription;
|
StreamSubscription<Uint8List>? subscription;
|
||||||
final targetPrefix = widget.repeater.publicKey.sublist(0, 6);
|
final targetPrefix = widget.repeater.publicKey.sublist(0, 6);
|
||||||
|
bool isAdmin = false;
|
||||||
subscription = _connector.receivedFrames.listen((frame) {
|
subscription = _connector.receivedFrames.listen((frame) {
|
||||||
if (frame.isEmpty) return;
|
if (frame.isEmpty) return;
|
||||||
final code = frame[0];
|
final code = frame[0];
|
||||||
if (code != pushCodeLoginSuccess && code != pushCodeLoginFail) return;
|
if (code != pushCodeLoginSuccess && code != pushCodeLoginFail) return;
|
||||||
if (frame.length < 8) return;
|
if (frame.length < 8) return;
|
||||||
|
// NOTE: a bug in the repeater firmware only ever sends 1 or 0 back, not the
|
||||||
|
// expected client permissions
|
||||||
|
isAdmin = (frame[1] == 1);
|
||||||
final prefix = frame.sublist(2, 8);
|
final prefix = frame.sublist(2, 8);
|
||||||
if (!listEquals(prefix, targetPrefix)) return;
|
if (!listEquals(prefix, targetPrefix)) return;
|
||||||
|
|
||||||
@@ -235,7 +263,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||||||
final result = await completer.future;
|
final result = await completer.future;
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
await subscription.cancel();
|
await subscription.cancel();
|
||||||
return result;
|
return (result, isAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ import '../services/storage_service.dart';
|
|||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import 'path_management_dialog.dart';
|
import 'path_management_dialog.dart';
|
||||||
|
|
||||||
class RoomLoginDialog extends StatefulWidget {
|
class RoomLoginDialog extends StatefulWidget {
|
||||||
final Contact room;
|
final Contact room;
|
||||||
final Function(String password) onLogin;
|
final Function(String password, bool isAdmin) onLogin;
|
||||||
|
|
||||||
const RoomLoginDialog({super.key, required this.room, required this.onLogin});
|
const RoomLoginDialog({super.key, required this.room, required this.onLogin});
|
||||||
|
|
||||||
@@ -114,6 +115,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||||||
: '${selection.hopCount} hops';
|
: '${selection.hopCount} hops';
|
||||||
appLogger.info('Login routing: $selectionLabel', tag: 'RoomLogin');
|
appLogger.info('Login routing: $selectionLabel', tag: 'RoomLogin');
|
||||||
bool? loginResult;
|
bool? loginResult;
|
||||||
|
bool isAdmin = false;
|
||||||
for (int attempt = 0; attempt < _maxAttempts; attempt++) {
|
for (int attempt = 0; attempt < _maxAttempts; attempt++) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -126,7 +128,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||||||
);
|
);
|
||||||
await _connector.sendFrame(loginFrame);
|
await _connector.sendFrame(loginFrame);
|
||||||
|
|
||||||
loginResult = await _awaitLoginResponse(timeout);
|
(loginResult, isAdmin) = await _awaitLoginResponse(timeout);
|
||||||
if (loginResult == true) {
|
if (loginResult == true) {
|
||||||
appLogger.info('Login succeeded for ${room.name}', tag: 'RoomLogin');
|
appLogger.info('Login succeeded for ${room.name}', tag: 'RoomLogin');
|
||||||
break;
|
break;
|
||||||
@@ -166,7 +168,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.pop(context, password);
|
Navigator.pop(context, password);
|
||||||
Future.microtask(() => widget.onLogin(password));
|
Future.microtask(() => widget.onLogin(password, isAdmin));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final room = _resolveRepeater(_connector);
|
final room = _resolveRepeater(_connector);
|
||||||
@@ -175,26 +177,29 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isLoggingIn = false;
|
_isLoggingIn = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.login_failed(e.toString())),
|
content: Text(context.l10n.login_failed(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool?> _awaitLoginResponse(Duration timeout) async {
|
Future<(bool?, bool)> _awaitLoginResponse(Duration timeout) async {
|
||||||
final completer = Completer<bool?>();
|
final completer = Completer<bool?>();
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
StreamSubscription<Uint8List>? subscription;
|
StreamSubscription<Uint8List>? subscription;
|
||||||
final targetPrefix = widget.room.publicKey.sublist(0, 6);
|
final targetPrefix = widget.room.publicKey.sublist(0, 6);
|
||||||
|
bool isAdmin = false;
|
||||||
|
|
||||||
subscription = _connector.receivedFrames.listen((frame) {
|
subscription = _connector.receivedFrames.listen((frame) {
|
||||||
if (frame.isEmpty) return;
|
if (frame.isEmpty) return;
|
||||||
final code = frame[0];
|
final code = frame[0];
|
||||||
if (code != pushCodeLoginSuccess && code != pushCodeLoginFail) return;
|
if (code != pushCodeLoginSuccess && code != pushCodeLoginFail) return;
|
||||||
|
// NOTE: a bug in the repeater firmware only ever sends 1 or 0 back, not the
|
||||||
|
// expected client permissions
|
||||||
|
isAdmin = (frame[1] == 1);
|
||||||
if (frame.length < 8) return;
|
if (frame.length < 8) return;
|
||||||
final prefix = frame.sublist(2, 8);
|
final prefix = frame.sublist(2, 8);
|
||||||
if (!listEquals(prefix, targetPrefix)) return;
|
if (!listEquals(prefix, targetPrefix)) return;
|
||||||
@@ -214,7 +219,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||||||
final result = await completer.future;
|
final result = await completer.future;
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
await subscription.cancel();
|
await subscription.cancel();
|
||||||
return result;
|
return (result, isAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
flserial
|
flserial
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 7.0.0+9
|
version: 8.0.0+10
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.2
|
sdk: ^3.9.2
|
||||||
|
|||||||
+1
-69
@@ -1,69 +1 @@
|
|||||||
{
|
{}
|
||||||
"bg": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"de": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"es": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"fr": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"hu": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"it": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ja": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ko": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"nl": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"pl": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"pt": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ru": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"sk": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"sl": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"sv": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"uk": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"zh": [
|
|
||||||
"chat_sendMessage"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
flserial
|
flserial
|
||||||
flutter_local_notifications_windows
|
flutter_local_notifications_windows
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
Reference in New Issue
Block a user