mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-08-08 08:47:32 +10:00
Compare commits
16 Commits
ui
..
dev-offline
| Author | SHA1 | Date | |
|---|---|---|---|
| ba4fd3eff5 | |||
| beb3e1996d | |||
| 3f4e6f4e13 | |||
| e1cc285c8a | |||
| f4b32e8a8a | |||
| 01bf95c98e | |||
| fa044dd204 | |||
| 72fea3fc32 | |||
| f0bd61144c | |||
| 61c897630c | |||
| a270e2e6d1 | |||
| 247db6a36d | |||
| 78d08afb47 | |||
| c77264cc81 | |||
| d6ed8c5f13 | |||
| 209fee48ca |
@@ -91,6 +91,3 @@ keystore.properties
|
||||
|
||||
# Cloudflare Wrangler
|
||||
.wrangler
|
||||
|
||||
# Claude Code local working dir (worktrees, jobs, settings)
|
||||
.claude/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@ class MeshCoreUsbManager {
|
||||
String? get activePortKey => _activePortKey;
|
||||
String? get activePortDisplayLabel => _activePortLabel ?? _activePortKey;
|
||||
bool get isConnected => _service.isConnected;
|
||||
Object? get lastError => _service.lastError;
|
||||
Stream<Uint8List> get frameStream => _service.frameStream;
|
||||
|
||||
// --- Configuration ---
|
||||
|
||||
@@ -224,12 +224,6 @@ const int reqTypeGetTelemetry = 0x03;
|
||||
const int reqTypeGetAccessList = 0x05;
|
||||
const int reqTypeGetNeighbors = 0x06;
|
||||
|
||||
Uint8List buildTelemetryBinaryPayload() {
|
||||
// Room servers/repeaters read byte 1 as an inverse telemetry permission mask.
|
||||
// Zero means "request every telemetry field allowed for this contact".
|
||||
return Uint8List.fromList([reqTypeGetTelemetry, 0x00, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
// Repeater response codes
|
||||
const int respServerLoginOk = 0;
|
||||
|
||||
@@ -457,13 +451,8 @@ String pubKeyToHex(Uint8List pubKey) {
|
||||
|
||||
// Helper to convert hex string to public key
|
||||
Uint8List hexToPubKey(String hex) {
|
||||
if (hex.length != pubKeySize * 2) {
|
||||
throw FormatException(
|
||||
'Public key hex must be ${pubKeySize * 2} chars, got ${hex.length}',
|
||||
);
|
||||
}
|
||||
final result = Uint8List(pubKeySize);
|
||||
for (int i = 0; i < pubKeySize; i++) {
|
||||
for (int i = 0; i < pubKeySize && i * 2 + 1 < hex.length; i++) {
|
||||
result[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
|
||||
}
|
||||
return result;
|
||||
@@ -956,7 +945,7 @@ Uint8List buildSendTelemetryReq(Uint8List? pubKey) {
|
||||
writer.writeBytes(Uint8List(3)); // reserved bytes
|
||||
writer.writeBytes(pubKey);
|
||||
} else {
|
||||
writer.writeBytes(Uint8List(3)); // reserved bytes
|
||||
writer.writeBytes(Uint8List(4)); // reserved bytes
|
||||
}
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
@@ -3,10 +3,6 @@ class MeshCoreUuids {
|
||||
static const String rxCharacteristic = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
|
||||
static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
|
||||
|
||||
/// Known advertised-name prefixes used by stock MeshCore firmware builds.
|
||||
/// Discovery no longer filters on these (it filters on the [service] UUID so
|
||||
/// that community forks with custom names are still found); kept for
|
||||
/// reference and possible future display heuristics.
|
||||
static const List<String> deviceNamePrefixes = [
|
||||
"MeshCore-",
|
||||
"Whisper-",
|
||||
|
||||
@@ -96,34 +96,6 @@ class CayenneLpp {
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case lppDigitalInput:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt8(),
|
||||
});
|
||||
break;
|
||||
case lppDigitalOutput:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt8(),
|
||||
});
|
||||
break;
|
||||
case lppAnalogInput:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readInt16BE() / 100,
|
||||
});
|
||||
break;
|
||||
case lppAnalogOutput:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readInt16BE() / 100,
|
||||
});
|
||||
break;
|
||||
case lppGenericSensor:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
@@ -159,17 +131,6 @@ class CayenneLpp {
|
||||
'value': buffer.readUInt8() / 2,
|
||||
});
|
||||
break;
|
||||
case lppAccelerometer:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': {
|
||||
'x': buffer.readInt16BE() / 1000,
|
||||
'y': buffer.readInt16BE() / 1000,
|
||||
'z': buffer.readInt16BE() / 1000,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case lppBarometricPressure:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
@@ -177,13 +138,6 @@ class CayenneLpp {
|
||||
'value': buffer.readUInt16BE() / 10,
|
||||
});
|
||||
break;
|
||||
case lppAltitude:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readInt16BE(),
|
||||
});
|
||||
break;
|
||||
case lppVoltage:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
@@ -198,13 +152,6 @@ class CayenneLpp {
|
||||
'value': buffer.readInt16BE() / 1000,
|
||||
});
|
||||
break;
|
||||
case lppFrequency:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt32BE(),
|
||||
});
|
||||
break;
|
||||
case lppPercentage:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
@@ -226,56 +173,6 @@ class CayenneLpp {
|
||||
'value': buffer.readUInt16BE(),
|
||||
});
|
||||
break;
|
||||
case lppDistance:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt32BE() / 1000,
|
||||
});
|
||||
break;
|
||||
case lppEnergy:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt32BE() / 1000,
|
||||
});
|
||||
break;
|
||||
case lppDirection:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt16BE(),
|
||||
});
|
||||
break;
|
||||
case lppUnixTime:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt32BE(),
|
||||
});
|
||||
break;
|
||||
case lppGyrometer:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': {
|
||||
'x': buffer.readInt16BE() / 100,
|
||||
'y': buffer.readInt16BE() / 100,
|
||||
'z': buffer.readInt16BE() / 100,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case lppColour:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': {
|
||||
'red': buffer.readUInt8(),
|
||||
'green': buffer.readUInt8(),
|
||||
'blue': buffer.readUInt8(),
|
||||
},
|
||||
});
|
||||
break;
|
||||
case lppGps:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
@@ -287,24 +184,6 @@ class CayenneLpp {
|
||||
},
|
||||
});
|
||||
break;
|
||||
case lppSwitch:
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': buffer.readUInt8(),
|
||||
});
|
||||
break;
|
||||
case lppPolyline:
|
||||
final size = buffer.readUInt8();
|
||||
telemetry.add({
|
||||
'channel': channel,
|
||||
'type': type,
|
||||
'value': {
|
||||
'size': size,
|
||||
'data': _bytesToHex(_readPolylinePayload(buffer, size)),
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return telemetry;
|
||||
}
|
||||
@@ -337,19 +216,6 @@ class CayenneLpp {
|
||||
);
|
||||
|
||||
switch (type) {
|
||||
case lppDigitalInput:
|
||||
channelData['values']['digitalInput'] = buffer.readUInt8();
|
||||
break;
|
||||
case lppDigitalOutput:
|
||||
channelData['values']['digitalOutput'] = buffer.readUInt8();
|
||||
break;
|
||||
case lppAnalogInput:
|
||||
channelData['values']['analogInput'] = buffer.readInt16BE() / 100.0;
|
||||
break;
|
||||
case lppAnalogOutput:
|
||||
channelData['values']['analogOutput'] =
|
||||
buffer.readInt16BE() / 100.0;
|
||||
break;
|
||||
case lppGenericSensor:
|
||||
channelData['values']['generic'] = buffer.readUInt32BE();
|
||||
break;
|
||||
@@ -365,29 +231,15 @@ class CayenneLpp {
|
||||
case lppRelativeHumidity:
|
||||
channelData['values']['humidity'] = buffer.readUInt8() / 2.0;
|
||||
break;
|
||||
case lppAccelerometer:
|
||||
channelData['values']['accelerometer'] = {
|
||||
'x': buffer.readInt16BE() / 1000.0,
|
||||
'y': buffer.readInt16BE() / 1000.0,
|
||||
'z': buffer.readInt16BE() / 1000.0,
|
||||
};
|
||||
break;
|
||||
case lppBarometricPressure:
|
||||
channelData['values']['pressure'] = buffer.readUInt16BE() / 10.0;
|
||||
break;
|
||||
case lppAltitude:
|
||||
// MeshCore encodes standalone barometric altitude as LPP type 121.
|
||||
channelData['values']['altitude'] = buffer.readInt16BE();
|
||||
break;
|
||||
case lppVoltage:
|
||||
channelData['values']['voltage'] = buffer.readInt16BE() / 100.0;
|
||||
break;
|
||||
case lppCurrent:
|
||||
channelData['values']['current'] = buffer.readInt16BE() / 1000.0;
|
||||
break;
|
||||
case lppFrequency:
|
||||
channelData['values']['frequency'] = buffer.readUInt32BE();
|
||||
break;
|
||||
case lppPercentage:
|
||||
channelData['values']['percentage'] = buffer.readUInt8();
|
||||
break;
|
||||
@@ -397,32 +249,6 @@ class CayenneLpp {
|
||||
case lppPower:
|
||||
channelData['values']['power'] = buffer.readUInt16BE();
|
||||
break;
|
||||
case lppDistance:
|
||||
channelData['values']['distance'] = buffer.readUInt32BE() / 1000.0;
|
||||
break;
|
||||
case lppEnergy:
|
||||
channelData['values']['energy'] = buffer.readUInt32BE() / 1000.0;
|
||||
break;
|
||||
case lppDirection:
|
||||
channelData['values']['direction'] = buffer.readUInt16BE();
|
||||
break;
|
||||
case lppUnixTime:
|
||||
channelData['values']['time'] = buffer.readUInt32BE();
|
||||
break;
|
||||
case lppGyrometer:
|
||||
channelData['values']['gyrometer'] = {
|
||||
'x': buffer.readInt16BE() / 100.0,
|
||||
'y': buffer.readInt16BE() / 100.0,
|
||||
'z': buffer.readInt16BE() / 100.0,
|
||||
};
|
||||
break;
|
||||
case lppColour:
|
||||
channelData['values']['colour'] = {
|
||||
'red': buffer.readUInt8(),
|
||||
'green': buffer.readUInt8(),
|
||||
'blue': buffer.readUInt8(),
|
||||
};
|
||||
break;
|
||||
case lppGps:
|
||||
channelData['values']['gps'] = {
|
||||
'latitude': buffer.readInt24BE() / 10000.0,
|
||||
@@ -430,48 +256,22 @@ class CayenneLpp {
|
||||
'altitude': buffer.readInt24BE() / 100.0,
|
||||
};
|
||||
break;
|
||||
case lppSwitch:
|
||||
channelData['values']['switch'] = buffer.readUInt8() != 0;
|
||||
break;
|
||||
case lppPolyline:
|
||||
final size = buffer.readUInt8();
|
||||
channelData['values']['polyline'] = {
|
||||
'size': size,
|
||||
'data': _bytesToHex(_readPolylinePayload(buffer, size)),
|
||||
};
|
||||
break;
|
||||
// Add more types as needed...
|
||||
default:
|
||||
// Stop parsing to avoid losing alignment on an unknown LPP type.
|
||||
return _sortedChannelValues(channels);
|
||||
//Stopped parsing to avoid misalignment
|
||||
return channels.values.toList();
|
||||
}
|
||||
}
|
||||
|
||||
return _sortedChannelValues(channels);
|
||||
final List<Map<String, dynamic>> channelsOut = channels.values.toList();
|
||||
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
|
||||
return channelsOut;
|
||||
} catch (e) {
|
||||
// Handle parsing errors, possibly due to malformed data
|
||||
appLogger.error('Error parsing Cayenne LPP data: $e');
|
||||
// Preserve any fields parsed before the malformed value.
|
||||
return _sortedChannelValues(channels);
|
||||
return <
|
||||
Map<String, dynamic>
|
||||
>[]; // Return an empty list on error to avoid crashing the app
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8List _readPolylinePayload(BufferReader buffer, int size) {
|
||||
final declaredPayloadSize = size > 0 ? size - 1 : 0;
|
||||
final availablePayloadSize = declaredPayloadSize <= buffer.remaining
|
||||
? declaredPayloadSize
|
||||
: buffer.remaining;
|
||||
return buffer.readBytes(availablePayloadSize);
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> _sortedChannelValues(
|
||||
Map<int, Map<String, dynamic>> channels,
|
||||
) {
|
||||
final channelsOut = channels.values.toList();
|
||||
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
|
||||
return channelsOut;
|
||||
}
|
||||
|
||||
static String _bytesToHex(Uint8List bytes) {
|
||||
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
|
||||
IconData contactTypeIcon(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Icons.chat;
|
||||
case advTypeRepeater:
|
||||
return Icons.cell_tower;
|
||||
case advTypeRoom:
|
||||
return Icons.group;
|
||||
case advTypeSensor:
|
||||
return Icons.sensors;
|
||||
default:
|
||||
return Icons.device_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
Color contactTypeColor(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Colors.blue;
|
||||
case advTypeRepeater:
|
||||
return Colors.orange;
|
||||
case advTypeRoom:
|
||||
return Colors.purple;
|
||||
case advTypeSensor:
|
||||
return Colors.green;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
Color colorForName(String name) {
|
||||
const colors = [
|
||||
Colors.blue,
|
||||
Colors.green,
|
||||
Colors.orange,
|
||||
Colors.purple,
|
||||
Colors.pink,
|
||||
Colors.teal,
|
||||
Colors.indigo,
|
||||
Colors.cyan,
|
||||
Colors.amber,
|
||||
Colors.deepOrange,
|
||||
];
|
||||
return colors[name.hashCode.abs() % colors.length];
|
||||
}
|
||||
|
||||
String firstCharacterOrEmoji(String name) {
|
||||
if (name.isEmpty) return '?';
|
||||
final emoji = firstEmoji(name);
|
||||
if (emoji != null) return emoji;
|
||||
final runes = name.runes.toList();
|
||||
if (runes.isEmpty) return '?';
|
||||
return String.fromCharCode(runes[0]).toUpperCase();
|
||||
}
|
||||
@@ -8,29 +8,24 @@ class PathHelper {
|
||||
.join(',');
|
||||
}
|
||||
|
||||
static String hopHex(int byte) {
|
||||
return byte.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||
}
|
||||
|
||||
static String? hopName(int byte, List<Contact> allContacts) {
|
||||
final matches = allContacts
|
||||
.where(
|
||||
(c) =>
|
||||
c.publicKey.first == byte &&
|
||||
(c.type == advTypeRepeater || c.type == advTypeRoom),
|
||||
)
|
||||
.toList();
|
||||
if (matches.isEmpty) return null;
|
||||
if (matches.length == 1) return matches.first.name;
|
||||
return matches.map((c) => c.name).join(' | ');
|
||||
}
|
||||
|
||||
static String resolvePathNames(
|
||||
List<int> pathBytes,
|
||||
List<Contact> allContacts,
|
||||
) {
|
||||
return pathBytes
|
||||
.map((b) => hopName(b, allContacts) ?? hopHex(b))
|
||||
.map((b) {
|
||||
final hex = b.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||
final matches = allContacts
|
||||
.where(
|
||||
(c) =>
|
||||
c.publicKey.first == b &&
|
||||
(c.type == advTypeRepeater || c.type == advTypeRoom),
|
||||
)
|
||||
.toList();
|
||||
if (matches.isEmpty) return hex;
|
||||
if (matches.length == 1) return matches.first.name;
|
||||
return matches.map((c) => c.name).join(' | ');
|
||||
})
|
||||
.join(' \u2192 ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../models/contact.dart';
|
||||
|
||||
class PathHopResolver {
|
||||
const PathHopResolver._();
|
||||
|
||||
static List<Contact?> resolve({
|
||||
required List<int> pathBytes,
|
||||
required List<Contact> contacts,
|
||||
LatLng? endpoint,
|
||||
bool resolveFromEnd = false,
|
||||
}) {
|
||||
final candidatesByPrefix = <int, List<Contact>>{};
|
||||
for (final contact in contacts) {
|
||||
if (contact.publicKey.isEmpty) continue;
|
||||
if (contact.type != advTypeRepeater && contact.type != advTypeRoom) {
|
||||
continue;
|
||||
}
|
||||
candidatesByPrefix
|
||||
.putIfAbsent(contact.publicKey.first, () => <Contact>[])
|
||||
.add(contact);
|
||||
}
|
||||
for (final candidates in candidatesByPrefix.values) {
|
||||
candidates.sort((a, b) => b.lastSeen.compareTo(a.lastSeen));
|
||||
}
|
||||
|
||||
final resolved = List<Contact?>.filled(pathBytes.length, null);
|
||||
final indexes = resolveFromEnd
|
||||
? List<int>.generate(pathBytes.length, (i) => pathBytes.length - 1 - i)
|
||||
: List<int>.generate(pathBytes.length, (i) => i);
|
||||
final distance = Distance();
|
||||
var previousPosition = endpoint;
|
||||
|
||||
for (final index in indexes) {
|
||||
final candidates = candidatesByPrefix[pathBytes[index]];
|
||||
if (candidates == null || candidates.isEmpty) continue;
|
||||
|
||||
var bestIndex = 0;
|
||||
if (previousPosition != null && candidates.length > 1) {
|
||||
double? nearestDistance;
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
final position = _positionOf(candidates[i]);
|
||||
if (position == null) continue;
|
||||
final candidateDistance = distance(previousPosition, position);
|
||||
if (nearestDistance == null || candidateDistance < nearestDistance) {
|
||||
nearestDistance = candidateDistance;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final contact = candidates.removeAt(bestIndex);
|
||||
resolved[index] = contact;
|
||||
previousPosition = _positionOf(contact) ?? previousPosition;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
static LatLng? _positionOf(Contact contact) {
|
||||
if (!contact.hasLocation ||
|
||||
contact.latitude == null ||
|
||||
contact.longitude == null) {
|
||||
return null;
|
||||
}
|
||||
return LatLng(contact.latitude!, contact.longitude!);
|
||||
}
|
||||
}
|
||||
@@ -25,19 +25,7 @@ void showDismissibleSnackBar(
|
||||
DismissDirection? dismissDirection,
|
||||
Clip? clipBehavior,
|
||||
}) {
|
||||
// Callers often reach here after an async gap; the context may already be
|
||||
// unmounted, or deactivated (popped but not yet disposed) — ancestor
|
||||
// lookups on a deactivated element throw. Showing nothing is the right
|
||||
// outcome in both cases.
|
||||
if (!context.mounted) return;
|
||||
var isActive = true;
|
||||
assert(() {
|
||||
isActive = (context as Element).debugIsActive;
|
||||
return true;
|
||||
}());
|
||||
if (!isActive) return;
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
if (messenger == null) return;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
key: key,
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Изтрий",
|
||||
"common_enable": "Активирай",
|
||||
"common_disable": "Деактивирай",
|
||||
"common_autoRefresh": "Автоматично обновяване",
|
||||
"common_interval": "Интервал",
|
||||
"common_reboot": "Рестартирай",
|
||||
"common_loading": "Зареждане...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Цифров вход",
|
||||
"telemetry_digitalOutputLabel": "Цифров изход",
|
||||
"telemetry_analogInputLabel": "Аналогов вход",
|
||||
"telemetry_analogOutputLabel": "Аналогов изход",
|
||||
"telemetry_genericLabel": "Общ сензор",
|
||||
"telemetry_luminosityLabel": "Осветеност",
|
||||
"telemetry_presenceLabel": "Присъствие",
|
||||
"telemetry_humidityLabel": "Влажност",
|
||||
"telemetry_accelerometerLabel": "Акселерометър",
|
||||
"telemetry_pressureLabel": "Налягане",
|
||||
"telemetry_altitudeLabel": "Надморска височина",
|
||||
"telemetry_frequencyLabel": "Честота",
|
||||
"telemetry_percentageLabel": "Процент",
|
||||
"telemetry_concentrationLabel": "Концентрация",
|
||||
"telemetry_powerLabel": "Мощност",
|
||||
"telemetry_distanceLabel": "Разстояние",
|
||||
"telemetry_energyLabel": "Енергия",
|
||||
"telemetry_directionLabel": "Посока",
|
||||
"telemetry_timeLabel": "Време",
|
||||
"telemetry_gyrometerLabel": "Жироскоп",
|
||||
"telemetry_colourLabel": "Цвят",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Превключвател",
|
||||
"telemetry_polylineLabel": "Полилиния",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Брой заявки",
|
||||
"telemetry_error": "Неуспешно получаване на данни",
|
||||
"telemetry_noData": "Няма налични данни за телеметрията.",
|
||||
"telemetry_channelTitle": "Канал {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2138,9 +2099,6 @@
|
||||
"translation_composerTitle": "Преведете преди да изпратите",
|
||||
"translation_enableSubtitle": "Превеждайте входящите съобщения и позволявайте предварително превеждане преди изпращане.",
|
||||
"translation_composerSubtitle": "Контролира началния статус на иконата за превод, създадена от композитора.",
|
||||
"translation_autoIncomingTitle": "Автоматичен превод на съобщения",
|
||||
"translation_autoIncomingSubtitle": "Превежда автоматично съобщенията за известия, както и за чатове или канали.",
|
||||
"translation_translateMessage": "Преведи съобщението",
|
||||
"translation_targetLanguage": "Целеви език",
|
||||
"translation_useAppLanguage": "Използвайте езика на приложението",
|
||||
"translation_downloadedModelLabel": "Изтегнат модел",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_newMessages": "Нови съобщения",
|
||||
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
|
||||
"repeater_chanUtil": "Използване на канала",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_pending": "Изпращане",
|
||||
"common_undo": "Отмяни",
|
||||
"messageStatus_delivered": "Доставен",
|
||||
"messageStatus_sent": "Изпратено",
|
||||
"messageStatus_failed": "Не успях да изпратя",
|
||||
"messageStatus_repeated": "Слушах го многократно",
|
||||
"contacts_moreOptions": "Повече възможности",
|
||||
"contacts_searchOpen": "Търсене на контакти",
|
||||
"contacts_searchClose": "Затвори търсене",
|
||||
"routing_title": "Маршрутизиране",
|
||||
"routing_modeAuto": "Автомобил",
|
||||
"routing_modeFlood": "Наводнение",
|
||||
"routing_modeManual": "Ръководство",
|
||||
"routing_modeAutoHint": "Автоматично избира най-известния път, като при липса на информация, използва стратегия за \"запълване\" на празните пространства.",
|
||||
"routing_modeFloodHint": "Излъчване през всички ретранслатори. Най-надежният начин, но изисква повече време на въздуха.",
|
||||
"routing_modeManualHint": "Винаги следва точно пътя, който сте определили.",
|
||||
"routing_currentRoute": "Текущ маршрут",
|
||||
"routing_directNoHops": "Директ – без превключватели",
|
||||
"routing_noPathYet": "Все още няма път. Съобщението продължава да се изпраща, докато не бъде открит маршрут.",
|
||||
"routing_floodBroadcast": "Предаване през всички ретранслатори",
|
||||
"routing_editPath": "Редактиране на пътя",
|
||||
"routing_forgetPath": "Забравете за пътя",
|
||||
"routing_knownPaths": "Известни маршрути",
|
||||
"routing_knownPathsHint": "Натиснете бутона, за да превключите към него.",
|
||||
"routing_inUse": "В експлоатация",
|
||||
"routing_qualityStrong": "Силен първи скок",
|
||||
"routing_qualityGood": "Добър първи опит",
|
||||
"routing_qualityFair": "Първият добър скок",
|
||||
"routing_qualityWorked": "Беше изпълнено/Доведено до край",
|
||||
"routing_qualityFlood": "Получено чрез информация, разпространена в резултат на навод.",
|
||||
"routing_qualityUntested": "Не тестван",
|
||||
"routing_neverWorked": "никога не е потвърдено",
|
||||
"routing_floodDelivery": "Доставка при навод",
|
||||
"pathEditor_title": "Създаване на път",
|
||||
"pathEditor_hopCounter": "{count} от 64 различни вида малц",
|
||||
"pathEditor_noHops": "Все още няма добавени хмел. Можете да използвате бутоните по-долу, за да ги добавите по ред, или да запазите рецептата без хмел, за да я изпратите директно.",
|
||||
"pathEditor_addHops": "Добавете хмела в реда, в който е посочено.",
|
||||
"pathEditor_searchRepeaters": "Търсене на повтори",
|
||||
"pathEditor_advancedHex": "Разширено: необработен шестничен път",
|
||||
"pathEditor_hexLabel": "Префикси на шестнадесетична система",
|
||||
"pathEditor_hexHelper": "Два шест-символни идентификатора на скок, разделени със запетаи",
|
||||
"pathEditor_invalidTokens": "Невалидно: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_tooManyHops": "Максимум 64 крачета",
|
||||
"pathEditor_usePath": "Използвайте този маршрут.",
|
||||
"pathEditor_removeHop": "Премахнете хмела",
|
||||
"pathEditor_unknownHop": "Неизвестен репитер",
|
||||
"map_zoomIn": "Увеличи",
|
||||
"map_zoomOut": "Приближете се по-малко",
|
||||
"map_centerMap": "Карта на центъра",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth изисква браузър, базиран на Chromium.",
|
||||
"channels_communityShortId": "Идентификационен номер: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS потвърдено",
|
||||
"pathTrace_legendInferred": "Извлечена позиция"
|
||||
"dialog_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.",
|
||||
"dialog_disconnectedTitle": "Прекъснато",
|
||||
"dialog_disconnectedMessage": "Свързването ви с вашия спътник е прекъснато.",
|
||||
"contact_connectCompanion": "Свържете се с спътник, за да получите достъп до функциите на repeater и room server."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Löschen",
|
||||
"common_enable": "Aktivieren",
|
||||
"common_disable": "Deaktivieren",
|
||||
"common_autoRefresh": "Automatische Aktualisierung",
|
||||
"common_interval": "Intervall",
|
||||
"common_reboot": "Neustart",
|
||||
"common_loading": "Laden...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digitaleingang",
|
||||
"telemetry_digitalOutputLabel": "Digitalausgang",
|
||||
"telemetry_analogInputLabel": "Analogeingang",
|
||||
"telemetry_analogOutputLabel": "Analogausgang",
|
||||
"telemetry_genericLabel": "Allgemeiner Sensor",
|
||||
"telemetry_luminosityLabel": "Helligkeit",
|
||||
"telemetry_presenceLabel": "Anwesenheit",
|
||||
"telemetry_humidityLabel": "Luftfeuchtigkeit",
|
||||
"telemetry_accelerometerLabel": "Beschleunigungsmesser",
|
||||
"telemetry_pressureLabel": "Druck",
|
||||
"telemetry_altitudeLabel": "Höhe",
|
||||
"telemetry_frequencyLabel": "Frequenz",
|
||||
"telemetry_percentageLabel": "Prozentsatz",
|
||||
"telemetry_concentrationLabel": "Konzentration",
|
||||
"telemetry_powerLabel": "Leistung",
|
||||
"telemetry_distanceLabel": "Entfernung",
|
||||
"telemetry_energyLabel": "Energie",
|
||||
"telemetry_directionLabel": "Richtung",
|
||||
"telemetry_timeLabel": "Zeit",
|
||||
"telemetry_gyrometerLabel": "Gyroskop",
|
||||
"telemetry_colourLabel": "Farbe",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Schalter",
|
||||
"telemetry_polylineLabel": "Polylinie",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Anzahl der Anfragen",
|
||||
"telemetry_error": "Daten konnten nicht abgerufen werden",
|
||||
"telemetry_noData": "Keine Telemetriedaten verfügbar.",
|
||||
"telemetry_channelTitle": "Kanal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2166,9 +2127,6 @@
|
||||
"translation_enableSubtitle": "Nachrichten empfangen und übersetzen sowie die Möglichkeit bieten, Nachrichten vor dem Versenden zu übersetzen.",
|
||||
"translation_enableTitle": "Aktivieren Sie die Übersetzung",
|
||||
"translation_composerSubtitle": "Steuert den Standardzustand des Icons für die Übersetzung des Komponisten.",
|
||||
"translation_autoIncomingTitle": "Nachrichten automatisch übersetzen",
|
||||
"translation_autoIncomingSubtitle": "Übersetzt Nachrichten für Benachrichtigungen sowie für Chats oder Kanäle automatisch.",
|
||||
"translation_translateMessage": "Nachricht übersetzen",
|
||||
"translation_targetLanguage": "Zielsprache",
|
||||
"translation_useAppLanguage": "Verwenden Sie die App-Sprache",
|
||||
"translation_downloadedModelLabel": "Heruntergeladenes Modell",
|
||||
@@ -2382,97 +2340,8 @@
|
||||
"settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware",
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten",
|
||||
"repeater_chanUtil": "Nutzung des Kanals",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "Gesendet",
|
||||
"messageStatus_delivered": "Geliefert",
|
||||
"common_undo": "Rückgängig machen",
|
||||
"messageStatus_pending": "Versenden",
|
||||
"messageStatus_failed": "Nicht gesendet",
|
||||
"messageStatus_repeated": "Wiederholt gehört",
|
||||
"contacts_moreOptions": "Weitere Optionen",
|
||||
"contacts_searchOpen": "Kontakte suchen",
|
||||
"contacts_searchClose": "Erweiterte Suche",
|
||||
"routing_title": "Routenplanung",
|
||||
"routing_modeAuto": "Auto",
|
||||
"routing_modeFlood": "Überschwemmung",
|
||||
"routing_modeManual": "Handbuch",
|
||||
"routing_modeFloodHint": "Übertragung über alle Repeater. Die zuverlässigste Methode, jedoch mit höherem Datenverbrauch.",
|
||||
"routing_modeAutoHint": "Wählt automatisch den bekanntesten Pfad aus und verwendet eine Flutungsmethode, wenn kein Pfad bekannt ist.",
|
||||
"routing_modeManualHint": "Sendet immer genau den von Ihnen festgelegten Weg.",
|
||||
"routing_currentRoute": "Aktuelle Route",
|
||||
"routing_directNoHops": "Direkt – ohne Zwischenverstärkung",
|
||||
"routing_noPathYet": "Noch kein Pfad gefunden. Die Nachricht wird gesendet, bis ein Weg entdeckt wurde.",
|
||||
"routing_floodBroadcast": "Übertragung über jeden Repeater",
|
||||
"routing_editPath": "Pfad bearbeiten",
|
||||
"routing_forgetPath": "Vergiss den Weg",
|
||||
"routing_knownPaths": "Bekannte Routen",
|
||||
"routing_knownPathsHint": "Wählen Sie den Pfad, um zu diesem zu wechseln.",
|
||||
"routing_inUse": "Im Gebrauch",
|
||||
"routing_qualityStrong": "Ein starker erster Sprung",
|
||||
"routing_qualityGood": "Ein guter erster Schritt",
|
||||
"routing_qualityFair": "Erster erfolgreicher Schritt",
|
||||
"routing_qualityWorked": "Hat erfolgreich geliefert",
|
||||
"routing_qualityFlood": "Information erhalten durch Nachrichten über die Überschwemmung",
|
||||
"routing_qualityUntested": "Nicht getestet",
|
||||
"routing_lastWorked": "war beschäftigt {when}",
|
||||
"routing_neverWorked": "nie bestätigt",
|
||||
"routing_floodDelivery": "Lieferung bei Überschwemmung",
|
||||
"pathEditor_title": "Pfad erstellen",
|
||||
"pathEditor_hopCounter": "{count} von 64 Hopfengewächsen",
|
||||
"pathEditor_noHops": "Noch keine Hopfen hinzugefügt. Klicken Sie auf die Schaltflächen unten, um sie nacheinander hinzuzufügen, oder speichern Sie die Rezepter ohne Hopfen, um sie direkt zu versenden.",
|
||||
"pathEditor_addHops": "Fügen Sie die Hopfen in der richtigen Reihenfolge hinzu.",
|
||||
"pathEditor_searchRepeaters": "Suche nach wiederholten Nachrichten",
|
||||
"pathEditor_advancedHex": "Fortgeschritten: Roh-Hex-Pfad",
|
||||
"pathEditor_hexLabel": "Hex-Präfixe",
|
||||
"pathEditor_hexHelper": "Zwei Hexadezimalzeichen pro Sprung, getrennt durch Kommas",
|
||||
"pathEditor_invalidTokens": "Ungültig: {tokens}",
|
||||
"pathEditor_tooManyHops": "Maximal 64 Hopfengreifer",
|
||||
"pathEditor_usePath": "Verwenden Sie diesen Pfad.",
|
||||
"pathEditor_removeHop": "Hop entfernen",
|
||||
"pathEditor_unknownHop": "Unbekannter Repeater",
|
||||
"map_zoomIn": "Zoomen",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Auszoomen",
|
||||
"map_centerMap": "Zentralkarte",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth benötigt einen Chromium-Browser.",
|
||||
"channels_communityShortId": "ID: {id}…",
|
||||
"pathTrace_legendGpsConfirmed": "GPS-Bestätigung",
|
||||
"pathTrace_legendInferred": "Abgeleitete Position"
|
||||
"dialog_connectCompanion": "Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.",
|
||||
"dialog_disconnectedTitle": "Getrennt",
|
||||
"dialog_disconnectedMessage": "Du wurdest von deinem Begleiter getrennt.",
|
||||
"contact_connectCompanion": "Mit einem Companion verbinden, um auf Repeater- und Raumserver-Funktionen zuzugreifen."
|
||||
}
|
||||
|
||||
+75
-301
@@ -28,12 +28,6 @@
|
||||
"common_remove": "Remove",
|
||||
"common_enable": "Enable",
|
||||
"common_disable": "Disable",
|
||||
"common_undo": "Undo",
|
||||
"messageStatus_sent": "Sent",
|
||||
"messageStatus_delivered": "Delivered",
|
||||
"messageStatus_pending": "Sending",
|
||||
"messageStatus_failed": "Failed to send",
|
||||
"messageStatus_repeated": "Heard repeated",
|
||||
"common_reboot": "Reboot",
|
||||
"common_loading": "Loading...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -53,8 +47,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_autoRefresh": "Autorefresh",
|
||||
"common_interval": "Interval",
|
||||
"scanner_title": "MeshCore Open",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
@@ -143,7 +135,6 @@
|
||||
"scanner_chromeRequired": "Chrome Browser Required",
|
||||
"scanner_chromeRequiredMessage": "This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.",
|
||||
"scanner_enableBluetooth": "Enable Bluetooth",
|
||||
"scanner_bluetoothWebUnsupported": "Bluetooth isn't available in the browser. Connect over USB instead.",
|
||||
"device_quickSwitch": "Quick switch",
|
||||
"device_meshcore": "MeshCore",
|
||||
"settings_title": "Settings",
|
||||
@@ -304,6 +295,17 @@
|
||||
"appSettings_routeWeightFailureDecrementSubtitle": "Weight removed from a path after failed delivery",
|
||||
"appSettings_maxMessageRetries": "Max Message Retries",
|
||||
"appSettings_maxMessageRetriesSubtitle": "Number of retry attempts before marking a message as failed",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"@path_routeWeight": {
|
||||
"placeholders": {
|
||||
"weight": {
|
||||
"type": "String"
|
||||
},
|
||||
"max": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_battery": "Battery",
|
||||
"appSettings_batteryChemistry": "Battery Chemistry",
|
||||
"appSettings_batteryChemistryPerDevice": "Set per device ({deviceName})",
|
||||
@@ -449,9 +451,6 @@
|
||||
}
|
||||
},
|
||||
"contacts_newGroup": "New Group",
|
||||
"contacts_moreOptions": "More options",
|
||||
"contacts_searchOpen": "Search contacts",
|
||||
"contacts_searchClose": "Close search",
|
||||
"contacts_groupName": "Group name",
|
||||
"contacts_groupNameRequired": "Group name is required",
|
||||
"contacts_groupNameReserved": "This group name is reserved",
|
||||
@@ -776,6 +775,15 @@
|
||||
}
|
||||
},
|
||||
"debugFrame_hexDump": "Hex Dump:",
|
||||
"chat_pathManagement": "Path Management",
|
||||
"chat_ShowAllPaths": "Show all paths",
|
||||
"chat_routingMode": "Routing mode",
|
||||
"chat_autoUseSavedPath": "Auto (use saved path)",
|
||||
"chat_forceFloodMode": "Force Flood Mode",
|
||||
"chat_recentAckPaths": "Recent ACK Paths (tap to use):",
|
||||
"chat_pathHistoryFull": "Path history is full. Remove entries to add new ones.",
|
||||
"chat_hopSingular": "hop",
|
||||
"chat_hopPlural": "hops",
|
||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
||||
"@chat_hopsCount": {
|
||||
"placeholders": {
|
||||
@@ -784,80 +792,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_successes": "successes",
|
||||
"chat_score": "Score",
|
||||
"chat_removePath": "Remove path",
|
||||
"chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.",
|
||||
"chat_pathActions": "Path Actions:",
|
||||
"chat_setCustomPath": "Set Custom Path",
|
||||
"chat_setCustomPathSubtitle": "Manually specify routing path",
|
||||
"chat_clearPath": "Clear Path",
|
||||
"chat_clearPathSubtitle": "Force rediscovery on next send",
|
||||
"chat_pathCleared": "Path cleared. Next message will rediscover route.",
|
||||
"chat_floodModeSubtitle": "Use routing toggle in app bar",
|
||||
"chat_floodModeEnabled": "Flood mode enabled. Toggle back via routing icon in app bar.",
|
||||
"chat_fullPath": "Full Path",
|
||||
"routing_title": "Routing",
|
||||
"routing_modeAuto": "Auto",
|
||||
"routing_modeFlood": "Flood",
|
||||
"routing_modeManual": "Manual",
|
||||
"routing_modeAutoHint": "Picks the best known path automatically, flooding when none is known.",
|
||||
"routing_modeFloodHint": "Broadcasts through every repeater. Most reliable, but uses more airtime.",
|
||||
"routing_modeManualHint": "Always sends along the exact path you set.",
|
||||
"routing_currentRoute": "Current route",
|
||||
"routing_directNoHops": "Direct — no repeater hops",
|
||||
"routing_noPathYet": "No path yet. The next message floods until a route is discovered.",
|
||||
"routing_floodBroadcast": "Broadcast through every repeater",
|
||||
"routing_editPath": "Edit path",
|
||||
"routing_forgetPath": "Forget path",
|
||||
"routing_knownPaths": "Known paths",
|
||||
"routing_knownPathsHint": "Tap a path to switch to it.",
|
||||
"routing_inUse": "In use",
|
||||
"routing_qualityStrong": "Strong first hop",
|
||||
"routing_qualityGood": "Good first hop",
|
||||
"routing_qualityFair": "Fair first hop",
|
||||
"routing_qualityWorked": "Has delivered",
|
||||
"routing_qualityFlood": "Heard via flood",
|
||||
"routing_qualityUntested": "Untested",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"@routing_lastWorked": {
|
||||
"chat_pathDetailsNotAvailable": "Path details not available yet. Try sending a message to refresh.",
|
||||
"chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
"@chat_pathSetHops": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing_neverWorked": "never confirmed",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"hopCount": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing_floodDelivery": "Flood delivery",
|
||||
"pathEditor_title": "Build Path",
|
||||
"pathEditor_hopCounter": "{count} of 64 hops",
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathEditor_noHops": "No hops yet. Tap repeaters below to add them in order, or save with no hops to send direct.",
|
||||
"pathEditor_addHops": "Add hops in order",
|
||||
"pathEditor_searchRepeaters": "Search repeaters",
|
||||
"pathEditor_advancedHex": "Advanced: raw hex path",
|
||||
"pathEditor_hexLabel": "Hex prefixes",
|
||||
"pathEditor_hexHelper": "Two hex characters per hop, separated by commas",
|
||||
"pathEditor_invalidTokens": "Invalid: {tokens}",
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"status": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathEditor_tooManyHops": "Maximum 64 hops",
|
||||
"pathEditor_usePath": "Use this path",
|
||||
"pathEditor_removeHop": "Remove hop",
|
||||
"pathEditor_unknownHop": "Unknown repeater",
|
||||
"chat_pathSavedLocally": "Saved locally. Connect to sync.",
|
||||
"chat_pathDeviceConfirmed": "Device confirmed.",
|
||||
"chat_pathDeviceNotConfirmed": "Device not confirmed yet.",
|
||||
@@ -901,17 +860,6 @@
|
||||
},
|
||||
"chat_invalidLink": "Invalid link format",
|
||||
"map_title": "Node Map",
|
||||
"map_searchHint": "Search node name or ID",
|
||||
"map_activity": "Activity",
|
||||
"map_online": "Online",
|
||||
"map_recent": "Recent",
|
||||
"map_stale": "Stale",
|
||||
"map_visible": "Visible",
|
||||
"map_hidden": "Hidden",
|
||||
"map_centerOnNode": "Center on node",
|
||||
"map_details": "Details",
|
||||
"map_noGps": "No GPS",
|
||||
"map_noResults": "No matching nodes",
|
||||
"map_lineOfSight": "Line of Sight",
|
||||
"map_losScreenTitle": "Line of Sight",
|
||||
"map_noNodesWithLocation": "No nodes with location data",
|
||||
@@ -1114,6 +1062,9 @@
|
||||
"time_allTime": "All Time",
|
||||
"dialog_disconnect": "Disconnect",
|
||||
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
||||
"dialog_disconnectedTitle": "Disconnected",
|
||||
"dialog_disconnectedMessage": "You have been disconnected from your companion.",
|
||||
"dialog_connectCompanion": "Connect to a companion to access repeater and room server features.",
|
||||
"login_repeaterLogin": "Repeater Login",
|
||||
"login_roomLogin": "Room Server Login",
|
||||
"login_password": "Password",
|
||||
@@ -1150,8 +1101,41 @@
|
||||
"login_failedMessage": "Login failed. Either the password is incorrect or the repeater is unreachable.",
|
||||
"common_reload": "Reload",
|
||||
"common_clear": "Clear",
|
||||
"path_currentPath": "Current path: {path}",
|
||||
"@path_currentPath": {
|
||||
"placeholders": {
|
||||
"path": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"path_usingHopsPath": "Using {count} {count, plural, =1{hop} other{hops}} path",
|
||||
"@path_usingHopsPath": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"path_enterCustomPath": "Enter Custom Path",
|
||||
"path_currentPathLabel": "Current path",
|
||||
"path_hexPrefixInstructions": "Enter 2-character hex prefixes for each hop, separated by commas.",
|
||||
"path_hexPrefixExample": "Example: A1,F2,3C (each node uses first byte of its public key)",
|
||||
"path_labelHexPrefixes": "Path (hex prefixes)",
|
||||
"path_helperMaxHops": "Max 64 hops. Each prefix is 2 hex characters (1 byte)",
|
||||
"path_selectFromContacts": "Or select from contacts:",
|
||||
"path_noRepeatersFound": "No repeaters or room servers found.",
|
||||
"path_customPathsRequire": "Custom paths require intermediate hops that can relay messages.",
|
||||
"path_invalidHexPrefixes": "Invalid hex prefixes: {prefixes}",
|
||||
"@path_invalidHexPrefixes": {
|
||||
"placeholders": {
|
||||
"prefixes": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"path_tooLong": "Path too long. Maximum 64 hops allowed.",
|
||||
"path_setPath": "Set Path",
|
||||
"repeater_management": "Repeater Management",
|
||||
"room_management": "Room Server Management",
|
||||
"repeater_guest": "Repeater Information",
|
||||
@@ -1178,6 +1162,9 @@
|
||||
},
|
||||
"repeater_statusTitle": "Repeater Status",
|
||||
"repeater_routingMode": "Routing mode",
|
||||
"repeater_autoUseSavedPath": "Auto (use saved path)",
|
||||
"repeater_forceFloodMode": "Force Flood Mode",
|
||||
"repeater_pathManagement": "Path management",
|
||||
"repeater_refresh": "Refresh",
|
||||
"repeater_statusRequestTimeout": "Status request timed out.",
|
||||
"repeater_errorLoadingStatus": "Error loading status: {error}",
|
||||
@@ -1677,120 +1664,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digital Input",
|
||||
"telemetry_digitalOutputLabel": "Digital Output",
|
||||
"telemetry_analogInputLabel": "Analog Input",
|
||||
"telemetry_analogOutputLabel": "Analog Output",
|
||||
"telemetry_genericLabel": "Generic Sensor",
|
||||
"telemetry_luminosityLabel": "Luminosity",
|
||||
"telemetry_presenceLabel": "Presence",
|
||||
"telemetry_humidityLabel": "Humidity",
|
||||
"telemetry_accelerometerLabel": "Accelerometer",
|
||||
"telemetry_pressureLabel": "Pressure",
|
||||
"telemetry_altitudeLabel": "Altitude",
|
||||
"telemetry_frequencyLabel": "Frequency",
|
||||
"telemetry_percentageLabel": "Percentage",
|
||||
"telemetry_concentrationLabel": "Concentration",
|
||||
"telemetry_powerLabel": "Power",
|
||||
"telemetry_distanceLabel": "Distance",
|
||||
"telemetry_energyLabel": "Energy",
|
||||
"telemetry_directionLabel": "Direction",
|
||||
"telemetry_timeLabel": "Time",
|
||||
"telemetry_gyrometerLabel": "Gyrometer",
|
||||
"telemetry_colourLabel": "Colour",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Switch",
|
||||
"telemetry_polylineLabel": "Polyline",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"@telemetry_altitudeValue": {
|
||||
"placeholders": {
|
||||
"meters": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"@telemetry_frequencyValue": {
|
||||
"placeholders": {
|
||||
"hertz": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"@telemetry_pressureValue": {
|
||||
"placeholders": {
|
||||
"hpa": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"@telemetry_luminosityValue": {
|
||||
"placeholders": {
|
||||
"lux": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"@telemetry_powerValue": {
|
||||
"placeholders": {
|
||||
"watts": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"@telemetry_distanceValue": {
|
||||
"placeholders": {
|
||||
"meters": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"@telemetry_energyValue": {
|
||||
"placeholders": {
|
||||
"kilowattHours": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"@telemetry_directionValue": {
|
||||
"placeholders": {
|
||||
"degrees": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"@telemetry_concentrationValue": {
|
||||
"placeholders": {
|
||||
"ppm": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"@telemetry_percentageValue": {
|
||||
"placeholders": {
|
||||
"percent": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_analogValue": "{value}",
|
||||
"@telemetry_analogValue": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_autoFetchQuantity": "Requests quantity",
|
||||
"telemetry_error": "Unable to retrieve data",
|
||||
"neighbors_receivedData": "Received Neighbors Data",
|
||||
"neighbors_requestTimedOut": "Neighbors request timed out.",
|
||||
"neighbors_errorLoading": "Error loading neighbors: {error}",
|
||||
@@ -2418,9 +2291,6 @@
|
||||
"translation_enableSubtitle": "Translate incoming messages and allow pre-send translation.",
|
||||
"translation_composerTitle": "Translate before sending",
|
||||
"translation_composerSubtitle": "Controls the default state of the composer translation icon.",
|
||||
"translation_autoIncomingTitle": "Auto-translate incoming messages",
|
||||
"translation_autoIncomingSubtitle": "Translates Messages for notification and for chat or channel automatically.",
|
||||
"translation_translateMessage": "Translate message",
|
||||
"translation_targetLanguage": "Target language",
|
||||
"translation_useAppLanguage": "Use app language",
|
||||
"translation_downloadedModelLabel": "Downloaded model",
|
||||
@@ -2499,101 +2369,5 @@
|
||||
"contact_typeRepeater": "Repeater",
|
||||
"contact_typeRoom": "Room",
|
||||
"contact_typeSensor": "Sensor",
|
||||
"contact_typeUnknown": "Unknown",
|
||||
"map_zoomIn": "Zoom in",
|
||||
"map_zoomOut": "Zoom out",
|
||||
"map_centerMap": "Center map",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth requires a Chromium browser",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_legendGpsConfirmed": "GPS confirmed",
|
||||
"pathTrace_legendInferred": "Inferred position",
|
||||
"pathMap_viewSingle": "Single",
|
||||
"pathMap_viewCombined": "Combined",
|
||||
"pathMap_play": "Play",
|
||||
"pathMap_pause": "Pause",
|
||||
"pathMap_replay": "Replay",
|
||||
"pathMap_stepBack": "Previous hop",
|
||||
"pathMap_stepForward": "Next hop",
|
||||
"pathMap_animationOn": "Show packet animation",
|
||||
"pathMap_animationOff": "Hide packet animation",
|
||||
"pathMap_hopOf": "Hop {current} of {total}",
|
||||
"@pathMap_hopOf": {
|
||||
"placeholders": {
|
||||
"current": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_observedPaths": "Observed paths: {count}",
|
||||
"@pathMap_observedPaths": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_primary": "Primary",
|
||||
"pathMap_alternate": "Alt {index}",
|
||||
"@pathMap_alternate": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_hopCount": "{count, plural, =1{1 hop} other{{count} hops}}",
|
||||
"@pathMap_hopCount": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_gpsCount": "{confirmed}/{total} GPS",
|
||||
"@pathMap_gpsCount": {
|
||||
"placeholders": {
|
||||
"confirmed": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_legendShared": "Shared segment",
|
||||
"pathMap_legendEstimated": "Estimated segment",
|
||||
"pathMap_sharedNodeCount": "Used by {count} paths",
|
||||
"@pathMap_sharedNodeCount": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_partialAnimation": "{count, plural, =1{1 hop has no location — the shown path is partial} other{{count} hops have no location — the shown path is partial}}",
|
||||
"@pathMap_partialAnimation": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathMap_showAllPaths": "Show all",
|
||||
"pathMap_hidePath": "Hide path",
|
||||
"pathMap_showPath": "Show path",
|
||||
"pathMap_collapsePanel": "Collapse panel",
|
||||
"pathMap_expandPanel": "Expand panel",
|
||||
"pathMap_noLocation": "No location",
|
||||
"pathMap_followPacket": "Lock view to packet",
|
||||
"pathMap_unfollowPacket": "Unlock view from packet"
|
||||
}
|
||||
"contact_typeUnknown": "Unknown"
|
||||
}
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Eliminar",
|
||||
"common_enable": "Activar",
|
||||
"common_disable": "Desactivar",
|
||||
"common_autoRefresh": "Actualización automática",
|
||||
"common_interval": "Intervalo",
|
||||
"common_reboot": "Reiniciar",
|
||||
"common_loading": "Cargando...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Entrada digital",
|
||||
"telemetry_digitalOutputLabel": "Salida digital",
|
||||
"telemetry_analogInputLabel": "Entrada analógica",
|
||||
"telemetry_analogOutputLabel": "Salida analógica",
|
||||
"telemetry_genericLabel": "Sensor genérico",
|
||||
"telemetry_luminosityLabel": "Luminosidad",
|
||||
"telemetry_presenceLabel": "Presencia",
|
||||
"telemetry_humidityLabel": "Humedad",
|
||||
"telemetry_accelerometerLabel": "Acelerómetro",
|
||||
"telemetry_pressureLabel": "Presión",
|
||||
"telemetry_altitudeLabel": "Altitud",
|
||||
"telemetry_frequencyLabel": "Frecuencia",
|
||||
"telemetry_percentageLabel": "Porcentaje",
|
||||
"telemetry_concentrationLabel": "Concentración",
|
||||
"telemetry_powerLabel": "Potencia",
|
||||
"telemetry_distanceLabel": "Distancia",
|
||||
"telemetry_energyLabel": "Energía",
|
||||
"telemetry_directionLabel": "Dirección",
|
||||
"telemetry_timeLabel": "Hora",
|
||||
"telemetry_gyrometerLabel": "Girómetro",
|
||||
"telemetry_colourLabel": "Color",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Interruptor",
|
||||
"telemetry_polylineLabel": "Polilínea",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Número de solicitudes",
|
||||
"telemetry_error": "No se pudieron obtener los datos",
|
||||
"telemetry_noData": "No hay datos de telemetría disponibles.",
|
||||
"telemetry_channelTitle": "Canal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2167,9 +2128,6 @@
|
||||
"translation_enableTitle": "Habilitar la traducción",
|
||||
"translation_composerTitle": "Traducir antes de enviar",
|
||||
"translation_composerSubtitle": "Controla el estado predeterminado del icono de traducción del compositor.",
|
||||
"translation_autoIncomingTitle": "Traducir mensajes automáticamente",
|
||||
"translation_autoIncomingSubtitle": "Traduce mensajes para notificaciones y para chats o canales automáticamente.",
|
||||
"translation_translateMessage": "Traducir mensaje",
|
||||
"translation_targetLanguage": "Idioma de destino",
|
||||
"translation_useAppLanguage": "Utilizar el idioma de la aplicación",
|
||||
"translation_downloadedModelLabel": "Modelo descargado",
|
||||
@@ -2382,97 +2340,8 @@
|
||||
"settings_companionDebugLogSubtitle": "Comandos, respuestas y datos brutos para protocolos BLE/TCP/USB",
|
||||
"chat_markAsUnread": "Marcar como no leído",
|
||||
"repeater_chanUtil": "Utilización del canal",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_pending": "Enviar",
|
||||
"common_undo": "Deshacer",
|
||||
"messageStatus_sent": "Sentido",
|
||||
"messageStatus_delivered": "Entregado",
|
||||
"messageStatus_failed": "No se pudo enviar",
|
||||
"messageStatus_repeated": "Escuché repetidamente",
|
||||
"contacts_moreOptions": "Más opciones",
|
||||
"contacts_searchOpen": "Buscar contactos",
|
||||
"contacts_searchClose": "Búsqueda avanzada",
|
||||
"routing_title": "Ruteo",
|
||||
"routing_modeAuto": "Coche",
|
||||
"routing_modeFlood": "Inundación",
|
||||
"routing_modeManual": "Manual",
|
||||
"routing_modeAutoHint": "Selecciona automáticamente la ruta más conocida, y si no hay ninguna ruta conocida, utiliza la ruta más directa.",
|
||||
"routing_modeFloodHint": "Transmisiones a través de todos los repetidores. Es la opción más fiable, pero utiliza más tiempo de transmisión.",
|
||||
"routing_modeManualHint": "Siempre sigue exactamente la ruta que usted ha definido.",
|
||||
"routing_currentRoute": "Ruta actual",
|
||||
"routing_directNoHops": "Directo — sin saltos de repetidor",
|
||||
"routing_noPathYet": "Aún no hay un camino definido. El mensaje se envía continuamente hasta que se encuentre una ruta.",
|
||||
"routing_floodBroadcast": "Transmisión a través de todos los repetidores.",
|
||||
"routing_editPath": "Editar ruta",
|
||||
"routing_forgetPath": "Olvídate del camino",
|
||||
"routing_knownPaths": "Rutas conocidas",
|
||||
"routing_knownPathsHint": "Seleccione una opción para cambiar a esa.",
|
||||
"routing_inUse": "En uso",
|
||||
"routing_qualityStrong": "Primer salto exitoso",
|
||||
"routing_qualityGood": "Primer paso exitoso",
|
||||
"routing_qualityWorked": "Ha cumplido",
|
||||
"routing_qualityFair": "Primer salto de calidad",
|
||||
"routing_qualityFlood": "Se ha escuchado a través de rumores.",
|
||||
"routing_qualityUntested": "Sin probar",
|
||||
"routing_lastWorked": "trabajó {when}",
|
||||
"routing_neverWorked": "nunca confirmado",
|
||||
"routing_floodDelivery": "Entrega por inundación",
|
||||
"pathEditor_title": "Crear ruta",
|
||||
"pathEditor_hopCounter": "{count} de 64 granos de lúpulo",
|
||||
"pathEditor_noHops": "Aún no se han añadido lúpulos. Haga clic en los repetidores para añadirlos en el orden deseado, o guarde la receta sin lúpulos para enviarla directamente.",
|
||||
"pathEditor_addHops": "Añadir los lúpulos en el orden adecuado.",
|
||||
"pathEditor_searchRepeaters": "Buscar repetidores",
|
||||
"pathEditor_advancedHex": "Avanzado: ruta hexadecimal sin procesar",
|
||||
"pathEditor_hexLabel": "Prefijos hexadecimales",
|
||||
"pathEditor_hexHelper": "Dos caracteres hexadecimales por salto, separados por comas.",
|
||||
"pathEditor_invalidTokens": "Inválido: {tokens}",
|
||||
"pathEditor_tooManyHops": "Máximo 64 saltos",
|
||||
"pathEditor_usePath": "Utilice esta ruta.",
|
||||
"pathEditor_removeHop": "Eliminar el lúpulo",
|
||||
"pathEditor_unknownHop": "Repetidor desconocido",
|
||||
"map_zoomIn": "Acercar",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Acercar",
|
||||
"map_centerMap": "Mapa del centro",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth requiere un navegador Chromium.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "Confirmado mediante GPS",
|
||||
"pathTrace_legendInferred": "Posición inferida"
|
||||
"dialog_connectCompanion": "Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.",
|
||||
"dialog_disconnectedTitle": "Desconectado",
|
||||
"dialog_disconnectedMessage": "Te has desconectado de tu compañero.",
|
||||
"contact_connectCompanion": "Conéctate a un compañero para acceder a las funciones del repetidor y del servidor de la sala."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Supprimer",
|
||||
"common_enable": "Activer",
|
||||
"common_disable": "Désactiver",
|
||||
"common_autoRefresh": "Actualisation automatique",
|
||||
"common_interval": "Intervalle",
|
||||
"common_reboot": "Redémarrer",
|
||||
"common_loading": "Chargement...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Entrée numérique",
|
||||
"telemetry_digitalOutputLabel": "Sortie numérique",
|
||||
"telemetry_analogInputLabel": "Entrée analogique",
|
||||
"telemetry_analogOutputLabel": "Sortie analogique",
|
||||
"telemetry_genericLabel": "Capteur générique",
|
||||
"telemetry_luminosityLabel": "Luminosité",
|
||||
"telemetry_presenceLabel": "Présence",
|
||||
"telemetry_humidityLabel": "Humidité",
|
||||
"telemetry_accelerometerLabel": "Accéléromètre",
|
||||
"telemetry_pressureLabel": "Pression",
|
||||
"telemetry_altitudeLabel": "Altitude",
|
||||
"telemetry_frequencyLabel": "Fréquence",
|
||||
"telemetry_percentageLabel": "Pourcentage",
|
||||
"telemetry_concentrationLabel": "Concentration",
|
||||
"telemetry_powerLabel": "Puissance",
|
||||
"telemetry_distanceLabel": "Distance",
|
||||
"telemetry_energyLabel": "Énergie",
|
||||
"telemetry_directionLabel": "Direction",
|
||||
"telemetry_timeLabel": "Heure",
|
||||
"telemetry_gyrometerLabel": "Gyromètre",
|
||||
"telemetry_colourLabel": "Couleur",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Interrupteur",
|
||||
"telemetry_polylineLabel": "Polyligne",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Nombre de requêtes",
|
||||
"telemetry_error": "Impossible de récupérer les données",
|
||||
"telemetry_noData": "Aucune donnée de télémétrie disponible.",
|
||||
"telemetry_channelTitle": "Canal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2138,9 +2099,6 @@
|
||||
"translation_title": "Traduction",
|
||||
"translation_enableSubtitle": "Traduire les messages entrants et permettre la traduction avant l'envoi.",
|
||||
"translation_composerSubtitle": "Contrôle l'état par défaut de l'icône de traduction du composant.",
|
||||
"translation_autoIncomingTitle": "Traduire automatiquement les messages",
|
||||
"translation_autoIncomingSubtitle": "Traduit automatiquement les messages pour les notifications et pour les discussions ou les canaux.",
|
||||
"translation_translateMessage": "Traduire le message",
|
||||
"translation_targetLanguage": "Langue cible",
|
||||
"translation_useAppLanguage": "Utiliser la langue de l'application",
|
||||
"translation_downloadedModelLabel": "Modèle téléchargé",
|
||||
@@ -2361,97 +2319,8 @@
|
||||
"chat_newMessages": "Nouveaux messages",
|
||||
"settings_companionDebugLogSubtitle": "Commandes, réponses et données brutes pour les protocoles BLE/TCP/USB",
|
||||
"repeater_chanUtil": "Utilisation du canal",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_undo": "Annuler",
|
||||
"messageStatus_sent": "Envoyer",
|
||||
"messageStatus_delivered": "Livré",
|
||||
"messageStatus_pending": "Envoyer",
|
||||
"messageStatus_failed": "Échec de l'envoi",
|
||||
"messageStatus_repeated": "Répété plusieurs fois",
|
||||
"contacts_searchOpen": "Rechercher des contacts",
|
||||
"contacts_moreOptions": "Plus d'options",
|
||||
"contacts_searchClose": "Recherche avancée",
|
||||
"routing_title": "Planification des itinéraires",
|
||||
"routing_modeAuto": "Voiture",
|
||||
"routing_modeFlood": "Inondation",
|
||||
"routing_modeManual": "Manuel",
|
||||
"routing_modeFloodHint": "Diffusion via tous les répéteurs. La méthode la plus fiable, mais qui utilise plus de temps d'antenne.",
|
||||
"routing_modeAutoHint": "Sélectionne automatiquement le chemin le plus connu, et utilise la méthode de \"inondation\" si aucun chemin n'est connu.",
|
||||
"routing_modeManualHint": "Il suit toujours le chemin précis que vous avez défini.",
|
||||
"routing_currentRoute": "Itinéraire actuel",
|
||||
"routing_directNoHops": "Direct — sans relais",
|
||||
"routing_noPathYet": "Aucune voie encore trouvée. Le message suivant est envoyé jusqu'à ce qu'une route soit découverte.",
|
||||
"routing_floodBroadcast": "Diffusion via tous les répéteurs",
|
||||
"routing_editPath": "Modifier le chemin",
|
||||
"routing_forgetPath": "Oubliez le chemin",
|
||||
"routing_knownPaths": "Chemins connus",
|
||||
"routing_knownPathsHint": "Créez un raccourci pour y accéder.",
|
||||
"routing_inUse": "En cours d'utilisation",
|
||||
"routing_qualityStrong": "Première étape réussie",
|
||||
"routing_qualityGood": "Première étape réussie",
|
||||
"routing_qualityFair": "Première étape réussie",
|
||||
"routing_qualityWorked": "A livré",
|
||||
"routing_qualityFlood": "Rapporté par des informations provenant de plusieurs sources.",
|
||||
"routing_qualityUntested": "Non testé",
|
||||
"routing_lastWorked": "a travaillé {when}",
|
||||
"routing_neverWorked": "jamais confirmé",
|
||||
"routing_floodDelivery": "Livraison en cas de inondation",
|
||||
"pathEditor_hopCounter": "{count} parmi 64 houblons",
|
||||
"pathEditor_title": "Créer un chemin",
|
||||
"pathEditor_noHops": "Aucun houblon ajouté pour le moment. Cliquez sur les répétiteurs ci-dessous pour les ajouter dans l'ordre souhaité, ou enregistrez sans houblon pour envoyer directement.",
|
||||
"pathEditor_addHops": "Ajoutez les houblons dans l'ordre souhaité.",
|
||||
"pathEditor_searchRepeaters": "Rechercher des répétiteurs",
|
||||
"pathEditor_advancedHex": "Avancé : chemin hexadécimal brut",
|
||||
"pathEditor_hexLabel": "Préfixes hexadécimaux",
|
||||
"pathEditor_hexHelper": "Deux caractères hexadécimaux par saut, séparés par des virgules.",
|
||||
"pathEditor_invalidTokens": "Incorrect : {tokens}",
|
||||
"pathEditor_tooManyHops": "Maximum 64 sauts",
|
||||
"pathEditor_usePath": "Utilisez ce chemin.",
|
||||
"pathEditor_removeHop": "Éliminer le haricot",
|
||||
"pathEditor_unknownHop": "Répéteur non identifié",
|
||||
"map_zoomIn": "Zoomez",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Zoomez",
|
||||
"map_centerMap": "Carte du centre",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth nécessite un navigateur Chromium.",
|
||||
"channels_communityShortId": "ID : {id}…",
|
||||
"pathTrace_legendGpsConfirmed": "Le GPS a confirmé.",
|
||||
"pathTrace_legendInferred": "Position déduite"
|
||||
"dialog_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.",
|
||||
"dialog_disconnectedTitle": "Déconnecté",
|
||||
"dialog_disconnectedMessage": "Vous avez été déconnecté de votre compagnon.",
|
||||
"contact_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités du répéteur et du serveur de salle."
|
||||
}
|
||||
|
||||
+4
-135
@@ -27,8 +27,6 @@
|
||||
"common_remove": "Eltávolít",
|
||||
"common_enable": "Engedélyezés",
|
||||
"common_disable": "Leteteszt",
|
||||
"common_autoRefresh": "Automatikus frissítés",
|
||||
"common_interval": "Intervallum",
|
||||
"common_reboot": "Újraindítás",
|
||||
"common_loading": "Betöltés...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1468,43 +1466,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digitális bemenet",
|
||||
"telemetry_digitalOutputLabel": "Digitális kimenet",
|
||||
"telemetry_analogInputLabel": "Analóg bemenet",
|
||||
"telemetry_analogOutputLabel": "Analóg kimenet",
|
||||
"telemetry_genericLabel": "Általános érzékelő",
|
||||
"telemetry_luminosityLabel": "Fényerő",
|
||||
"telemetry_presenceLabel": "Jelenlét",
|
||||
"telemetry_humidityLabel": "Páratartalom",
|
||||
"telemetry_accelerometerLabel": "Gyorsulásmérő",
|
||||
"telemetry_pressureLabel": "Nyomás",
|
||||
"telemetry_altitudeLabel": "Magasság",
|
||||
"telemetry_frequencyLabel": "Frekvencia",
|
||||
"telemetry_percentageLabel": "Százalék",
|
||||
"telemetry_concentrationLabel": "Koncentráció",
|
||||
"telemetry_powerLabel": "Teljesítmény",
|
||||
"telemetry_distanceLabel": "Távolság",
|
||||
"telemetry_energyLabel": "Energia",
|
||||
"telemetry_directionLabel": "Irány",
|
||||
"telemetry_timeLabel": "Idő",
|
||||
"telemetry_gyrometerLabel": "Giroszkóp",
|
||||
"telemetry_colourLabel": "Szín",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Kapcsoló",
|
||||
"telemetry_polylineLabel": "Töröttvonal",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Kérések száma",
|
||||
"telemetry_error": "Nem sikerült lekérni az adatokat",
|
||||
"telemetry_noData": "Nincsenek elérhető telemetriadatok.",
|
||||
"telemetry_channelTitle": "{channel} csatorna",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2176,9 +2137,6 @@
|
||||
"translation_enableSubtitle": "Fordítsa az érkező üzeneteket, és lehetővé tegye a küldés előtti fordítást.",
|
||||
"translation_composerTitle": "Fordítsa el, mielőtt elküldi",
|
||||
"translation_composerSubtitle": "Ellenőrzi a zeneszerző fordítási ikon alapértékét.",
|
||||
"translation_autoIncomingTitle": "Üzenetek automatikus fordítása",
|
||||
"translation_autoIncomingSubtitle": "Automatikusan lefordítja az üzeneteket az értesítésekhez, valamint a csevegésekhez vagy csatornákhoz.",
|
||||
"translation_translateMessage": "Üzenet fordítása",
|
||||
"translation_targetLanguage": "Célnyelv",
|
||||
"translation_useAppLanguage": "Használja az alkalmazás nyelvének beállítását.",
|
||||
"translation_downloadedModelLabel": "Letöltött modell",
|
||||
@@ -2392,97 +2350,8 @@
|
||||
"settings_companionDebugLog": "Párhuzamos hibakeresési napló",
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB parancsok, válaszok és alapvető adatok",
|
||||
"repeater_chanUtil": "Csatorna-használat",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_delivered": "Szállítva",
|
||||
"common_undo": "Még egyszer",
|
||||
"messageStatus_sent": "Elküldve",
|
||||
"messageStatus_pending": "Elküldés",
|
||||
"messageStatus_failed": "Nem sikerült elküldeni",
|
||||
"messageStatus_repeated": "Ismételtem",
|
||||
"contacts_moreOptions": "További lehetőségek",
|
||||
"contacts_searchOpen": "Keresssz kapcsolatokat",
|
||||
"contacts_searchClose": "Teljesítse a keresést",
|
||||
"routing_title": "Útvonal meghatározás",
|
||||
"routing_modeAuto": "Autó",
|
||||
"routing_modeFlood": "Áradás",
|
||||
"routing_modeManual": "Használati útmutató",
|
||||
"routing_modeAutoHint": "Automatikusan kiválasztja a legismertebb útvonalat, és ha egyik sem ismert, akkor \"vízzel\" tölti ki.",
|
||||
"routing_modeFloodHint": "Átvisszaadások minden erősítőn keresztül. A legmegbízhatóbb megoldás, de több időt igényel.",
|
||||
"routing_modeManualHint": "Mindig pontosan az útvonalat követi, amelyet megad.",
|
||||
"routing_currentRoute": "Jelenlegi útvonal",
|
||||
"routing_directNoHops": "Közvetlen – nincs átjáró állomás",
|
||||
"routing_noPathYet": "Még nincs útvonal. A következő üzenet a keresésig vár.",
|
||||
"routing_floodBroadcast": "Azonnali továbbítás minden erősítőn keresztül.",
|
||||
"routing_editPath": "Útvonal szerkesztése",
|
||||
"routing_forgetPath": "Felejtsd el a útvonalat",
|
||||
"routing_knownPaths": "Jellegzetes útvonalak",
|
||||
"routing_knownPathsHint": "Készíts egy útvonalat, hogy átválhass rá.",
|
||||
"routing_inUse": "Használatban",
|
||||
"routing_qualityStrong": "Erős első lépés",
|
||||
"routing_qualityGood": "Jó első lépés",
|
||||
"routing_qualityFair": "Jó első lépés",
|
||||
"routing_qualityWorked": "Előállított",
|
||||
"routing_qualityFlood": "Információt hallottam a katasztrófa miatt.",
|
||||
"routing_qualityUntested": "Vizsgálatnak nem подвержен",
|
||||
"routing_neverWorked": "sosem megerősítve",
|
||||
"routing_floodDelivery": "Vízparti szállítás",
|
||||
"pathEditor_title": "Út megépítése",
|
||||
"pathEditor_hopCounter": "{count} db 64-ből",
|
||||
"pathEditor_noHops": "Még nem adtam hozzá a bazsalikomot. A lent található gombokat használhatod, hogy sorrendben adjd hozzá, vagy mentheted anélkül, hogy bazsalikomot adnál hozzá, hogy közvetlenül elküldd.",
|
||||
"pathEditor_addHops": "Adja hozzá a bazsaidat a megfelelő sorrendben.",
|
||||
"pathEditor_searchRepeaters": "Ismétlő eszközök keresése",
|
||||
"pathEditor_advancedHex": "Haladó szint: alapvető hex-út",
|
||||
"pathEditor_hexLabel": "Hex előtagok",
|
||||
"pathEditor_hexHelper": "Két hatjegyű szám minden lépésen, amelyek egymástól elválasztják a kommák.",
|
||||
"pathEditor_invalidTokens": "Érvénytelen: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_tooManyHops": "A maximális szám 64.",
|
||||
"pathEditor_usePath": "Használja ezt az útvonalat.",
|
||||
"pathEditor_removeHop": "Távolítsa el a bazsalikomot",
|
||||
"pathEditor_unknownHop": "Tudatlan erősítő",
|
||||
"map_zoomIn": "Nagyítva",
|
||||
"map_zoomOut": "Kicsökkentett nézet",
|
||||
"map_centerMap": "Központi tér térkép",
|
||||
"chrome_bluetoothRequiresChromium": "A Web Bluetooth-hoz egy Chromium-alapú böngésző szükséges.",
|
||||
"channels_communityShortId": "Az azonosító: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS-en megerősítve",
|
||||
"pathTrace_legendInferred": "Feltehető helyzet"
|
||||
"dialog_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez.",
|
||||
"dialog_disconnectedTitle": "Kapcsolat megszakadt",
|
||||
"dialog_disconnectedMessage": "A kapcsolat megszakadt a kísérővel.",
|
||||
"contact_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Elimina",
|
||||
"common_enable": "Abilita",
|
||||
"common_disable": "Disattivare",
|
||||
"common_autoRefresh": "Aggiornamento automatico",
|
||||
"common_interval": "Intervallo",
|
||||
"common_reboot": "Riavvia",
|
||||
"common_loading": "Caricamento...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Ingresso digitale",
|
||||
"telemetry_digitalOutputLabel": "Uscita digitale",
|
||||
"telemetry_analogInputLabel": "Ingresso analogico",
|
||||
"telemetry_analogOutputLabel": "Uscita analogica",
|
||||
"telemetry_genericLabel": "Sensore generico",
|
||||
"telemetry_luminosityLabel": "Luminosità",
|
||||
"telemetry_presenceLabel": "Presenza",
|
||||
"telemetry_humidityLabel": "Umidità",
|
||||
"telemetry_accelerometerLabel": "Accelerometro",
|
||||
"telemetry_pressureLabel": "Pressione",
|
||||
"telemetry_altitudeLabel": "Altitudine",
|
||||
"telemetry_frequencyLabel": "Frequenza",
|
||||
"telemetry_percentageLabel": "Percentuale",
|
||||
"telemetry_concentrationLabel": "Concentrazione",
|
||||
"telemetry_powerLabel": "Potenza",
|
||||
"telemetry_distanceLabel": "Distanza",
|
||||
"telemetry_energyLabel": "Energia",
|
||||
"telemetry_directionLabel": "Direzione",
|
||||
"telemetry_timeLabel": "Ora",
|
||||
"telemetry_gyrometerLabel": "Giroscopio",
|
||||
"telemetry_colourLabel": "Colore",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Interruttore",
|
||||
"telemetry_polylineLabel": "Polilinea",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Numero di richieste",
|
||||
"telemetry_error": "Impossibile recuperare i dati",
|
||||
"telemetry_noData": "Nessun dato di telemetria disponibile.",
|
||||
"telemetry_channelTitle": "Canale {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2139,9 +2100,6 @@
|
||||
"translation_enableTitle": "Abilitare la traduzione",
|
||||
"translation_title": "Traduzione",
|
||||
"translation_composerSubtitle": "Controlla lo stato predefinito dell'icona di traduzione del compositore.",
|
||||
"translation_autoIncomingTitle": "Traduci automaticamente i messaggi",
|
||||
"translation_autoIncomingSubtitle": "Traduce automaticamente i messaggi per le notifiche e per le chat o i canali.",
|
||||
"translation_translateMessage": "Traduci messaggio",
|
||||
"translation_targetLanguage": "Lingua di destinazione",
|
||||
"translation_useAppLanguage": "Utilizza la lingua dell'app",
|
||||
"translation_downloadedModelLabel": "Modello scaricato",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_newMessages": "Nuovi messaggi",
|
||||
"chat_markAsUnread": "Segna come non letto",
|
||||
"repeater_chanUtil": "Utilizzo del canale",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_undo": "Annulla",
|
||||
"messageStatus_delivered": "Consegnato",
|
||||
"messageStatus_sent": "Invia",
|
||||
"messageStatus_pending": "Invio",
|
||||
"messageStatus_failed": "Impossibile inviare",
|
||||
"messageStatus_repeated": "Sentito ripetutamente",
|
||||
"contacts_moreOptions": "Ulteriori opzioni",
|
||||
"contacts_searchOpen": "Cerca contatti",
|
||||
"contacts_searchClose": "Ricerca avanzata",
|
||||
"routing_title": "Instradamento",
|
||||
"routing_modeAuto": "Auto",
|
||||
"routing_modeFlood": "Inondazione",
|
||||
"routing_modeManual": "Manuale",
|
||||
"routing_modeAutoHint": "Seleziona automaticamente il percorso più noto, e in caso di assenza di informazioni, utilizza un percorso casuale.",
|
||||
"routing_modeFloodHint": "Trasmissioni tramite ogni ripetitore. Il metodo più affidabile, ma richiede più tempo di trasmissione.",
|
||||
"routing_modeManualHint": "Invia sempre esattamente il percorso che hai definito.",
|
||||
"routing_currentRoute": "Percorso attuale",
|
||||
"routing_directNoHops": "Diretto — senza passaggi tramite ripetitori",
|
||||
"routing_noPathYet": "Al momento non è stata individuata alcuna via. Il messaggio viene inviato ripetutamente finché non viene trovata una rotta.",
|
||||
"routing_floodBroadcast": "Trasmissione attraverso ogni ripetitore",
|
||||
"routing_editPath": "Percorso di modifica",
|
||||
"routing_forgetPath": "Dimentica il percorso",
|
||||
"routing_knownPaths": "Percorsi noti",
|
||||
"routing_knownPathsHint": "Seleziona un percorso per accedere a questa opzione.",
|
||||
"routing_inUse": "In uso",
|
||||
"routing_qualityStrong": "Primo salto molto deciso",
|
||||
"routing_qualityGood": "Primo tentativo di successo",
|
||||
"routing_qualityFair": "Primo salto di qualità",
|
||||
"routing_qualityWorked": "È stato consegnato",
|
||||
"routing_qualityFlood": "Ho sentito tramite un messaggio urgente",
|
||||
"routing_qualityUntested": "Non testato",
|
||||
"routing_neverWorked": "mai confermato",
|
||||
"routing_floodDelivery": "Consegna in caso di alluvione",
|
||||
"pathEditor_title": "Creare percorso",
|
||||
"pathEditor_hopCounter": "{count} tra 64 varietà di luppolo",
|
||||
"pathEditor_noHops": "Al momento non ci sono ingredienti aggiuntivi. Per aggiungerli nell'ordine desiderato, cliccate sui ripetitori sottostanti. In alternativa, potete salvare la ricetta senza ingredienti aggiuntivi per inviarla direttamente.",
|
||||
"pathEditor_addHops": "Aggiungere i luppoli nell'ordine desiderato.",
|
||||
"pathEditor_searchRepeaters": "Ricerca ripetitori",
|
||||
"pathEditor_advancedHex": "Avanzato: percorso esadecimale grezzo",
|
||||
"pathEditor_hexLabel": "Prefissi esadecimali",
|
||||
"pathEditor_hexHelper": "Due caratteri esadecimali per ogni salto, separati da virgole.",
|
||||
"pathEditor_invalidTokens": "Non valido: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"pathEditor_tooManyHops": "Massimo 64 orari",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_usePath": "Utilizza questo percorso",
|
||||
"pathEditor_removeHop": "Rimuovere il luppolo",
|
||||
"pathEditor_unknownHop": "Ripetitore sconosciuto",
|
||||
"map_zoomIn": "Ingrandisci",
|
||||
"map_zoomOut": "Riduci la visualizzazione",
|
||||
"map_centerMap": "Mappa del centro",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth richiede un browser basato su Chromium.",
|
||||
"pathTrace_legendGpsConfirmed": "Il GPS conferma",
|
||||
"pathTrace_legendInferred": "Posizione dedotta"
|
||||
"dialog_connectCompanion": "Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.",
|
||||
"dialog_disconnectedTitle": "Disconnesso",
|
||||
"dialog_disconnectedMessage": "Sei stato disconnesso dal tuo compagno.",
|
||||
"contact_connectCompanion": "Connettiti a un companion per accedere alle funzioni del repeater e del server di stanza."
|
||||
}
|
||||
|
||||
+78
-209
@@ -27,8 +27,6 @@
|
||||
"common_remove": "削除",
|
||||
"common_enable": "有効化する",
|
||||
"common_disable": "無効化する",
|
||||
"common_autoRefresh": "自動更新",
|
||||
"common_interval": "間隔",
|
||||
"common_reboot": "再起動",
|
||||
"common_loading": "読み込み中...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -216,7 +214,7 @@
|
||||
"settings_txPower": "TX 信号電力 (dBm)",
|
||||
"settings_txPowerHelper": "0 - 22",
|
||||
"settings_txPowerInvalid": "無効な送信電力 (0-22 dBm)",
|
||||
"settings_clientRepeat": "オフグリッドリピータ",
|
||||
"settings_clientRepeat": "オフグリッド(電力網から孤立した状態)の繰り返し",
|
||||
"settings_clientRepeatSubtitle": "このデバイスが、他のデバイスに対してメッシュパケットを繰り返し送信できるようにする。",
|
||||
"settings_clientRepeatFreqWarning": "オフグリッドでの再送には、433MHz、869MHz、または918MHzの周波数が必要です。",
|
||||
"settings_error": "エラー:{message}",
|
||||
@@ -270,7 +268,7 @@
|
||||
"appSettings_pathsWillBeCleared": "5回失敗した後、経路が再開されます。",
|
||||
"appSettings_pathsWillNotBeCleared": "パスは自動で削除されません。",
|
||||
"appSettings_autoRouteRotation": "自動ルートの切り替え",
|
||||
"appSettings_autoRouteRotationSubtitle": "最適なルートと、フラッドモードを切り替える",
|
||||
"appSettings_autoRouteRotationSubtitle": "最適なルートと、洪水モードを切り替える",
|
||||
"appSettings_autoRouteRotationEnabled": "自動ルートの切り替え機能が有効になっています",
|
||||
"appSettings_autoRouteRotationDisabled": "自動ルートの変更機能が無効になっています。",
|
||||
"appSettings_maxRouteWeight": "最大ルート重量",
|
||||
@@ -310,7 +308,7 @@
|
||||
"appSettings_batteryLipo": "LiPo (3.0-4.2V)",
|
||||
"appSettings_mapDisplay": "地図の表示",
|
||||
"appSettings_showRepeaters": "繰り返し再生機能",
|
||||
"appSettings_showRepeatersSubtitle": "地図上にリピータノードを表示する",
|
||||
"appSettings_showRepeatersSubtitle": "地図上にリピーターノードを表示する",
|
||||
"appSettings_showChatNodes": "チャットノードの表示",
|
||||
"appSettings_showChatNodesSubtitle": "地図上にチャットノードを表示する",
|
||||
"appSettings_showOtherNodes": "他のノードを表示する",
|
||||
@@ -424,7 +422,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "リピータの管理",
|
||||
"contacts_manageRepeater": "リピーターの管理",
|
||||
"contacts_manageRoom": "ルームサーバーの管理",
|
||||
"contacts_roomLogin": "ルームサーバーへのログイン",
|
||||
"contacts_openChat": "自由な会話",
|
||||
@@ -735,7 +733,7 @@
|
||||
"chat_ShowAllPaths": "すべての経路を表示",
|
||||
"chat_routingMode": "ルーティングモード",
|
||||
"chat_autoUseSavedPath": "自動 (保存されたパスを使用)",
|
||||
"chat_forceFloodMode": "強制的にフラッドモードを起動",
|
||||
"chat_forceFloodMode": "強制的に洪水モードを起動",
|
||||
"chat_recentAckPaths": "最近使用したACKパス(タップして使用):",
|
||||
"chat_pathHistoryFull": "パスの履歴は完全です。エントリを削除して、新しいものを追加できます。",
|
||||
"chat_hopSingular": "ジャンプ",
|
||||
@@ -758,7 +756,7 @@
|
||||
"chat_clearPathSubtitle": "次回送信時に、以前の情報を再取得する",
|
||||
"chat_pathCleared": "経路が確保されました。次のメッセージでルートを再確認します。",
|
||||
"chat_floodModeSubtitle": "アプリのバーにあるルーティング切り替え機能を使用する",
|
||||
"chat_floodModeEnabled": "フラッドモードが有効になっています。アプリのメニューバーにあるルートアイコンを使用して、モードを切り替えることができます。",
|
||||
"chat_floodModeEnabled": "洪水モードが有効になっています。アプリのメニューバーにあるルートアイコンを使用して、モードを切り替えることができます。",
|
||||
"chat_fullPath": "フルパス",
|
||||
"chat_pathDetailsNotAvailable": "経路の詳細については、まだ情報がありません。「リフレッシュ」ボタンを押して、再度お試しください。",
|
||||
"chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
@@ -779,7 +777,7 @@
|
||||
"chat_path": "道",
|
||||
"chat_publicKey": "公開鍵",
|
||||
"chat_compressOutgoingMessages": "送信されるメッセージを圧縮する",
|
||||
"chat_floodForced": "フラッド(強制的な)",
|
||||
"chat_floodForced": "洪水(強制的な)",
|
||||
"chat_directForced": "直接的な(強制的な)",
|
||||
"chat_hopsForced": "{count} 本のホップ(強制的に採取)",
|
||||
"@chat_hopsForced": {
|
||||
@@ -789,7 +787,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_floodAuto": "フラッド (自動)",
|
||||
"chat_floodAuto": "洪水 (自動)",
|
||||
"chat_direct": "直接",
|
||||
"chat_poiShared": "共有されたPOI",
|
||||
"chat_unread": "未読: {count}",
|
||||
@@ -834,7 +832,7 @@
|
||||
}
|
||||
},
|
||||
"map_chat": "チャット",
|
||||
"map_repeater": "リピータ",
|
||||
"map_repeater": "繰り返し送信装置",
|
||||
"map_room": "部屋",
|
||||
"map_sensor": "センサー",
|
||||
"map_pinDm": "ピン(DM)",
|
||||
@@ -866,7 +864,7 @@
|
||||
"map_filterNodes": "フィルタノード",
|
||||
"map_nodeTypes": "ノードの種類",
|
||||
"map_chatNodes": "チャットノード",
|
||||
"map_repeaters": "リピータ",
|
||||
"map_repeaters": "繰り返し送信装置",
|
||||
"map_otherNodes": "その他のノード",
|
||||
"map_keyPrefix": "主要なプレフィックス",
|
||||
"map_filterByKeyPrefix": "主要なプレフィックスでフィルタリングする",
|
||||
@@ -879,7 +877,7 @@
|
||||
"map_lastSeenTime": "最後に確認された時間",
|
||||
"map_sharedPin": "共有パスワード",
|
||||
"map_joinRoom": "部屋に参加する",
|
||||
"map_manageRepeater": "リピータの管理",
|
||||
"map_manageRepeater": "リピーターの管理",
|
||||
"map_tapToAdd": "ノードをクリックして、パスに追加します。",
|
||||
"map_runTrace": "パスの追跡を実行",
|
||||
"map_removeLast": "最後のものを削除",
|
||||
@@ -1012,12 +1010,12 @@
|
||||
"login_enterPassword": "パスワードを入力してください",
|
||||
"login_savePassword": "パスワードを保存する",
|
||||
"login_savePasswordSubtitle": "パスワードは、このデバイスに安全に保存されます。",
|
||||
"login_repeaterDescription": "設定やステータスにアクセスするために、リピータのパスワードを入力してください。",
|
||||
"login_repeaterDescription": "設定やステータスにアクセスするために、リピーターのパスワードを入力してください。",
|
||||
"login_roomDescription": "設定やステータスへのアクセスには、部屋のパスワードを入力してください。",
|
||||
"login_routing": "経路設定",
|
||||
"login_routingMode": "ルーティングモード",
|
||||
"login_autoUseSavedPath": "自動 (保存されたパスを使用)",
|
||||
"login_forceFloodMode": "強制的にフラッドモードを起動",
|
||||
"login_forceFloodMode": "強制的に洪水モードを起動",
|
||||
"login_managePaths": "パスの管理",
|
||||
"login_login": "ログイン",
|
||||
"login_attempt": "試行回数:{current}/{max}",
|
||||
@@ -1066,7 +1064,7 @@
|
||||
"path_helperMaxHops": "最大64個のホップ。各プレフィックスは2つの16進数文字(1バイト)で構成されています。",
|
||||
"path_selectFromContacts": "または、連絡先リストから選択してください:",
|
||||
"path_noRepeatersFound": "繰り返し機能やルームサーバーは見つかりませんでした。",
|
||||
"path_customPathsRequire": "カスタムパスには、メッセージを中継できるリピータが必要です。",
|
||||
"path_customPathsRequire": "カスタムパスには、メッセージを中継できる中間地点が必要です。",
|
||||
"path_invalidHexPrefixes": "無効な16進数プレフィックス: {prefixes}",
|
||||
"@path_invalidHexPrefixes": {
|
||||
"placeholders": {
|
||||
@@ -1077,23 +1075,23 @@
|
||||
},
|
||||
"path_tooLong": "経路が長すぎる。最大64回のジャンプのみ許可。",
|
||||
"path_setPath": "パスを設定",
|
||||
"repeater_management": "リピータ管理",
|
||||
"repeater_management": "リピーター管理",
|
||||
"room_management": "ルームサーバーの管理",
|
||||
"repeater_managementTools": "管理ツール",
|
||||
"repeater_status": "ステータス",
|
||||
"repeater_statusSubtitle": "リピータの状態、統計情報、および隣接するネットワークの情報を表示する",
|
||||
"repeater_statusSubtitle": "リピーターの状態、統計情報、および隣接するネットワークの情報を表示する",
|
||||
"repeater_telemetry": "テレメトリー",
|
||||
"repeater_telemetrySubtitle": "センサーおよびシステムの状態に関するテレメトリの表示",
|
||||
"repeater_cli": "CLI(コマンドラインインターフェース)",
|
||||
"repeater_cliSubtitle": "リピータへのコマンドを送信する",
|
||||
"repeater_cliSubtitle": "リピーターへのコマンドを送信する",
|
||||
"repeater_neighbors": "近隣住民",
|
||||
"repeater_neighborsSubtitle": "ゼロホップの隣接ノードを表示する。",
|
||||
"repeater_settings": "設定",
|
||||
"repeater_settingsSubtitle": "リピータのパラメータを設定する",
|
||||
"repeater_settingsSubtitle": "リピーターのパラメータを設定する",
|
||||
"repeater_statusTitle": "再送ステータス",
|
||||
"repeater_routingMode": "ルーティングモード",
|
||||
"repeater_autoUseSavedPath": "自動 (保存されたパスを使用)",
|
||||
"repeater_forceFloodMode": "強制的にフラッドモードを起動",
|
||||
"repeater_forceFloodMode": "強制的に洪水モードを起動",
|
||||
"repeater_pathManagement": "経路管理",
|
||||
"repeater_refresh": "リフレッシュ",
|
||||
"repeater_statusRequestTimeout": "ステータス情報の取得に失敗しました。",
|
||||
@@ -1138,7 +1136,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetTxTotal": "合計: {total}, フラッド: {flood}, 直接: {direct}",
|
||||
"repeater_packetTxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}",
|
||||
"@repeater_packetTxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -1152,7 +1150,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetRxTotal": "合計: {total}, フラッド: {flood}, 直接: {direct}",
|
||||
"repeater_packetRxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}",
|
||||
"@repeater_packetRxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -1185,10 +1183,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_settingsTitle": "リピータ設定",
|
||||
"repeater_settingsTitle": "リピーター設定",
|
||||
"repeater_basicSettings": "基本設定",
|
||||
"repeater_repeaterName": "リピータ名",
|
||||
"repeater_repeaterNameHelper": "このリピータの名前",
|
||||
"repeater_repeaterName": "送信装置名",
|
||||
"repeater_repeaterNameHelper": "このリピーターの名前",
|
||||
"repeater_adminPassword": "管理者パスワード",
|
||||
"repeater_adminPasswordHelper": "完全アクセス権のパスワード",
|
||||
"repeater_guestPassword": "ゲスト用のパスワード",
|
||||
@@ -1208,7 +1206,7 @@
|
||||
"repeater_longitudeHelper": "度分表記(例:-122.4194)",
|
||||
"repeater_features": "特徴",
|
||||
"repeater_packetForwarding": "パケット転送",
|
||||
"repeater_packetForwardingSubtitle": "リピータがパケットを転送できるように設定する",
|
||||
"repeater_packetForwardingSubtitle": "リピーターがパケットを転送できるように設定する",
|
||||
"repeater_guestAccess": "ゲストへのアクセス",
|
||||
"repeater_guestAccessSubtitle": "ゲストへの読み取り専用アクセスを許可する",
|
||||
"repeater_privacyMode": "プライバシーモード",
|
||||
@@ -1223,7 +1221,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_floodAdvertInterval": "フラッドに関する広告の表示間隔",
|
||||
"repeater_floodAdvertInterval": "洪水に関する広告の表示間隔",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} 時間",
|
||||
"@repeater_floodAdvertIntervalHours": {
|
||||
"placeholders": {
|
||||
@@ -1234,15 +1232,15 @@
|
||||
},
|
||||
"repeater_encryptedAdvertInterval": "暗号化された広告表示間",
|
||||
"repeater_dangerZone": "危険区域",
|
||||
"repeater_rebootRepeater": "リピータを再起動する",
|
||||
"repeater_rebootRepeaterSubtitle": "リピータデバイスを再起動する",
|
||||
"repeater_rebootRepeaterConfirm": "本当にこのリピータを再起動したいですか?",
|
||||
"repeater_rebootRepeater": "リピーターを再起動する",
|
||||
"repeater_rebootRepeaterSubtitle": "リピーターデバイスを再起動する",
|
||||
"repeater_rebootRepeaterConfirm": "本当にこのリピーターを再起動したいですか?",
|
||||
"repeater_regenerateIdentityKey": "IDキーの再生成",
|
||||
"repeater_regenerateIdentityKeySubtitle": "新しい公開鍵/秘密鍵のペアを生成する",
|
||||
"repeater_regenerateIdentityKeyConfirm": "これにより、リピータには新しい識別情報が割り当てられます。続行しますか?",
|
||||
"repeater_regenerateIdentityKeyConfirm": "これにより、リピーターには新しい識別情報が割り当てられます。続行しますか?",
|
||||
"repeater_eraseFileSystem": "ファイルシステムを削除する",
|
||||
"repeater_eraseFileSystemSubtitle": "リピータファイルシステムをフォーマットする",
|
||||
"repeater_eraseFileSystemConfirm": "警告:この操作により、リピータ内のすべてのデータが消去されます。この操作は元に戻すことができません!",
|
||||
"repeater_eraseFileSystemSubtitle": "リピーターファイルシステムをフォーマットする",
|
||||
"repeater_eraseFileSystemConfirm": "警告:この操作により、リピーター内のすべてのデータが消去されます。この操作は元に戻すことができません!",
|
||||
"repeater_eraseSerialOnly": "Erase機能は、シリアルコンソール経由でのみ利用可能です。",
|
||||
"repeater_commandSent": "送信されたコマンド: {command}",
|
||||
"@repeater_commandSent": {
|
||||
@@ -1270,7 +1268,7 @@
|
||||
"repeater_refreshMultiAcks": "複数のACKをリフレッシュする",
|
||||
"repeater_networkHealth": "ネットワークの状態",
|
||||
"repeater_loopDetect": "ループ検出",
|
||||
"repeater_loopDetectHelper": "ルーティングループを検知する",
|
||||
"repeater_loopDetectHelper": "ルーティングループに見えるような、洪水パケットを送信する",
|
||||
"repeater_loopDetectOff": "オフ",
|
||||
"repeater_loopDetectMinimal": "最小限の",
|
||||
"repeater_loopDetectModerate": "適度な",
|
||||
@@ -1286,16 +1284,16 @@
|
||||
}
|
||||
},
|
||||
"repeater_ownerInfo": "事業者の情報",
|
||||
"repeater_ownerInfoHelper": "このリピータに関する公開メタデータ",
|
||||
"repeater_ownerInfoHelper": "このリピーターに関する公開メタデータ",
|
||||
"repeater_refreshOwnerInfo": "オペレーター情報の更新",
|
||||
"repeater_floodMax": "最大ホップ数",
|
||||
"repeater_floodMaxHelper": "フラッドパケットが移動できる最大ホップ数 (0-64)",
|
||||
"repeater_floodMaxHelper": "洪水パケットが移動できる最大ホップ数 (0-64)",
|
||||
"repeater_advancedSettings": "高度な",
|
||||
"repeater_advancedSettingsSubtitle": "経験豊富なオペレーター向けの調整ノブ",
|
||||
"repeater_pathHashMode": "パスハッシュモード",
|
||||
"repeater_pathHashModeHelper": "このリピータのIDをフローパス/ループ検出タグにエンコードするために使用されるバイト数。 0=1バイト (256個のID、最大64ホップ)、1=2バイト (65,000個のID、最大32ホップ)、2=3バイト (160万個のID、最大21ホップ)。 v1.13およびそれ以前のファームウェアでは、マルチバイトパスがサポートされていません。 v1.14以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。",
|
||||
"repeater_pathHashModeHelper": "このリピーターのIDをフローパス/ループ検出タグにエンコードするために使用されるバイト数。 0=1バイト (256個のID、最大64ホップ)、1=2バイト (65,000個のID、最大32ホップ)、2=3バイト (160万個のID、最大21ホップ)。 v1.13およびそれ以前のファームウェアでは、マルチバイトパスがサポートされていません。 v1.14以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。",
|
||||
"repeater_txDelay": "フロイド・TXでの遅延",
|
||||
"repeater_txDelayHelper": "フラッド時の交通量に対応するための再送信間隔を、パケットの通信時間を掛けた値(0~2、デフォルト0.5)で設定します。値を大きくすると衝突が減りますが、通信速度が遅くなります。",
|
||||
"repeater_txDelayHelper": "洪水時の交通量に対応するための再送信間隔を、パケットの通信時間を掛けた値(0~2、デフォルト0.5)で設定します。値を大きくすると衝突が減りますが、通信速度が遅くなります。",
|
||||
"repeater_directTxDelay": "直接的なTX遅延",
|
||||
"repeater_directTxDelayHelper": "直接(フラッドではない)トラフィックに対する再送信間隔を、パケットの空中時間(0~2、デフォルト0.3)の倍数として設定する。",
|
||||
"repeater_intThresh": "干渉閾値",
|
||||
@@ -1303,10 +1301,10 @@
|
||||
"repeater_agcResetInterval": "AGCのリセット間隔",
|
||||
"repeater_agcResetIntervalHelper": "ラジオの自動ゲイン制御をリセットする頻度について:ゲインが固定状態になった場合に、回復するために、何度リセットするかを設定します。4の倍数でリセットする場合、0を設定すると、定期的なリセットは停止します。",
|
||||
"repeater_actionsTitle": "行動",
|
||||
"repeater_sendAdvert": "フラッドに関する広告を送信",
|
||||
"repeater_sendAdvertSubtitle": "ネットワークを通じて、フラッドに関する広告を放送する",
|
||||
"repeater_sendAdvert": "洪水に関する広告を送信",
|
||||
"repeater_sendAdvertSubtitle": "ネットワークを通じて、洪水に関する広告を放送する",
|
||||
"repeater_sendAdvertZeroHop": "ゼロホップ形式の広告を送信する",
|
||||
"repeater_sendAdvertZeroHopSubtitle": "ワンホップでの広告放送(リピータなし)",
|
||||
"repeater_sendAdvertZeroHopSubtitle": "ワンホップでの広告放送(中継なし)",
|
||||
"repeater_clockSync": "現在、時刻を同期する",
|
||||
"repeater_clockSyncSubtitle": "スマートフォンの時刻をルーターに設定する",
|
||||
"repeater_actionSucceeded": "{action} が成功しました",
|
||||
@@ -1328,7 +1326,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_settingsSavedRebootNeeded": "設定を保存しました — リピータを再起動して適用してください",
|
||||
"repeater_settingsSavedRebootNeeded": "設定を保存しました — リピーターを再起動して適用してください",
|
||||
"repeater_settingsPartialFailure": "設定の一部でエラーが発生しました:{failures}",
|
||||
"@repeater_settingsPartialFailure": {
|
||||
"placeholders": {
|
||||
@@ -1367,7 +1365,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_cliTitle": "リピータのコマンドラインインターフェース",
|
||||
"repeater_cliTitle": "リピーターのコマンドラインインターフェース",
|
||||
"repeater_debugNextCommand": "次のコマンドのデバッグ",
|
||||
"repeater_commandHelp": "コマンドヘルプ",
|
||||
"repeater_clearHistory": "明確な歴史",
|
||||
@@ -1401,14 +1399,14 @@
|
||||
"repeater_cliHelpClearStats": "さまざまな統計カウンターをゼロにリセットする。",
|
||||
"repeater_cliHelpSetAf": "空き時間係数を設定します。",
|
||||
"repeater_cliHelpSetTx": "LoRaの送信電力をdBmで設定します。(設定変更後、再起動が必要です)",
|
||||
"repeater_cliHelpSetRepeat": "このノードに対するリピータの役割を有効化または無効化します。",
|
||||
"repeater_cliHelpSetRepeat": "このノードに対するリピーターの役割を有効化または無効化します。",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(ルームサーバー設定)「オン」に設定した場合、空白のパスワードでのログインは可能ですが、ルームへの投稿はできません。(閲覧のみ)",
|
||||
"repeater_cliHelpSetFloodMax": "インバウンドフラッドパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。",
|
||||
"repeater_cliHelpSetFloodMax": "インバウンドフラッパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。",
|
||||
"repeater_cliHelpSetIntThresh": "干渉閾値を設定します(dB単位)。デフォルト値は14です。0に設定すると、チャンネル間の干渉を検出する機能を無効にします。",
|
||||
"repeater_cliHelpSetAgcResetInterval": "オートゲインコントローラーのリセット間隔を設定します。 0 に設定すると無効化されます。",
|
||||
"repeater_cliHelpSetMultiAcks": "「ダブルACK」機能の有効化または無効化を可能にします。",
|
||||
"repeater_cliHelpSetAdvertInterval": "ローカル(ホップなし)の広告パケットを送信する間隔を分単位で設定します。 0 に設定すると、機能を無効にします。",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "フラッド広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "洪水広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。",
|
||||
"repeater_cliHelpSetGuestPassword": "ゲストのパスワードを設定/更新します。(繰り返し利用の場合、ゲストのログインは「統計情報を取得」のリクエストを送信できます)",
|
||||
"repeater_cliHelpSetName": "広告の名前を設定します。",
|
||||
"repeater_cliHelpSetLat": "広告表示の地図の緯度を設定します。(度分秒表記)",
|
||||
@@ -1429,14 +1427,14 @@
|
||||
"repeater_cliHelpLogStart": "パケットのログ記録を開始し、ファイルシステムに保存する。",
|
||||
"repeater_cliHelpLogStop": "ファイルシステムへのパケットログの記録を停止する。",
|
||||
"repeater_cliHelpLogErase": "ファイルシステムからパケットログを削除する。",
|
||||
"repeater_cliHelpNeighbors": "ゼロホップ広告を通じて受信した他のリピータノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。",
|
||||
"repeater_cliHelpNeighbors": "ゼロホップ広告を通じて受信した他のリピーターノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。",
|
||||
"repeater_cliHelpNeighborRemove": "隣接リストから、最初に一致するエントリ(pubkeyプレフィックス(16進数)で特定)を削除します。",
|
||||
"repeater_cliHelpRegion": "(特定のシリーズのみ)定義されたすべての地域と、現在のフラッド許可状況を一覧表示します。",
|
||||
"repeater_cliHelpRegion": "(特定のシリーズのみ)定義されたすべての地域と、現在の洪水許可状況を一覧表示します。",
|
||||
"repeater_cliHelpRegionLoad": "注:これは特殊な複数コマンドの呼び出しです。その後の各コマンドは、地域名であり(スペースを使用して親階層を示し、少なくとも1つのスペースが必要です)、空行/コマンドで終了します。",
|
||||
"repeater_cliHelpRegionGet": "指定された名前のプレフィックスを持つ地域を検索します(または、グローバルな範囲の場合は「*」)。結果として、「region-name (parent-name) 'F'」と返答します。",
|
||||
"repeater_cliHelpRegionPut": "指定された名前で、領域の定義を追加または更新します。",
|
||||
"repeater_cliHelpRegionRemove": "指定された名前を持つ領域の定義を削除します。(正確に一致している必要があり、子領域は存在してはなりません)",
|
||||
"repeater_cliHelpRegionAllowf": "指定された領域に対して、「フラッド」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)",
|
||||
"repeater_cliHelpRegionAllowf": "指定された領域に対して、「洪水」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)",
|
||||
"repeater_cliHelpRegionDenyf": "指定された領域における「FLOOD」権限を削除します。(注:現時点では、グローバル/従来の範囲での使用は推奨されません!)",
|
||||
"repeater_cliHelpRegionHome": "現在の「ホーム」地域に返信します。(まだ適用されていない、将来利用を予定)",
|
||||
"repeater_cliHelpRegionHomeSet": "「ホーム」地域を設定します。",
|
||||
@@ -1453,7 +1451,7 @@
|
||||
"repeater_settingsCategory": "設定",
|
||||
"repeater_bridge": "橋",
|
||||
"repeater_logging": "ログ記録",
|
||||
"repeater_neighborsRepeaterOnly": "近隣住民(リピータのみ)",
|
||||
"repeater_neighborsRepeaterOnly": "近隣住民(リピーターのみ)",
|
||||
"repeater_regionManagementRepeaterOnly": "地域管理(ブロードキャスト用のみ)",
|
||||
"repeater_regionNote": "地域レベルでの管理のため、地域定義と権限の管理を行うための機能が導入されました。",
|
||||
"repeater_gpsManagement": "GPS管理",
|
||||
@@ -1468,43 +1466,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "デジタル入力",
|
||||
"telemetry_digitalOutputLabel": "デジタル出力",
|
||||
"telemetry_analogInputLabel": "アナログ入力",
|
||||
"telemetry_analogOutputLabel": "アナログ出力",
|
||||
"telemetry_genericLabel": "汎用センサー",
|
||||
"telemetry_luminosityLabel": "照度",
|
||||
"telemetry_presenceLabel": "在室",
|
||||
"telemetry_humidityLabel": "湿度",
|
||||
"telemetry_accelerometerLabel": "加速度計",
|
||||
"telemetry_pressureLabel": "気圧",
|
||||
"telemetry_altitudeLabel": "高度",
|
||||
"telemetry_frequencyLabel": "周波数",
|
||||
"telemetry_percentageLabel": "パーセント",
|
||||
"telemetry_concentrationLabel": "濃度",
|
||||
"telemetry_powerLabel": "電力",
|
||||
"telemetry_distanceLabel": "距離",
|
||||
"telemetry_energyLabel": "エネルギー",
|
||||
"telemetry_directionLabel": "方向",
|
||||
"telemetry_timeLabel": "時刻",
|
||||
"telemetry_gyrometerLabel": "ジャイロメーター",
|
||||
"telemetry_colourLabel": "色",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "スイッチ",
|
||||
"telemetry_polylineLabel": "ポリライン",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "リクエスト数",
|
||||
"telemetry_error": "データを取得できません",
|
||||
"telemetry_noData": "テレメトリデータは利用できません。",
|
||||
"telemetry_channelTitle": "チャンネル {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -1567,7 +1528,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_repeatersNeighbors": "近隣のリピータ",
|
||||
"neighbors_repeatersNeighbors": "繰り返し送信する、近隣",
|
||||
"neighbors_noData": "近隣のデータは利用できません。",
|
||||
"neighbors_unknownContact": "不明な {pubkey}",
|
||||
"@neighbors_unknownContact": {
|
||||
@@ -1588,12 +1549,12 @@
|
||||
"channelPath_title": "パケットパス",
|
||||
"channelPath_viewMap": "地図を表示する",
|
||||
"channelPath_otherObservedPaths": "観察されたその他の経路",
|
||||
"channelPath_repeaterHops": "リピータホップ",
|
||||
"channelPath_repeaterHops": "ホップの繰り返し",
|
||||
"channelPath_noHopDetails": "このパッケージに関する詳細な情報は提供されていません。",
|
||||
"channelPath_messageDetails": "メッセージの詳細",
|
||||
"channelPath_senderLabel": "送信者",
|
||||
"channelPath_timeLabel": "時間",
|
||||
"channelPath_repeatsLabel": "リピータ",
|
||||
"channelPath_repeatsLabel": "繰り返し",
|
||||
"channelPath_pathLabel": "{index} 番目の経路",
|
||||
"channelPath_observedLabel": "観察",
|
||||
"channelPath_observedPathTitle": "観察された経路 {index} • {hops}",
|
||||
@@ -1631,7 +1592,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_unknownPath": "不明",
|
||||
"channelPath_floodPath": "フラッド",
|
||||
"channelPath_floodPath": "洪水",
|
||||
"channelPath_directPath": "直接",
|
||||
"channelPath_observedZeroOf": "{total}個のホップ",
|
||||
"@channelPath_observedZeroOf": {
|
||||
@@ -1653,7 +1614,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_mapTitle": "経路図",
|
||||
"channelPath_noRepeaterLocations": "この経路にリピータの位置情報はありません。",
|
||||
"channelPath_noRepeaterLocations": "この経路には、中継装置の設置場所がありません。",
|
||||
"channelPath_primaryPath": "{index}番目の経路(主要経路)",
|
||||
"@channelPath_primaryPath": {
|
||||
"placeholders": {
|
||||
@@ -1831,7 +1792,7 @@
|
||||
"listFilter_addToFavorites": "お気に入りに追加",
|
||||
"listFilter_removeFromFavorites": "お気に入りから削除",
|
||||
"listFilter_users": "利用者",
|
||||
"listFilter_repeaters": "リピータ",
|
||||
"listFilter_repeaters": "繰り返し送信装置",
|
||||
"listFilter_roomServers": "ルーム用サーバー",
|
||||
"listFilter_unreadOnly": "未読のみ",
|
||||
"listFilter_newGroup": "新しいグループ",
|
||||
@@ -1976,8 +1937,8 @@
|
||||
},
|
||||
"contacts_pathTrace": "経路追跡",
|
||||
"contacts_ping": "パング",
|
||||
"contacts_repeaterPathTrace": "リピータまでの経路を追跡する",
|
||||
"contacts_repeaterPing": "リピータにPING",
|
||||
"contacts_repeaterPathTrace": "リピーターまでの経路を追跡する",
|
||||
"contacts_repeaterPing": "PING 繰り返し",
|
||||
"contacts_roomPathTrace": "部屋のサーバーへの経路を追跡する",
|
||||
"contacts_roomPing": "ピンルーム用サーバー",
|
||||
"contacts_chatTraceRoute": "経路の追跡ルート",
|
||||
@@ -1994,7 +1955,7 @@
|
||||
"contacts_contactImported": "連絡先が登録されました。",
|
||||
"contacts_contactImportFailed": "連絡先のインポートに失敗しました。",
|
||||
"contacts_zeroHopAdvert": "ゼロホップ広告",
|
||||
"contacts_floodAdvert": "フラッドに関する広告",
|
||||
"contacts_floodAdvert": "洪水に関する広告",
|
||||
"contacts_copyAdvertToClipboard": "広告をクリップボードにコピー",
|
||||
"contacts_addContactFromClipboard": "クリップボードから連絡先を追加する",
|
||||
"contacts_ShareContact": "連絡先をクリップボードにコピー",
|
||||
@@ -2037,7 +1998,7 @@
|
||||
}
|
||||
},
|
||||
"notification_receivedNewMessage": "新しいメッセージを受信",
|
||||
"settings_gpxExportRepeaters": "GPX へのエクスポート用リピータ/ルームサーバー",
|
||||
"settings_gpxExportRepeaters": "GPX へのエクスポート用リピーター/ルームサーバー",
|
||||
"settings_gpxExportRepeatersSubtitle": "GPXファイルに場所情報を付加した、レピーター/ルームサーバーのエクスポート",
|
||||
"settings_gpxExportContacts": "GPX 形式へのエクスポート",
|
||||
"settings_gpxExportContactsSubtitle": "GPXファイルに位置情報を保存して、他の人と共有する。",
|
||||
@@ -2047,20 +2008,20 @@
|
||||
"settings_gpxExportNoContacts": "エクスポートする連絡先は存在しません。",
|
||||
"settings_gpxExportNotAvailable": "このデバイス/OSではサポートされていません",
|
||||
"settings_gpxExportError": "エクスポート時にエラーが発生しました。",
|
||||
"settings_gpxExportRepeatersRoom": "リピータ/ルームサーバーの位置情報",
|
||||
"settings_gpxExportRepeatersRoom": "中継装置およびルームサーバーの設置場所",
|
||||
"settings_gpxExportChat": "関連施設",
|
||||
"settings_gpxExportAllContacts": "すべての連絡先場所",
|
||||
"settings_gpxExportShareText": "meshcore-openからエクスポートされた地図データ",
|
||||
"settings_gpxExportShareSubject": "meshcore-open GPX形式の地図データのエクスポート",
|
||||
"snrIndicator_nearByRepeaters": "近くのリピータ",
|
||||
"snrIndicator_nearByRepeaters": "近くの電波中継局",
|
||||
"snrIndicator_lastSeen": "最後に確認された場所",
|
||||
"contactsSettings_title": "連絡先設定",
|
||||
"contactsSettings_autoAddTitle": "自動検出",
|
||||
"contactsSettings_otherTitle": "その他の連絡に関する設定",
|
||||
"contactsSettings_autoAddUsersTitle": "自動でユーザーを追加する",
|
||||
"contactsSettings_autoAddUsersSubtitle": "利用者が自動的に発見したユーザーを追加できるようにする。",
|
||||
"contactsSettings_autoAddRepeatersTitle": "リピータを自動追加",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "発見したリピータを、自動的に追加できるようにする。",
|
||||
"contactsSettings_autoAddRepeatersTitle": "自動で繰り返し設定",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "発見した中継局を、自動的に追加できるようにする。",
|
||||
"contactsSettings_autoAddRoomServersTitle": "自動でルームサーバーを追加",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "利用者が、発見した部屋のサーバーを自動的に追加できるようにする。",
|
||||
"contactsSettings_autoAddSensorsTitle": "自動でセンサーを追加",
|
||||
@@ -2162,7 +2123,7 @@
|
||||
"contact_teleLocSubtitle": "位置情報共有を許可する",
|
||||
"contact_teleEnv": "テレメトリ環境",
|
||||
"contact_teleEnvSubtitle": "環境センサーのデータを共有することを許可する",
|
||||
"map_showOverlaps": "リピータキーの重複",
|
||||
"map_showOverlaps": "リピーターキーの重複",
|
||||
"map_runTraceWithReturnPath": "元の経路に戻る。",
|
||||
"@translation_downloadFailed": {
|
||||
"placeholders": {
|
||||
@@ -2176,9 +2137,6 @@
|
||||
"translation_composerTitle": "送信する前に翻訳する",
|
||||
"translation_enableTitle": "翻訳機能を有効にする",
|
||||
"translation_composerSubtitle": "作曲家翻訳アイコンのデフォルト状態を制御する。",
|
||||
"translation_autoIncomingTitle": "メッセージを自動翻訳",
|
||||
"translation_autoIncomingSubtitle": "通知やチャット、チャンネルのメッセージを自動的に翻訳します。",
|
||||
"translation_translateMessage": "メッセージを翻訳",
|
||||
"translation_targetLanguage": "翻訳対象言語",
|
||||
"translation_useAppLanguage": "アプリの言語設定",
|
||||
"translation_downloadedModelLabel": "ダウンロードしたモデル",
|
||||
@@ -2232,7 +2190,7 @@
|
||||
"repeater_clockSyncAfterLoginSubtitle": "ログインが成功した場合、自動的に「時刻同期」を送信する。",
|
||||
"room_guest": "ルームサーバーに関する情報",
|
||||
"chat_sendMessage": "メッセージを送信する",
|
||||
"repeater_guest": "リピータに関する情報",
|
||||
"repeater_guest": "繰り返し送信に関する情報",
|
||||
"repeater_guestTools": "ゲスト向けツール",
|
||||
"repeater_getCategory": "価値を取得する",
|
||||
"repeater_powerMgmt": "電力管理",
|
||||
@@ -2243,7 +2201,7 @@
|
||||
"repeater_cliHelpStartOta": "サポートされているボードに対して、無線でファームウェアのアップデートを開始します。",
|
||||
"repeater_cliHelpTime": "デバイスのクロックを、指定されたUnixエポックの秒数に設定します。クロックは逆方向に進むことはできません。",
|
||||
"repeater_cliHelpBoard": "製造元の名前/ハードウェア識別子を表示します。",
|
||||
"repeater_cliHelpDiscoverNeighbors": "近隣のノードに対して、ノードの探索リクエストを送信します。(リピータ機能のみ)",
|
||||
"repeater_cliHelpDiscoverNeighbors": "近隣のノードに対して、ノードの探索リクエストを送信します。(中継機能のみ)",
|
||||
"repeater_cliHelpPowersaving": "省電力モードがオンになっているかどうかを表示します。",
|
||||
"repeater_cliHelpPowersavingOnOff": "省電力モード(対応している場合)を有効または無効にします。",
|
||||
"repeater_cliHelpErase": "(シリアルモードのみ)デバイスのファイルシステムをフォーマットします。すべての設定と連絡先を消去します。",
|
||||
@@ -2256,10 +2214,10 @@
|
||||
"repeater_cliHelpSetFreq": "(シリアル設定のみ)特定の周波数のみを素早く設定できます。再起動が必要です。「ラジオ設定」を使用すると、ラジオのすべてのパラメータを設定できます。",
|
||||
"repeater_cliHelpSetBridgeChannel": "(ESPNowブリッジのみ)ブリッジで使用するWi-Fiチャンネル(1~14)を設定します。",
|
||||
"repeater_cliHelpGetName": "設定されたノードの名前を表示します。",
|
||||
"repeater_cliHelpGetRole": "ファームウェアの役割(リピータ、ルームサーバーなど)を表示します。",
|
||||
"repeater_cliHelpGetRole": "ファームウェアの役割(リピーター、ルームサーバーなど)を表示します。",
|
||||
"repeater_cliHelpGetPublicKey": "デバイスの公開鍵を表示します。",
|
||||
"repeater_cliHelpGetPrvKey": "(シリアル番号のみ)デバイスのプライベートキーを表示します。機密情報として扱ってください。",
|
||||
"repeater_cliHelpGetRepeat": "パケット転送(リピータ機能)が有効になっているかどうかを表示します。",
|
||||
"repeater_cliHelpGetRepeat": "パケット転送(リピーター機能)が有効になっているかどうかを表示します。",
|
||||
"repeater_cliHelpGetTx": "現在のTX(送信)電力のdBm値を表示します。",
|
||||
"repeater_cliHelpGetFreq": "設定された無線周波数をMHzで表示します。",
|
||||
"repeater_cliHelpGetRadio": "以下のすべての無線パラメータを表示: 周波数、帯域幅、スプレッドファクター、符号化レート。",
|
||||
@@ -2271,18 +2229,18 @@
|
||||
"repeater_cliHelpGetMultiAcks": "ダブルACKモードが有効 (1) か無効 (0) かを示す。",
|
||||
"repeater_cliHelpGetAllowReadOnly": "ゲストによる読み取り専用アクセスが許可されているかどうかを示す。",
|
||||
"repeater_cliHelpGetAdvertInterval": "ローカル広告の時間を分単位で表示します。",
|
||||
"repeater_cliHelpGetFloodAdvertInterval": "フラッドに関する広告の放送時間を時間単位で表示します。",
|
||||
"repeater_cliHelpGetFloodAdvertInterval": "洪水に関する広告の放送時間を時間単位で表示します。",
|
||||
"repeater_cliHelpGetGuestPassword": "設定されたゲストパスワードを表示します。",
|
||||
"repeater_cliHelpGetLat": "設定された緯度を表示します。",
|
||||
"repeater_cliHelpGetLon": "設定された経度を表示します。",
|
||||
"repeater_cliHelpGetRxDelay": "rxdelay の基本値を表示します。",
|
||||
"repeater_cliHelpGetTxDelay": "フラッドモードにおける送信遅延の要因を示します。",
|
||||
"repeater_cliHelpGetTxDelay": "洪水モードにおける送信遅延の要因を示します。",
|
||||
"repeater_cliHelpGetDirectTxDelay": "ダイレクトモードの遅延要素を示します。",
|
||||
"repeater_cliHelpGetFloodMax": "フラッドパケットの最大ホップ数を表示します。",
|
||||
"repeater_cliHelpGetFloodMax": "最大浸水範囲の回数を表示します。",
|
||||
"repeater_cliHelpGetOwnerInfo": "所有者の連絡先情報を表示します。",
|
||||
"repeater_cliHelpGetPathHashMode": "パスハッシュモード(0/1/2)を表示します。",
|
||||
"repeater_cliHelpGetLoopDetect": "ループ検出の感度を示す。",
|
||||
"repeater_cliHelpGetAcl": "(シリアルのみ)リピータ上のアクセス制御設定を一覧表示します。",
|
||||
"repeater_cliHelpGetAcl": "(シリアルのみ)リピーター上のアクセス制御設定を一覧表示します。",
|
||||
"repeater_cliHelpGetBridgeEnabled": "橋が有効になっているかどうかを表示します。",
|
||||
"repeater_cliHelpGetBridgeDelay": "橋の遅延時間をミリ秒(ms)で表示します。",
|
||||
"repeater_cliHelpGetBridgeSource": "RX または TX パケットを橋渡ししているかどうかを示す。",
|
||||
@@ -2300,8 +2258,8 @@
|
||||
"repeater_cliHelpSensorList": "カスタムセンサーの設定をすべてリスト表示し、オプションで指定できる開始インデックスからページ分割して表示します。",
|
||||
"repeater_cliHelpRegionDefault": "現在のデフォルトの地域範囲を表示します。",
|
||||
"repeater_cliHelpRegionDefaultSet": "デフォルトの地域範囲を設定します。「<null>」を使用すると、設定をリセットできます。",
|
||||
"repeater_cliHelpRegionListAllowed": "フラッド時の通行が許可されている地域の一覧",
|
||||
"repeater_cliHelpRegionListDenied": "フラッドによる交通を遮断している地域の一覧",
|
||||
"repeater_cliHelpRegionListAllowed": "洪水時の通行が許可されている地域の一覧",
|
||||
"repeater_cliHelpRegionListDenied": "洪水による交通を遮断している地域の一覧",
|
||||
"repeater_cliHelpStatsPackets": "(シリアルのみ)パケットレベルの統計情報を表示します。",
|
||||
"repeater_cliHelpStatsRadio": "(シリーズのみ)ラジオの統計情報を表示します。",
|
||||
"repeater_cliHelpStatsCore": "(シリアルのみ)主要なファームウェアの統計情報を表示します。",
|
||||
@@ -2392,97 +2350,8 @@
|
||||
"chat_newMessages": "新しいメッセージ",
|
||||
"chat_markAsUnread": "未読としてマークする",
|
||||
"repeater_chanUtil": "チャンネルの利用状況",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_delivered": "配達",
|
||||
"common_undo": "元に戻す",
|
||||
"messageStatus_pending": "送信",
|
||||
"messageStatus_sent": "送信",
|
||||
"messageStatus_failed": "送信できませんでした",
|
||||
"contacts_moreOptions": "さらに多くの選択肢",
|
||||
"messageStatus_repeated": "何度も聞いた",
|
||||
"contacts_searchOpen": "連絡先を検索する",
|
||||
"contacts_searchClose": "検索を終了",
|
||||
"routing_modeFlood": "洪水",
|
||||
"routing_title": "経路設定",
|
||||
"routing_modeAuto": "自動車",
|
||||
"routing_modeManual": "マニュアル",
|
||||
"routing_modeAutoHint": "最も一般的な経路を自動的に選択し、経路が不明な場合は、水没状態にします。",
|
||||
"routing_modeFloodHint": "すべてのリピーターを通じて放送。最も信頼性が高いですが、より多くの時間を使用します。",
|
||||
"routing_modeManualHint": "常に、あなたが設定した正確な経路を辿って移動します。",
|
||||
"routing_currentRoute": "現在までのルート",
|
||||
"routing_directNoHops": "直接接続—中継装置を経由しない",
|
||||
"routing_noPathYet": "まだ経路は確立されていません。「次のメッセージを送信し、経路が特定されるまで待ちます」。",
|
||||
"routing_floodBroadcast": "すべてのリピーターを通じて放送",
|
||||
"routing_editPath": "パスの編集",
|
||||
"routing_forgetPath": "道にこだわらない",
|
||||
"routing_knownPaths": "既知の経路",
|
||||
"routing_knownPathsHint": "そのアプリケーションに切り替えるためのショートカットを作成します。",
|
||||
"routing_inUse": "使用中",
|
||||
"routing_qualityStrong": "最初の段階で大きな成果を上げる",
|
||||
"routing_qualityGood": "最初の成功",
|
||||
"routing_qualityFair": "最初の試みは成功を収めた",
|
||||
"routing_qualityWorked": "完了しました",
|
||||
"routing_qualityFlood": "氾濫によって伝聞",
|
||||
"routing_qualityUntested": "未検証",
|
||||
"routing_lastWorked": "{when}に勤務",
|
||||
"routing_neverWorked": "確認されていない",
|
||||
"routing_floodDelivery": "洪水による配送",
|
||||
"pathEditor_title": "経路の作成",
|
||||
"pathEditor_hopCounter": "64個のホップのうち、{count}個",
|
||||
"pathEditor_noHops": "まだホップは追加されていません。ホップを順番に追加するには、以下の「タップ」ボタンをクリックしてください。または、ホップを一切追加せずに直接送信するには、「保存」ボタンをクリックしてください。",
|
||||
"pathEditor_addHops": "ホップを、指定された順番に加える",
|
||||
"pathEditor_searchRepeaters": "繰り返し検索",
|
||||
"pathEditor_advancedHex": "高度なレベル:生のヘックスパス",
|
||||
"pathEditor_hexLabel": "ヘックスプレフィックス",
|
||||
"pathEditor_hexHelper": "各ホップごとに2つのハッシュ文字を、カンマで区切って記述",
|
||||
"pathEditor_invalidTokens": "無効: {tokens}",
|
||||
"pathEditor_tooManyHops": "最大64段階",
|
||||
"pathEditor_usePath": "この経路を使用してください",
|
||||
"pathEditor_removeHop": "ホップを取り除く",
|
||||
"pathEditor_unknownHop": "不明な増幅器",
|
||||
"map_zoomIn": "ズームイン",
|
||||
"map_zoomOut": "ズームアウト",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_centerMap": "中心地図",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth は、Chromium ブラウザが必要です。",
|
||||
"channels_communityShortId": "ID: {id}…",
|
||||
"pathTrace_legendGpsConfirmed": "GPSによる確認",
|
||||
"pathTrace_legendInferred": "推測される位置"
|
||||
"dialog_connectCompanion": "コネクトしてリピーターとルームサーバー機能にアクセス",
|
||||
"dialog_disconnectedTitle": "切断済み",
|
||||
"dialog_disconnectedMessage": "コンパニオンとの接続が切れました。",
|
||||
"contact_connectCompanion": "リピーターおよびルームサーバー機能にアクセスするには、コンパニオンに接続してください。"
|
||||
}
|
||||
|
||||
+4
-135
@@ -27,8 +27,6 @@
|
||||
"common_remove": "제거",
|
||||
"common_enable": "활성화",
|
||||
"common_disable": "비활성화",
|
||||
"common_autoRefresh": "자동 새로고침",
|
||||
"common_interval": "간격",
|
||||
"common_reboot": "재부팅",
|
||||
"common_loading": "로딩 중...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1468,43 +1466,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "디지털 입력",
|
||||
"telemetry_digitalOutputLabel": "디지털 출력",
|
||||
"telemetry_analogInputLabel": "아날로그 입력",
|
||||
"telemetry_analogOutputLabel": "아날로그 출력",
|
||||
"telemetry_genericLabel": "일반 센서",
|
||||
"telemetry_luminosityLabel": "조도",
|
||||
"telemetry_presenceLabel": "존재 감지",
|
||||
"telemetry_humidityLabel": "습도",
|
||||
"telemetry_accelerometerLabel": "가속도계",
|
||||
"telemetry_pressureLabel": "압력",
|
||||
"telemetry_altitudeLabel": "고도",
|
||||
"telemetry_frequencyLabel": "주파수",
|
||||
"telemetry_percentageLabel": "백분율",
|
||||
"telemetry_concentrationLabel": "농도",
|
||||
"telemetry_powerLabel": "전력",
|
||||
"telemetry_distanceLabel": "거리",
|
||||
"telemetry_energyLabel": "에너지",
|
||||
"telemetry_directionLabel": "방향",
|
||||
"telemetry_timeLabel": "시간",
|
||||
"telemetry_gyrometerLabel": "자이로미터",
|
||||
"telemetry_colourLabel": "색상",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "스위치",
|
||||
"telemetry_polylineLabel": "폴리라인",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "요청 수",
|
||||
"telemetry_error": "데이터를 가져올 수 없습니다",
|
||||
"telemetry_noData": "텔레메트리 데이터는 제공되지 않습니다.",
|
||||
"telemetry_channelTitle": "채널 {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2176,9 +2137,6 @@
|
||||
"translation_enableTitle": "번역 기능 활성화",
|
||||
"translation_composerTitle": "보내기 전에 번역",
|
||||
"translation_composerSubtitle": "컴포저 번역 아이콘의 기본 상태를 제어합니다.",
|
||||
"translation_autoIncomingTitle": "메시지 자동 번역",
|
||||
"translation_autoIncomingSubtitle": "알림과 채팅 또는 채널의 메시지를 자동으로 번역합니다.",
|
||||
"translation_translateMessage": "메시지 번역",
|
||||
"translation_targetLanguage": "목표 언어",
|
||||
"translation_useAppLanguage": "앱 언어 사용",
|
||||
"translation_downloadedModelLabel": "다운로드한 모델",
|
||||
@@ -2392,97 +2350,8 @@
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터",
|
||||
"chat_markAsUnread": "미리 읽지 않음으로 표시",
|
||||
"repeater_chanUtil": "채널 활용도",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_pending": "발송",
|
||||
"messageStatus_sent": "발송",
|
||||
"messageStatus_delivered": "배송 완료",
|
||||
"common_undo": "취소",
|
||||
"messageStatus_failed": "발송 실패",
|
||||
"messageStatus_repeated": "반복적으로 들었습니다",
|
||||
"contacts_searchOpen": "연락처 검색",
|
||||
"contacts_moreOptions": "더 많은 옵션",
|
||||
"contacts_searchClose": "검색 닫기",
|
||||
"routing_title": "라우팅",
|
||||
"routing_modeAuto": "자동",
|
||||
"routing_modeFlood": "홍수",
|
||||
"routing_modeManual": "사용 설명서",
|
||||
"routing_modeAutoHint": "가장 잘 알려진 경로를 자동으로 선택하고, 경로가 없을 경우에는 무작위로 경로를 선택합니다.",
|
||||
"routing_modeFloodHint": "모든 증폭기를 통해 방송됩니다. 가장 안정적이지만, 더 많은 송출 시간을 사용합니다.",
|
||||
"routing_modeManualHint": "항상 설정하신 정확한 경로를 따라 이동합니다.",
|
||||
"routing_currentRoute": "현재 경로",
|
||||
"routing_directNoHops": "직접 연결 – 중계 장치 사용 없이",
|
||||
"routing_noPathYet": "아직 경로가 없습니다. 다음 메시지가 도착할 때까지 계속 탐색합니다.",
|
||||
"routing_floodBroadcast": "모든 증폭기를 통해 방송",
|
||||
"routing_editPath": "경로 편집",
|
||||
"routing_forgetPath": "길을 잊어라",
|
||||
"routing_knownPaths": "알려진 경로",
|
||||
"routing_knownPathsHint": "해당 항목으로 전환하기 위한 경로를 선택합니다.",
|
||||
"routing_inUse": "사용 중",
|
||||
"routing_qualityStrong": "강력한 첫 번째 단계",
|
||||
"routing_qualityGood": "좋은 첫 시작",
|
||||
"routing_qualityFair": "처음 시도",
|
||||
"routing_qualityWorked": "완료됨",
|
||||
"routing_qualityFlood": "홍수 피해 상황을 통해 들었습니다.",
|
||||
"routing_qualityUntested": "검증되지 않음",
|
||||
"routing_lastWorked": "{when}에 일했습니다",
|
||||
"routing_neverWorked": "확인되지 않음",
|
||||
"routing_floodDelivery": "홍수 피해 지역 배송",
|
||||
"pathEditor_title": "경로 만들기",
|
||||
"pathEditor_hopCounter": "64개의 홉 중 {count}",
|
||||
"pathEditor_noHops": "현재 홉은 추가되지 않았습니다. 아래의 탭을 사용하여 순서대로 추가하거나, 홉 없이 바로 전송하려면 \"홉 없음\"으로 저장하십시오.",
|
||||
"pathEditor_addHops": "홉을 순서대로 첨가해주세요.",
|
||||
"pathEditor_searchRepeaters": "반복 검색",
|
||||
"pathEditor_advancedHex": "고급: 원시 헥스 경로",
|
||||
"pathEditor_hexLabel": "헥스 접두사",
|
||||
"pathEditor_hexHelper": "각 홉마다 2개의 6자리 숫자, 쉼표로 구분",
|
||||
"pathEditor_invalidTokens": "유효하지 않음: {tokens}",
|
||||
"pathEditor_tooManyHops": "최대 64개의 홉",
|
||||
"pathEditor_usePath": "이 경로를 사용하세요",
|
||||
"pathEditor_removeHop": "홉 제거",
|
||||
"pathEditor_unknownHop": "알 수 없는 중계기",
|
||||
"map_zoomIn": "줌 인",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "줌 아웃",
|
||||
"map_centerMap": "중심 지도",
|
||||
"chrome_bluetoothRequiresChromium": "웹 블루투스는 크롬 브라우저가 필요합니다.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS 확인 완료",
|
||||
"pathTrace_legendInferred": "추론된 위치"
|
||||
"dialog_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.",
|
||||
"dialog_disconnectedTitle": "연결 끊김",
|
||||
"dialog_disconnectedMessage": "컴패니언과의 연결이 끊어졌습니다.",
|
||||
"contact_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요."
|
||||
}
|
||||
|
||||
+228
-810
File diff suppressed because it is too large
Load Diff
+148
-472
@@ -92,24 +92,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Деактивирай';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Отмяни';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Изпратено';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Доставен';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Изпращане';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Не успях да изпратя';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Слушах го многократно';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Рестартирай';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Автоматично обновяване';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Интервал';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – Отворена версия';
|
||||
|
||||
@@ -318,10 +294,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Активирайте Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Бързо превключване';
|
||||
|
||||
@@ -818,6 +790,11 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Батерия';
|
||||
|
||||
@@ -1019,15 +996,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Нова група';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Повече възможности';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Търсене на контакти';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Затвори търсене';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Група';
|
||||
|
||||
@@ -1508,6 +1476,35 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Хексадесетичен Dump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Управление на пътища';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Покажи всички пътища';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Режим на маршрутизиране';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Автоматично (използвай запазения път)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Принуди режим на наводняване';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Неотдавни ACK пътища (докоснете, за да използвате):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Историята на пътя е пълна. Премахнете записи, за да добавите нови.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'скочи';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'скоци';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1519,6 +1516,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Успехи';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Премахни пътя';
|
||||
|
||||
@@ -1526,146 +1529,52 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Няма история на пътищата още.\nИзпратете съобщение, за да откриете пътища.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Действия по пътя:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Задайте персонализиран път';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Ръчно укажете маршрутен път';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Почисти Път';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Принуди преоткриване при следващо изпращане';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Пътят е почистен. Следващото съобщение ще открие маршрута отново.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Използвайте превключвателя за маршрутизиране в лентата на приложението.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Режим на наводнение е активиран. Включете го отново чрез иконката за маршрутизиране в лентата на приложението.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Пълен път';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Маршрутизиране';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Детайлите за пътя все още не са налични. Опитайте да изпратите съобщение, за да освежите.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Автомобил';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Наводнение';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Ръководство';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Автоматично избира най-известния път, като при липса на информация, използва стратегия за \"запълване\" на празните пространства.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Излъчване през всички ретранслатори. Най-надежният начин, но изисква повече време на въздуха.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Винаги следва точно пътя, който сте определили.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Текущ маршрут';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Директ – без превключватели';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Все още няма път. Съобщението продължава да се изпраща, докато не бъде открит маршрут.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Предаване през всички ретранслатори';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Редактиране на пътя';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Забравете за пътя';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Известни маршрути';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Натиснете бутона, за да превключите към него.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'В експлоатация';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Силен първи скок';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Добър първи опит';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Първият добър скок';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Беше изпълнено/Доведено до край';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Получено чрез информация, разпространена в резултат на навод.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Не тестван';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Пътят е зададен: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'никога не е потвърдено';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Доставка при навод';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Създаване на път';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count от 64 различни вида малц';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Все още няма добавени хмел. Можете да използвате бутоните по-долу, за да ги добавите по ред, или да запазите рецептата без хмел, за да я изпратите директно.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Добавете хмела в реда, в който е посочено.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Търсене на повтори';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Разширено: необработен шестничен път';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Префикси на шестнадесетична система';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Два шест-символни идентификатора на скок, разделени със запетаи';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Невалидно: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Максимум 64 крачета';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Използвайте този маршрут.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Премахнете хмела';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Неизвестен репитер';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Запазено локално. Свържете се за синхронизиране.';
|
||||
@@ -1742,39 +1651,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Карта на възлите';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Линия на видимост';
|
||||
|
||||
@@ -2103,6 +1979,17 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Сигурни ли сте, че искате да се откъснете от това устройство?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Прекъснато';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Свързването ви с вашия спътник е прекъснато.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Повторител Вход';
|
||||
|
||||
@@ -2168,13 +2055,66 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Изчисти';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Текущ път: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Използване на $count $_temp0 път';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Въведете персонализиран път';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Текущ път';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Въведете 2-символни шестнадесетични префикси за всеки хоп, разделени с кама.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'A1,F2,3C (всяка нода използва първия байт от публичния си ключ)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Пътеки (шестнадесетични префикси)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Максимум 64 скока. Всеки префикс е 2 шестнадесетични знака (1 байт).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Изберете от контакти:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Няма намерени репетитори или сървъри на стаи.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Персонализираните пътища изискват междинни скокове, които могат да препращат съобщения.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Невалидни шестнадесетични префикси: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Пътят е твърде дълъг. Максимум 64 скока са разрешени.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Задайте път';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Управление на повторители';
|
||||
|
||||
@@ -2241,6 +2181,16 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Режим на маршрутизиране';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Автоматично (използвай запазения път)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Принуди режим на наводняване';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Управление на пътища';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Презареди';
|
||||
|
||||
@@ -3337,139 +3287,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Цифров вход';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Цифров изход';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Аналогов вход';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Аналогов изход';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Общ сензор';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Осветеност';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Присъствие';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Влажност';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Акселерометър';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Налягане';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Надморска височина';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Честота';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Процент';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Концентрация';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Мощност';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Разстояние';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Енергия';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Посока';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Време';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Жироскоп';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Цвят';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Превключвател';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Полилиния';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Брой заявки';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Неуспешно получаване на данни';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Получени данни за съседи';
|
||||
|
||||
@@ -4355,16 +4172,6 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Контролира началния статус на иконата за превод, създадена от композитора.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => 'Автоматичен превод на съобщения';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Превежда автоматично съобщенията за известия, както и за чатове или канали.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Преведи съобщението';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Целеви език';
|
||||
|
||||
@@ -4492,135 +4299,4 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Увеличи';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Приближете се по-малко';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Карта на центъра';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth изисква браузър, базиран на Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'Идентификационен номер: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS потвърдено';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Извлечена позиция';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+147
-475
@@ -92,24 +92,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Deaktivieren';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Rückgängig machen';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Gesendet';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Geliefert';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Versenden';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Nicht gesendet';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Wiederholt gehört';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Neustart';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatische Aktualisierung';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervall';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – Open-Version';
|
||||
|
||||
@@ -321,10 +297,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Bluetooth aktivieren';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Schnelles Umschalten';
|
||||
|
||||
@@ -814,6 +786,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Akku';
|
||||
|
||||
@@ -1015,15 +992,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Neue Gruppe';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Weitere Optionen';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Kontakte suchen';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Erweiterte Suche';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Gruppenname';
|
||||
|
||||
@@ -1506,6 +1474,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hex-Dump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Pfadverwaltung';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Alle Pfade anzeigen';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Routenmodus';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath =>
|
||||
'Automatisch (gespeicherten Pfad verwenden)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Flut-Modus erzwingen';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Aktuelle ACK-Pfade (antippen, um zu verwenden):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'Sprung';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'Sprünge';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1517,6 +1515,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Erfolgreich';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Pfad entfernen';
|
||||
|
||||
@@ -1524,148 +1528,51 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Keine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Pfadaktionen:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Lege benutzerdefinierten Pfad fest';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Manuellen Routenpfad festlegen';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Pfad zurücksetzen';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Setze Pfad zurück, erkenne neuen Pfad bei nächster Sendung.';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Pfad zurückgesetzt. Nächste Nachricht wird Route neu entdecken.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Verwende den Routingschalter in der App-Leiste';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled => 'Flutmodus aktiviert.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Vollständiger Pfad';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Routenplanung';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Die Pfaddetails sind noch nicht verfügbar. Versuchen Sie, eine Nachricht zu senden, um zu aktualisieren.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Überschwemmung';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Handbuch';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Wählt automatisch den bekanntesten Pfad aus und verwendet eine Flutungsmethode, wenn kein Pfad bekannt ist.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Übertragung über alle Repeater. Die zuverlässigste Methode, jedoch mit höherem Datenverbrauch.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Sendet immer genau den von Ihnen festgelegten Weg.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Aktuelle Route';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direkt – ohne Zwischenverstärkung';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Noch kein Pfad gefunden. Die Nachricht wird gesendet, bis ein Weg entdeckt wurde.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Übertragung über jeden Repeater';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Pfad bearbeiten';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Vergiss den Weg';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Bekannte Routen';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Wählen Sie den Pfad, um zu diesem zu wechseln.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'Im Gebrauch';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Ein starker erster Sprung';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Ein guter erster Schritt';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Erster erfolgreicher Schritt';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Hat erfolgreich geliefert';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Information erhalten durch Nachrichten über die Überschwemmung';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Nicht getestet';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'war beschäftigt $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Pfad gesetzt: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nie bestätigt';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Lieferung bei Überschwemmung';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Pfad erstellen';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count von 64 Hopfengewächsen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Noch keine Hopfen hinzugefügt. Klicken Sie auf die Schaltflächen unten, um sie nacheinander hinzuzufügen, oder speichern Sie die Rezepter ohne Hopfen, um sie direkt zu versenden.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops =>
|
||||
'Fügen Sie die Hopfen in der richtigen Reihenfolge hinzu.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters =>
|
||||
'Suche nach wiederholten Nachrichten';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Fortgeschritten: Roh-Hex-Pfad';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Hex-Präfixe';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Zwei Hexadezimalzeichen pro Sprung, getrennt durch Kommas';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Ungültig: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximal 64 Hopfengreifer';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Verwenden Sie diesen Pfad.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Hop entfernen';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Unbekannter Repeater';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Lokal Gespeichert. Bitte Verbinden zum Synchronisieren.';
|
||||
@@ -1741,39 +1648,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Karte';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Sichtlinie';
|
||||
|
||||
@@ -2103,6 +1977,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Getrennt';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Du wurdest von deinem Begleiter getrennt.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Beim Repeater anmelden';
|
||||
|
||||
@@ -2169,13 +2054,65 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Löschen';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Aktiver Pfad: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'Hops',
|
||||
one: 'Hop',
|
||||
);
|
||||
return 'Verwenden Sie $count $_temp0 Pfad';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Gebe Pfad ein';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Aktueller Pfad';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Gebe für jeden Zwischen-Hop das 2-stellige Hex-Präfix ein, getrennt durch Kommas.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Beispiel: A1,F2,3C (jeder Knoten verwendet den ersten Byte seines öffentlichen Schlüssels)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Pfad (Hex-Präfixe)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Max 64 Sprünge. Jede Präfixe ist 2 Hexadezimalzeichen (1 Byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Oder wähle aus Kontakten aus:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Keine Repeater oder Raumserver gefunden.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Benutzerdefinierte Pfade erfordern Zwischen-Hops, die Nachrichten weiterleiten können.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Ungültige Hexadezimal-Präfixe: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Pfad zu lang. Maximal 64 Hops erlaubt.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Pfad festlegen';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Repeater-Verwaltung';
|
||||
|
||||
@@ -2240,6 +2177,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Routenmodus';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Automatisch (gespeicherten Pfad verwenden)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Flut-Modus erzwingen';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Pfadverwaltung';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Aktualisieren';
|
||||
|
||||
@@ -3346,139 +3293,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digitaleingang';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digitalausgang';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analogeingang';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analogausgang';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Allgemeiner Sensor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Helligkeit';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Anwesenheit';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Luftfeuchtigkeit';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Beschleunigungsmesser';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Druck';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Höhe';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frequenz';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Prozentsatz';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Konzentration';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Leistung';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Entfernung';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energie';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Richtung';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Zeit';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyroskop';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Farbe';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Schalter';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polylinie';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Anzahl der Anfragen';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Daten konnten nicht abgerufen werden';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Empfangene Nachbarsdaten';
|
||||
|
||||
@@ -4373,17 +4187,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Steuert den Standardzustand des Icons für die Übersetzung des Komponisten.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Nachrichten automatisch übersetzen';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Übersetzt Nachrichten für Benachrichtigungen sowie für Chats oder Kanäle automatisch.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Nachricht übersetzen';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Zielsprache';
|
||||
|
||||
@@ -4513,135 +4316,4 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Zoomen';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Auszoomen';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Zentralkarte';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth benötigt einen Chromium-Browser.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id…';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS-Bestätigung';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Abgeleitete Position';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+143
-471
@@ -92,24 +92,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Disable';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Undo';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Sent';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Delivered';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Sending';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Failed to send';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Heard repeated';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Reboot';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Autorefresh';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Interval';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore Open';
|
||||
|
||||
@@ -315,10 +291,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Enable Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Quick switch';
|
||||
|
||||
@@ -799,6 +771,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Number of retry attempts before marking a message as failed';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Battery';
|
||||
|
||||
@@ -998,15 +975,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'New Group';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'More options';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Search contacts';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Close search';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Group name';
|
||||
|
||||
@@ -1478,6 +1446,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hex Dump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Path Management';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Show all paths';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Routing mode';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Auto (use saved path)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Force Flood Mode';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Recent ACK Paths (tap to use):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Path history is full. Remove entries to add new ones.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'hop';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'hops';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1489,6 +1485,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'successes';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Remove path';
|
||||
|
||||
@@ -1496,144 +1498,50 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'No path history yet.\nSend a message to discover paths.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Path Actions:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Set Custom Path';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Manually specify routing path';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Clear Path';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => 'Force rediscovery on next send';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Path cleared. Next message will rediscover route.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle => 'Use routing toggle in app bar';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Flood mode enabled. Toggle back via routing icon in app bar.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Full Path';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Routing';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Path details not available yet. Try sending a message to refresh.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Flood';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Manual';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Picks the best known path automatically, flooding when none is known.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Broadcasts through every repeater. Most reliable, but uses more airtime.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Always sends along the exact path you set.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Current route';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direct — no repeater hops';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'No path yet. The next message floods until a route is discovered.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Broadcast through every repeater';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Edit path';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Forget path';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Known paths';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => 'Tap a path to switch to it.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'In use';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Strong first hop';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Good first hop';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Fair first hop';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Has delivered';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Heard via flood';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Untested';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Path set: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'never confirmed';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Flood delivery';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Build Path';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count of 64 hops';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'No hops yet. Tap repeaters below to add them in order, or save with no hops to send direct.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Add hops in order';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Search repeaters';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Advanced: raw hex path';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Hex prefixes';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Two hex characters per hop, separated by commas';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Invalid: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximum 64 hops';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Use this path';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Remove hop';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Unknown repeater';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally => 'Saved locally. Connect to sync.';
|
||||
|
||||
@@ -1707,39 +1615,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Line of Sight';
|
||||
|
||||
@@ -2063,6 +1938,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Are you sure you want to disconnect from this device?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Disconnected';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'You have been disconnected from your companion.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Connect to a companion to access repeater and room server features.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Repeater Login';
|
||||
|
||||
@@ -2128,12 +2014,64 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Clear';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Current path: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Using $count $_temp0 path';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Enter Custom Path';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Current path';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Enter 2-character hex prefixes for each hop, separated by commas.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Example: A1,F2,3C (each node uses first byte of its public key)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Path (hex prefixes)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Max 64 hops. Each prefix is 2 hex characters (1 byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Or select from contacts:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound => 'No repeaters or room servers found.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Custom paths require intermediate hops that can relay messages.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Invalid hex prefixes: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Path too long. Maximum 64 hops allowed.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Set Path';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Repeater Management';
|
||||
|
||||
@@ -2197,6 +2135,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Routing mode';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Auto (use saved path)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Force Flood Mode';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Path management';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Refresh';
|
||||
|
||||
@@ -3277,139 +3224,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digital Input';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digital Output';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analog Input';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analog Output';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Generic Sensor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Luminosity';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Presence';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Humidity';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Accelerometer';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Pressure';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Altitude';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frequency';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Percentage';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentration';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Power';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Distance';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energy';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Direction';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Time';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyrometer';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Colour';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Switch';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polyline';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Requests quantity';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Unable to retrieve data';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Received Neighbors Data';
|
||||
|
||||
@@ -4282,17 +4096,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Controls the default state of the composer translation icon.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Auto-translate incoming messages';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Translates Messages for notification and for chat or channel automatically.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Translate message';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Target language';
|
||||
|
||||
@@ -4418,135 +4221,4 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Zoom in';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Zoom out';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Center map';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth requires a Chromium browser';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS confirmed';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Inferred position';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+147
-474
@@ -92,24 +92,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Desactivar';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Deshacer';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Sentido';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Entregado';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Enviar';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'No se pudo enviar';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Escuché repetidamente';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Reiniciar';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Actualización automática';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervalo';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore: Versión abierta';
|
||||
|
||||
@@ -319,10 +295,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Habilitar Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Cambiar rápidamente';
|
||||
|
||||
@@ -813,6 +785,11 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Número de intentos de reintento antes de marcar un mensaje como fallido.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batería';
|
||||
|
||||
@@ -1014,15 +991,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nuevo Grupo';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Más opciones';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Buscar contactos';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Búsqueda avanzada';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nombre del grupo';
|
||||
|
||||
@@ -1505,6 +1473,34 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Mapeo Hexadecimal:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Gestión de Rutas';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Mostrar todos los caminos';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Modo de enrutamiento';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Auto (usar la ruta guardada)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Modo Inundación Forzado';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Rutas de ACK Recientes (tocar para usar):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'El historial de rutas está completo. Eliminar entradas para añadir nuevas.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'salta';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'salta';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1516,6 +1512,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Éxitos';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Eliminar ruta';
|
||||
|
||||
@@ -1523,147 +1525,53 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Aún no hay historial de rutas.\nEnvía un mensaje para descubrir rutas.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Acciones de Ruta:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Establecer Ruta Personalizada';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle =>
|
||||
'Especificar manualmente la ruta de enrutamiento';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Limpiar Ruta';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Forzar redescubrimiento en el próximo envío';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Ruta eliminada. El siguiente mensaje redescubrirá la ruta.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Utilizar el interruptor de enrutamiento en la barra de herramientas';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'El modo de inundación está habilitado. Desactívalo mediante el icono de enrutamiento en la barra de herramientas de la aplicación.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Ruta completa';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Ruteo';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Los detalles de la ruta aún no están disponibles. Intenta enviar un mensaje para refrescar.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Coche';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Inundación';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Manual';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Selecciona automáticamente la ruta más conocida, y si no hay ninguna ruta conocida, utiliza la ruta más directa.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Transmisiones a través de todos los repetidores. Es la opción más fiable, pero utiliza más tiempo de transmisión.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Siempre sigue exactamente la ruta que usted ha definido.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Ruta actual';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Directo — sin saltos de repetidor';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Aún no hay un camino definido. El mensaje se envía continuamente hasta que se encuentre una ruta.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Transmisión a través de todos los repetidores.';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Editar ruta';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Olvídate del camino';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Rutas conocidas';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Seleccione una opción para cambiar a esa.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'En uso';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Primer salto exitoso';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Primer paso exitoso';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Primer salto de calidad';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Ha cumplido';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Se ha escuchado a través de rumores.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Sin probar';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'trabajó $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Ruta establecida: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nunca confirmado';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Entrega por inundación';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Crear ruta';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count de 64 granos de lúpulo';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Aún no se han añadido lúpulos. Haga clic en los repetidores para añadirlos en el orden deseado, o guarde la receta sin lúpulos para enviarla directamente.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Añadir los lúpulos en el orden adecuado.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Buscar repetidores';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex =>
|
||||
'Avanzado: ruta hexadecimal sin procesar';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Prefijos hexadecimales';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Dos caracteres hexadecimales por salto, separados por comas.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Inválido: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Máximo 64 saltos';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Utilice esta ruta.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Eliminar el lúpulo';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Repetidor desconocido';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Guardado localmente. Conéctate para sincronizar.';
|
||||
@@ -1738,39 +1646,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mapa de Nodos';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Línea de visión';
|
||||
|
||||
@@ -2099,6 +1974,17 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'¿Está seguro de que desea desconectarse de este dispositivo?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Desconectado';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Te has desconectado de tu compañero.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Iniciar sesión en el Repetidor';
|
||||
|
||||
@@ -2164,13 +2050,66 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Borrar';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Ruta actual: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Usando $count $_temp0 ruta';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Introducir Ruta Personalizada';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Ruta actual';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Introduzca los prefijos hexadecimales de 2 caracteres para cada salto, separados por comas.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Ejemplo: A1,F2,3C (cada nodo utiliza el primer byte de su clave pública).';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Prefijos hexadecimales';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Máximo 64 saltos. Cada prefijo tiene 2 caracteres hexadecimales (1 byte).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'O seleccionar de contactos:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'No se encontraron repetidores ni servidores de sala.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Las rutas personalizadas requieren saltos intermedios que pueden transmitir mensajes.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Prefijos hexadecimales inválidos: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'La ruta es demasiado larga. Se permiten un máximo de 64 saltos.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Establecer Ruta';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Gestión de Repetidores';
|
||||
|
||||
@@ -2235,6 +2174,15 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Modo de enrutamiento';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Auto (usar la ruta guardada)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Modo Inundación Forzado';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Gestión de rutas';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Actualizar';
|
||||
|
||||
@@ -3333,139 +3281,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Entrada digital';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Salida digital';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Entrada analógica';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Salida analógica';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Sensor genérico';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Luminosidad';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Presencia';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Humedad';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Acelerómetro';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Presión';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Altitud';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frecuencia';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Porcentaje';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentración';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Potencia';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Distancia';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energía';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Dirección';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Hora';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Girómetro';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Color';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Interruptor';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polilínea';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Número de solicitudes';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'No se pudieron obtener los datos';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Recibidas Datos de Vecinos';
|
||||
|
||||
@@ -4359,17 +4174,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Controla el estado predeterminado del icono de traducción del compositor.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Traducir mensajes automáticamente';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Traduce mensajes para notificaciones y para chats o canales automáticamente.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Traducir mensaje';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Idioma de destino';
|
||||
|
||||
@@ -4499,135 +4303,4 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Acercar';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Acercar';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Mapa del centro';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth requiere un navegador Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'Confirmado mediante GPS';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Posición inferida';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+149
-473
@@ -92,24 +92,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Désactiver';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Annuler';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Envoyer';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Livré';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Envoyer';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Échec de l\'envoi';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Répété plusieurs fois';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Redémarrer';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Actualisation automatique';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervalle';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore Open';
|
||||
|
||||
@@ -321,10 +297,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Activer le Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Basculement rapide';
|
||||
|
||||
@@ -819,6 +791,11 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Nombre de tentatives de relance avant de marquer un message comme ayant échoué.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batterie';
|
||||
|
||||
@@ -1020,15 +997,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nouveau Groupe';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Plus d\'options';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Rechercher des contacts';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Recherche avancée';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nom du groupe';
|
||||
|
||||
@@ -1511,6 +1479,35 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Vidéo de Dump Hexadécimal :';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Gestion des chemins';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Afficher tous les chemins';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Mode de routage';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Mode tout le réseau forcé';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Chemins ACK récents (touchez pour utiliser) :';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'L\'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'saut';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'sauts';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1522,6 +1519,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Succès';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Supprimer le chemin';
|
||||
|
||||
@@ -1529,146 +1532,53 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Aucune historique de parcours disponible.\nEnvoyez un message pour découvrir les parcours.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Actions du chemin :';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Définir un chemin personnalisé';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle =>
|
||||
'Spécifier manuellement le chemin de routage';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Effacer le chemin';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Forcer la redécouverte lors de la prochaine envoi';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Le chemin est dégagé. Le prochain message redécouvrira le tracé.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Désactive l\'apprentissage du chemin (à éviter). Utiliser le commutateur de routage dans la barre d\'application pour rebasculer en mode auto par la suite.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Le mode envoi à tout le réseau est activé. Changer via l\'icône de routage dans la barre d\'outils.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Chemin complet';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Planification des itinéraires';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Les détails du chemin ne sont pas encore disponibles. Essayez d\'envoyer un message pour rafraîchir.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Voiture';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Inondation';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Manuel';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Sélectionne automatiquement le chemin le plus connu, et utilise la méthode de \"inondation\" si aucun chemin n\'est connu.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Diffusion via tous les répéteurs. La méthode la plus fiable, mais qui utilise plus de temps d\'antenne.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Il suit toujours le chemin précis que vous avez défini.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Itinéraire actuel';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direct — sans relais';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Aucune voie encore trouvée. Le message suivant est envoyé jusqu\'à ce qu\'une route soit découverte.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Diffusion via tous les répéteurs';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Modifier le chemin';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Oubliez le chemin';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Chemins connus';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => 'Créez un raccourci pour y accéder.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'En cours d\'utilisation';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Première étape réussie';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Première étape réussie';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Première étape réussie';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'A livré';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Rapporté par des informations provenant de plusieurs sources.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Non testé';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'a travaillé $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Chemin défini : $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'jamais confirmé';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Livraison en cas de inondation';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Créer un chemin';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count parmi 64 houblons';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Aucun houblon ajouté pour le moment. Cliquez sur les répétiteurs ci-dessous pour les ajouter dans l\'ordre souhaité, ou enregistrez sans houblon pour envoyer directement.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops =>
|
||||
'Ajoutez les houblons dans l\'ordre souhaité.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Rechercher des répétiteurs';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Avancé : chemin hexadécimal brut';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Préfixes hexadécimaux';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Deux caractères hexadécimaux par saut, séparés par des virgules.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Incorrect : $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximum 64 sauts';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Utilisez ce chemin.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Éliminer le haricot';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Répéteur non identifié';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Sauvegardé localement. Connectez-vous pour synchroniser.';
|
||||
@@ -1745,39 +1655,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Carte des nœuds';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Ligne de vue';
|
||||
|
||||
@@ -2108,6 +1985,17 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Déconnecté';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Vous avez été déconnecté de votre compagnon.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Connexion au répéteur';
|
||||
|
||||
@@ -2173,13 +2061,66 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Effacer';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Chemin actuel : $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Utiliser $count $_temp0 chemin';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Entrer un chemin personnalisé';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Chemin actuel';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Entrez les préfixes hexadécimaux de 2 caractères pour chaque saut, séparés par des virgules.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Exemple : A1,F2,3C (chaque nœud utilise le premier octet de sa clé publique).';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Préfixes hexadécimaux';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Max 64 sauts. Chaque préfixe fait 2 caractères hexadécimaux (1 octet)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Sélectionner à partir des contacts :';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Aucun répéteur ou room server n\'a été trouvé.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Les chemins personnalisés nécessitent des sauts intermédiaires qui peuvent transmettre des messages.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Préfixes hexadécimaux invalides : $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Le chemin est trop long. Maximum 64 sauts autorisés.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Définir le chemin';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Gestion des répéteurs';
|
||||
|
||||
@@ -2245,6 +2186,16 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Mode de routage';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Auto (utiliser le chemin sauvegardé)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Mode tout le réseau forcé';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Gestion des chemins';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Rafraîchir';
|
||||
|
||||
@@ -3353,139 +3304,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Entrée numérique';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Sortie numérique';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Entrée analogique';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Sortie analogique';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Capteur générique';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Luminosité';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Présence';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Humidité';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Accéléromètre';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Pression';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Altitude';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Fréquence';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Pourcentage';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentration';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Puissance';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Distance';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Énergie';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Direction';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Heure';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyromètre';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Couleur';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Interrupteur';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polyligne';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Nombre de requêtes';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Impossible de récupérer les données';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Données des voisins reçues';
|
||||
|
||||
@@ -4386,17 +4204,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Contrôle l\'état par défaut de l\'icône de traduction du composant.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Traduire automatiquement les messages';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Traduit automatiquement les messages pour les notifications et pour les discussions ou les canaux.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Traduire le message';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Langue cible';
|
||||
|
||||
@@ -4525,135 +4332,4 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Zoomez';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Zoomez';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Carte du centre';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth nécessite un navigateur Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID : $id…';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'Le GPS a confirmé.';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Position déduite';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+149
-474
@@ -92,24 +92,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Leteteszt';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Még egyszer';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Elküldve';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Szállítva';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Elküldés';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Nem sikerült elküldeni';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Ismételtem';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Újraindítás';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatikus frissítés';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervallum';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore nyitott';
|
||||
|
||||
@@ -318,10 +294,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Engedje be a Bluetooth funkciót';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Gyors váltás';
|
||||
|
||||
@@ -817,6 +789,11 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'A próbálkozások száma, mielőtt egy üzenetet hibásnak jelölünk.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Akku';
|
||||
|
||||
@@ -1020,15 +997,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Új csoport';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'További lehetőségek';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Keresssz kapcsolatokat';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Teljesítse a keresést';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Csoport neve';
|
||||
|
||||
@@ -1514,6 +1482,36 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hex-dump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Útvonal-kezelés';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Mutasson meg minden útvonalat';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Útvonal-kezelési mód';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath =>
|
||||
'Automatikus (az eddigi útvonal használata)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Erőforrás-alapú áramlás mód';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Legutóbbi használt útvonalak (gombra kattintva):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Az előző lépések listája teljes. Törölj ki a bejegyzéseket, hogy újokat hozzáadhatsd.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'ugor';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'babér';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1525,6 +1523,12 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'sikerek';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Törölje a elérési útvonalat';
|
||||
|
||||
@@ -1532,148 +1536,52 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Még nincs útvonal-történet.\nKüldjön egy üzenetet, hogy megtudja a lehetséges útvonalakat.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Céltúrások:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Beállítsd a saját útvonalat';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Kézzel megadott útvonal';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Egyértelmű út';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'A parancs új küldéskor újra kell aktivizálnia.';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Útvonal cleared. A következő üzenet újból feltérképezheti az útvonalat.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Használja a \"útvonal\" kapcsolót az alkalmazás sávjában.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Árvízvédelmi mód bekapcsolva. A visszaállítás a alkalmazásban található útvonal ikon segítségével.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Teljes elérési út';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Útvonal meghatározás';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Az útvonal részletei még nem elérhetők. Próbálja meg küldeni egy üzenetet, hogy frissítse az információkat.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Autó';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Áradás';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Használati útmutató';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Automatikusan kiválasztja a legismertebb útvonalat, és ha egyik sem ismert, akkor \"vízzel\" tölti ki.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Átvisszaadások minden erősítőn keresztül. A legmegbízhatóbb megoldás, de több időt igényel.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Mindig pontosan az útvonalat követi, amelyet megad.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Jelenlegi útvonal';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Közvetlen – nincs átjáró állomás';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Még nincs útvonal. A következő üzenet a keresésig vár.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Azonnali továbbítás minden erősítőn keresztül.';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Útvonal szerkesztése';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Felejtsd el a útvonalat';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Jellegzetes útvonalak';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Készíts egy útvonalat, hogy átválhass rá.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'Használatban';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Erős első lépés';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Jó első lépés';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Jó első lépés';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Előállított';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Információt hallottam a katasztrófa miatt.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Vizsgálatnak nem подвержен';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Path set: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'sosem megerősítve';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Vízparti szállítás';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Út megépítése';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count db 64-ből';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Még nem adtam hozzá a bazsalikomot. A lent található gombokat használhatod, hogy sorrendben adjd hozzá, vagy mentheted anélkül, hogy bazsalikomot adnál hozzá, hogy közvetlenül elküldd.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops =>
|
||||
'Adja hozzá a bazsaidat a megfelelő sorrendben.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Ismétlő eszközök keresése';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Haladó szint: alapvető hex-út';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Hex előtagok';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Két hatjegyű szám minden lépésen, amelyek egymástól elválasztják a kommák.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Érvénytelen: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'A maximális szám 64.';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Használja ezt az útvonalat.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Távolítsa el a bazsalikomot';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Tudatlan erősítő';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Helyileg mentve. Kapcsolódjon a szinkronizáláshoz.';
|
||||
@@ -1748,39 +1656,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Grafikus ábrázás';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Látási vonal';
|
||||
|
||||
@@ -2111,6 +1986,16 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Biztosan szeretné kiírni ezt a készüléket?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Lejárat';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage => 'Lehentetőtől megszakadtál.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Csatlakozzon egy kísérőhöz a ismétlő és szobaszerver funkciók eléréséhez.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Ismételt bejelentkezés';
|
||||
|
||||
@@ -2177,13 +2062,67 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Egyértelmű';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Jelenlegi útvonal: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'ugrások',
|
||||
one: 'ugrás',
|
||||
);
|
||||
return '$count $_temp0 útvonal használata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Adja meg a saját elérési útvonalat';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Jelenlegi útvonal';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Adja meg a 2 karakteres hexadecimális előtagokat minden lépéshez, tagolva kommával.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Példa: A1, F2, 3C (minden csomó az első részét használja a nyilvános kulcsából)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Út (hex-prefixek)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'A maximális hossz 64 karakter. Minden előző rész 2 hatos számjegyből áll (1 bájt).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts =>
|
||||
'Válasszon a kontaktlista elembek közül:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Nincs megtalálva semmilyen ismétlődő vagy helyiség-szolgáltató szervert.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Az egyedi útvonalaknak szükségük van átjáró pontokra, amelyek képesek üzeneteket továbbítani.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Érvénytelen hexadecimális előtagok: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Az út túl hosszú. A maximális engedélyezett lépések száma 64.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Útvonal meghatározása';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Adatkapcsolás kezelése';
|
||||
|
||||
@@ -2249,6 +2188,16 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Útvonal-kezelési mód';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Automatikus (az eddigi útvonal használata)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Erőforrás-alapú áramlás mód';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Útvonal-kezelés';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Újrafriszol';
|
||||
|
||||
@@ -3347,139 +3296,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
return '$celsius °C / $fahrenheit °F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digitális bemenet';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digitális kimenet';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analóg bemenet';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analóg kimenet';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Általános érzékelő';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Fényerő';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Jelenlét';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Páratartalom';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Gyorsulásmérő';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Nyomás';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Magasság';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frekvencia';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Százalék';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Koncentráció';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Teljesítmény';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Távolság';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energia';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Irány';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Idő';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Giroszkóp';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Szín';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Kapcsoló';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Töröttvonal';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Kérések száma';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Nem sikerült lekérni az adatokat';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Kapott szomszédok adatait';
|
||||
|
||||
@@ -4375,16 +4191,6 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Ellenőrzi a zeneszerző fordítási ikon alapértékét.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => 'Üzenetek automatikus fordítása';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Automatikusan lefordítja az üzeneteket az értesítésekhez, valamint a csevegésekhez vagy csatornákhoz.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Üzenet fordítása';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Célnyelv';
|
||||
|
||||
@@ -4513,135 +4319,4 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Nagyítva';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Kicsökkentett nézet';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Központi tér térkép';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'A Web Bluetooth-hoz egy Chromium-alapú böngésző szükséges.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'Az azonosító: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS-en megerősítve';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Feltehető helyzet';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+147
-475
@@ -92,24 +92,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Disattivare';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Annulla';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Invia';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Consegnato';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Invio';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Impossibile inviare';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Sentito ripetutamente';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Riavvia';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Aggiornamento automatico';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervallo';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore Open';
|
||||
|
||||
@@ -321,10 +297,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Abilita il Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Passa velocemente';
|
||||
|
||||
@@ -816,6 +788,11 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Numero di tentativi di riprova prima di considerare un messaggio come fallito.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batteria';
|
||||
|
||||
@@ -1016,15 +993,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nuovo Gruppo';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Ulteriori opzioni';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Cerca contatti';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Ricerca avanzata';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nome gruppo';
|
||||
|
||||
@@ -1507,6 +1475,34 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Dumpa Esadecimale:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Gestione Percorsi';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Mostra tutti i percorsi';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Modalità di routing';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Utilizza il percorso salvato';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Modalità Inondamento Forzato';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Percorsi ACK Recenti (tocca per usare):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'La cronologia del percorso è piena. Rimuovi gli elementi per aggiungere nuovi.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'salta';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'salta';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1518,6 +1514,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'successi';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Rimuovi percorso';
|
||||
|
||||
@@ -1525,148 +1527,53 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Non c\'è ancora una cronologia del percorso.\nInvia un messaggio per scoprire i percorsi.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Azioni Percorso:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Imposta Percorso Personalizzato';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle =>
|
||||
'Specifica manualmente il percorso di routing';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Cancella Percorso';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Riprova la scoperta alla prossima invio';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Percorso sgomberato. Il prossimo messaggio riidentifierà il percorso.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Utilizza l\'interruttore di routing nella barra delle applicazioni';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Modalità alluvione abilitata. Disattivala tramite l\'icona di routing nella barra in alto.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Percorso Completo';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Instradamento';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'I dettagli del percorso non sono ancora disponibili. Prova a inviare un messaggio per ricaricare.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Inondazione';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Manuale';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Seleziona automaticamente il percorso più noto, e in caso di assenza di informazioni, utilizza un percorso casuale.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Trasmissioni tramite ogni ripetitore. Il metodo più affidabile, ma richiede più tempo di trasmissione.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Invia sempre esattamente il percorso che hai definito.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Percorso attuale';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops =>
|
||||
'Diretto — senza passaggi tramite ripetitori';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Al momento non è stata individuata alcuna via. Il messaggio viene inviato ripetutamente finché non viene trovata una rotta.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Trasmissione attraverso ogni ripetitore';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Percorso di modifica';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Dimentica il percorso';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Percorsi noti';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Seleziona un percorso per accedere a questa opzione.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'In uso';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Primo salto molto deciso';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Primo tentativo di successo';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Primo salto di qualità';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'È stato consegnato';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Ho sentito tramite un messaggio urgente';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Non testato';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Percorso impostato: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'mai confermato';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Consegna in caso di alluvione';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Creare percorso';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count tra 64 varietà di luppolo';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Al momento non ci sono ingredienti aggiuntivi. Per aggiungerli nell\'ordine desiderato, cliccate sui ripetitori sottostanti. In alternativa, potete salvare la ricetta senza ingredienti aggiuntivi per inviarla direttamente.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops =>
|
||||
'Aggiungere i luppoli nell\'ordine desiderato.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Ricerca ripetitori';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Avanzato: percorso esadecimale grezzo';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Prefissi esadecimali';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Due caratteri esadecimali per ogni salto, separati da virgole.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Non valido: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Massimo 64 orari';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Utilizza questo percorso';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Rimuovere il luppolo';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Ripetitore sconosciuto';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Salvatato localmente. Connetti per sincronizzare.';
|
||||
@@ -1742,39 +1649,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mappa Nodi';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linea di vista';
|
||||
|
||||
@@ -2102,6 +1976,17 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Sei sicuro di voler disconnetterti da questo dispositivo?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Disconnesso';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Sei stato disconnesso dal tuo compagno.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Login Ripetitore';
|
||||
|
||||
@@ -2167,13 +2052,66 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Cancella';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Percorso corrente: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Utilizzare $count $_temp0 percorso';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Inserisci percorso personalizzato';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Percorso corrente';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Inserire i prefissi esadecimali a 2 caratteri per ogni salto, separati da virgole.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Esempio: A1,F2,3C (ogni nodo utilizza il primo byte della sua chiave pubblica)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Prefisso esadecimale (percorso)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Massimo 64 salti. Ogni prefisso è composto da 2 caratteri esadecimali (1 byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Seleziona da contatti:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Non sono stati trovati ripetitori o server di stanza.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'I percorsi personalizzati richiedono salti intermedi che possono inoltrare messaggi.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Prefissi esadecimali non validi: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Il percorso è troppo lungo. Massimo 64 salti consentiti.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Imposta Percorso';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Gestione Ripetitori';
|
||||
|
||||
@@ -2240,6 +2178,15 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Modalità di routing';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Percorso salvato automatico';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Modalità Inondamento Forzato';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Gestione dei percorsi';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Aggiorna';
|
||||
|
||||
@@ -3340,139 +3287,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Ingresso digitale';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Uscita digitale';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Ingresso analogico';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Uscita analogica';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Sensore generico';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Luminosità';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Presenza';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Umidità';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Accelerometro';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Pressione';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Altitudine';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frequenza';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Percentuale';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentrazione';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Potenza';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Distanza';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energia';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Direzione';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Ora';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Giroscopio';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Colore';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Interruttore';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polilinea';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Numero di richieste';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Impossibile recuperare i dati';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Ricevute dati vicini';
|
||||
|
||||
@@ -4366,17 +4180,6 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Controlla lo stato predefinito dell\'icona di traduzione del compositore.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Traduci automaticamente i messaggi';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Traduce automaticamente i messaggi per le notifiche e per le chat o i canali.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Traduci messaggio';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Lingua di destinazione';
|
||||
|
||||
@@ -4505,135 +4308,4 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Ingrandisci';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Riduci la visualizzazione';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Mappa del centro';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth richiede un browser basato su Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'Il GPS conferma';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Posizione dedotta';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+208
-537
File diff suppressed because it is too large
Load Diff
+141
-466
@@ -92,24 +92,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => '비활성화';
|
||||
|
||||
@override
|
||||
String get common_undo => '취소';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => '발송';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => '배송 완료';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => '발송';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => '발송 실패';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => '반복적으로 들었습니다';
|
||||
|
||||
@override
|
||||
String get common_reboot => '재부팅';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => '자동 새로고침';
|
||||
|
||||
@override
|
||||
String get common_interval => '간격';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore 공개';
|
||||
|
||||
@@ -307,10 +283,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => '블루투스 활성화';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => '빠른 전환';
|
||||
|
||||
@@ -771,6 +743,11 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_maxMessageRetriesSubtitle => '메시지를 실패로 처리하기 전 시도 횟수';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => '배터리';
|
||||
|
||||
@@ -962,15 +939,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => '새로운 그룹';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => '더 많은 옵션';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => '연락처 검색';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => '검색 닫기';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => '그룹 이름';
|
||||
|
||||
@@ -1437,6 +1405,34 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => '헥스 덤프:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => '경로 관리';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => '모든 경로 표시';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => '라우팅 방식';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => '자동 (저장된 경로 사용)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => '강수 모드 활성화';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => '최근 사용한 ACK 경로 (사용하려면 탭):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'이력 기록은 이미 가득 차 있습니다. 항목을 삭제하여 새로운 항목을 추가할 수 있습니다.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => '점프';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => '홉';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1448,146 +1444,61 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => '성공 사례';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => '경로 제거';
|
||||
|
||||
@override
|
||||
String get chat_noPathHistoryYet => '아직 경로 기록이 없습니다.\n경로를 찾기 위해 메시지를 보내세요.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => '경로 작업:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => '사용자 지정 경로 설정';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => '수동으로 경로를 지정';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => '명확한 길';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => '다음 전송 시, 강제 재전송 설정';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared => '경로가 확보되었습니다. 다음 메시지는 경로를 다시 찾을 것입니다.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle => '앱 바에서 라우팅 스위치를 사용';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'홍수 모드 활성화됨. 앱 바의 경로 아이콘을 사용하여 다시 전환할 수 있습니다.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => '전체 경로';
|
||||
|
||||
@override
|
||||
String get routing_title => '라우팅';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'경로 정보는 아직 제공되지 않습니다. 메시지를 보내어 다시 시도해 보세요.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => '자동';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => '홍수';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => '사용 설명서';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'가장 잘 알려진 경로를 자동으로 선택하고, 경로가 없을 경우에는 무작위로 경로를 선택합니다.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'모든 증폭기를 통해 방송됩니다. 가장 안정적이지만, 더 많은 송출 시간을 사용합니다.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint => '항상 설정하신 정확한 경로를 따라 이동합니다.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => '현재 경로';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => '직접 연결 – 중계 장치 사용 없이';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet => '아직 경로가 없습니다. 다음 메시지가 도착할 때까지 계속 탐색합니다.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => '모든 증폭기를 통해 방송';
|
||||
|
||||
@override
|
||||
String get routing_editPath => '경로 편집';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => '길을 잊어라';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => '알려진 경로';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => '해당 항목으로 전환하기 위한 경로를 선택합니다.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => '사용 중';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => '강력한 첫 번째 단계';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => '좋은 첫 시작';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => '처음 시도';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => '완료됨';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => '홍수 피해 상황을 통해 들었습니다.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => '검증되지 않음';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return '$when에 일했습니다';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Path set: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => '확인되지 않음';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => '홍수 피해 지역 배송';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => '경로 만들기';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '64개의 홉 중 $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'현재 홉은 추가되지 않았습니다. 아래의 탭을 사용하여 순서대로 추가하거나, 홉 없이 바로 전송하려면 \"홉 없음\"으로 저장하십시오.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => '홉을 순서대로 첨가해주세요.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => '반복 검색';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => '고급: 원시 헥스 경로';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => '헥스 접두사';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper => '각 홉마다 2개의 6자리 숫자, 쉼표로 구분';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return '유효하지 않음: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => '최대 64개의 홉';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => '이 경로를 사용하세요';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => '홉 제거';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => '알 수 없는 중계기';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally => '로컬에 저장. 동기화 연결';
|
||||
|
||||
@@ -1660,39 +1571,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => '노드 매핑';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => '시야';
|
||||
|
||||
@@ -2012,6 +1890,15 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get dialog_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => '연결 끊김';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage => '컴패니언과의 연결이 끊어졌습니다.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion => '리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => '다시 로그인';
|
||||
|
||||
@@ -2074,12 +1961,64 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => '명확하게';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return '현재 경로: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Using $count $_temp0 path';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => '사용자 지정 경로 입력';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => '현재 경로';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'각 단계에 대한 2자리 헥사데진 접두사를 쉼표로 구분하여 입력하세요.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'예시: A1, F2, 3C (각 노드는 자신의 공개 키의 첫 번째 바이트를 사용)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => '경로 (헥스 접두사)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'최대 64개의 홉. 각 접두사는 2개의 16진수 문자(1바이트)로 구성됩니다.';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => '또 연락처 목록에서 선택:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound => '반복 장치 또는 서버는 찾을 수 없습니다.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'사용자 정의 경로에는 메시지를 전달할 수 있는 중간 경로가 필요합니다.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return '유효하지 않은 16진수 접두사: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => '경로가 너무 길어. 최대 64개의 연결만 허용됩니다.';
|
||||
|
||||
@override
|
||||
String get path_setPath => '경로 설정';
|
||||
|
||||
@override
|
||||
String get repeater_management => '리피터 관리';
|
||||
|
||||
@@ -2141,6 +2080,15 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => '라우팅 방식';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => '자동 (저장된 경로 사용)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => '강수 모드 활성화';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => '경로 관리';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => '새롭게';
|
||||
|
||||
@@ -3157,139 +3105,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => '디지털 입력';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => '디지털 출력';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => '아날로그 입력';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => '아날로그 출력';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => '일반 센서';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => '조도';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => '존재 감지';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => '습도';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => '가속도계';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => '압력';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => '고도';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => '주파수';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => '백분율';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => '농도';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => '전력';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => '거리';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => '에너지';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => '방향';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => '시간';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => '자이로미터';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => '색상';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => '스위치';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => '폴리라인';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => '요청 수';
|
||||
|
||||
@override
|
||||
String get telemetry_error => '데이터를 가져올 수 없습니다';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => '이웃 정보 수집';
|
||||
|
||||
@@ -4136,16 +3951,6 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get translation_composerSubtitle => '컴포저 번역 아이콘의 기본 상태를 제어합니다.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => '메시지 자동 번역';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'알림과 채팅 또는 채널의 메시지를 자동으로 번역합니다.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => '메시지 번역';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => '목표 언어';
|
||||
|
||||
@@ -4268,134 +4073,4 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => '줌 인';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => '줌 아웃';
|
||||
|
||||
@override
|
||||
String get map_centerMap => '중심 지도';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium => '웹 블루투스는 크롬 브라우저가 필요합니다.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS 확인 완료';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => '추론된 위치';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+147
-470
@@ -92,24 +92,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Uitschakelen';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Achterhalen/Annuleren';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Verzonden';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Leverd';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Verzenden';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Niet verzonden';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Hearsay, herhaald';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Herstarten';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatisch vernieuwen';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Tijdsinterval';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore Open';
|
||||
|
||||
@@ -317,10 +293,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Activeer Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Snelle overschakeling';
|
||||
|
||||
@@ -808,6 +780,11 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batterij';
|
||||
|
||||
@@ -1008,15 +985,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nieuwe Groep';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Meer opties';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Zoek contactpersonen';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Zoeken';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Groepnaam';
|
||||
|
||||
@@ -1494,6 +1462,34 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hex-dump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Beheer van Paden';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Toon alle paden';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Routeerwijze';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Dwing Floodsmodus';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Recente ACK Paden (tik om te gebruiken):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'De voorgeschiedenis is vol. Verwijder vermeldingen om er nieuwe aan toe te voegen.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'Hop';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'hoppen';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1505,6 +1501,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Succesvol';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Pad verwijderen';
|
||||
|
||||
@@ -1512,144 +1514,52 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Geen geschiedenis van paden nog beschikbaar.\nVerzend een bericht om paden te ontdekken.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Padacties:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Stel aangepaste pad in';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Handmatig routepad specificeren';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Duidelijke Pad';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Dwing herontdekking bij volgende verzending';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Pad is vrijgegeven. Volgende bericht herontdekt route.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Gebruik de route-schakelaar in de app-balk';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Volledige Pad';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Routeplanning';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'De paddetails zijn nog niet beschikbaar. Probeer een bericht te sturen om te vernieuwen.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Overstroming';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Handleiding';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Selecteert automatisch het bekendste pad, en gebruikt een flood-algoritme als er geen bekend pad is.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Uitzendingen via elke zender. De meest betrouwbare methode, maar vereist meer uitzendtijd.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Stuurt altijd de exacte route die u heeft aangegeven.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Huidige route';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direct – zonder tussenliggende schakels';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Er is nog geen route gevonden. De berichten blijven binnenkomen totdat een route is ontdekt.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Uitgestoten via elke zender.';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Pad bewerken';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Vergeet het pad';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Bekende routes';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => 'Maak een route om er naartoe te gaan.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'In gebruik';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Sterke eerste sprong';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Een goede eerste stap';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Een goede eerste hop';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Is geleverd';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Hears via een overstroming';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Niet getest';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Pad ingesteld: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nooit bevestigd';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes zijn behaald, $failures zijn mislukt';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Levering bij overstroming';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Pad creëren';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count van 64 hopgranen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Er zijn nog geen hop toegevoegd. Klik op de onderstaande knoppen om ze in de juiste volgorde toe te voegen, of sla de bestelling op zonder hop om deze direct te versturen.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Voeg hop toe in de juiste volgorde.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Zoek naar herhaaldelijke zenders';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Geavanceerd: ruwe hex-pad';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Hex-voorkanten';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Twee hex-tekens per stap, gescheiden door komma\'s';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Ongeldig: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximaal 64 hopken';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Gebruik deze route.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Verwijder de hop';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Onbekend type zender';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Opgeslagen lokaal. Verbinden om te synchroniseren.';
|
||||
@@ -1725,39 +1635,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Kaart van de knopen';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Zichtlijn';
|
||||
|
||||
@@ -2086,6 +1963,17 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Verbroken';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Je bent losgekoppeld van je companion.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Inloggen Repeater';
|
||||
|
||||
@@ -2151,12 +2039,65 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Schoonmaken';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Huidige pad: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Gebruik $count $_temp0 pad';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Voer aangepaste pad in';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Huidige pad';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Voer 2-letter hex-voorgiffen voor elke hop in, gescheiden door komma\'s.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Voorbeeld: A1,F2,3C (elke node gebruikt het eerste byte van zijn openbare sleutel)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Pad (hex-voorkeursletters)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Maximaal 64 sprongen. Elke prefix is 2 hexadecimale tekens (1 byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Of select contacten:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound => 'Geen repeaters of roomservers gevonden.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Aangepaste paden vereisen tussentse overstappen die berichten kunnen doorgeven.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Ongeldige hex-voorkeursletters: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Pad is te lang. Maximaal 64 sprongen zijn toegestaan.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Stel Pad in';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Beheer Repeaters';
|
||||
|
||||
@@ -2221,6 +2162,16 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Routeerwijze';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Automatisch (gebruik opgeslagen pad)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Dwing Floodmodus Af';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Beheer van paden';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Vernieuwen';
|
||||
|
||||
@@ -3316,139 +3267,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digitale ingang';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digitale uitgang';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analoge ingang';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analoge uitgang';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Algemene sensor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Lichtsterkte';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Aanwezigheid';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Luchtvochtigheid';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Versnellingsmeter';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Druk';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Hoogte';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frequentie';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Percentage';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentratie';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Vermogen';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Afstand';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energie';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Richting';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Tijd';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyrometer';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Kleur';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Schakelaar';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polylijn';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Aantal aanvragen';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Kan gegevens niet ophalen';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Ontvangen Buurdata';
|
||||
|
||||
@@ -4339,16 +4157,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Stelt de standaardstatus van het pictogram voor de vertaling van de componist in.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => 'Berichten automatisch vertalen';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Vertaalt berichten automatisch voor meldingen en voor chats of kanalen.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Bericht vertalen';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Doeltaal';
|
||||
|
||||
@@ -4476,135 +4284,4 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Inzoomen';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Inzoomen';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Centraal overzicht';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth vereist een Chromium-browser.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS-locatie bevestigd';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Afgeleide positie';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+150
-475
@@ -92,24 +92,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Wyłącz';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Wycofaj';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Wysłane';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Dostarczone';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Wysyłanie';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Nie udało się wysłać';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Usłyszałem to wielokrotnie';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Uruchom ponownie';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatyczne odświeżanie';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Interwał';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – wersja open source';
|
||||
|
||||
@@ -322,10 +298,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Włącz Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Szybka zmiana';
|
||||
|
||||
@@ -818,6 +790,11 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Bateria';
|
||||
|
||||
@@ -1026,15 +1003,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nowa Grupa';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Więcej opcji';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Wyszukaj kontakty';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Zaawansowane wyszukiwanie';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nazwa grupy';
|
||||
|
||||
@@ -1517,6 +1485,35 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Zrzut hex:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Zarządzanie ścieżkami';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Pokaż wszystkie ścieżki';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Tryb routingu';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Automatyczne (użyj zapisanej ścieżki)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Wymuś tryb zalewowy';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Ostatnie ścieżki ACK (naciśnij, aby użyć):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Historia ścieżek jest pełna. Usuń wpisy, aby dodać nowe.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'skok';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'skoki';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1530,6 +1527,12 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Sukcesy';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Usuń ścieżkę';
|
||||
|
||||
@@ -1537,148 +1540,52 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Brak historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Działania ścieżki:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Ustaw ścieżkę niestandardową';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Ręcznie określ trasę.';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Wyczyść Ścieżkę';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Wymuś ponowne wyznaczenie trasy przy następnym wysłaniu';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Ścieżka wyczyszczona. Następna wiadomość odnajdzie trasę.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Użyj przełącznika routingu w pasku narzędzi.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Tryb zalewowy włączony. Przełącz z powrotem ikoną routingu w pasku aplikacji.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Pełna ścieżka';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Planowanie tras';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Szczegóły ścieżki jeszcze niedostępne. Spróbuj wysłać wiadomość, aby odświeżyć.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Samochód';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Powódź';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Instrukcja obsługi';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Automatycznie wybiera najpopularniejszą ścieżkę, a w przypadku braku znanej, przechodzi do trybu \"przepływu\".';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Transmisje za pośrednictwem każdego repeatera. Najbardziej niezawodna metoda, ale zużywa więcej czasu transmisji.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Zawsze prowadzi dokładnie po trasie, którą określiłeś.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Obecna trasa';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops =>
|
||||
'Bezpośrednio – bez pośrednictwa repeaterów';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Na razie nie ma żadnej ścieżki. Komunikacja trwa do momentu, gdy zostanie odkryta trasa.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Transmisja za pośrednictwem każdego urządzenia powielającego';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Edytuj ścieżkę';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Zapomnij o ścieżce';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Znane trasy';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Wybierz ścieżkę, aby przełączyć się na nią.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'W użyciu';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Silny pierwszy skok';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Świetny początek';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Świetny pierwszy krzak';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Zostało dostarczone';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Usłyszano dzięki doniesieniom';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Nieużywany';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'pracował $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Ścieżka ustawiona: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nigdy nie zostało potwierdzone';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Dostawa w przypadku powodzi';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Stworzenie ścieżki';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count z 64 rodzajów chmielu';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Na razie nie dodano żadnych chmielu. Aby dodać je w odpowiedniej kolejności, kliknij w odpowiednie przyciski poniżej, lub zapisz przepis bez chmielu, aby wysłać go bezpośrednio.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Dodawaj chmiel zgodnie z kolejnością.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Funkcje powtarzania';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex =>
|
||||
'Zaawansowane: ścieżka w formacie szesnastkowym';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Prefiksy heksadecymalne';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Dwa znaki szesnastkowe na każdym kroku, oddzielone przecinkami';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Nieprawidłowe: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maksymalnie 64 hopów';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Użyj tej ścieżki.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Usuń dziką psiankę';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Nieznany repeater';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Zapisano lokalnie. Połącz się, aby zsynchronizować.';
|
||||
@@ -1754,39 +1661,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mapa węzłów';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linia wzroku';
|
||||
|
||||
@@ -2116,6 +1990,17 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Czy na pewno chcesz się odłączyć od tego urządzenia?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Rozłączono';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Zostałeś rozłączony ze swoim towarzyszem.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Logowanie do przekaźnika';
|
||||
|
||||
@@ -2181,13 +2066,68 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Wyczyść';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Aktualna ścieżka: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'skoków',
|
||||
many: 'skoków',
|
||||
few: 'skoki',
|
||||
one: 'skok',
|
||||
);
|
||||
return 'Użyj ścieżki $count $_temp0.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Wprowadź własną ścieżkę';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Aktualna ścieżka';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Wprowadź 2-znakowe prefiksy szesnastkowe dla każdego skoku, oddzielone przecinkami.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'A1,F2,3C (każdy węzeł używa pierwszego bajtu swojego klucza publicznego)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Ścieżka (prefiksy hex)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Albo wybierz z kontaktów:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Nie znaleziono przekaźników ani serwerów pokoi.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Dostosowane ścieżki wymagają pośrednich skoków, które mogą przekazywać wiadomości.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Nieprawidłowe prefiksy szesnastkowe: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Ścieżka jest zbyt długa. Dozwolonych skoków wynosi 64.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Ustaw Ścieżkę';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Zarządzanie przekaźnikami';
|
||||
|
||||
@@ -2252,6 +2192,16 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Tryb routingu';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Automatycznie (użyj zapisanej ścieżki)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Wymuś tryb zalewowy';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Zarządzanie ścieżkami';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Odśwież';
|
||||
|
||||
@@ -3349,139 +3299,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Wejście cyfrowe';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Wyjście cyfrowe';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Wejście analogowe';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Wyjście analogowe';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Czujnik ogólny';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Jasność';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Obecność';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Wilgotność';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Akcelerometr';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Ciśnienie';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Wysokość';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Częstotliwość';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Procent';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Stężenie';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Moc';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Odległość';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energia';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Kierunek';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Czas';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Żyrometr';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Kolor';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Przełącznik';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polilinia';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Liczba żądań';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Nie udało się pobrać danych';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Otrzymano dane sąsiedztwa';
|
||||
|
||||
@@ -4378,17 +4195,6 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Kontroluje domyślny stan ikony tłumaczenia w edytorze.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Automatycznie tłumacz wiadomości';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Automatycznie tłumaczy wiadomości do powiadomień oraz do czatów lub kanałów.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Przetłumacz wiadomość';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Język docelowy';
|
||||
|
||||
@@ -4514,135 +4320,4 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Przybliż';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Przybliż z powrotem';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Mapa centrum';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth wymaga przeglądarki Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS potwierdzone';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Wywnioskowana pozycja';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+147
-475
@@ -92,24 +92,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Desativar';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Desfazer';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Enviado';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Entregue';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Enviar';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Falhou ao enviar';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Ouvi repetidamente';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Reiniciar';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Atualização automática';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervalo';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore: Versão aberta';
|
||||
|
||||
@@ -320,10 +296,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Ative o Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Mudar rapidamente';
|
||||
|
||||
@@ -815,6 +787,11 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Número de tentativas de reenvio antes de classificar uma mensagem como falha.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Bateria';
|
||||
|
||||
@@ -1016,15 +993,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Novo Grupo';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Mais opções';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Pesquisar contatos';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Pesquisa avançada';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nome do grupo';
|
||||
|
||||
@@ -1504,6 +1472,34 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Espaço Hexadecimal:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Gerenciamento de Caminhos';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Mostrar todos os caminhos';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Modo de roteamento';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Auto (usar caminho salvo)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Modo de Inundação Forçado';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Rotas de ACK Recentes (toque para usar):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'O histórico está cheio. Remova entradas para adicionar novas.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'pule';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'salta';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1515,6 +1511,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Sucessos';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Remover caminho';
|
||||
|
||||
@@ -1522,148 +1524,53 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Ainda não há histórico de caminhos.\nEnvie uma mensagem para descobrir caminhos.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Ações do Caminho:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Definir Caminho Personalizado';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle =>
|
||||
'Especifique manualmente o caminho de roteamento';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Limpar Caminho';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Forçar a descoberta na próxima transmissão';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Caminho limpo. A próxima mensagem redescobrirá a rota.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Use a chave de roteamento na barra de ferramentas';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Modo de inundação ativado. Desative-o novamente através do ícone de roteamento na barra de ferramentas.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Caminho Completo';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Rotas';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Os detalhes do caminho ainda não estão disponíveis. Tente enviar uma mensagem para atualizar.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Carro';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Inundação';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Manual';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Seleciona automaticamente o caminho mais conhecido, e, se nenhum caminho conhecido for encontrado, utiliza a estratégia de \"inundação\".';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Transmissão através de todos os repetidores. É a opção mais confiável, mas utiliza mais tempo de transmissão.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Sempre segue exatamente o caminho que você define.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Rota atual';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direto – sem saltos de repetidor';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Ainda não há um caminho definido. A mensagem continua a ser enviada até que uma rota seja encontrada.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Transmissão através de todos os repetidores';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Editar caminho';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Esqueça o caminho';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Rotas conhecidas';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Toque em um caminho para alternar para ele.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'Em uso';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Primeiro salto notável';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Primeiro salto bem-sucedido';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Primeira etapa bem-sucedida';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Foi entregue';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Informação obtida através de relatos generalizados.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Não testado';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Caminho definido: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nunca confirmado';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery =>
|
||||
'Entrega em áreas afetadas por inundações';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Criar Caminho';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count de 64 gramas de lúpulo';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Ainda não há lúpulos adicionados. Clique nos repetidores abaixo para adicioná-los na ordem desejada, ou salve sem adicionar lúpulos para enviar diretamente.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Adicione os lúpulos na seguinte ordem.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Encontrar repetidores';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Avançado: caminho hexadecimal bruto';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Prefixos hexadecimais';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Dois caracteres hexadecimais por salto, separados por vírgulas.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Inválido: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Máximo de 64 saltos';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Utilize este caminho.';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Remova o lúpulo';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Repetidor desconhecido';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Salvo localmente. Conectar para sincronizar.';
|
||||
@@ -1738,39 +1645,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mapa de Nós';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linha de visão';
|
||||
|
||||
@@ -2099,6 +1973,17 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Tem certeza de que deseja desconectar deste dispositivo?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Desconectado';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Você foi desconectado do seu companheiro.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Login ao Repetidor';
|
||||
|
||||
@@ -2164,13 +2049,66 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Limpar';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Caminho atual: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Usando $count $_temp0 caminho';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Insira Caminho Personalizado';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Caminho atual';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Insira os prefixos hexadecimais de 2 caracteres para cada salto, separados por vírgulas.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'A1,F2,3C (cada nó usa o primeiro byte de sua chave pública)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Prefixo Hexadecimal';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Máximo de 64 saltos. Cada prefixo tem 2 caracteres hexadecimais (1 byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Ou selecione de contatos:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Não foram encontrados repetidores ou servidores de sala.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Caminhos personalizados exigem saltos intermediários que podem transmitir mensagens.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Prefixos hexadecimais inválidos: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Caminho muito longo. Máximo de 64 saltos permitidos.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Definir Caminho';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Gerenciamento de Repetidor';
|
||||
|
||||
@@ -2235,6 +2173,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Modo de roteamento';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Auto (usar caminho salvo)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Modo de Inundação Forçado';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Gerenciamento de caminhos';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Atualizar';
|
||||
|
||||
@@ -3333,139 +3280,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Entrada digital';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Saída digital';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Entrada analógica';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Saída analógica';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Sensor genérico';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Luminosidade';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Presença';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Humidade';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Acelerómetro';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Pressão';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Altitude';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frequência';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Percentagem';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Concentração';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Potência';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Distância';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energia';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Direção';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Hora';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Girómetro';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Cor';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Interruptor';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polilinha';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Número de solicitações';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Não foi possível obter os dados';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Dados dos Vizinhos Recebidos';
|
||||
|
||||
@@ -4356,17 +4170,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Controla o estado padrão do ícone de tradução do compositor.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Traduzir mensagens automaticamente';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Traduz automaticamente mensagens para notificações e para chats ou canais.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Traduzir mensagem';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Língua-alvo';
|
||||
|
||||
@@ -4493,135 +4296,4 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Ampliar';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Ampliar';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Mapa do centro';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'O Web Bluetooth requer um navegador Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS confirmado';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Posição inferida';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+151
-477
@@ -92,24 +92,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Выключить';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Отменить';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Отправлено';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Доставлено';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Отправка';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Не удалось отправить';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Услышал несколько раз';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Перезагрузить';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Автообновление';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Интервал';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore Open';
|
||||
|
||||
@@ -320,10 +296,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Включите Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Быстрое переключение';
|
||||
|
||||
@@ -817,6 +789,11 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Батарея';
|
||||
|
||||
@@ -1017,15 +994,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Новая группа';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Больше вариантов';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Найти контакты';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Закрыть поиск';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Имя группы';
|
||||
|
||||
@@ -1505,6 +1473,35 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Шестнадцатеричный дамп:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Управление маршрутами';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Показать все пути';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Режим маршрутизации';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Авто (использовать сохранённый маршрут)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Принудительный режим рассылки';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Недавние подтверждённые маршруты (нажмите, чтобы использовать):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'История маршрутов заполнена. Удалите записи, чтобы добавить новые.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'хоп';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'хопов';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1518,6 +1515,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'успешно';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Оценка';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Удалить маршрут';
|
||||
|
||||
@@ -1525,150 +1528,54 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'История маршрутов пока пуста.\nОтправьте сообщение, чтобы обнаружить маршруты.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Действия с маршрутом:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Указать маршрут вручную';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Вручную задать маршрут передачи';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Очистить маршрут';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Принудительно обновить маршрут при следующей отправке';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Маршрут очищен. Следующее сообщение обновит маршрут.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Используйте переключатель маршрутизации в панели приложения';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Режим рассылки включён. Отключите через значок маршрутизации в панели приложения.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Полный маршрут';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Маршрутизация';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Детали маршрута ещё недоступны. Попробуйте отправить сообщение для обновления.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Авто';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Наводнение';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Инструкция';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Автоматически выбирает наиболее известный путь, и если такой путь неизвестен, использует алгоритм поиска пути.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Передача сигнала через все ретрансляторы. Самый надежный способ, но требует больше времени на передачу.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Всегда следует точно по указанному вами маршруту.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Текущий маршрут';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops =>
|
||||
'Прямое соединение – без использования ретрансляторов';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Пока нет пути. Следующее сообщение будет отправлено до тех пор, пока не будет обнаружен маршрут.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Транслируется через все ретрансляторы';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Изменить путь';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Забудьте о маршруте';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Известные маршруты';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Создайте маршрут для переключения на этот пункт.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'В эксплуатации';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Сильный первый скачок';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Хорошее начало';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Первый хороший урожай';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Осуществлено';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Узнал из новостей, распространяющихся в интернете.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Непроверенный';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'хопов',
|
||||
many: 'хопов',
|
||||
few: 'хопа',
|
||||
one: 'хоп',
|
||||
);
|
||||
return 'Маршрут установлен: $hopCount $_temp0 — $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'никогда не было подтверждено';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Доставка при затоплении';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Создать маршрут';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count из 64 хмеля';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'На данный момент хмель еще не добавлен. Чтобы добавить его, нажмите на соответствующие кнопки ниже в нужном порядке, или сохраните рецепт без хмеля, чтобы отправить его напрямую.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops =>
|
||||
'Добавляйте хмель в соответствии с указанным порядком.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Поиск повторителей';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex =>
|
||||
'Продвинутый уровень: прямой путь в шестнадцатеричном формате';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Префиксы шестнадцатеричной системы';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Два шестнадцатеричных символа на каждом шаге, разделенные запятыми.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Неверно: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops =>
|
||||
'Максимальное количество ингредиентов – 64';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Используйте этот путь';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Удалить хмель';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Неизвестный ретранслятор';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Сохранено локально. Подключитесь для синхронизации.';
|
||||
@@ -1743,39 +1650,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Карта нод';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Линия видимости';
|
||||
|
||||
@@ -2103,6 +1977,17 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Вы уверены, что хотите отключиться от этого устройства?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Отключено';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Вы были отключены от вашего компаньона.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Вход в репитер';
|
||||
|
||||
@@ -2169,12 +2054,66 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Очистить';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Текущий маршрут: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'хопов',
|
||||
many: 'хопов',
|
||||
few: 'хопа',
|
||||
one: 'хоп',
|
||||
);
|
||||
return 'Используется маршрут из $count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Введите маршрут вручную';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Текущий маршрут';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Введите 2-символьные шестнадцатеричные префиксы для каждого хопа, разделённые запятыми.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Пример: A1,F2,3C (каждый узел использует первый байт своего публичного ключа)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Маршрут (шестнадцатеричные префиксы)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Максимум 64 хопа. Каждый префикс — 2 шестнадцатеричных символа (1 байт)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Или выберите из контактов:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound => 'Репитеры или серверы комнат не найдены.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Пользовательские маршруты требуют промежуточных узлов, способных ретранслировать сообщения.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Недопустимые шестнадцатеричные префиксы: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Маршрут слишком длинный. Максимум 64 хопа.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Установить маршрут';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Управление репитером';
|
||||
|
||||
@@ -2239,6 +2178,16 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Режим маршрутизации';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Авто (использовать сохранённый маршрут)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Принудительный режим рассылки';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Управление маршрутами';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Обновить';
|
||||
|
||||
@@ -3339,139 +3288,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Цифровой вход';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Цифровой выход';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Аналоговый вход';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Аналоговый выход';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Общий датчик';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Освещённость';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Присутствие';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Влажность';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Акселерометр';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Давление';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Высота';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Частота';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Процент';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Концентрация';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Мощность';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Расстояние';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Энергия';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Направление';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Время';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Гирометр';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Цвет';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Переключатель';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Полилиния';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters м';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Гц';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa гПа';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux лк';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts Вт';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters м';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours кВт⋅ч';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Количество запросов';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Не удалось получить данные';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Полученные данные о соседях';
|
||||
|
||||
@@ -4372,17 +4188,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Управляет исходным состоянием значка перевода, предоставляемого редактором.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Автоматически переводить сообщения';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Автоматически переводит сообщения для уведомлений, а также для чатов и каналов.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Перевести сообщение';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Целевой язык';
|
||||
|
||||
@@ -4509,135 +4314,4 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Неизвестно';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Увеличить масштаб';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Увеличить масштаб';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Карта центра';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Для работы Web Bluetooth требуется браузер на основе Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'Идентификатор: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS подтверждено';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Выведенная позиция';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+148
-474
@@ -92,24 +92,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Zakázať';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Zrušiť';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Odoslané';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Doručené';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Odoslanie';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Neúspešné odeslanie';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Slyšal som to opakovane';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Restartovať';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatické obnovenie';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Časový interval';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – Verzia pre verejnosť';
|
||||
|
||||
@@ -319,10 +295,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Povolte Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Rýchle prepínač';
|
||||
|
||||
@@ -804,6 +776,11 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Počet pokusov o odošleť pred označením správy ako neúspešnej';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batéria';
|
||||
|
||||
@@ -1005,15 +982,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nová skupina';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Ďalšie možnosti';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Vyhľadajte kontakty';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Zavrieť vyhľadávanie';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Názov skupiny';
|
||||
|
||||
@@ -1495,6 +1463,35 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hexová analýza:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Správa ciest';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Zobraziť všetky cesty';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Režim trasy';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Použiť uloženú cestu';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode =>
|
||||
'Zavrieť režim núdzového povodňového režimu';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Nedávne cesty ACK (klepni na použitie):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'História ciest je plná. Odstráňte záznamy, aby ste mohli pridať nové.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'Skok';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'Skákať';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1506,6 +1503,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Úspechy';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Odstrániť cestu';
|
||||
|
||||
@@ -1513,148 +1516,52 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Zatiaľ žiadna história trás.\nPošlite správu a objavte trasy.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Cesty:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Nastaviť vlastnú cestu';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Ručne zadajte trasu.';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Vyčistiš cestu';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Znovu nájsť vynútene pri nasledujúcej pošlite';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Cesta vyčistená. Nasledujúce prepočetné získa trasu znova.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Použite prepínanie trasy v navigačnom paneli.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Odosporňovacia prevádzka je zapnutá. Vypnite ju znova cez ikonu routovania v navigačnom páse.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Celá cesta';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Navigácia';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Podrobnosti o ceste zatiaľ dostupné nie sú. Skúste poslať správu na obnovenie.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Povodňová vlna';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Ručná príručka';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Automaticky vyberá najznámejší trasa, a ak žiadna nie je známa, použije náhodnú trasu.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Prenos prostredníctvom všetkých opakovačov. Najspoľahlivejší spôsob, ale vyžaduje viac času vysielania.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Vždy dodáva presne podľa zadaného trasy.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Aktuálna trasa';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Priamo – bez prechodných trás';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Zatiaľ neexistuje žiadna cesta. Nasledujúce správy budú pokračovať, kým sa nenájde trasa.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast =>
|
||||
'Prenos prostredníctvom každého opakovača';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Upraviť trasu';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Zabudnite na trasu';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Známe cesty';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Kliknite na cestu, aby ste sa k nej presunuli.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'V prevádzke';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Silný prvý krok';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Úspešný prvý krok';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Prvá, spravodlivá fáza';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Dosiahnutý úspech';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood =>
|
||||
'Zistil som to z informácií, ktoré som získal v dôsledku povodňovej situácie.';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Neotestované';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Cesta nastavená: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nikedy nebolo potvrdené';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Doručenie v prípade povodní';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Vytvorenie cesty';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count z 64 chmelových zŕš';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Zatiaľ žiadne chmel. Kliknite na opakované, aby ste ich pridali postupne, alebo uložte bez chmelu, aby ste ho mohli poslať priamo.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Pridávajte chmel podľa zadaného poriadku.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Hľadať opakované';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Pokročilé: pôvodná hexová cesta';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Prefiksy pre hexadecimálne čísla';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Dve hexové čísla na každý krok, oddelené čiarkami';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Neplatné: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximálne 64 krokov';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Použite túto cestu';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Odstráňte chmel';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop =>
|
||||
'Neznáme zariadenie na opakované vysielanie';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Uložené lokálne. Spojte sa na synchronizáciu.';
|
||||
@@ -1730,39 +1637,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mapa uzlov';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Úroveň výhľadu';
|
||||
|
||||
@@ -2089,6 +1963,17 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Ste si istý/á, že chcete odpojiť od tohto zariadenia?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Odpojené';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Od vášho spoločníka ste boli odpojený.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Opätovné prihlásenie';
|
||||
|
||||
@@ -2155,13 +2040,66 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Zmazať';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Aktívna cesta: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Používa $count $_temp0 cestu';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Zadajte vlastný priebeh';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Aktuálny priebeh';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Zadajte 2-miestne hexové predpony pre každú fázu, oddelené čiarkami.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'A1,F2,3C (každý uzel používa prvý bajt svojho verejného kľúča)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Cesty (hexové predpony)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Max 64 skokov. Každý prefix je 2 hexadecimálne znaky (1 bajt).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Vyberte sa z kontaktov:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Nenašli sa žiadne opakovače ani serverové miestnosti.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Vlastné cesty vyžadujú medziletoch, ktoré môžu prenášať správky.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Neplatné hexové predpony: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong =>
|
||||
'Cesta je príliš dlhá. Umožnené je maximum 64 skokov.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Nastaviť cestu';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Správa opakérov';
|
||||
|
||||
@@ -2226,6 +2164,16 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Režim trasy';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Použiť uloženú cestu';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode =>
|
||||
'Zavrieť režim núdzového povodňového režimu';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Správa trás';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Obnoviť';
|
||||
|
||||
@@ -3318,139 +3266,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digitálny vstup';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digitálny výstup';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analógový vstup';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analógový výstup';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Všeobecný senzor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Osvetlenie';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Prítomnosť';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Vlhkosť';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Akcelerometer';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Tlak';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Nadmorská výška';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frekvencia';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Percento';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Koncentrácia';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Výkon';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Vzdialenosť';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energia';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Smer';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Čas';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyrometer';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Farba';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Prepínač';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Lomená čiara';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Počet požiadaviek';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Nepodarilo sa získať údaje';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Obdielo dáta suseda';
|
||||
|
||||
@@ -4337,16 +4152,6 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Riadi výchoce stav ikony pre preklad, ktorú používa program.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => 'Automaticky prekladať správy';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Automaticky prekladá správy pre upozornenia aj pre čet alebo kanál.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Preložiť správu';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Cieľový jazyk';
|
||||
|
||||
@@ -4475,135 +4280,4 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Zväčšiť';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Zmenť zamer zblízka';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Mapa centra';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth vyžaduje prehliadač Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS potvrdilo';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Odvodená poloha';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+144
-471
@@ -92,25 +92,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Izklopiti';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Preobrn';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Pošljeno';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Dostavljeno';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Pošiljanje';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed =>
|
||||
'Uspešno ni bilo mogo, da se sporočilo pošlje';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Slišal sem večkrat';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Ponoviti';
|
||||
|
||||
@@ -130,12 +111,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return '$percent %';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Samodejno osveževanje';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Časovni interval';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – Odprto';
|
||||
|
||||
@@ -318,10 +293,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Omogočite Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Hitro preklop';
|
||||
|
||||
@@ -806,6 +777,11 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Baterija';
|
||||
|
||||
@@ -1005,15 +981,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Nova skupina';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Več možnosti';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Iskanje kontaktov';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Izklopi iskanje';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Ime skupine';
|
||||
|
||||
@@ -1493,6 +1460,34 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Izpis heksadecimalnih vrednosti:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Upravljanje poti';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Prikaži vse poti';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Navodilo za usmerjevalni način';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Avto (uporabi shranjeno pot)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Nasilje obvezati v način';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Nedavni poti ACK (tap za uporabo):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'skok';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'skokov';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1504,6 +1499,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Uspešni';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Izbriši pot';
|
||||
|
||||
@@ -1511,144 +1512,51 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Potni ukazi:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Nastavi Prilozeno Pot';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Ročno določite potniško pot.';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Počisti pot';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => 'Ob naslednji pošiljanju znova zbrati.';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Pot je očiščena. Naslednje sporočilo bo ponovno odkril pot.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Uporabi tipko usmerjevanja v meniju aplikacije.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Narejena je bila omrežna modaliteta. Vklopi jo znova preko ikone v meniju aplikacije.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Polna pot';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Navigacija';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Podrobnosti poti zaenkrat niso na voljo. Poskusite poslati sporočilo za osvežitev.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Avto';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Poplavo';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Navodilo';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Samodejno izbere najbolj poznano pot, in sicer, ko ni na voljo nobena.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Prenosi preko vseh repetitorjev. Najzanesljivejši način, vendar zahteva več časa.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Vedno sledi natančni poti, ki jo ste določili.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Trenutna pot';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Neposredno – brez prehodov';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Žep trenutno ni mogoče najti. Naslednje sporočilo bo posredovano, dokler ne bo ugotovljeno, kje je pot.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Prenos preko vseh repetitiv';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Uredi pot';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Pozabi na pot';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Poznati poti';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => 'Kliknite na pot, da jo izberete.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'V uporabi';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Močan prvi korak';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Prva uspešna faza';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Prva, uspešna faza';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Izpolnil';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Slišano preko poplave';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Ne preizkušen';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'delal/a $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Pot nastavljen: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'nikoli ni bilo potrjeno';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Dostava zaradi poplave';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Izgradnja poti';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count od 64 različnih sort hropa';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Še niso dodani hmelji. Za dodajanje hmelja v vrstnem redu kliknite na povezavo spodaj, ali pa shranite brez dodanega hmelja, da ga lahko posredujete neposredno.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Dodajte suho travo v skladu s postopkom.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Iskanje ponovitev';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Napredno: surovi šestnajstni pot';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Predfiks za heksadecimalno šifro';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Dva šestbitna znaka na vsak skok, ločena z vejico';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Neveljaven: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Največ 64 hopov';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Uporabite to poto';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Odstranite hmelj';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Neznani ponovitelj';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Shrano lokalno. Povežite se za sinhronizacijo.';
|
||||
@@ -1723,39 +1631,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Mapa omrežja';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linija vida';
|
||||
|
||||
@@ -2086,6 +1961,17 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Ste prepričani, da želite se odklopiti s tega naprave?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Prekinjeno';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Prekinjena povezava s vašim spre伴ovalcem.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Ponovni vnos';
|
||||
|
||||
@@ -2151,13 +2037,65 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Ponoviti';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Trenutna pot: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Uporablja $count $_temp0 pot';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Vnesite prilagojeno pot';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Trenutna pot';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Vnesite 2-karakterne heksadecimalne prefixe za vsako skopo, ločeno z zvezekami.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Primer: A1,F2,3C (vsak notranji element uporablja prvi bajt svojega javnega ključa)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Pot (heksafixne skrajšave)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Maksimalno 64 skokov. Vsak prefiks je 2 heksadecimalna znamenja (1 bajt).';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Izberi iz kontaktov:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Ne najdenih ponoviteljev ali strežnikov sob.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Prilojene poti zahtevajo medhodne prenose, ki lahko prenašajo sporočila.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Neveljačni šesteročlenski prefiksi: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Pot je prevelika. Dovoljeno največ 64 skokov.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Nastavi Pot';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Upravljanje ponovitve';
|
||||
|
||||
@@ -2223,6 +2161,15 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Navodilo za usmerjevalni način';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Avto (uporabi shranjeno pot)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Nasilje obvezati v način';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Upravljanje poti';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Ponovno obnavljati';
|
||||
|
||||
@@ -3314,139 +3261,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digitalni vhod';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digitalni izhod';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analogni vhod';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analogni izhod';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Splošni senzor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Osvetljenost';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Prisotnost';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Vlažnost';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Merilnik pospeška';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Tlak';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Nadmorska višina';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frekvenca';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Odstotek';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Koncentracija';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Moč';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Razdalja';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energija';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Smer';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Čas';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Žiroskop';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Barva';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Stikalo';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polilinija';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Število zahtev';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Podatkov ni bilo mogoče pridobiti';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Prejeto podatke o sosedih';
|
||||
|
||||
@@ -4336,16 +4150,6 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Ureja privzeto stanje ikone za prevod, ki jo uporablja avtor.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => 'Samodejno prevajaj sporočila';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Samodejno prevaja sporočila za obvestila ter za klepete ali kanale.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Prevedi sporočilo';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Ciljna jezika';
|
||||
|
||||
@@ -4474,135 +4278,4 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Povečaj';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Povečajte pogled';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Krajšarska karta';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth zahteva brskalnik Chromium.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS potrdilo';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Izpeljana lokacija';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+144
-471
@@ -92,24 +92,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Inaktivera';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Ångra';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Sen';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Levererad';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Skicka';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Misslyckades med att skicka';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Hördes upprepade gånger';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Start om';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Automatisk uppdatering';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Intervall';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore – Öppen version';
|
||||
|
||||
@@ -316,10 +292,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Aktivera Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Snabb växling';
|
||||
|
||||
@@ -799,6 +771,11 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Antal försök att skicka om ett meddelande innan det markeras som misslyckat.';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batteri';
|
||||
|
||||
@@ -999,15 +976,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Ny grupp';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Fler alternativ';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Sök efter kontakter';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Avancerad sökning';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Gruppnamn';
|
||||
|
||||
@@ -1486,6 +1454,35 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Hexdump:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Stigarhantering';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Visa alla vägar';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Ruttläge';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Automatisk (använd sparad sökväg)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Tvinga Översvämningsläge';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Nyligen Ack-vägar (tryck för att använda):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Historisk sökväg är full. Ta bort poster för att lägga till nya.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'hoppa';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'hoppar';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1497,6 +1494,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'framgångar';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Ta bort sökväg';
|
||||
|
||||
@@ -1504,144 +1507,50 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Ingen historik ännu.\nSkicka ett meddelande för att upptäcka spår.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Stigar:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Ange anpassad sökväg';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Ange ruttväg manuellt';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Rensa Vägen';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => 'Tvinga fram omstart vid nästa sändning';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Routen är nu fri. Nästa meddelande kommer att upptäcka rutten igen.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle => 'Använd routningsomkopplaren i appraden';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Översvämningsläge aktiverat. Stäng av via ruttikonen i appraden.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Fullständig sökväg';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Ruttplanering';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Stigaruppgifterna är ännu inte tillgängliga. Försök att skicka ett meddelande för att uppdatera.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Bil';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Översvämning';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Instruktioner';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Väljer automatiskt den bästa kända vägen, och använder en \"flooding\"-strategi om ingen väg är känd.';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Sändningar via alla repetrar. Det mest pålitliga alternativet, men kräver mer sändtid.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Skickar alltid den exakta väg du har angivit.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Nuvarande rutt';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => 'Direkt – utan mellanliggande routrar';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Ingen väg hittad ännu. Nästa meddelande skickas tills en rutt har upptäckts.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Sändas via alla repetrar';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Redigera sökväg';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Glöm vägen';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Kända vägar';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => 'Välj en väg för att byta till den.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'I användning';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'En stark start';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Bra första steg';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Bra första hopp';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Har levererat';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Fått information via nyhetsflöde';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Ej testat';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'arbetade $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'hoppar',
|
||||
one: 'hopp',
|
||||
);
|
||||
return 'Sökväg inställd: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'aldrig bekräftat';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Leverans vid översvämningsområde';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Skapa väg';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count av 64 humlor';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Inga humle än. Använd knapparna nedan för att lägga till dem i rätt ordning, eller spara utan humle för att skicka direkt.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Tillsätt humlen i rätt ordning.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Sök efter återupptagna samtal';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => 'Avancerat: rå hex-sökväg';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => 'Hex-prefikser';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Två hex-tecken per steg, separerade med kommatecken.';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Ogiltigt: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Maximalt 64 humlörter';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Använd denna väg';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Ta bort humlen';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Okänd förstärkare';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Sparat lokalt. Anslut för att synkronisera.';
|
||||
@@ -1716,39 +1625,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Nodkarta';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Synlinje';
|
||||
|
||||
@@ -2074,6 +1950,17 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Är du säker på att du vill koppla från enheten?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Ansluten ej';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Du har kopplats från din companion.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Återuppta Inloggning';
|
||||
|
||||
@@ -2139,13 +2026,65 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Rensa';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Nuvarande sökväg: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'hops',
|
||||
one: 'hop',
|
||||
);
|
||||
return 'Använda $count $_temp0 sökväg';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Ange anpassad sökväg';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Nuvarande sökväg';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Ange 2-tecknets hex-prefett för varje hopp, åtskilda med komma.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Exempel: A1,F2,3C (varje nod använder det första bytet av sitt publika nyckel)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Hexprefixer';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Max 64 hopp. Varje prefix är 2 hex-tecken (1 byte)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Välj istället från kontakter:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Inga återuppspelare eller rumsservrar hittades.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Anpassade sökvägar kräver mellansteg som kan vidarebefordra meddelanden.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Ogiltiga hex-prefikser: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Sökvägen är för lång. Max 64 hopp tillåtna.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Ange Sökväg';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Återuppspelarens Hantering';
|
||||
|
||||
@@ -2210,6 +2149,15 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Ruttläge';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => 'Automatisk (använd sparad sökväg)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Tvinga Översvämningsläge';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Stigarhantering';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Uppdatera';
|
||||
|
||||
@@ -3294,139 +3242,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Digital ingång';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Digital utgång';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Analog ingång';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Analog utgång';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Allmän sensor';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Ljusstyrka';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Närvaro';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Luftfuktighet';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Accelerometer';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Tryck';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Höjd';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Frekvens';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Procent';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Koncentration';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Effekt';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Avstånd';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Energi';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Riktning';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Tid';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Gyrometer';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Färg';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Brytare';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Polylinje';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Antal förfrågningar';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Det gick inte att hämta data';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Mottagna grannars data';
|
||||
|
||||
@@ -4309,17 +4124,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Styr standardtillståndet för kompositorns översättningsikon.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Översätt meddelanden automatiskt';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Översätter meddelanden automatiskt för aviseringar och för chattar eller kanaler.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Översätt meddelande';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Målmedvetet språk';
|
||||
|
||||
@@ -4448,135 +4252,4 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Zooma in';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Zooma ut';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Kartöversikt';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth kräver en Chromium-baserad webbläsare.';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS-verifierat';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Antagen position';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+151
-475
@@ -92,24 +92,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => 'Вимкнути';
|
||||
|
||||
@override
|
||||
String get common_undo => 'Скасувати';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => 'Надіслано';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => 'Доставлено';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => 'Надсилання';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => 'Не вдалося надіслати';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => 'Почув неодноразово';
|
||||
|
||||
@override
|
||||
String get common_reboot => 'Перезавантажити';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => 'Автооновлення';
|
||||
|
||||
@override
|
||||
String get common_interval => 'Інтервал';
|
||||
|
||||
@override
|
||||
String get scanner_title => 'MeshCore: Відкритий доступ';
|
||||
|
||||
@@ -319,10 +295,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => 'Увімкніть Bluetooth';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Швидке перемикання';
|
||||
|
||||
@@ -811,6 +783,11 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
String get appSettings_maxMessageRetriesSubtitle =>
|
||||
'Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Батарея';
|
||||
|
||||
@@ -1012,15 +989,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => 'Нова група';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => 'Більше можливостей';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => 'Пошук контактів';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => 'Закрити пошук';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Назва групи';
|
||||
|
||||
@@ -1500,6 +1468,35 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => 'Дамп Hex:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => 'Керування шляхами';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => 'Показати всі шляхи';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => 'Режим маршрутизації';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => 'Авто (використовувати збережений шлях)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Примусово через всю мережу';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
'Підтверджені шляхи (натисніть, щоб використати):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull =>
|
||||
'Історія шляхів заповнена. Видаліть записи, щоб додати нові.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'Перехід';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'переходів';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
@@ -1513,6 +1510,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
return '$count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => 'Успішно';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Оцінка';
|
||||
|
||||
@override
|
||||
String get chat_removePath => 'Видалити шлях';
|
||||
|
||||
@@ -1520,148 +1523,54 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Дії зі шляхом:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => 'Встановити власний шлях';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => 'Вказати шлях маршрутизації вручну';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Очистити шлях';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Примусово повторити пошук при наступному надсиланні';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
'Шлях очищено. Наступне повідомлення оновить маршрут.';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle =>
|
||||
'Використовувати перемикач маршрутизації в панелі застосунку';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Повний шлях';
|
||||
|
||||
@override
|
||||
String get routing_title => 'Маршрутизація';
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
'Деталі шляху ще недоступні. Спробуйте надіслати повідомлення для оновлення.';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => 'Автомобіль';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => 'Повені';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => 'Інструкція';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint =>
|
||||
'Автоматично обирає найкращий відомий шлях, та у разі відсутності відомого шляху, використовує алгоритм \"занурення\".';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint =>
|
||||
'Передавання через усі ретранслятори. Найбільш надійний спосіб, але потребує більше часу.';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint =>
|
||||
'Завжди доставляє точно за вказаним вами маршрутом.';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => 'Поточний маршрут';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops =>
|
||||
'Пряме з\'єднання – без проміжних ретрансляторів';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet =>
|
||||
'Поки що немає жодного шляху. Повідомлення продовжуються надходити, поки не буде знайдено маршрут.';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => 'Поширення через усі ретранслятори';
|
||||
|
||||
@override
|
||||
String get routing_editPath => 'Редагувати шлях';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => 'Забудь про шлях';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => 'Відомі маршрути';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint =>
|
||||
'Виберіть опцію, щоб переключитися на неї.';
|
||||
|
||||
@override
|
||||
String get routing_inUse => 'У робочому стані';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => 'Сильний перший стрибок';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => 'Чудова перша спроба';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => 'Перший, але вдалий, крок';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => 'Доставлено';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => 'Дізнався через новини';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => 'Не протестовано';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return 'worked $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
hopCount,
|
||||
locale: localeName,
|
||||
other: 'переходів',
|
||||
many: 'переходів',
|
||||
few: 'переходи',
|
||||
one: 'перехід',
|
||||
);
|
||||
return 'Шлях встановлено: $hopCount $_temp0 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => 'ніколи не підтверджено';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => 'Доставка під час повені';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => 'Створити маршрут';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count з 64 штук хмелю';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'Ще не додано хміль. Натисніть на відповідні кнопки, щоб додати його в потрібному порядку, або збережіть рецепт без хмілю, щоб відправити його безпосередньо.';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => 'Додавайте хміль у наступній послідовності.';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => 'Пошук повторювачів';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex =>
|
||||
'Просунутий рівень: пряма шлях у форматі шестнадцяткової системи.';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel =>
|
||||
'Префікси для шестнадцяткової системи числення';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper =>
|
||||
'Два шестизначні символи на кожний крок, розділені комами';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return 'Неправильно: $tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => 'Максимум 64 хмелеві колоди';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => 'Використовуйте цей шлях';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => 'Видалити хміль';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => 'Невідомий ретранслятор';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally =>
|
||||
'Збережено локально. Підключіться для синхронізації.';
|
||||
@@ -1736,39 +1645,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => 'Карта вузлів';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Пряма видимість';
|
||||
|
||||
@@ -2096,6 +1972,17 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
String get dialog_disconnectConfirm =>
|
||||
'Ви впевнені, що хочете відключитись від цього пристрою?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => 'Від’єднано';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage =>
|
||||
'Вас від’єднано від вашого супутника.';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion =>
|
||||
'Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Вхід у ретранслятор';
|
||||
|
||||
@@ -2161,13 +2048,67 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => 'Очистити';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return 'Поточний шлях: $path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'переходами',
|
||||
many: 'переходами',
|
||||
few: 'переходами',
|
||||
one: 'переходом',
|
||||
);
|
||||
return 'Використання шляху з $count $_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => 'Ввести власний шлях';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => 'Поточний шлях';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions =>
|
||||
'Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample =>
|
||||
'Приклад: A1,F2,3C (кожен вузол використовує перший байт свого відкритого ключа).';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => 'Hex-префікси';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops =>
|
||||
'Макс. 64 переходи. Кожен префікс — 2 шістнадцяткові символи (1 байт)';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => 'Вибрати з контактів:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Ретрансляторів або серверів кімнат не знайдено.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
'Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return 'Некоректні hex-префікси: $prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => 'Шлях занадто довгий. Максимум 64 переходи.';
|
||||
|
||||
@override
|
||||
String get path_setPath => 'Встановити шлях';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Керування ретранслятором';
|
||||
|
||||
@@ -2232,6 +2173,16 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => 'Режим маршрутизації';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath =>
|
||||
'Авто (використовувати збережений шлях)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Примусово через всю мережу';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Керування шляхами';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => 'Оновити';
|
||||
|
||||
@@ -3334,139 +3285,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => 'Цифровий вхід';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => 'Цифровий вихід';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => 'Аналоговий вхід';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => 'Аналоговий вихід';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => 'Загальний датчик';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => 'Освітленість';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => 'Присутність';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => 'Вологість';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => 'Акселерометр';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => 'Тиск';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => 'Висота';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => 'Частота';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => 'Відсоток';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => 'Концентрація';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => 'Потужність';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => 'Відстань';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => 'Енергія';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => 'Напрямок';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => 'Час';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => 'Гірометр';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => 'Колір';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => 'Перемикач';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => 'Полілінія';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters м';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Гц';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa гПа';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux лк';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts Вт';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters м';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours кВт⋅год';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => 'Кількість запитів';
|
||||
|
||||
@override
|
||||
String get telemetry_error => 'Не вдалося отримати дані';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Дані сусідів отримано';
|
||||
|
||||
@@ -4369,17 +4187,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
String get translation_composerSubtitle =>
|
||||
'Контролює стан ікон перекладу, який використовується за замовчуванням.';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle =>
|
||||
'Автоматично перекладати повідомлення';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle =>
|
||||
'Автоматично перекладає повідомлення для сповіщень, а також для чатів і каналів.';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => 'Перекласти повідомлення';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => 'Цільова мова';
|
||||
|
||||
@@ -4508,135 +4315,4 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Невідомо';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => 'Увійти в режим збільшення';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => 'Видалити зум';
|
||||
|
||||
@override
|
||||
String get map_centerMap => 'Карта центру';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth вимагає браузера на основі Chromium';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID: $id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => 'GPS підтверджено';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => 'Висновок щодо положення';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+122
-464
@@ -92,24 +92,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get common_disable => '禁用';
|
||||
|
||||
@override
|
||||
String get common_undo => '撤销';
|
||||
|
||||
@override
|
||||
String get messageStatus_sent => '发送';
|
||||
|
||||
@override
|
||||
String get messageStatus_delivered => '已送达';
|
||||
|
||||
@override
|
||||
String get messageStatus_pending => '发送';
|
||||
|
||||
@override
|
||||
String get messageStatus_failed => '发送失败';
|
||||
|
||||
@override
|
||||
String get messageStatus_repeated => '多次听到';
|
||||
|
||||
@override
|
||||
String get common_reboot => '重启';
|
||||
|
||||
@@ -129,12 +111,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get common_autoRefresh => '自动刷新';
|
||||
|
||||
@override
|
||||
String get common_interval => '间隔';
|
||||
|
||||
@override
|
||||
String get scanner_title => '连接设备';
|
||||
|
||||
@@ -305,10 +281,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get scanner_enableBluetooth => '启用蓝牙';
|
||||
|
||||
@override
|
||||
String get scanner_bluetoothWebUnsupported =>
|
||||
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => '快速切换';
|
||||
|
||||
@@ -757,6 +729,11 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_maxMessageRetriesSubtitle => '在将消息标记为失败之前,允许尝试的次数';
|
||||
|
||||
@override
|
||||
String path_routeWeight(String weight, String max) {
|
||||
return '$weight/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_battery => '电池';
|
||||
|
||||
@@ -948,15 +925,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_newGroup => '新建群聊';
|
||||
|
||||
@override
|
||||
String get contacts_moreOptions => '更多选择';
|
||||
|
||||
@override
|
||||
String get contacts_searchOpen => '搜索联系人';
|
||||
|
||||
@override
|
||||
String get contacts_searchClose => '高级搜索';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => '群聊名称';
|
||||
|
||||
@@ -1423,149 +1391,85 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get debugFrame_hexDump => '十六进制数据:';
|
||||
|
||||
@override
|
||||
String get chat_pathManagement => '路径管理';
|
||||
|
||||
@override
|
||||
String get chat_ShowAllPaths => '显示所有路径';
|
||||
|
||||
@override
|
||||
String get chat_routingMode => '路由模式';
|
||||
|
||||
@override
|
||||
String get chat_autoUseSavedPath => '自动(使用保存的路径)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => '强制泛洪模式';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => '最近使用的 ACK 路径(点击使用):';
|
||||
|
||||
@override
|
||||
String get chat_pathHistoryFull => '路径历史已满,请删除后再添加。';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => '跳';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => '跳';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
return '$count 跳';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_successes => '成功';
|
||||
|
||||
@override
|
||||
String get chat_score => 'Score';
|
||||
|
||||
@override
|
||||
String get chat_removePath => '移除路径';
|
||||
|
||||
@override
|
||||
String get chat_noPathHistoryYet => '暂无路径历史。\n发送消息以探索路径。';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => '路径操作:';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPath => '设置自定义路径';
|
||||
|
||||
@override
|
||||
String get chat_setCustomPathSubtitle => '手动指定路由路径';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => '清除路径';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => '清除当前路径,下次发送将重新尝试。';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared => '路径已清除。下一条消息将重新路由。';
|
||||
|
||||
@override
|
||||
String get chat_floodModeSubtitle => '在应用栏中切换路由模式。';
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled => '泛洪模式已启用。可通过应用栏的路由图标切换。';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => '完整路径';
|
||||
|
||||
@override
|
||||
String get routing_title => '路由';
|
||||
String get chat_pathDetailsNotAvailable => '路径信息暂不可用,请尝试发送消息刷新。';
|
||||
|
||||
@override
|
||||
String get routing_modeAuto => '汽车';
|
||||
|
||||
@override
|
||||
String get routing_modeFlood => '洪水';
|
||||
|
||||
@override
|
||||
String get routing_modeManual => '手册';
|
||||
|
||||
@override
|
||||
String get routing_modeAutoHint => '自动选择已知最佳路径,当没有已知路径时,则进行“洪水”搜索。';
|
||||
|
||||
@override
|
||||
String get routing_modeFloodHint => '通过所有中继站进行广播。 这种方式最可靠,但占用更多的时间。';
|
||||
|
||||
@override
|
||||
String get routing_modeManualHint => '总是按照您设置的路径进行导航。';
|
||||
|
||||
@override
|
||||
String get routing_currentRoute => '当前路线';
|
||||
|
||||
@override
|
||||
String get routing_directNoHops => '直接连接— 无中继跳';
|
||||
|
||||
@override
|
||||
String get routing_noPathYet => '目前还没有找到路径。直到找到路径,才会收到后续消息。';
|
||||
|
||||
@override
|
||||
String get routing_floodBroadcast => '通过所有中继器进行广播';
|
||||
|
||||
@override
|
||||
String get routing_editPath => '编辑路径';
|
||||
|
||||
@override
|
||||
String get routing_forgetPath => '忘记原路';
|
||||
|
||||
@override
|
||||
String get routing_knownPaths => '已知的路径';
|
||||
|
||||
@override
|
||||
String get routing_knownPathsHint => '点击该路径以切换到它。';
|
||||
|
||||
@override
|
||||
String get routing_inUse => '使用中';
|
||||
|
||||
@override
|
||||
String get routing_qualityStrong => '强劲的初始阶段';
|
||||
|
||||
@override
|
||||
String get routing_qualityGood => '不错的开端';
|
||||
|
||||
@override
|
||||
String get routing_qualityFair => '第一次尝试,结果良好';
|
||||
|
||||
@override
|
||||
String get routing_qualityWorked => '已完成';
|
||||
|
||||
@override
|
||||
String get routing_qualityFlood => '通过新闻报道';
|
||||
|
||||
@override
|
||||
String get routing_qualityUntested => '未经测试';
|
||||
|
||||
@override
|
||||
String routing_lastWorked(String when) {
|
||||
return '工作于 $when';
|
||||
String chat_pathSetHops(int hopCount, String status) {
|
||||
return '路径设置:$hopCount 跳 - $status';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_neverWorked => '从未得到证实';
|
||||
|
||||
@override
|
||||
String routing_deliveryCounts(int successes, int failures) {
|
||||
return '$successes delivered, $failures failed';
|
||||
}
|
||||
|
||||
@override
|
||||
String get routing_floodDelivery => '洪水配送';
|
||||
|
||||
@override
|
||||
String get pathEditor_title => '构建路径';
|
||||
|
||||
@override
|
||||
String pathEditor_hopCounter(int count) {
|
||||
return '$count of 64 hops';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_noHops =>
|
||||
'目前还没有添加任何啤酒花。点击下面的“添加”按钮,按顺序添加,或者直接保存,不添加任何啤酒花。';
|
||||
|
||||
@override
|
||||
String get pathEditor_addHops => '按照顺序添加啤酒花';
|
||||
|
||||
@override
|
||||
String get pathEditor_searchRepeaters => '重复搜索';
|
||||
|
||||
@override
|
||||
String get pathEditor_advancedHex => '高级:原始十六进制路径';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexLabel => '十六进制前缀';
|
||||
|
||||
@override
|
||||
String get pathEditor_hexHelper => '每次跳跃,使用两个十六进制字符,用逗号分隔。';
|
||||
|
||||
@override
|
||||
String pathEditor_invalidTokens(String tokens) {
|
||||
return '无效:$tokens';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathEditor_tooManyHops => '最多 64 个跳跃';
|
||||
|
||||
@override
|
||||
String get pathEditor_usePath => '请使用此路径';
|
||||
|
||||
@override
|
||||
String get pathEditor_removeHop => '去除啤酒花';
|
||||
|
||||
@override
|
||||
String get pathEditor_unknownHop => '未知的重复器';
|
||||
|
||||
@override
|
||||
String get chat_pathSavedLocally => '已本地保存,连接设备后可同步。';
|
||||
|
||||
@@ -1638,39 +1542,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get map_title => '节点地图';
|
||||
|
||||
@override
|
||||
String get map_searchHint => 'Search node name or ID';
|
||||
|
||||
@override
|
||||
String get map_activity => 'Activity';
|
||||
|
||||
@override
|
||||
String get map_online => 'Online';
|
||||
|
||||
@override
|
||||
String get map_recent => 'Recent';
|
||||
|
||||
@override
|
||||
String get map_stale => 'Stale';
|
||||
|
||||
@override
|
||||
String get map_visible => 'Visible';
|
||||
|
||||
@override
|
||||
String get map_hidden => 'Hidden';
|
||||
|
||||
@override
|
||||
String get map_centerOnNode => 'Center on node';
|
||||
|
||||
@override
|
||||
String get map_details => 'Details';
|
||||
|
||||
@override
|
||||
String get map_noGps => 'No GPS';
|
||||
|
||||
@override
|
||||
String get map_noResults => 'No matching nodes';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => '视线';
|
||||
|
||||
@@ -1990,6 +1861,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get dialog_disconnectConfirm => '确定要断开与此设备的连接吗?';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedTitle => '已断开连接';
|
||||
|
||||
@override
|
||||
String get dialog_disconnectedMessage => '你已与你的伙伴断开连接。';
|
||||
|
||||
@override
|
||||
String get dialog_connectCompanion => '连接伴机以访问中继器和房间服务器功能。';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => '转发节点登录';
|
||||
|
||||
@@ -2051,12 +1931,54 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get common_clear => '清除';
|
||||
|
||||
@override
|
||||
String path_currentPath(String path) {
|
||||
return '当前路径:$path';
|
||||
}
|
||||
|
||||
@override
|
||||
String path_usingHopsPath(int count) {
|
||||
return '使用 $count 跳路径';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_enterCustomPath => '输入自定义路径';
|
||||
|
||||
@override
|
||||
String get path_currentPathLabel => '当前路径';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixInstructions => '请输入每个中继节点的2字符十六进制前缀,用逗号分隔。';
|
||||
|
||||
@override
|
||||
String get path_hexPrefixExample => '例如:A1, F2, 3C(每个节点使用其公钥的第一字节)';
|
||||
|
||||
@override
|
||||
String get path_labelHexPrefixes => '路径(十六进制前缀)';
|
||||
|
||||
@override
|
||||
String get path_helperMaxHops => '最多 64 跳。每个前缀由 2 个十六进制字符(1 字节)组成。';
|
||||
|
||||
@override
|
||||
String get path_selectFromContacts => '或从联系人列表中选择:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound => '未找到任何转发节点或房间服务器。';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire => '自定义路径需要中间节点转发消息。';
|
||||
|
||||
@override
|
||||
String path_invalidHexPrefixes(String prefixes) {
|
||||
return '无效的十六进制前缀:$prefixes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get path_tooLong => '路径过长,最多允许 64 跳。';
|
||||
|
||||
@override
|
||||
String get path_setPath => '设置路径';
|
||||
|
||||
@override
|
||||
String get repeater_management => '转发节点管理';
|
||||
|
||||
@@ -2117,6 +2039,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_routingMode => '路由模式';
|
||||
|
||||
@override
|
||||
String get repeater_autoUseSavedPath => '自动(使用保存的路径)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => '强制泛洪模式';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => '路径管理';
|
||||
|
||||
@override
|
||||
String get repeater_refresh => '刷新';
|
||||
|
||||
@@ -3077,139 +3008,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_digitalInputLabel => '数字输入';
|
||||
|
||||
@override
|
||||
String get telemetry_digitalOutputLabel => '数字输出';
|
||||
|
||||
@override
|
||||
String get telemetry_analogInputLabel => '模拟输入';
|
||||
|
||||
@override
|
||||
String get telemetry_analogOutputLabel => '模拟输出';
|
||||
|
||||
@override
|
||||
String get telemetry_genericLabel => '通用传感器';
|
||||
|
||||
@override
|
||||
String get telemetry_luminosityLabel => '照度';
|
||||
|
||||
@override
|
||||
String get telemetry_presenceLabel => '存在检测';
|
||||
|
||||
@override
|
||||
String get telemetry_humidityLabel => '湿度';
|
||||
|
||||
@override
|
||||
String get telemetry_accelerometerLabel => '加速度计';
|
||||
|
||||
@override
|
||||
String get telemetry_pressureLabel => '气压';
|
||||
|
||||
@override
|
||||
String get telemetry_altitudeLabel => '高度';
|
||||
|
||||
@override
|
||||
String get telemetry_frequencyLabel => '频率';
|
||||
|
||||
@override
|
||||
String get telemetry_percentageLabel => '百分比';
|
||||
|
||||
@override
|
||||
String get telemetry_concentrationLabel => '浓度';
|
||||
|
||||
@override
|
||||
String get telemetry_powerLabel => '功率';
|
||||
|
||||
@override
|
||||
String get telemetry_distanceLabel => '距离';
|
||||
|
||||
@override
|
||||
String get telemetry_energyLabel => '能量';
|
||||
|
||||
@override
|
||||
String get telemetry_directionLabel => '方向';
|
||||
|
||||
@override
|
||||
String get telemetry_timeLabel => '时间';
|
||||
|
||||
@override
|
||||
String get telemetry_gyrometerLabel => '陀螺仪';
|
||||
|
||||
@override
|
||||
String get telemetry_colourLabel => '颜色';
|
||||
|
||||
@override
|
||||
String get telemetry_gpsLabel => 'GPS';
|
||||
|
||||
@override
|
||||
String get telemetry_switchLabel => '开关';
|
||||
|
||||
@override
|
||||
String get telemetry_polylineLabel => '折线';
|
||||
|
||||
@override
|
||||
String telemetry_altitudeValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_frequencyValue(String hertz) {
|
||||
return '$hertz Hz';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_pressureValue(String hpa) {
|
||||
return '$hpa hPa';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_luminosityValue(String lux) {
|
||||
return '$lux lx';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_powerValue(String watts) {
|
||||
return '$watts W';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_distanceValue(String meters) {
|
||||
return '$meters m';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_energyValue(String kilowattHours) {
|
||||
return '$kilowattHours kWh';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_directionValue(String degrees) {
|
||||
return '$degrees°';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_concentrationValue(String ppm) {
|
||||
return '$ppm ppm';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_percentageValue(String percent) {
|
||||
return '$percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String telemetry_analogValue(String value) {
|
||||
return '$value';
|
||||
}
|
||||
|
||||
@override
|
||||
String get telemetry_autoFetchQuantity => '请求次数';
|
||||
|
||||
@override
|
||||
String get telemetry_error => '无法获取数据';
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => '已接收邻居信息';
|
||||
|
||||
@@ -4027,15 +3825,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get translation_composerSubtitle => '控制作曲家翻译图标的默认状态。';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingTitle => '自动翻译消息';
|
||||
|
||||
@override
|
||||
String get translation_autoIncomingSubtitle => '自动为通知以及聊天或频道翻译消息。';
|
||||
|
||||
@override
|
||||
String get translation_translateMessage => '翻译消息';
|
||||
|
||||
@override
|
||||
String get translation_targetLanguage => '目标语言';
|
||||
|
||||
@@ -4158,135 +3947,4 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contact_typeUnknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get map_zoomIn => '放大';
|
||||
|
||||
@override
|
||||
String get map_zoomOut => '放大';
|
||||
|
||||
@override
|
||||
String get map_centerMap => '中心地图';
|
||||
|
||||
@override
|
||||
String get chrome_bluetoothRequiresChromium =>
|
||||
'Web Bluetooth 需要 Chromium 浏览器';
|
||||
|
||||
@override
|
||||
String channels_communityShortId(String id) {
|
||||
return 'ID:$id...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathTrace_legendGpsConfirmed => '通过GPS确认';
|
||||
|
||||
@override
|
||||
String get pathTrace_legendInferred => '推测的位置';
|
||||
|
||||
@override
|
||||
String get pathMap_viewSingle => 'Single';
|
||||
|
||||
@override
|
||||
String get pathMap_viewCombined => 'Combined';
|
||||
|
||||
@override
|
||||
String get pathMap_play => 'Play';
|
||||
|
||||
@override
|
||||
String get pathMap_pause => 'Pause';
|
||||
|
||||
@override
|
||||
String get pathMap_replay => 'Replay';
|
||||
|
||||
@override
|
||||
String get pathMap_stepBack => 'Previous hop';
|
||||
|
||||
@override
|
||||
String get pathMap_stepForward => 'Next hop';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOn => 'Show packet animation';
|
||||
|
||||
@override
|
||||
String get pathMap_animationOff => 'Hide packet animation';
|
||||
|
||||
@override
|
||||
String pathMap_hopOf(int current, int total) {
|
||||
return 'Hop $current of $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_observedPaths(int count) {
|
||||
return 'Observed paths: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_primary => 'Primary';
|
||||
|
||||
@override
|
||||
String pathMap_alternate(int index) {
|
||||
return 'Alt $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_hopCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops',
|
||||
one: '1 hop',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_gpsCount(int confirmed, int total) {
|
||||
return '$confirmed/$total GPS';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_legendShared => 'Shared segment';
|
||||
|
||||
@override
|
||||
String get pathMap_legendEstimated => 'Estimated segment';
|
||||
|
||||
@override
|
||||
String pathMap_sharedNodeCount(int count) {
|
||||
return 'Used by $count paths';
|
||||
}
|
||||
|
||||
@override
|
||||
String pathMap_partialAnimation(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count hops have no location — the shown path is partial',
|
||||
one: '1 hop has no location — the shown path is partial',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathMap_showAllPaths => 'Show all';
|
||||
|
||||
@override
|
||||
String get pathMap_hidePath => 'Hide path';
|
||||
|
||||
@override
|
||||
String get pathMap_showPath => 'Show path';
|
||||
|
||||
@override
|
||||
String get pathMap_collapsePanel => 'Collapse panel';
|
||||
|
||||
@override
|
||||
String get pathMap_expandPanel => 'Expand panel';
|
||||
|
||||
@override
|
||||
String get pathMap_noLocation => 'No location';
|
||||
|
||||
@override
|
||||
String get pathMap_followPacket => 'Lock view to packet';
|
||||
|
||||
@override
|
||||
String get pathMap_unfollowPacket => 'Unlock view from packet';
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Verwijderen",
|
||||
"common_enable": "Activeren",
|
||||
"common_disable": "Uitschakelen",
|
||||
"common_autoRefresh": "Automatisch vernieuwen",
|
||||
"common_interval": "Tijdsinterval",
|
||||
"common_reboot": "Herstarten",
|
||||
"common_loading": "Laden...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digitale ingang",
|
||||
"telemetry_digitalOutputLabel": "Digitale uitgang",
|
||||
"telemetry_analogInputLabel": "Analoge ingang",
|
||||
"telemetry_analogOutputLabel": "Analoge uitgang",
|
||||
"telemetry_genericLabel": "Algemene sensor",
|
||||
"telemetry_luminosityLabel": "Lichtsterkte",
|
||||
"telemetry_presenceLabel": "Aanwezigheid",
|
||||
"telemetry_humidityLabel": "Luchtvochtigheid",
|
||||
"telemetry_accelerometerLabel": "Versnellingsmeter",
|
||||
"telemetry_pressureLabel": "Druk",
|
||||
"telemetry_altitudeLabel": "Hoogte",
|
||||
"telemetry_frequencyLabel": "Frequentie",
|
||||
"telemetry_percentageLabel": "Percentage",
|
||||
"telemetry_concentrationLabel": "Concentratie",
|
||||
"telemetry_powerLabel": "Vermogen",
|
||||
"telemetry_distanceLabel": "Afstand",
|
||||
"telemetry_energyLabel": "Energie",
|
||||
"telemetry_directionLabel": "Richting",
|
||||
"telemetry_timeLabel": "Tijd",
|
||||
"telemetry_gyrometerLabel": "Gyrometer",
|
||||
"telemetry_colourLabel": "Kleur",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Schakelaar",
|
||||
"telemetry_polylineLabel": "Polylijn",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Aantal aanvragen",
|
||||
"telemetry_error": "Kan gegevens niet ophalen",
|
||||
"telemetry_noData": "Geen telemetriedata beschikbaar.",
|
||||
"telemetry_channelTitle": "Kanaal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2140,9 +2101,6 @@
|
||||
"translation_composerTitle": "Vertaal voor verzending",
|
||||
"translation_composerSubtitle": "Stelt de standaardstatus van het pictogram voor de vertaling van de componist in.",
|
||||
"translation_useAppLanguage": "Gebruik de taal van de app",
|
||||
"translation_autoIncomingTitle": "Berichten automatisch vertalen",
|
||||
"translation_autoIncomingSubtitle": "Vertaalt berichten automatisch voor meldingen en voor chats of kanalen.",
|
||||
"translation_translateMessage": "Bericht vertalen",
|
||||
"translation_targetLanguage": "Doeltaal",
|
||||
"translation_downloadedModelLabel": "Gedownloade model",
|
||||
"translation_presetModelLabel": "Voorgeprogrammeerd Hugging Face-model",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_markAsUnread": "Markeer als ongelezen",
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB commando's, antwoorden en ruwe data",
|
||||
"repeater_chanUtil": "Gebruik van het kanaal",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "Verzonden",
|
||||
"common_undo": "Achterhalen/Annuleren",
|
||||
"messageStatus_delivered": "Leverd",
|
||||
"messageStatus_pending": "Verzenden",
|
||||
"messageStatus_failed": "Niet verzonden",
|
||||
"messageStatus_repeated": "Hearsay, herhaald",
|
||||
"contacts_moreOptions": "Meer opties",
|
||||
"contacts_searchOpen": "Zoek contactpersonen",
|
||||
"contacts_searchClose": "Zoeken",
|
||||
"routing_title": "Routeplanning",
|
||||
"routing_modeAuto": "Auto",
|
||||
"routing_modeFlood": "Overstroming",
|
||||
"routing_modeManual": "Handleiding",
|
||||
"routing_modeAutoHint": "Selecteert automatisch het bekendste pad, en gebruikt een flood-algoritme als er geen bekend pad is.",
|
||||
"routing_modeFloodHint": "Uitzendingen via elke zender. De meest betrouwbare methode, maar vereist meer uitzendtijd.",
|
||||
"routing_modeManualHint": "Stuurt altijd de exacte route die u heeft aangegeven.",
|
||||
"routing_currentRoute": "Huidige route",
|
||||
"routing_directNoHops": "Direct – zonder tussenliggende schakels",
|
||||
"routing_noPathYet": "Er is nog geen route gevonden. De berichten blijven binnenkomen totdat een route is ontdekt.",
|
||||
"routing_floodBroadcast": "Uitgestoten via elke zender.",
|
||||
"routing_editPath": "Pad bewerken",
|
||||
"routing_forgetPath": "Vergeet het pad",
|
||||
"routing_knownPaths": "Bekende routes",
|
||||
"routing_knownPathsHint": "Maak een route om er naartoe te gaan.",
|
||||
"routing_inUse": "In gebruik",
|
||||
"routing_qualityStrong": "Sterke eerste sprong",
|
||||
"routing_qualityGood": "Een goede eerste stap",
|
||||
"routing_qualityFair": "Een goede eerste hop",
|
||||
"routing_qualityWorked": "Is geleverd",
|
||||
"routing_qualityFlood": "Hears via een overstroming",
|
||||
"routing_qualityUntested": "Niet getest",
|
||||
"routing_neverWorked": "nooit bevestigd",
|
||||
"routing_deliveryCounts": "{successes} zijn behaald, {failures} zijn mislukt",
|
||||
"routing_floodDelivery": "Levering bij overstroming",
|
||||
"pathEditor_title": "Pad creëren",
|
||||
"pathEditor_hopCounter": "{count} van 64 hopgranen",
|
||||
"pathEditor_noHops": "Er zijn nog geen hop toegevoegd. Klik op de onderstaande knoppen om ze in de juiste volgorde toe te voegen, of sla de bestelling op zonder hop om deze direct te versturen.",
|
||||
"pathEditor_addHops": "Voeg hop toe in de juiste volgorde.",
|
||||
"pathEditor_searchRepeaters": "Zoek naar herhaaldelijke zenders",
|
||||
"pathEditor_advancedHex": "Geavanceerd: ruwe hex-pad",
|
||||
"pathEditor_hexLabel": "Hex-voorkanten",
|
||||
"pathEditor_hexHelper": "Twee hex-tekens per stap, gescheiden door komma's",
|
||||
"pathEditor_invalidTokens": "Ongeldig: {tokens}",
|
||||
"pathEditor_tooManyHops": "Maximaal 64 hopken",
|
||||
"pathEditor_usePath": "Gebruik deze route.",
|
||||
"pathEditor_removeHop": "Verwijder de hop",
|
||||
"pathEditor_unknownHop": "Onbekend type zender",
|
||||
"map_zoomIn": "Inzoomen",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"map_zoomOut": "Inzoomen",
|
||||
"map_centerMap": "Centraal overzicht",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth vereist een Chromium-browser.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS-locatie bevestigd",
|
||||
"pathTrace_legendInferred": "Afgeleide positie"
|
||||
"dialog_connectCompanion": "Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.",
|
||||
"dialog_disconnectedTitle": "Verbroken",
|
||||
"dialog_disconnectedMessage": "Je bent losgekoppeld van je companion.",
|
||||
"contact_connectCompanion": "Maak verbinding met een companion om toegang te krijgen tot repeater- en roomserverfuncties."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Usuń",
|
||||
"common_enable": "Włącz",
|
||||
"common_disable": "Wyłącz",
|
||||
"common_autoRefresh": "Automatyczne odświeżanie",
|
||||
"common_interval": "Interwał",
|
||||
"common_reboot": "Uruchom ponownie",
|
||||
"common_loading": "Ładowanie...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1292,43 +1290,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Wejście cyfrowe",
|
||||
"telemetry_digitalOutputLabel": "Wyjście cyfrowe",
|
||||
"telemetry_analogInputLabel": "Wejście analogowe",
|
||||
"telemetry_analogOutputLabel": "Wyjście analogowe",
|
||||
"telemetry_genericLabel": "Czujnik ogólny",
|
||||
"telemetry_luminosityLabel": "Jasność",
|
||||
"telemetry_presenceLabel": "Obecność",
|
||||
"telemetry_humidityLabel": "Wilgotność",
|
||||
"telemetry_accelerometerLabel": "Akcelerometr",
|
||||
"telemetry_pressureLabel": "Ciśnienie",
|
||||
"telemetry_altitudeLabel": "Wysokość",
|
||||
"telemetry_frequencyLabel": "Częstotliwość",
|
||||
"telemetry_percentageLabel": "Procent",
|
||||
"telemetry_concentrationLabel": "Stężenie",
|
||||
"telemetry_powerLabel": "Moc",
|
||||
"telemetry_distanceLabel": "Odległość",
|
||||
"telemetry_energyLabel": "Energia",
|
||||
"telemetry_directionLabel": "Kierunek",
|
||||
"telemetry_timeLabel": "Czas",
|
||||
"telemetry_gyrometerLabel": "Żyrometr",
|
||||
"telemetry_colourLabel": "Kolor",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Przełącznik",
|
||||
"telemetry_polylineLabel": "Polilinia",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Liczba żądań",
|
||||
"telemetry_error": "Nie udało się pobrać danych",
|
||||
"telemetry_noData": "Brak dostępnych danych telemetrycznych.",
|
||||
"telemetry_channelTitle": "Kanał {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2176,9 +2137,6 @@
|
||||
"translation_enableTitle": "Włącz tłumaczenie",
|
||||
"translation_enableSubtitle": "Tłumaczenie otrzymywanych wiadomości oraz umożliwienie tłumaczenia przed wysłaniem.",
|
||||
"translation_composerSubtitle": "Kontroluje domyślny stan ikony tłumaczenia w edytorze.",
|
||||
"translation_autoIncomingTitle": "Automatycznie tłumacz wiadomości",
|
||||
"translation_autoIncomingSubtitle": "Automatycznie tłumaczy wiadomości do powiadomień oraz do czatów lub kanałów.",
|
||||
"translation_translateMessage": "Przetłumacz wiadomość",
|
||||
"translation_targetLanguage": "Język docelowy",
|
||||
"translation_useAppLanguage": "Użyj języka aplikacji",
|
||||
"translation_downloadedModelLabel": "Pobudowany model",
|
||||
@@ -2392,97 +2350,8 @@
|
||||
"chat_markAsUnread": "Oznacz jako nieprzeczytane",
|
||||
"settings_companionDebugLog": "Log debugowania (dla pomocy w rozwiązywaniu problemów)",
|
||||
"repeater_chanUtil": "Wykorzystanie kanału",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "Wysłane",
|
||||
"messageStatus_delivered": "Dostarczone",
|
||||
"messageStatus_pending": "Wysyłanie",
|
||||
"common_undo": "Wycofaj",
|
||||
"messageStatus_failed": "Nie udało się wysłać",
|
||||
"messageStatus_repeated": "Usłyszałem to wielokrotnie",
|
||||
"contacts_moreOptions": "Więcej opcji",
|
||||
"contacts_searchOpen": "Wyszukaj kontakty",
|
||||
"contacts_searchClose": "Zaawansowane wyszukiwanie",
|
||||
"routing_title": "Planowanie tras",
|
||||
"routing_modeAuto": "Samochód",
|
||||
"routing_modeFlood": "Powódź",
|
||||
"routing_modeManual": "Instrukcja obsługi",
|
||||
"routing_modeAutoHint": "Automatycznie wybiera najpopularniejszą ścieżkę, a w przypadku braku znanej, przechodzi do trybu \"przepływu\".",
|
||||
"routing_modeFloodHint": "Transmisje za pośrednictwem każdego repeatera. Najbardziej niezawodna metoda, ale zużywa więcej czasu transmisji.",
|
||||
"routing_modeManualHint": "Zawsze prowadzi dokładnie po trasie, którą określiłeś.",
|
||||
"routing_currentRoute": "Obecna trasa",
|
||||
"routing_directNoHops": "Bezpośrednio – bez pośrednictwa repeaterów",
|
||||
"routing_noPathYet": "Na razie nie ma żadnej ścieżki. Komunikacja trwa do momentu, gdy zostanie odkryta trasa.",
|
||||
"routing_floodBroadcast": "Transmisja za pośrednictwem każdego urządzenia powielającego",
|
||||
"routing_editPath": "Edytuj ścieżkę",
|
||||
"routing_forgetPath": "Zapomnij o ścieżce",
|
||||
"routing_knownPaths": "Znane trasy",
|
||||
"routing_knownPathsHint": "Wybierz ścieżkę, aby przełączyć się na nią.",
|
||||
"routing_inUse": "W użyciu",
|
||||
"routing_qualityStrong": "Silny pierwszy skok",
|
||||
"routing_qualityGood": "Świetny początek",
|
||||
"routing_qualityFair": "Świetny pierwszy krzak",
|
||||
"routing_qualityWorked": "Zostało dostarczone",
|
||||
"routing_qualityFlood": "Usłyszano dzięki doniesieniom",
|
||||
"routing_qualityUntested": "Nieużywany",
|
||||
"routing_lastWorked": "pracował {when}",
|
||||
"routing_neverWorked": "nigdy nie zostało potwierdzone",
|
||||
"routing_floodDelivery": "Dostawa w przypadku powodzi",
|
||||
"pathEditor_title": "Stworzenie ścieżki",
|
||||
"pathEditor_hopCounter": "{count} z 64 rodzajów chmielu",
|
||||
"pathEditor_noHops": "Na razie nie dodano żadnych chmielu. Aby dodać je w odpowiedniej kolejności, kliknij w odpowiednie przyciski poniżej, lub zapisz przepis bez chmielu, aby wysłać go bezpośrednio.",
|
||||
"pathEditor_addHops": "Dodawaj chmiel zgodnie z kolejnością.",
|
||||
"pathEditor_searchRepeaters": "Funkcje powtarzania",
|
||||
"pathEditor_advancedHex": "Zaawansowane: ścieżka w formacie szesnastkowym",
|
||||
"pathEditor_hexLabel": "Prefiksy heksadecymalne",
|
||||
"pathEditor_hexHelper": "Dwa znaki szesnastkowe na każdym kroku, oddzielone przecinkami",
|
||||
"pathEditor_invalidTokens": "Nieprawidłowe: {tokens}",
|
||||
"pathEditor_tooManyHops": "Maksymalnie 64 hopów",
|
||||
"pathEditor_usePath": "Użyj tej ścieżki.",
|
||||
"pathEditor_removeHop": "Usuń dziką psiankę",
|
||||
"pathEditor_unknownHop": "Nieznany repeater",
|
||||
"map_zoomIn": "Przybliż",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Przybliż z powrotem",
|
||||
"map_centerMap": "Mapa centrum",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth wymaga przeglądarki Chromium.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS potwierdzone",
|
||||
"pathTrace_legendInferred": "Wywnioskowana pozycja"
|
||||
"dialog_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.",
|
||||
"dialog_disconnectedTitle": "Rozłączono",
|
||||
"dialog_disconnectedMessage": "Zostałeś rozłączony ze swoim towarzyszem.",
|
||||
"contact_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji repeatera i serwera pokojowego."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Remover",
|
||||
"common_enable": "Ativar",
|
||||
"common_disable": "Desativar",
|
||||
"common_autoRefresh": "Atualização automática",
|
||||
"common_interval": "Intervalo",
|
||||
"common_reboot": "Reiniciar",
|
||||
"common_loading": "Carregando...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Entrada digital",
|
||||
"telemetry_digitalOutputLabel": "Saída digital",
|
||||
"telemetry_analogInputLabel": "Entrada analógica",
|
||||
"telemetry_analogOutputLabel": "Saída analógica",
|
||||
"telemetry_genericLabel": "Sensor genérico",
|
||||
"telemetry_luminosityLabel": "Luminosidade",
|
||||
"telemetry_presenceLabel": "Presença",
|
||||
"telemetry_humidityLabel": "Humidade",
|
||||
"telemetry_accelerometerLabel": "Acelerómetro",
|
||||
"telemetry_pressureLabel": "Pressão",
|
||||
"telemetry_altitudeLabel": "Altitude",
|
||||
"telemetry_frequencyLabel": "Frequência",
|
||||
"telemetry_percentageLabel": "Percentagem",
|
||||
"telemetry_concentrationLabel": "Concentração",
|
||||
"telemetry_powerLabel": "Potência",
|
||||
"telemetry_distanceLabel": "Distância",
|
||||
"telemetry_energyLabel": "Energia",
|
||||
"telemetry_directionLabel": "Direção",
|
||||
"telemetry_timeLabel": "Hora",
|
||||
"telemetry_gyrometerLabel": "Girómetro",
|
||||
"telemetry_colourLabel": "Cor",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Interruptor",
|
||||
"telemetry_polylineLabel": "Polilinha",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Número de solicitações",
|
||||
"telemetry_error": "Não foi possível obter os dados",
|
||||
"telemetry_noData": "Não estão disponíveis dados de telemetria.",
|
||||
"telemetry_channelTitle": "Canal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2139,9 +2100,6 @@
|
||||
"translation_enableTitle": "Ativar a tradução",
|
||||
"translation_title": "Tradução",
|
||||
"translation_composerSubtitle": "Controla o estado padrão do ícone de tradução do compositor.",
|
||||
"translation_autoIncomingTitle": "Traduzir mensagens automaticamente",
|
||||
"translation_autoIncomingSubtitle": "Traduz automaticamente mensagens para notificações e para chats ou canais.",
|
||||
"translation_translateMessage": "Traduzir mensagem",
|
||||
"translation_targetLanguage": "Língua-alvo",
|
||||
"translation_useAppLanguage": "Utilize o idioma da aplicação",
|
||||
"translation_downloadedModelLabel": "Modelo baixado",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_markAsUnread": "Marcar como não lido",
|
||||
"chat_newMessages": "Novas mensagens",
|
||||
"repeater_chanUtil": "Utilização do canal",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_undo": "Desfazer",
|
||||
"messageStatus_sent": "Enviado",
|
||||
"messageStatus_pending": "Enviar",
|
||||
"messageStatus_delivered": "Entregue",
|
||||
"messageStatus_failed": "Falhou ao enviar",
|
||||
"messageStatus_repeated": "Ouvi repetidamente",
|
||||
"contacts_moreOptions": "Mais opções",
|
||||
"contacts_searchOpen": "Pesquisar contatos",
|
||||
"contacts_searchClose": "Pesquisa avançada",
|
||||
"routing_title": "Rotas",
|
||||
"routing_modeAuto": "Carro",
|
||||
"routing_modeFlood": "Inundação",
|
||||
"routing_modeManual": "Manual",
|
||||
"routing_modeAutoHint": "Seleciona automaticamente o caminho mais conhecido, e, se nenhum caminho conhecido for encontrado, utiliza a estratégia de \"inundação\".",
|
||||
"routing_modeFloodHint": "Transmissão através de todos os repetidores. É a opção mais confiável, mas utiliza mais tempo de transmissão.",
|
||||
"routing_modeManualHint": "Sempre segue exatamente o caminho que você define.",
|
||||
"routing_currentRoute": "Rota atual",
|
||||
"routing_directNoHops": "Direto – sem saltos de repetidor",
|
||||
"routing_noPathYet": "Ainda não há um caminho definido. A mensagem continua a ser enviada até que uma rota seja encontrada.",
|
||||
"routing_floodBroadcast": "Transmissão através de todos os repetidores",
|
||||
"routing_editPath": "Editar caminho",
|
||||
"routing_forgetPath": "Esqueça o caminho",
|
||||
"routing_knownPaths": "Rotas conhecidas",
|
||||
"routing_knownPathsHint": "Toque em um caminho para alternar para ele.",
|
||||
"routing_inUse": "Em uso",
|
||||
"routing_qualityStrong": "Primeiro salto notável",
|
||||
"routing_qualityGood": "Primeiro salto bem-sucedido",
|
||||
"routing_qualityFair": "Primeira etapa bem-sucedida",
|
||||
"routing_qualityWorked": "Foi entregue",
|
||||
"routing_qualityFlood": "Informação obtida através de relatos generalizados.",
|
||||
"routing_qualityUntested": "Não testado",
|
||||
"routing_neverWorked": "nunca confirmado",
|
||||
"routing_floodDelivery": "Entrega em áreas afetadas por inundações",
|
||||
"pathEditor_title": "Criar Caminho",
|
||||
"pathEditor_hopCounter": "{count} de 64 gramas de lúpulo",
|
||||
"pathEditor_noHops": "Ainda não há lúpulos adicionados. Clique nos repetidores abaixo para adicioná-los na ordem desejada, ou salve sem adicionar lúpulos para enviar diretamente.",
|
||||
"pathEditor_addHops": "Adicione os lúpulos na seguinte ordem.",
|
||||
"pathEditor_searchRepeaters": "Encontrar repetidores",
|
||||
"pathEditor_advancedHex": "Avançado: caminho hexadecimal bruto",
|
||||
"pathEditor_hexLabel": "Prefixos hexadecimais",
|
||||
"pathEditor_hexHelper": "Dois caracteres hexadecimais por salto, separados por vírgulas.",
|
||||
"pathEditor_invalidTokens": "Inválido: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"pathEditor_tooManyHops": "Máximo de 64 saltos",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_usePath": "Utilize este caminho.",
|
||||
"pathEditor_removeHop": "Remova o lúpulo",
|
||||
"pathEditor_unknownHop": "Repetidor desconhecido",
|
||||
"map_zoomIn": "Ampliar",
|
||||
"map_zoomOut": "Ampliar",
|
||||
"map_centerMap": "Mapa do centro",
|
||||
"chrome_bluetoothRequiresChromium": "O Web Bluetooth requer um navegador Chromium.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS confirmado",
|
||||
"pathTrace_legendInferred": "Posição inferida"
|
||||
"dialog_connectCompanion": "Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.",
|
||||
"dialog_disconnectedTitle": "Desconectado",
|
||||
"dialog_disconnectedMessage": "Você foi desconectado do seu companheiro.",
|
||||
"contact_connectCompanion": "Conecte-se a um companheiro para acessar recursos de repetidor e servidor de sala."
|
||||
}
|
||||
|
||||
+4
-135
@@ -39,8 +39,6 @@
|
||||
"common_notAvailable": "—",
|
||||
"common_voltageValue": "{volts} В",
|
||||
"common_percentValue": "{percent}%",
|
||||
"common_autoRefresh": "Автообновление",
|
||||
"common_interval": "Интервал",
|
||||
"scanner_title": "MeshCore Open",
|
||||
"scanner_scanning": "Поиск устройств...",
|
||||
"scanner_connecting": "Подключение...",
|
||||
@@ -688,43 +686,6 @@
|
||||
"telemetry_voltageValue": "{volts}В",
|
||||
"telemetry_currentValue": "{amps}А",
|
||||
"telemetry_temperatureValue": "{celsius}°C / {fahrenheit}°F",
|
||||
"telemetry_digitalInputLabel": "Цифровой вход",
|
||||
"telemetry_digitalOutputLabel": "Цифровой выход",
|
||||
"telemetry_analogInputLabel": "Аналоговый вход",
|
||||
"telemetry_analogOutputLabel": "Аналоговый выход",
|
||||
"telemetry_genericLabel": "Общий датчик",
|
||||
"telemetry_luminosityLabel": "Освещённость",
|
||||
"telemetry_presenceLabel": "Присутствие",
|
||||
"telemetry_humidityLabel": "Влажность",
|
||||
"telemetry_accelerometerLabel": "Акселерометр",
|
||||
"telemetry_pressureLabel": "Давление",
|
||||
"telemetry_altitudeLabel": "Высота",
|
||||
"telemetry_frequencyLabel": "Частота",
|
||||
"telemetry_percentageLabel": "Процент",
|
||||
"telemetry_concentrationLabel": "Концентрация",
|
||||
"telemetry_powerLabel": "Мощность",
|
||||
"telemetry_distanceLabel": "Расстояние",
|
||||
"telemetry_energyLabel": "Энергия",
|
||||
"telemetry_directionLabel": "Направление",
|
||||
"telemetry_timeLabel": "Время",
|
||||
"telemetry_gyrometerLabel": "Гирометр",
|
||||
"telemetry_colourLabel": "Цвет",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Переключатель",
|
||||
"telemetry_polylineLabel": "Полилиния",
|
||||
"telemetry_altitudeValue": "{meters} м",
|
||||
"telemetry_frequencyValue": "{hertz} Гц",
|
||||
"telemetry_pressureValue": "{hpa} гПа",
|
||||
"telemetry_luminosityValue": "{lux} лк",
|
||||
"telemetry_powerValue": "{watts} Вт",
|
||||
"telemetry_distanceValue": "{meters} м",
|
||||
"telemetry_energyValue": "{kilowattHours} кВт⋅ч",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Количество запросов",
|
||||
"telemetry_error": "Не удалось получить данные",
|
||||
"neighbors_receivedData": "Полученные данные о соседях",
|
||||
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
|
||||
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
|
||||
@@ -1307,9 +1268,6 @@
|
||||
"translation_title": "Перевод",
|
||||
"translation_enableTitle": "Включить перевод",
|
||||
"translation_composerSubtitle": "Управляет исходным состоянием значка перевода, предоставляемого редактором.",
|
||||
"translation_autoIncomingTitle": "Автоматически переводить сообщения",
|
||||
"translation_autoIncomingSubtitle": "Автоматически переводит сообщения для уведомлений, а также для чатов и каналов.",
|
||||
"translation_translateMessage": "Перевести сообщение",
|
||||
"translation_targetLanguage": "Целевой язык",
|
||||
"translation_useAppLanguage": "Используйте язык приложения",
|
||||
"translation_downloadedModelLabel": "Загруженная модель",
|
||||
@@ -1657,97 +1615,8 @@
|
||||
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
|
||||
"repeater_chanUtil": "Использование канала",
|
||||
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_pending": "Отправка",
|
||||
"common_undo": "Отменить",
|
||||
"messageStatus_delivered": "Доставлено",
|
||||
"messageStatus_sent": "Отправлено",
|
||||
"messageStatus_failed": "Не удалось отправить",
|
||||
"messageStatus_repeated": "Услышал несколько раз",
|
||||
"contacts_moreOptions": "Больше вариантов",
|
||||
"contacts_searchOpen": "Найти контакты",
|
||||
"contacts_searchClose": "Закрыть поиск",
|
||||
"routing_title": "Маршрутизация",
|
||||
"routing_modeAuto": "Авто",
|
||||
"routing_modeFlood": "Наводнение",
|
||||
"routing_modeManual": "Инструкция",
|
||||
"routing_modeAutoHint": "Автоматически выбирает наиболее известный путь, и если такой путь неизвестен, использует алгоритм поиска пути.",
|
||||
"routing_modeFloodHint": "Передача сигнала через все ретрансляторы. Самый надежный способ, но требует больше времени на передачу.",
|
||||
"routing_modeManualHint": "Всегда следует точно по указанному вами маршруту.",
|
||||
"routing_currentRoute": "Текущий маршрут",
|
||||
"routing_directNoHops": "Прямое соединение – без использования ретрансляторов",
|
||||
"routing_noPathYet": "Пока нет пути. Следующее сообщение будет отправлено до тех пор, пока не будет обнаружен маршрут.",
|
||||
"routing_floodBroadcast": "Транслируется через все ретрансляторы",
|
||||
"routing_editPath": "Изменить путь",
|
||||
"routing_forgetPath": "Забудьте о маршруте",
|
||||
"routing_knownPaths": "Известные маршруты",
|
||||
"routing_knownPathsHint": "Создайте маршрут для переключения на этот пункт.",
|
||||
"routing_inUse": "В эксплуатации",
|
||||
"routing_qualityStrong": "Сильный первый скачок",
|
||||
"routing_qualityGood": "Хорошее начало",
|
||||
"routing_qualityFair": "Первый хороший урожай",
|
||||
"routing_qualityWorked": "Осуществлено",
|
||||
"routing_qualityFlood": "Узнал из новостей, распространяющихся в интернете.",
|
||||
"routing_qualityUntested": "Непроверенный",
|
||||
"routing_neverWorked": "никогда не было подтверждено",
|
||||
"routing_floodDelivery": "Доставка при затоплении",
|
||||
"pathEditor_title": "Создать маршрут",
|
||||
"pathEditor_hopCounter": "{count} из 64 хмеля",
|
||||
"pathEditor_noHops": "На данный момент хмель еще не добавлен. Чтобы добавить его, нажмите на соответствующие кнопки ниже в нужном порядке, или сохраните рецепт без хмеля, чтобы отправить его напрямую.",
|
||||
"pathEditor_addHops": "Добавляйте хмель в соответствии с указанным порядком.",
|
||||
"pathEditor_searchRepeaters": "Поиск повторителей",
|
||||
"pathEditor_advancedHex": "Продвинутый уровень: прямой путь в шестнадцатеричном формате",
|
||||
"pathEditor_hexLabel": "Префиксы шестнадцатеричной системы",
|
||||
"pathEditor_hexHelper": "Два шестнадцатеричных символа на каждом шаге, разделенные запятыми.",
|
||||
"pathEditor_invalidTokens": "Неверно: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_tooManyHops": "Максимальное количество ингредиентов – 64",
|
||||
"pathEditor_usePath": "Используйте этот путь",
|
||||
"pathEditor_removeHop": "Удалить хмель",
|
||||
"pathEditor_unknownHop": "Неизвестный ретранслятор",
|
||||
"map_zoomIn": "Увеличить масштаб",
|
||||
"map_zoomOut": "Увеличить масштаб",
|
||||
"map_centerMap": "Карта центра",
|
||||
"chrome_bluetoothRequiresChromium": "Для работы Web Bluetooth требуется браузер на основе Chromium.",
|
||||
"channels_communityShortId": "Идентификатор: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS подтверждено",
|
||||
"pathTrace_legendInferred": "Выведенная позиция"
|
||||
"dialog_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.",
|
||||
"dialog_disconnectedTitle": "Отключено",
|
||||
"dialog_disconnectedMessage": "Вы были отключены от вашего компаньона.",
|
||||
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям репитера и серверу комнаты."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Odstrániť",
|
||||
"common_enable": "Povolit",
|
||||
"common_disable": "Zakázať",
|
||||
"common_autoRefresh": "Automatické obnovenie",
|
||||
"common_interval": "Časový interval",
|
||||
"common_reboot": "Restartovať",
|
||||
"common_loading": "Načítavanie...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digitálny vstup",
|
||||
"telemetry_digitalOutputLabel": "Digitálny výstup",
|
||||
"telemetry_analogInputLabel": "Analógový vstup",
|
||||
"telemetry_analogOutputLabel": "Analógový výstup",
|
||||
"telemetry_genericLabel": "Všeobecný senzor",
|
||||
"telemetry_luminosityLabel": "Osvetlenie",
|
||||
"telemetry_presenceLabel": "Prítomnosť",
|
||||
"telemetry_humidityLabel": "Vlhkosť",
|
||||
"telemetry_accelerometerLabel": "Akcelerometer",
|
||||
"telemetry_pressureLabel": "Tlak",
|
||||
"telemetry_altitudeLabel": "Nadmorská výška",
|
||||
"telemetry_frequencyLabel": "Frekvencia",
|
||||
"telemetry_percentageLabel": "Percento",
|
||||
"telemetry_concentrationLabel": "Koncentrácia",
|
||||
"telemetry_powerLabel": "Výkon",
|
||||
"telemetry_distanceLabel": "Vzdialenosť",
|
||||
"telemetry_energyLabel": "Energia",
|
||||
"telemetry_directionLabel": "Smer",
|
||||
"telemetry_timeLabel": "Čas",
|
||||
"telemetry_gyrometerLabel": "Gyrometer",
|
||||
"telemetry_colourLabel": "Farba",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Prepínač",
|
||||
"telemetry_polylineLabel": "Lomená čiara",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Počet požiadaviek",
|
||||
"telemetry_error": "Nepodarilo sa získať údaje",
|
||||
"telemetry_noData": "Nejsú dostupné žiadne údaje z telemetrie.",
|
||||
"telemetry_channelTitle": "Kanál {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2139,9 +2100,6 @@
|
||||
"translation_composerTitle": "Preložte pred odeslaním",
|
||||
"translation_title": "Preklad",
|
||||
"translation_composerSubtitle": "Riadi výchoce stav ikony pre preklad, ktorú používa program.",
|
||||
"translation_autoIncomingTitle": "Automaticky prekladať správy",
|
||||
"translation_autoIncomingSubtitle": "Automaticky prekladá správy pre upozornenia aj pre čet alebo kanál.",
|
||||
"translation_translateMessage": "Preložiť správu",
|
||||
"translation_targetLanguage": "Cieľový jazyk",
|
||||
"translation_useAppLanguage": "Použite jazyk aplikácie",
|
||||
"translation_downloadedModelLabel": "Stiahnutý model",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"settings_companionDebugLog": "Logovanie pre ladenie (sprievodný log)",
|
||||
"chat_newMessages": "Nové správy",
|
||||
"repeater_chanUtil": "Využitie kanálu",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "Odoslané",
|
||||
"messageStatus_delivered": "Doručené",
|
||||
"messageStatus_pending": "Odoslanie",
|
||||
"common_undo": "Zrušiť",
|
||||
"messageStatus_failed": "Neúspešné odeslanie",
|
||||
"messageStatus_repeated": "Slyšal som to opakovane",
|
||||
"contacts_moreOptions": "Ďalšie možnosti",
|
||||
"contacts_searchOpen": "Vyhľadajte kontakty",
|
||||
"contacts_searchClose": "Zavrieť vyhľadávanie",
|
||||
"routing_title": "Navigácia",
|
||||
"routing_modeAuto": "Auto",
|
||||
"routing_modeFlood": "Povodňová vlna",
|
||||
"routing_modeManual": "Ručná príručka",
|
||||
"routing_modeAutoHint": "Automaticky vyberá najznámejší trasa, a ak žiadna nie je známa, použije náhodnú trasu.",
|
||||
"routing_modeFloodHint": "Prenos prostredníctvom všetkých opakovačov. Najspoľahlivejší spôsob, ale vyžaduje viac času vysielania.",
|
||||
"routing_modeManualHint": "Vždy dodáva presne podľa zadaného trasy.",
|
||||
"routing_currentRoute": "Aktuálna trasa",
|
||||
"routing_directNoHops": "Priamo – bez prechodných trás",
|
||||
"routing_noPathYet": "Zatiaľ neexistuje žiadna cesta. Nasledujúce správy budú pokračovať, kým sa nenájde trasa.",
|
||||
"routing_floodBroadcast": "Prenos prostredníctvom každého opakovača",
|
||||
"routing_editPath": "Upraviť trasu",
|
||||
"routing_forgetPath": "Zabudnite na trasu",
|
||||
"routing_knownPaths": "Známe cesty",
|
||||
"routing_knownPathsHint": "Kliknite na cestu, aby ste sa k nej presunuli.",
|
||||
"routing_inUse": "V prevádzke",
|
||||
"routing_qualityStrong": "Silný prvý krok",
|
||||
"routing_qualityGood": "Úspešný prvý krok",
|
||||
"routing_qualityFair": "Prvá, spravodlivá fáza",
|
||||
"routing_qualityWorked": "Dosiahnutý úspech",
|
||||
"routing_qualityFlood": "Zistil som to z informácií, ktoré som získal v dôsledku povodňovej situácie.",
|
||||
"routing_qualityUntested": "Neotestované",
|
||||
"routing_neverWorked": "nikedy nebolo potvrdené",
|
||||
"routing_floodDelivery": "Doručenie v prípade povodní",
|
||||
"pathEditor_title": "Vytvorenie cesty",
|
||||
"pathEditor_hopCounter": "{count} z 64 chmelových zŕš",
|
||||
"pathEditor_noHops": "Zatiaľ žiadne chmel. Kliknite na opakované, aby ste ich pridali postupne, alebo uložte bez chmelu, aby ste ho mohli poslať priamo.",
|
||||
"pathEditor_addHops": "Pridávajte chmel podľa zadaného poriadku.",
|
||||
"pathEditor_searchRepeaters": "Hľadať opakované",
|
||||
"pathEditor_advancedHex": "Pokročilé: pôvodná hexová cesta",
|
||||
"pathEditor_hexLabel": "Prefiksy pre hexadecimálne čísla",
|
||||
"pathEditor_hexHelper": "Dve hexové čísla na každý krok, oddelené čiarkami",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"pathEditor_invalidTokens": "Neplatné: {tokens}",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_tooManyHops": "Maximálne 64 krokov",
|
||||
"pathEditor_usePath": "Použite túto cestu",
|
||||
"pathEditor_removeHop": "Odstráňte chmel",
|
||||
"pathEditor_unknownHop": "Neznáme zariadenie na opakované vysielanie",
|
||||
"map_zoomIn": "Zväčšiť",
|
||||
"map_zoomOut": "Zmenť zamer zblízka",
|
||||
"map_centerMap": "Mapa centra",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth vyžaduje prehliadač Chromium.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS potvrdilo",
|
||||
"pathTrace_legendInferred": "Odvodená poloha"
|
||||
"dialog_connectCompanion": "Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.",
|
||||
"dialog_disconnectedTitle": "Odpojené",
|
||||
"dialog_disconnectedMessage": "Od vášho spoločníka ste boli odpojený.",
|
||||
"contact_connectCompanion": "Pripojte sa k spoločníkovi pre prístup k funkciám opakovača a miestneho servera."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Izbrisati",
|
||||
"common_enable": "Omogoči",
|
||||
"common_disable": "Izklopiti",
|
||||
"common_autoRefresh": "Samodejno osveževanje",
|
||||
"common_interval": "Časovni interval",
|
||||
"common_reboot": "Ponoviti",
|
||||
"common_loading": "Naložanje...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digitalni vhod",
|
||||
"telemetry_digitalOutputLabel": "Digitalni izhod",
|
||||
"telemetry_analogInputLabel": "Analogni vhod",
|
||||
"telemetry_analogOutputLabel": "Analogni izhod",
|
||||
"telemetry_genericLabel": "Splošni senzor",
|
||||
"telemetry_luminosityLabel": "Osvetljenost",
|
||||
"telemetry_presenceLabel": "Prisotnost",
|
||||
"telemetry_humidityLabel": "Vlažnost",
|
||||
"telemetry_accelerometerLabel": "Merilnik pospeška",
|
||||
"telemetry_pressureLabel": "Tlak",
|
||||
"telemetry_altitudeLabel": "Nadmorska višina",
|
||||
"telemetry_frequencyLabel": "Frekvenca",
|
||||
"telemetry_percentageLabel": "Odstotek",
|
||||
"telemetry_concentrationLabel": "Koncentracija",
|
||||
"telemetry_powerLabel": "Moč",
|
||||
"telemetry_distanceLabel": "Razdalja",
|
||||
"telemetry_energyLabel": "Energija",
|
||||
"telemetry_directionLabel": "Smer",
|
||||
"telemetry_timeLabel": "Čas",
|
||||
"telemetry_gyrometerLabel": "Žiroskop",
|
||||
"telemetry_colourLabel": "Barva",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Stikalo",
|
||||
"telemetry_polylineLabel": "Polilinija",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Število zahtev",
|
||||
"telemetry_error": "Podatkov ni bilo mogoče pridobiti",
|
||||
"telemetry_noData": "Niso na voljo podatki o telemetriji.",
|
||||
"telemetry_channelTitle": "Kanal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2138,9 +2099,6 @@
|
||||
"translation_enableSubtitle": "Prevedite vstopne sporočila in omogočite predhodno prevajanje.",
|
||||
"translation_enableTitle": "Omogočite prevod",
|
||||
"translation_composerSubtitle": "Ureja privzeto stanje ikone za prevod, ki jo uporablja avtor.",
|
||||
"translation_autoIncomingTitle": "Samodejno prevajaj sporočila",
|
||||
"translation_autoIncomingSubtitle": "Samodejno prevaja sporočila za obvestila ter za klepete ali kanale.",
|
||||
"translation_translateMessage": "Prevedi sporočilo",
|
||||
"translation_targetLanguage": "Ciljna jezika",
|
||||
"translation_useAppLanguage": "Uporabite jezik aplikacije",
|
||||
"translation_downloadedModelLabel": "Naložen model",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_newMessages": "Nove novice",
|
||||
"settings_companionDebugLogSubtitle": "Navodila, odgovori in surova podatka za BLE/TCP/USB.",
|
||||
"repeater_chanUtil": "Uporaba kanala",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_undo": "Preobrn",
|
||||
"messageStatus_delivered": "Dostavljeno",
|
||||
"messageStatus_sent": "Pošljeno",
|
||||
"messageStatus_pending": "Pošiljanje",
|
||||
"messageStatus_failed": "Uspešno ni bilo mogo, da se sporočilo pošlje",
|
||||
"messageStatus_repeated": "Slišal sem večkrat",
|
||||
"contacts_moreOptions": "Več možnosti",
|
||||
"contacts_searchOpen": "Iskanje kontaktov",
|
||||
"contacts_searchClose": "Izklopi iskanje",
|
||||
"routing_title": "Navigacija",
|
||||
"routing_modeAuto": "Avto",
|
||||
"routing_modeFlood": "Poplavo",
|
||||
"routing_modeManual": "Navodilo",
|
||||
"routing_modeAutoHint": "Samodejno izbere najbolj poznano pot, in sicer, ko ni na voljo nobena.",
|
||||
"routing_modeFloodHint": "Prenosi preko vseh repetitorjev. Najzanesljivejši način, vendar zahteva več časa.",
|
||||
"routing_modeManualHint": "Vedno sledi natančni poti, ki jo ste določili.",
|
||||
"routing_currentRoute": "Trenutna pot",
|
||||
"routing_directNoHops": "Neposredno – brez prehodov",
|
||||
"routing_noPathYet": "Žep trenutno ni mogoče najti. Naslednje sporočilo bo posredovano, dokler ne bo ugotovljeno, kje je pot.",
|
||||
"routing_floodBroadcast": "Prenos preko vseh repetitiv",
|
||||
"routing_editPath": "Uredi pot",
|
||||
"routing_forgetPath": "Pozabi na pot",
|
||||
"routing_knownPaths": "Poznati poti",
|
||||
"routing_knownPathsHint": "Kliknite na pot, da jo izberete.",
|
||||
"routing_inUse": "V uporabi",
|
||||
"routing_qualityStrong": "Močan prvi korak",
|
||||
"routing_qualityGood": "Prva uspešna faza",
|
||||
"routing_qualityFair": "Prva, uspešna faza",
|
||||
"routing_qualityWorked": "Izpolnil",
|
||||
"routing_qualityFlood": "Slišano preko poplave",
|
||||
"routing_qualityUntested": "Ne preizkušen",
|
||||
"routing_lastWorked": "delal/a {when}",
|
||||
"routing_neverWorked": "nikoli ni bilo potrjeno",
|
||||
"routing_floodDelivery": "Dostava zaradi poplave",
|
||||
"pathEditor_title": "Izgradnja poti",
|
||||
"pathEditor_hopCounter": "{count} od 64 različnih sort hropa",
|
||||
"pathEditor_noHops": "Še niso dodani hmelji. Za dodajanje hmelja v vrstnem redu kliknite na povezavo spodaj, ali pa shranite brez dodanega hmelja, da ga lahko posredujete neposredno.",
|
||||
"pathEditor_addHops": "Dodajte suho travo v skladu s postopkom.",
|
||||
"pathEditor_searchRepeaters": "Iskanje ponovitev",
|
||||
"pathEditor_advancedHex": "Napredno: surovi šestnajstni pot",
|
||||
"pathEditor_hexLabel": "Predfiks za heksadecimalno šifro",
|
||||
"pathEditor_hexHelper": "Dva šestbitna znaka na vsak skok, ločena z vejico",
|
||||
"pathEditor_invalidTokens": "Neveljaven: {tokens}",
|
||||
"pathEditor_tooManyHops": "Največ 64 hopov",
|
||||
"pathEditor_usePath": "Uporabite to poto",
|
||||
"pathEditor_removeHop": "Odstranite hmelj",
|
||||
"pathEditor_unknownHop": "Neznani ponovitelj",
|
||||
"map_zoomIn": "Povečaj",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Povečajte pogled",
|
||||
"map_centerMap": "Krajšarska karta",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth zahteva brskalnik Chromium.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS potrdilo",
|
||||
"pathTrace_legendInferred": "Izpeljana lokacija"
|
||||
"dialog_connectCompanion": "Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.",
|
||||
"dialog_disconnectedTitle": "Prekinjeno",
|
||||
"dialog_disconnectedMessage": "Prekinjena povezava s vašim spre伴ovalcem.",
|
||||
"contact_connectCompanion": "Povežite se s ponсоbnikom za dostop do funkcij pon 반복nika in strežnika prostorov."
|
||||
}
|
||||
|
||||
+4
-135
@@ -33,8 +33,6 @@
|
||||
"common_remove": "Ta bort",
|
||||
"common_enable": "Aktivera",
|
||||
"common_disable": "Inaktivera",
|
||||
"common_autoRefresh": "Automatisk uppdatering",
|
||||
"common_interval": "Intervall",
|
||||
"common_reboot": "Start om",
|
||||
"common_loading": "Laddar...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1282,43 +1280,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Digital ingång",
|
||||
"telemetry_digitalOutputLabel": "Digital utgång",
|
||||
"telemetry_analogInputLabel": "Analog ingång",
|
||||
"telemetry_analogOutputLabel": "Analog utgång",
|
||||
"telemetry_genericLabel": "Allmän sensor",
|
||||
"telemetry_luminosityLabel": "Ljusstyrka",
|
||||
"telemetry_presenceLabel": "Närvaro",
|
||||
"telemetry_humidityLabel": "Luftfuktighet",
|
||||
"telemetry_accelerometerLabel": "Accelerometer",
|
||||
"telemetry_pressureLabel": "Tryck",
|
||||
"telemetry_altitudeLabel": "Höjd",
|
||||
"telemetry_frequencyLabel": "Frekvens",
|
||||
"telemetry_percentageLabel": "Procent",
|
||||
"telemetry_concentrationLabel": "Koncentration",
|
||||
"telemetry_powerLabel": "Effekt",
|
||||
"telemetry_distanceLabel": "Avstånd",
|
||||
"telemetry_energyLabel": "Energi",
|
||||
"telemetry_directionLabel": "Riktning",
|
||||
"telemetry_timeLabel": "Tid",
|
||||
"telemetry_gyrometerLabel": "Gyrometer",
|
||||
"telemetry_colourLabel": "Färg",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Brytare",
|
||||
"telemetry_polylineLabel": "Polylinje",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Antal förfrågningar",
|
||||
"telemetry_error": "Det gick inte att hämta data",
|
||||
"telemetry_noData": "Inga telemetridata tillgängliga.",
|
||||
"telemetry_channelTitle": "Kanal {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2139,9 +2100,6 @@
|
||||
"translation_title": "Översättning",
|
||||
"translation_composerTitle": "Översätt innan du skickar",
|
||||
"translation_composerSubtitle": "Styr standardtillståndet för kompositorns översättningsikon.",
|
||||
"translation_autoIncomingTitle": "Översätt meddelanden automatiskt",
|
||||
"translation_autoIncomingSubtitle": "Översätter meddelanden automatiskt för aviseringar och för chattar eller kanaler.",
|
||||
"translation_translateMessage": "Översätt meddelande",
|
||||
"translation_targetLanguage": "Målmedvetet språk",
|
||||
"translation_useAppLanguage": "Använd appens språk",
|
||||
"translation_downloadedModelLabel": "Nedladdad modell",
|
||||
@@ -2354,97 +2312,8 @@
|
||||
"chat_newMessages": "Nya meddelanden",
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-kommandon, svar och rådata",
|
||||
"repeater_chanUtil": "Användning av kanal",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "Sen",
|
||||
"messageStatus_delivered": "Levererad",
|
||||
"common_undo": "Ångra",
|
||||
"messageStatus_pending": "Skicka",
|
||||
"messageStatus_failed": "Misslyckades med att skicka",
|
||||
"messageStatus_repeated": "Hördes upprepade gånger",
|
||||
"contacts_moreOptions": "Fler alternativ",
|
||||
"contacts_searchOpen": "Sök efter kontakter",
|
||||
"contacts_searchClose": "Avancerad sökning",
|
||||
"routing_title": "Ruttplanering",
|
||||
"routing_modeAuto": "Bil",
|
||||
"routing_modeFlood": "Översvämning",
|
||||
"routing_modeManual": "Instruktioner",
|
||||
"routing_modeAutoHint": "Väljer automatiskt den bästa kända vägen, och använder en \"flooding\"-strategi om ingen väg är känd.",
|
||||
"routing_modeFloodHint": "Sändningar via alla repetrar. Det mest pålitliga alternativet, men kräver mer sändtid.",
|
||||
"routing_modeManualHint": "Skickar alltid den exakta väg du har angivit.",
|
||||
"routing_currentRoute": "Nuvarande rutt",
|
||||
"routing_directNoHops": "Direkt – utan mellanliggande routrar",
|
||||
"routing_noPathYet": "Ingen väg hittad ännu. Nästa meddelande skickas tills en rutt har upptäckts.",
|
||||
"routing_floodBroadcast": "Sändas via alla repetrar",
|
||||
"routing_editPath": "Redigera sökväg",
|
||||
"routing_forgetPath": "Glöm vägen",
|
||||
"routing_knownPaths": "Kända vägar",
|
||||
"routing_knownPathsHint": "Välj en väg för att byta till den.",
|
||||
"routing_inUse": "I användning",
|
||||
"routing_qualityStrong": "En stark start",
|
||||
"routing_qualityGood": "Bra första steg",
|
||||
"routing_qualityFair": "Bra första hopp",
|
||||
"routing_qualityWorked": "Har levererat",
|
||||
"routing_qualityFlood": "Fått information via nyhetsflöde",
|
||||
"routing_qualityUntested": "Ej testat",
|
||||
"routing_lastWorked": "arbetade {when}",
|
||||
"routing_neverWorked": "aldrig bekräftat",
|
||||
"routing_floodDelivery": "Leverans vid översvämningsområde",
|
||||
"pathEditor_title": "Skapa väg",
|
||||
"pathEditor_hopCounter": "{count} av 64 humlor",
|
||||
"pathEditor_noHops": "Inga humle än. Använd knapparna nedan för att lägga till dem i rätt ordning, eller spara utan humle för att skicka direkt.",
|
||||
"pathEditor_addHops": "Tillsätt humlen i rätt ordning.",
|
||||
"pathEditor_searchRepeaters": "Sök efter återupptagna samtal",
|
||||
"pathEditor_advancedHex": "Avancerat: rå hex-sökväg",
|
||||
"pathEditor_hexLabel": "Hex-prefikser",
|
||||
"pathEditor_hexHelper": "Två hex-tecken per steg, separerade med kommatecken.",
|
||||
"pathEditor_invalidTokens": "Ogiltigt: {tokens}",
|
||||
"pathEditor_tooManyHops": "Maximalt 64 humlörter",
|
||||
"pathEditor_usePath": "Använd denna väg",
|
||||
"pathEditor_removeHop": "Ta bort humlen",
|
||||
"pathEditor_unknownHop": "Okänd förstärkare",
|
||||
"map_zoomIn": "Zooma in",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"map_zoomOut": "Zooma ut",
|
||||
"map_centerMap": "Kartöversikt",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth kräver en Chromium-baserad webbläsare.",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS-verifierat",
|
||||
"pathTrace_legendInferred": "Antagen position"
|
||||
"dialog_connectCompanion": "Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.",
|
||||
"dialog_disconnectedTitle": "Ansluten ej",
|
||||
"dialog_disconnectedMessage": "Du har kopplats från din companion.",
|
||||
"contact_connectCompanion": "Anslut till en companion för att få tillgång till repeater- och rumsserverfunktioner."
|
||||
}
|
||||
|
||||
+4
-135
@@ -34,8 +34,6 @@
|
||||
"common_remove": "Прибрати",
|
||||
"common_enable": "Увімкнути",
|
||||
"common_disable": "Вимкнути",
|
||||
"common_autoRefresh": "Автооновлення",
|
||||
"common_interval": "Інтервал",
|
||||
"common_reboot": "Перезавантажити",
|
||||
"common_loading": "Завантаження...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1293,43 +1291,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "Цифровий вхід",
|
||||
"telemetry_digitalOutputLabel": "Цифровий вихід",
|
||||
"telemetry_analogInputLabel": "Аналоговий вхід",
|
||||
"telemetry_analogOutputLabel": "Аналоговий вихід",
|
||||
"telemetry_genericLabel": "Загальний датчик",
|
||||
"telemetry_luminosityLabel": "Освітленість",
|
||||
"telemetry_presenceLabel": "Присутність",
|
||||
"telemetry_humidityLabel": "Вологість",
|
||||
"telemetry_accelerometerLabel": "Акселерометр",
|
||||
"telemetry_pressureLabel": "Тиск",
|
||||
"telemetry_altitudeLabel": "Висота",
|
||||
"telemetry_frequencyLabel": "Частота",
|
||||
"telemetry_percentageLabel": "Відсоток",
|
||||
"telemetry_concentrationLabel": "Концентрація",
|
||||
"telemetry_powerLabel": "Потужність",
|
||||
"telemetry_distanceLabel": "Відстань",
|
||||
"telemetry_energyLabel": "Енергія",
|
||||
"telemetry_directionLabel": "Напрямок",
|
||||
"telemetry_timeLabel": "Час",
|
||||
"telemetry_gyrometerLabel": "Гірометр",
|
||||
"telemetry_colourLabel": "Колір",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "Перемикач",
|
||||
"telemetry_polylineLabel": "Полілінія",
|
||||
"telemetry_altitudeValue": "{meters} м",
|
||||
"telemetry_frequencyValue": "{hertz} Гц",
|
||||
"telemetry_pressureValue": "{hpa} гПа",
|
||||
"telemetry_luminosityValue": "{lux} лк",
|
||||
"telemetry_powerValue": "{watts} Вт",
|
||||
"telemetry_distanceValue": "{meters} м",
|
||||
"telemetry_energyValue": "{kilowattHours} кВт⋅год",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "Кількість запитів",
|
||||
"telemetry_error": "Не вдалося отримати дані",
|
||||
"telemetry_noData": "Дані телеметрії недоступні.",
|
||||
"telemetry_channelTitle": "Канал {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2148,9 +2109,6 @@
|
||||
"translation_enableTitle": "Увімкнути переклад",
|
||||
"translation_enableSubtitle": "Перекладати отримані повідомлення та дозволяти попередній переклад перед відправкою.",
|
||||
"translation_composerSubtitle": "Контролює стан ікон перекладу, який використовується за замовчуванням.",
|
||||
"translation_autoIncomingTitle": "Автоматично перекладати повідомлення",
|
||||
"translation_autoIncomingSubtitle": "Автоматично перекладає повідомлення для сповіщень, а також для чатів і каналів.",
|
||||
"translation_translateMessage": "Перекласти повідомлення",
|
||||
"translation_targetLanguage": "Цільова мова",
|
||||
"translation_useAppLanguage": "Використовувати мову застосунку",
|
||||
"translation_downloadedModelLabel": "Завантажений шаблон",
|
||||
@@ -2334,97 +2292,8 @@
|
||||
"chat_newMessages": "Нові повідомлення",
|
||||
"chat_markAsUnread": "Позначити як непрочитане",
|
||||
"repeater_chanUtil": "Використання каналу",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_delivered": "Доставлено",
|
||||
"messageStatus_sent": "Надіслано",
|
||||
"common_undo": "Скасувати",
|
||||
"messageStatus_pending": "Надсилання",
|
||||
"messageStatus_failed": "Не вдалося надіслати",
|
||||
"messageStatus_repeated": "Почув неодноразово",
|
||||
"contacts_moreOptions": "Більше можливостей",
|
||||
"contacts_searchOpen": "Пошук контактів",
|
||||
"contacts_searchClose": "Закрити пошук",
|
||||
"routing_title": "Маршрутизація",
|
||||
"routing_modeAuto": "Автомобіль",
|
||||
"routing_modeFlood": "Повені",
|
||||
"routing_modeManual": "Інструкція",
|
||||
"routing_modeAutoHint": "Автоматично обирає найкращий відомий шлях, та у разі відсутності відомого шляху, використовує алгоритм \"занурення\".",
|
||||
"routing_modeFloodHint": "Передавання через усі ретранслятори. Найбільш надійний спосіб, але потребує більше часу.",
|
||||
"routing_modeManualHint": "Завжди доставляє точно за вказаним вами маршрутом.",
|
||||
"routing_currentRoute": "Поточний маршрут",
|
||||
"routing_directNoHops": "Пряме з'єднання – без проміжних ретрансляторів",
|
||||
"routing_noPathYet": "Поки що немає жодного шляху. Повідомлення продовжуються надходити, поки не буде знайдено маршрут.",
|
||||
"routing_floodBroadcast": "Поширення через усі ретранслятори",
|
||||
"routing_editPath": "Редагувати шлях",
|
||||
"routing_forgetPath": "Забудь про шлях",
|
||||
"routing_knownPaths": "Відомі маршрути",
|
||||
"routing_knownPathsHint": "Виберіть опцію, щоб переключитися на неї.",
|
||||
"routing_inUse": "У робочому стані",
|
||||
"routing_qualityStrong": "Сильний перший стрибок",
|
||||
"routing_qualityGood": "Чудова перша спроба",
|
||||
"routing_qualityFair": "Перший, але вдалий, крок",
|
||||
"routing_qualityWorked": "Доставлено",
|
||||
"routing_qualityFlood": "Дізнався через новини",
|
||||
"routing_qualityUntested": "Не протестовано",
|
||||
"routing_neverWorked": "ніколи не підтверджено",
|
||||
"routing_floodDelivery": "Доставка під час повені",
|
||||
"pathEditor_title": "Створити маршрут",
|
||||
"pathEditor_hopCounter": "{count} з 64 штук хмелю",
|
||||
"pathEditor_noHops": "Ще не додано хміль. Натисніть на відповідні кнопки, щоб додати його в потрібному порядку, або збережіть рецепт без хмілю, щоб відправити його безпосередньо.",
|
||||
"pathEditor_addHops": "Додавайте хміль у наступній послідовності.",
|
||||
"pathEditor_searchRepeaters": "Пошук повторювачів",
|
||||
"pathEditor_advancedHex": "Просунутий рівень: пряма шлях у форматі шестнадцяткової системи.",
|
||||
"pathEditor_hexLabel": "Префікси для шестнадцяткової системи числення",
|
||||
"pathEditor_hexHelper": "Два шестизначні символи на кожний крок, розділені комами",
|
||||
"pathEditor_invalidTokens": "Неправильно: {tokens}",
|
||||
"routing_lastWorked": "worked {when}",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_tooManyHops": "Максимум 64 хмелеві колоди",
|
||||
"pathEditor_usePath": "Використовуйте цей шлях",
|
||||
"pathEditor_removeHop": "Видалити хміль",
|
||||
"pathEditor_unknownHop": "Невідомий ретранслятор",
|
||||
"map_zoomIn": "Увійти в режим збільшення",
|
||||
"map_zoomOut": "Видалити зум",
|
||||
"map_centerMap": "Карта центру",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth вимагає браузера на основі Chromium",
|
||||
"channels_communityShortId": "ID: {id}...",
|
||||
"pathTrace_legendGpsConfirmed": "GPS підтверджено",
|
||||
"pathTrace_legendInferred": "Висновок щодо положення"
|
||||
"dialog_connectCompanion": "Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.",
|
||||
"dialog_disconnectedTitle": "Від’єднано",
|
||||
"dialog_disconnectedMessage": "Вас від’єднано від вашого супутника.",
|
||||
"contact_connectCompanion": "Підключіться до супутника, щоб отримати доступ до функцій репітера та серверів кімнат."
|
||||
}
|
||||
|
||||
+4
-135
@@ -34,8 +34,6 @@
|
||||
"common_remove": "移除",
|
||||
"common_enable": "启用",
|
||||
"common_disable": "禁用",
|
||||
"common_autoRefresh": "自动刷新",
|
||||
"common_interval": "间隔",
|
||||
"common_reboot": "重启",
|
||||
"common_loading": "正在加载...",
|
||||
"common_notAvailable": "—",
|
||||
@@ -1312,43 +1310,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry_digitalInputLabel": "数字输入",
|
||||
"telemetry_digitalOutputLabel": "数字输出",
|
||||
"telemetry_analogInputLabel": "模拟输入",
|
||||
"telemetry_analogOutputLabel": "模拟输出",
|
||||
"telemetry_genericLabel": "通用传感器",
|
||||
"telemetry_luminosityLabel": "照度",
|
||||
"telemetry_presenceLabel": "存在检测",
|
||||
"telemetry_humidityLabel": "湿度",
|
||||
"telemetry_accelerometerLabel": "加速度计",
|
||||
"telemetry_pressureLabel": "气压",
|
||||
"telemetry_altitudeLabel": "高度",
|
||||
"telemetry_frequencyLabel": "频率",
|
||||
"telemetry_percentageLabel": "百分比",
|
||||
"telemetry_concentrationLabel": "浓度",
|
||||
"telemetry_powerLabel": "功率",
|
||||
"telemetry_distanceLabel": "距离",
|
||||
"telemetry_energyLabel": "能量",
|
||||
"telemetry_directionLabel": "方向",
|
||||
"telemetry_timeLabel": "时间",
|
||||
"telemetry_gyrometerLabel": "陀螺仪",
|
||||
"telemetry_colourLabel": "颜色",
|
||||
"telemetry_gpsLabel": "GPS",
|
||||
"telemetry_switchLabel": "开关",
|
||||
"telemetry_polylineLabel": "折线",
|
||||
"telemetry_altitudeValue": "{meters} m",
|
||||
"telemetry_frequencyValue": "{hertz} Hz",
|
||||
"telemetry_pressureValue": "{hpa} hPa",
|
||||
"telemetry_luminosityValue": "{lux} lx",
|
||||
"telemetry_powerValue": "{watts} W",
|
||||
"telemetry_distanceValue": "{meters} m",
|
||||
"telemetry_energyValue": "{kilowattHours} kWh",
|
||||
"telemetry_directionValue": "{degrees}°",
|
||||
"telemetry_concentrationValue": "{ppm} ppm",
|
||||
"telemetry_percentageValue": "{percent}%",
|
||||
"telemetry_analogValue": "{value}",
|
||||
"telemetry_autoFetchQuantity": "请求次数",
|
||||
"telemetry_error": "无法获取数据",
|
||||
"telemetry_noData": "暂无遥测数据",
|
||||
"telemetry_channelTitle": "频道 {channel}",
|
||||
"@telemetry_channelTitle": {
|
||||
@@ -2144,9 +2105,6 @@
|
||||
"translation_composerTitle": "在发送之前进行翻译",
|
||||
"translation_enableTitle": "启用翻译功能",
|
||||
"translation_composerSubtitle": "控制作曲家翻译图标的默认状态。",
|
||||
"translation_autoIncomingTitle": "自动翻译消息",
|
||||
"translation_autoIncomingSubtitle": "自动为通知以及聊天或频道翻译消息。",
|
||||
"translation_translateMessage": "翻译消息",
|
||||
"translation_targetLanguage": "目标语言",
|
||||
"translation_useAppLanguage": "使用应用程序语言",
|
||||
"translation_downloadedModelLabel": "下载的模型",
|
||||
@@ -2359,97 +2317,8 @@
|
||||
"chat_newMessages": "新的消息",
|
||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
|
||||
"repeater_chanUtil": "频道利用率",
|
||||
"@routing_lastWorked": {
|
||||
"placeholders": {
|
||||
"when": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@routing_deliveryCounts": {
|
||||
"placeholders": {
|
||||
"successes": {
|
||||
"type": "int"
|
||||
},
|
||||
"failures": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_hopCounter": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pathEditor_invalidTokens": {
|
||||
"placeholders": {
|
||||
"tokens": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@channels_communityShortId": {
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"messageStatus_sent": "发送",
|
||||
"common_undo": "撤销",
|
||||
"messageStatus_delivered": "已送达",
|
||||
"messageStatus_pending": "发送",
|
||||
"messageStatus_failed": "发送失败",
|
||||
"messageStatus_repeated": "多次听到",
|
||||
"contacts_moreOptions": "更多选择",
|
||||
"contacts_searchOpen": "搜索联系人",
|
||||
"contacts_searchClose": "高级搜索",
|
||||
"routing_title": "路由",
|
||||
"routing_modeAuto": "汽车",
|
||||
"routing_modeFlood": "洪水",
|
||||
"routing_modeManual": "手册",
|
||||
"routing_modeAutoHint": "自动选择已知最佳路径,当没有已知路径时,则进行“洪水”搜索。",
|
||||
"routing_modeFloodHint": "通过所有中继站进行广播。 这种方式最可靠,但占用更多的时间。",
|
||||
"routing_modeManualHint": "总是按照您设置的路径进行导航。",
|
||||
"routing_currentRoute": "当前路线",
|
||||
"routing_directNoHops": "直接连接— 无中继跳",
|
||||
"routing_noPathYet": "目前还没有找到路径。直到找到路径,才会收到后续消息。",
|
||||
"routing_floodBroadcast": "通过所有中继器进行广播",
|
||||
"routing_editPath": "编辑路径",
|
||||
"routing_forgetPath": "忘记原路",
|
||||
"routing_knownPaths": "已知的路径",
|
||||
"routing_knownPathsHint": "点击该路径以切换到它。",
|
||||
"routing_inUse": "使用中",
|
||||
"routing_qualityStrong": "强劲的初始阶段",
|
||||
"routing_qualityGood": "不错的开端",
|
||||
"routing_qualityFair": "第一次尝试,结果良好",
|
||||
"routing_qualityWorked": "已完成",
|
||||
"routing_qualityFlood": "通过新闻报道",
|
||||
"routing_qualityUntested": "未经测试",
|
||||
"routing_lastWorked": "工作于 {when}",
|
||||
"routing_neverWorked": "从未得到证实",
|
||||
"routing_floodDelivery": "洪水配送",
|
||||
"pathEditor_title": "构建路径",
|
||||
"pathEditor_noHops": "目前还没有添加任何啤酒花。点击下面的“添加”按钮,按顺序添加,或者直接保存,不添加任何啤酒花。",
|
||||
"pathEditor_addHops": "按照顺序添加啤酒花",
|
||||
"pathEditor_searchRepeaters": "重复搜索",
|
||||
"pathEditor_advancedHex": "高级:原始十六进制路径",
|
||||
"pathEditor_hexLabel": "十六进制前缀",
|
||||
"pathEditor_hexHelper": "每次跳跃,使用两个十六进制字符,用逗号分隔。",
|
||||
"pathEditor_invalidTokens": "无效:{tokens}",
|
||||
"pathEditor_tooManyHops": "最多 64 个跳跃",
|
||||
"pathEditor_usePath": "请使用此路径",
|
||||
"pathEditor_removeHop": "去除啤酒花",
|
||||
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
|
||||
"pathEditor_hopCounter": "{count} of 64 hops",
|
||||
"pathEditor_unknownHop": "未知的重复器",
|
||||
"map_zoomIn": "放大",
|
||||
"map_zoomOut": "放大",
|
||||
"map_centerMap": "中心地图",
|
||||
"chrome_bluetoothRequiresChromium": "Web Bluetooth 需要 Chromium 浏览器",
|
||||
"channels_communityShortId": "ID:{id}...",
|
||||
"pathTrace_legendGpsConfirmed": "通过GPS确认",
|
||||
"pathTrace_legendInferred": "推测的位置"
|
||||
"dialog_connectCompanion": "连接伴机以访问中继器和房间服务器功能。",
|
||||
"dialog_disconnectedTitle": "已断开连接",
|
||||
"dialog_disconnectedMessage": "你已与你的伙伴断开连接。",
|
||||
"contact_connectCompanion": "连接至伴侣设备以访问中继器和房间服务器功能。"
|
||||
}
|
||||
|
||||
+22
-44
@@ -1,15 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'screens/chrome_required_screen.dart';
|
||||
import 'screens/contacts_screen.dart';
|
||||
import 'utils/platform_info.dart';
|
||||
|
||||
import 'connector/meshcore_connector.dart';
|
||||
import 'screens/scanner_screen.dart';
|
||||
import 'services/storage_service.dart';
|
||||
import 'services/message_retry_service.dart';
|
||||
import 'services/path_history_service.dart';
|
||||
@@ -24,21 +23,11 @@ import 'services/translation_service.dart';
|
||||
import 'services/ui_view_state_service.dart';
|
||||
import 'services/timeout_prediction_service.dart';
|
||||
import 'storage/prefs_manager.dart';
|
||||
import 'theme/mesh_theme.dart';
|
||||
import 'utils/app_logger.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// On desktop, debugPrint is not suppressed in release builds and every
|
||||
// call is a synchronous stdout write. The connector logs heavily on hot
|
||||
// paths (frame handling, queue/channel sync), which shows up as syscall
|
||||
// overhead on low-end Linux machines (issue #202). The in-app debug log
|
||||
// screens are unaffected — they store entries themselves.
|
||||
if (kReleaseMode) {
|
||||
debugPrint = (String? message, {int? wrapWidth}) {};
|
||||
}
|
||||
|
||||
// Initialize SharedPreferences cache
|
||||
await PrefsManager.initialize();
|
||||
|
||||
@@ -92,13 +81,8 @@ void main() async {
|
||||
timeoutPredictionService: timeoutPredictionService,
|
||||
);
|
||||
|
||||
await connector.loadContactCache();
|
||||
await connector.loadChannelSettings();
|
||||
await connector.loadCachedChannels();
|
||||
|
||||
// Load persisted channel messages
|
||||
await connector.loadAllChannelMessages();
|
||||
await connector.loadUnreadState();
|
||||
await connector.restoreLastCompanionScope();
|
||||
await connector.loadAllCachedDataForCurrentCompanion();
|
||||
|
||||
runApp(
|
||||
MeshCoreApp(
|
||||
@@ -201,8 +185,23 @@ class MeshCoreApp extends StatelessWidget {
|
||||
locale: _localeFromSetting(
|
||||
settingsService.settings.languageOverride,
|
||||
),
|
||||
theme: MeshTheme.light(),
|
||||
darkTheme: MeshTheme.dark(),
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||
useMaterial3: true,
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
),
|
||||
themeMode: _themeModeFromSetting(
|
||||
settingsService.settings.themeMode,
|
||||
),
|
||||
@@ -210,14 +209,11 @@ class MeshCoreApp extends StatelessWidget {
|
||||
// Update notification service with resolved locale
|
||||
final locale = Localizations.localeOf(context);
|
||||
NotificationService().setLocale(locale);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: _systemUiOverlayStyle(context),
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
);
|
||||
return child ?? const SizedBox.shrink();
|
||||
},
|
||||
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
|
||||
? const ChromeRequiredScreen()
|
||||
: const ScannerScreen(),
|
||||
: const ContactsScreen(),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -235,24 +231,6 @@ class MeshCoreApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
SystemUiOverlayStyle _systemUiOverlayStyle(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final iconBrightness = isDark ? Brightness.light : Brightness.dark;
|
||||
|
||||
// Keep Android system bars aligned with the resolved Flutter theme.
|
||||
return SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: iconBrightness,
|
||||
statusBarBrightness: isDark ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarColor: colorScheme.surface,
|
||||
systemNavigationBarIconBrightness: iconBrightness,
|
||||
systemNavigationBarDividerColor: colorScheme.surface,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
);
|
||||
}
|
||||
|
||||
Locale? _localeFromSetting(String? languageCode) {
|
||||
if (languageCode == null) return null;
|
||||
return Locale(languageCode);
|
||||
|
||||
@@ -113,7 +113,6 @@ class AppSettings {
|
||||
final int tcpServerPort;
|
||||
final bool jumpToOldestUnread;
|
||||
final bool translationEnabled;
|
||||
final bool autoTranslateIncomingMessages;
|
||||
final String? translationTargetLanguageCode;
|
||||
final bool composerTranslationEnabled;
|
||||
final String? translationModelSourceUrl;
|
||||
@@ -141,7 +140,7 @@ class AppSettings {
|
||||
this.mapKeyPrefix = '',
|
||||
this.mapShowMarkers = true,
|
||||
this.mapShowGuessedLocations = true,
|
||||
this.enableMessageTracing = true,
|
||||
this.enableMessageTracing = false,
|
||||
this.mapCacheBounds,
|
||||
this.mapCacheMinZoom = 10,
|
||||
this.mapCacheMaxZoom = 15,
|
||||
@@ -149,7 +148,7 @@ class AppSettings {
|
||||
this.notifyOnNewMessage = true,
|
||||
this.notifyOnNewChannelMessage = true,
|
||||
this.notifyOnNewAdvert = true,
|
||||
this.autoRouteRotationEnabled = true,
|
||||
this.autoRouteRotationEnabled = false,
|
||||
this.maxRouteWeight = 5.0,
|
||||
this.initialRouteWeight = 3.0,
|
||||
this.routeWeightSuccessIncrement = 0.5,
|
||||
@@ -167,7 +166,6 @@ class AppSettings {
|
||||
this.tcpServerPort = 0,
|
||||
this.jumpToOldestUnread = false,
|
||||
this.translationEnabled = false,
|
||||
this.autoTranslateIncomingMessages = true,
|
||||
this.translationTargetLanguageCode,
|
||||
this.composerTranslationEnabled = false,
|
||||
this.translationModelSourceUrl,
|
||||
@@ -228,7 +226,6 @@ class AppSettings {
|
||||
'tcp_server_port': tcpServerPort,
|
||||
'jump_to_oldest_unread': jumpToOldestUnread,
|
||||
'translation_enabled': translationEnabled,
|
||||
'auto_translate_incoming_messages': autoTranslateIncomingMessages,
|
||||
'translation_target_language_code': translationTargetLanguageCode,
|
||||
'composer_translation_enabled': composerTranslationEnabled,
|
||||
'translation_model_source_url': translationModelSourceUrl,
|
||||
@@ -264,7 +261,7 @@ class AppSettings {
|
||||
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
|
||||
mapShowGuessedLocations:
|
||||
json['map_show_guessed_locations'] as bool? ?? true,
|
||||
enableMessageTracing: json['enable_message_tracing'] as bool? ?? true,
|
||||
enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
|
||||
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
|
||||
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
|
||||
),
|
||||
@@ -276,7 +273,7 @@ class AppSettings {
|
||||
json['notify_on_new_channel_message'] as bool? ?? true,
|
||||
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
|
||||
autoRouteRotationEnabled:
|
||||
json['auto_route_rotation_enabled'] as bool? ?? true,
|
||||
json['auto_route_rotation_enabled'] as bool? ?? false,
|
||||
maxRouteWeight: (json['max_route_weight'] as num?)?.toDouble() ?? 5.0,
|
||||
initialRouteWeight:
|
||||
(json['initial_route_weight'] as num?)?.toDouble() ?? 3.0,
|
||||
@@ -310,8 +307,6 @@ class AppSettings {
|
||||
tcpServerPort: json['tcp_server_port'] as int? ?? 0,
|
||||
jumpToOldestUnread: json['jump_to_oldest_unread'] as bool? ?? false,
|
||||
translationEnabled: json['translation_enabled'] as bool? ?? false,
|
||||
autoTranslateIncomingMessages:
|
||||
json['auto_translate_incoming_messages'] as bool? ?? true,
|
||||
translationTargetLanguageCode:
|
||||
json['translation_target_language_code'] as String?,
|
||||
composerTranslationEnabled:
|
||||
@@ -401,7 +396,6 @@ class AppSettings {
|
||||
int? tcpServerPort,
|
||||
bool? jumpToOldestUnread,
|
||||
bool? translationEnabled,
|
||||
bool? autoTranslateIncomingMessages,
|
||||
Object? translationTargetLanguageCode = _unset,
|
||||
bool? composerTranslationEnabled,
|
||||
Object? translationModelSourceUrl = _unset,
|
||||
@@ -459,8 +453,6 @@ class AppSettings {
|
||||
tcpServerPort: tcpServerPort ?? this.tcpServerPort,
|
||||
jumpToOldestUnread: jumpToOldestUnread ?? this.jumpToOldestUnread,
|
||||
translationEnabled: translationEnabled ?? this.translationEnabled,
|
||||
autoTranslateIncomingMessages:
|
||||
autoTranslateIncomingMessages ?? this.autoTranslateIncomingMessages,
|
||||
translationTargetLanguageCode: translationTargetLanguageCode == _unset
|
||||
? this.translationTargetLanguageCode
|
||||
: translationTargetLanguageCode as String?,
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import 'path_history.dart';
|
||||
|
||||
/// One observed route rendered on the path map — the live traced path
|
||||
/// (primary) or an alternate from the contact's path history — resolved to
|
||||
/// map coordinates with per-hop confidence flags.
|
||||
class DisplayPath {
|
||||
final String id;
|
||||
final String label;
|
||||
final Color color;
|
||||
final bool isPrimary;
|
||||
|
||||
/// Outbound hop bytes, including hops that could not be placed on the map.
|
||||
final List<int> hopBytes;
|
||||
|
||||
/// Resolved map points: self, each locatable hop, then the target when its
|
||||
/// position is known. Hops with no position are skipped here but still
|
||||
/// counted in [unresolvedHops].
|
||||
final List<LatLng> points;
|
||||
|
||||
/// Display name for each entry of [points].
|
||||
final List<String> pointLabels;
|
||||
|
||||
/// Whether each entry of [points] is a GPS-grade position (vs inferred).
|
||||
final List<bool> pointConfirmed;
|
||||
|
||||
/// Per segment (length points-1): true when either endpoint is inferred or
|
||||
/// unlocatable hops were skipped in between — rendered dashed.
|
||||
final List<bool> segmentEstimated;
|
||||
|
||||
/// Per segment: the transmission ordinal of the segment's destination,
|
||||
/// used to highlight the matching hop-list row during animation.
|
||||
final List<int> rowForSegment;
|
||||
|
||||
/// Total transmissions on the full route (including unlocatable hops).
|
||||
final int totalTransmissions;
|
||||
|
||||
/// True when the route ends with a chat-target endpoint row.
|
||||
final bool hasTargetEndpoint;
|
||||
|
||||
final int gpsConfirmedHops;
|
||||
final int unresolvedHops;
|
||||
final double distanceMeters;
|
||||
|
||||
/// History metadata; null for the live traced (primary) path.
|
||||
final PathRecord? record;
|
||||
|
||||
const DisplayPath({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.color,
|
||||
required this.isPrimary,
|
||||
required this.hopBytes,
|
||||
required this.points,
|
||||
required this.pointLabels,
|
||||
required this.pointConfirmed,
|
||||
required this.segmentEstimated,
|
||||
required this.rowForSegment,
|
||||
required this.totalTransmissions,
|
||||
required this.hasTargetEndpoint,
|
||||
required this.gpsConfirmedHops,
|
||||
required this.unresolvedHops,
|
||||
required this.distanceMeters,
|
||||
this.record,
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ class PathRecord {
|
||||
final int tripTimeMs;
|
||||
final DateTime? timestamp;
|
||||
final bool wasFloodDiscovery;
|
||||
final int byteCount;
|
||||
final List<int> pathBytes;
|
||||
final int successCount;
|
||||
final int failureCount;
|
||||
@@ -17,6 +18,7 @@ class PathRecord {
|
||||
required this.successCount,
|
||||
required this.failureCount,
|
||||
this.routeWeight = 1.0,
|
||||
this.byteCount = 0,
|
||||
});
|
||||
|
||||
String get displayText =>
|
||||
@@ -48,6 +50,7 @@ class PathRecord {
|
||||
successCount: json['success_count'] as int? ?? 0,
|
||||
failureCount: json['failure_count'] as int? ?? 0,
|
||||
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
|
||||
byteCount: json['byte_count'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Timeline state for the packet-flow animation on the path map.
|
||||
///
|
||||
/// The packet travels each segment over [segmentMs] (scaled by [speed]),
|
||||
/// then dwells at the reached hop for [dwellMs] so the hop visibly lights up.
|
||||
/// Overlay layers listen to this controller directly; [activeSegment] only
|
||||
/// fires when the segment index changes so list highlights rebuild cheaply.
|
||||
class PathPlaybackController extends ChangeNotifier {
|
||||
static const double segmentMs = 1100;
|
||||
static const double dwellMs = 380;
|
||||
static const List<double> speedSteps = [0.5, 1.0, 2.0];
|
||||
|
||||
late final Ticker _ticker;
|
||||
List<LatLng> _points = const [];
|
||||
double _timelineMs = 0;
|
||||
Duration _lastTick = Duration.zero;
|
||||
bool _playing = false;
|
||||
bool _started = false;
|
||||
double _speed = 1.0;
|
||||
|
||||
/// Segment currently being traveled (clamped to the last segment), or -1
|
||||
/// while the animation has not been started — listeners use this for
|
||||
/// hop-list highlighting without rebuilding every tick.
|
||||
final ValueNotifier<int> activeSegment = ValueNotifier(-1);
|
||||
|
||||
PathPlaybackController(TickerProvider vsync) {
|
||||
_ticker = vsync.createTicker(_onTick);
|
||||
}
|
||||
|
||||
List<LatLng> get points => _points;
|
||||
bool get hasPath => _points.length >= 2;
|
||||
int get segmentCount => hasPath ? _points.length - 1 : 0;
|
||||
bool get playing => _playing;
|
||||
double get speed => _speed;
|
||||
|
||||
/// True once the user has started or stepped the animation; the packet
|
||||
/// overlay renders only in this state.
|
||||
bool get started => _started;
|
||||
|
||||
double get _slotMs => segmentMs + dwellMs;
|
||||
double get _totalMs => segmentCount * _slotMs;
|
||||
bool get isComplete => hasPath && _timelineMs >= _totalMs;
|
||||
|
||||
int get currentSegment {
|
||||
if (!hasPath) return 0;
|
||||
return (_timelineMs / _slotMs).floor().clamp(0, segmentCount - 1);
|
||||
}
|
||||
|
||||
/// Travel progress through [currentSegment]; 1.0 while dwelling at its end.
|
||||
double get segmentProgress {
|
||||
if (!hasPath) return 0;
|
||||
final within = _timelineMs - currentSegment * _slotMs;
|
||||
return (within / segmentMs).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Dwell progress (0..1) at the reached hop, or null while traveling.
|
||||
double? get dwellProgress {
|
||||
if (!hasPath || isComplete) return null;
|
||||
final within = _timelineMs - currentSegment * _slotMs;
|
||||
if (within < segmentMs) return null;
|
||||
return ((within - segmentMs) / dwellMs).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Index of the point the packet has most recently reached.
|
||||
int get reachedPointIndex {
|
||||
if (!hasPath) return 0;
|
||||
if (isComplete) return _points.length - 1;
|
||||
return segmentProgress >= 1.0 ? currentSegment + 1 : currentSegment;
|
||||
}
|
||||
|
||||
LatLng get position {
|
||||
if (!hasPath) return const LatLng(0, 0);
|
||||
final seg = currentSegment;
|
||||
final a = _points[seg];
|
||||
final b = _points[seg + 1];
|
||||
final t = segmentProgress;
|
||||
return LatLng(
|
||||
a.latitude + (b.latitude - a.latitude) * t,
|
||||
a.longitude + (b.longitude - a.longitude) * t,
|
||||
);
|
||||
}
|
||||
|
||||
/// Replaces the path and resets the animation to the start.
|
||||
void setPath(List<LatLng> points) {
|
||||
_ticker.stop();
|
||||
_points = List.unmodifiable(points);
|
||||
_timelineMs = 0;
|
||||
_playing = false;
|
||||
_started = false;
|
||||
activeSegment.value = -1;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void play() {
|
||||
if (!hasPath) return;
|
||||
if (isComplete) _timelineMs = 0;
|
||||
_started = true;
|
||||
_playing = true;
|
||||
activeSegment.value = currentSegment;
|
||||
if (!_ticker.isActive) {
|
||||
_lastTick = Duration.zero;
|
||||
_ticker.start();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void pause() {
|
||||
_ticker.stop();
|
||||
_playing = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void togglePlay() => _playing ? pause() : play();
|
||||
|
||||
void replay() {
|
||||
if (!hasPath) return;
|
||||
_timelineMs = 0;
|
||||
activeSegment.value = 0;
|
||||
play();
|
||||
}
|
||||
|
||||
/// Stops playback and hides the packet overlay.
|
||||
void stop() {
|
||||
_ticker.stop();
|
||||
_playing = false;
|
||||
_started = false;
|
||||
_timelineMs = 0;
|
||||
activeSegment.value = -1;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void stepForward() => _jumpToPoint(reachedPointIndex + 1);
|
||||
|
||||
void stepBack() => _jumpToPoint(reachedPointIndex - 1);
|
||||
|
||||
void cycleSpeed() {
|
||||
final index = speedSteps.indexOf(_speed);
|
||||
_speed = speedSteps[(index + 1) % speedSteps.length];
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _jumpToPoint(int index) {
|
||||
if (!hasPath) return;
|
||||
_ticker.stop();
|
||||
_playing = false;
|
||||
_started = true;
|
||||
final clamped = index.clamp(0, _points.length - 1);
|
||||
// Land at the start of the dwell window so the hop pulse plays.
|
||||
_timelineMs = clamped == 0 ? 0 : (clamped - 1) * _slotMs + segmentMs;
|
||||
activeSegment.value = currentSegment;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _onTick(Duration elapsed) {
|
||||
final dtMs = (elapsed - _lastTick).inMicroseconds / 1000.0;
|
||||
_lastTick = elapsed;
|
||||
_timelineMs = (_timelineMs + dtMs * _speed).clamp(0.0, _totalMs);
|
||||
if (_timelineMs >= _totalMs) {
|
||||
_ticker.stop();
|
||||
_playing = false;
|
||||
}
|
||||
if (activeSegment.value != currentSegment) {
|
||||
activeSegment.value = currentSegment;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker.dispose();
|
||||
activeSegment.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -181,276 +181,6 @@ class RadioSettings {
|
||||
txPowerDbm: 14,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Artyom (VVO)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 864.281,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Biysk (BSK)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 869.000,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_5,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Chelyabinsk (CEK)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Cherepovets (CEE)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.570,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Irkutsk (IKT)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Ivanovo (IWA)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Izhevsk (IJK)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.732,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Kaluga (KLF)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Kazan (KZN)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Khabarovsk (KHV)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 864.281,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Kirov (KVX)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Lipetsk (LPK)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.950,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf9,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Moscow (MOW)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Nizhny Novgorod (GOJ)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Novosibirsk (OVB)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 869.000,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf9,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Rostov-on-Don (ROV)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf9,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Ryazan (RZN)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.880,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf9,
|
||||
codingRate: LoRaCodingRate.cr4_5,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Samara (KUF)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 864.281,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Saratov (GSV)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 864.281,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia St. Petersburg (LED)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.856,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Tambov (TBW)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.950,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf10,
|
||||
codingRate: LoRaCodingRate.cr4_5,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Tula (TYA)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Tver (KLD)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 869.169,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Ufa (UFA)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.732,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_8,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Volgograd (VOG)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 869.525,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Voronezh (VOZ)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 868.731,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf8,
|
||||
codingRate: LoRaCodingRate.cr4_6,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Russia Yekaterinburg (SVX)',
|
||||
RadioSettings(
|
||||
frequencyMHz: 869.046,
|
||||
bandwidth: LoRaBandwidth.bw62_5,
|
||||
spreadingFactor: LoRaSpreadingFactor.sf7,
|
||||
codingRate: LoRaCodingRate.cr4_7,
|
||||
txPowerDbm: 20,
|
||||
),
|
||||
),
|
||||
(
|
||||
'Switzerland',
|
||||
RadioSettings(
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
import '../services/app_debug_log_service.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
@@ -59,57 +58,25 @@ class AppDebugLogScreen extends StatelessWidget {
|
||||
child: hasEntries
|
||||
? ListView.separated(
|
||||
itemCount: entries.length,
|
||||
separatorBuilder: (_, _) =>
|
||||
const Divider(height: 1, color: MeshPalette.line),
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final entry = entries[index];
|
||||
return Container(
|
||||
color: MeshPalette.bg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: _buildLevelIcon(entry.level),
|
||||
title: Text(
|
||||
'[${entry.tag}] ${entry.message}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildLevelIcon(context, entry.level),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '[${entry.tag}] ',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: _levelColor(entry.level),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: entry.message,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: MeshPalette.ink2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
entry.formattedTime,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 9.5,
|
||||
color: MeshPalette.ink4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
subtitle: Text(
|
||||
entry.formattedTime,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -118,25 +85,25 @@ class AppDebugLogScreen extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.bug_report_outlined,
|
||||
size: 64,
|
||||
color: MeshPalette.ink3,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.debugLog_noEntries,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: MeshPalette.ink3,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.debugLog_enableInSettings,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MeshPalette.ink3,
|
||||
color: Colors.grey[500],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -148,37 +115,18 @@ class AppDebugLogScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Color _levelColor(AppDebugLogLevel level) {
|
||||
Widget _buildLevelIcon(AppDebugLogLevel level) {
|
||||
switch (level) {
|
||||
case AppDebugLogLevel.info:
|
||||
return MeshPalette.blue;
|
||||
case AppDebugLogLevel.warning:
|
||||
return MeshPalette.warn;
|
||||
case AppDebugLogLevel.error:
|
||||
return MeshPalette.alert;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildLevelIcon(BuildContext context, AppDebugLogLevel level) {
|
||||
switch (level) {
|
||||
case AppDebugLogLevel.info:
|
||||
return const Icon(
|
||||
Icons.info_outline,
|
||||
size: 18,
|
||||
color: MeshPalette.blue,
|
||||
);
|
||||
return const Icon(Icons.info_outline, size: 18, color: Colors.blue);
|
||||
case AppDebugLogLevel.warning:
|
||||
return const Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
size: 18,
|
||||
color: MeshPalette.warn,
|
||||
color: Colors.orange,
|
||||
);
|
||||
case AppDebugLogLevel.error:
|
||||
return const Icon(
|
||||
Icons.error_outline,
|
||||
size: 18,
|
||||
color: MeshPalette.alert,
|
||||
);
|
||||
return const Icon(Icons.error_outline, size: 18, color: Colors.red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1001
-1417
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import 'package:flutter/services.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../services/ble_debug_log_service.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
@@ -33,7 +32,6 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AdaptiveAppBarTitle(context.l10n.debugLog_bleTitle),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: context.l10n.debugLog_copyLog,
|
||||
@@ -103,14 +101,23 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
itemCount: showingFrames
|
||||
? entries.length
|
||||
: rawEntries.length,
|
||||
separatorBuilder: (_, _) =>
|
||||
const Divider(height: 1, color: MeshPalette.line),
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (showingFrames) {
|
||||
final entry = entries[index];
|
||||
final time =
|
||||
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
|
||||
return GestureDetector(
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(entry.description),
|
||||
subtitle: Text('${entry.hexPreview}\n$time'),
|
||||
isThreeLine: true,
|
||||
leading: Icon(
|
||||
entry.outgoing
|
||||
? Icons.upload
|
||||
: Icons.download,
|
||||
size: 18,
|
||||
),
|
||||
onLongPress: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
@@ -124,60 +131,6 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
color: MeshPalette.bg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
entry.outgoing
|
||||
? Icons.upload
|
||||
: Icons.download,
|
||||
size: 18,
|
||||
color: entry.outgoing
|
||||
? MeshPalette.blue
|
||||
: MeshPalette.signal,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entry.description,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: MeshPalette.ink,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
entry.hexPreview,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 10,
|
||||
color: MeshPalette.ink3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
time,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 9.5,
|
||||
color: MeshPalette.ink4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,65 +138,18 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
||||
final info = _decodeRawPacket(entry.payload);
|
||||
final time =
|
||||
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
|
||||
return GestureDetector(
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(info.title),
|
||||
subtitle: Text('${info.summary}\n$time'),
|
||||
isThreeLine: true,
|
||||
leading: const Icon(Icons.download, size: 18),
|
||||
onTap: () => _showRawDialog(context, info),
|
||||
child: Container(
|
||||
color: MeshPalette.bg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.download,
|
||||
size: 18,
|
||||
color: MeshPalette.signal,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
info.title,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: MeshPalette.ink,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
info.summary,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 10,
|
||||
color: MeshPalette.ink3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
time,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 9.5,
|
||||
color: MeshPalette.ink4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
context.l10n.debugLog_noBleActivity,
|
||||
style: const TextStyle(color: MeshPalette.ink3),
|
||||
),
|
||||
child: Text(context.l10n.debugLog_noBleActivity),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+513
-669
File diff suppressed because it is too large
Load Diff
+1034
-508
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
|
||||
class ChromeRequiredScreen extends StatelessWidget {
|
||||
const ChromeRequiredScreen({super.key});
|
||||
@@ -9,95 +7,81 @@ class ChromeRequiredScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Icon in tinted circle
|
||||
Container(
|
||||
width: 88,
|
||||
height: 88,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.tertiary.withValues(alpha: 0.10),
|
||||
border: Border.all(
|
||||
color: scheme.tertiary.withValues(alpha: 0.25),
|
||||
width: 1.5,
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isDark
|
||||
? [const Color(0xFF1A1A1A), const Color(0xFF0D0D0D)]
|
||||
: [const Color(0xFFF5F7FA), const Color(0xFFE4E7EB)],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.browser_not_supported_rounded,
|
||||
size: 80,
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.scanner_chromeRequired,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.scanner_chromeRequiredMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: isDark ? Colors.white70 : Colors.black54,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
// We can't really "fix" it for them other than telling them to use Chrome
|
||||
// but we can provide a nice visual.
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: Colors.blue.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.info_outline, size: 20, color: Colors.blue),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"Web Bluetooth requires a Chromium browser",
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.browser_not_supported_rounded,
|
||||
size: 42,
|
||||
color: scheme.tertiary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// Title
|
||||
Text(
|
||||
l10n.scanner_chromeRequired,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSurface,
|
||||
letterSpacing: -0.3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Body text
|
||||
Text(
|
||||
l10n.scanner_chromeRequiredMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: scheme.onSurfaceVariant,
|
||||
height: 1.55,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Info chip
|
||||
MeshCard(
|
||||
margin: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
color: scheme.secondaryContainer.withValues(alpha: 0.35),
|
||||
borderColor: scheme.outline.withValues(alpha: 0.3),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 18,
|
||||
color: scheme.secondary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
l10n.chrome_bluetoothRequiresChromium,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
color: scheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/community.dart';
|
||||
import '../storage/community_store.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../widgets/qr_scanner_widget.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
/// Screen for scanning community QR codes to join communities.
|
||||
///
|
||||
@@ -39,87 +35,16 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
centerTitle: true,
|
||||
),
|
||||
body: _isProcessing
|
||||
? Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: QrScannerWidget(
|
||||
onScanned: (data) => _handleScannedData(context, data),
|
||||
validator: Community.isValidQrData,
|
||||
onValidationFailed: (_) => _showInvalidQrError(context),
|
||||
instructions: context.l10n.community_scanInstructions,
|
||||
overlay: _buildThemedOverlay(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildThemedOverlay(BuildContext context) {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// Dark semi-transparent background with cutout
|
||||
ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.black.withValues(alpha: 0.5),
|
||||
BlendMode.srcOut,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
backgroundBlendMode: BlendMode.dstOut,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
height: 250,
|
||||
width: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Corner brackets on top
|
||||
const ScannerCornerOverlay(
|
||||
scanWindowSize: 250,
|
||||
borderColor: MeshPalette.blue,
|
||||
borderWidth: 2,
|
||||
cornerLength: 24,
|
||||
),
|
||||
// Instructions pill below the scan window
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 250 + 24),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.72),
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.community_scanInstructions,
|
||||
style: const TextStyle(color: MeshPalette.ink2, fontSize: 13),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleScannedData(BuildContext context, String data) async {
|
||||
if (_isProcessing) return;
|
||||
|
||||
@@ -155,7 +80,7 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.community_invalidQrCode),
|
||||
backgroundColor: MeshPalette.alert,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -171,74 +96,29 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.community_invalidQrCode),
|
||||
backgroundColor: MeshPalette.warn,
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAlreadyMemberDialog(BuildContext context, Community community) {
|
||||
showMeshSheet(
|
||||
context,
|
||||
builder: (sheetContext) {
|
||||
final sheetScheme = Theme.of(sheetContext).colorScheme;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
BottomSheetHeader(title: context.l10n.community_alreadyMember),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 4),
|
||||
child: Text(
|
||||
context.l10n.community_alreadyMemberMessage(community.name),
|
||||
style: TextStyle(color: sheetScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
MeshCard(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.groups,
|
||||
color: MeshPalette.magenta,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
community.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'ID: ${community.shortCommunityId}...',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: sheetScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(sheetContext);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.common_ok),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(context.l10n.community_alreadyMember),
|
||||
content: Text(
|
||||
context.l10n.community_alreadyMemberMessage(community.name),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.common_ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -247,111 +127,77 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
Community community,
|
||||
) async {
|
||||
bool addPublicChannel = true;
|
||||
final completer = Completer<bool>();
|
||||
|
||||
await showMeshSheet<void>(
|
||||
context,
|
||||
builder: (sheetContext) => StatefulBuilder(
|
||||
builder: (sheetContext, setSheetState) {
|
||||
final joinScheme = Theme.of(sheetContext).colorScheme;
|
||||
return Column(
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (dialogContext, setDialogState) => AlertDialog(
|
||||
title: Text(context.l10n.community_joinTitle),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BottomSheetHeader(title: context.l10n.community_joinTitle),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 4),
|
||||
child: Text(
|
||||
context.l10n.community_joinConfirmation(community.name),
|
||||
style: TextStyle(color: joinScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
MeshCard(
|
||||
child: Row(
|
||||
children: [
|
||||
AvatarCircle(
|
||||
name: community.name,
|
||||
icon: Icons.groups,
|
||||
color: MeshPalette.magenta,
|
||||
size: 44,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
community.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
),
|
||||
Text(context.l10n.community_joinConfirmation(community.name)),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.groups,
|
||||
color: Theme.of(dialogContext).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
community.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'ID: ${community.shortCommunityId}...',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
Text(
|
||||
'ID: ${community.shortCommunityId}...',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11.5,
|
||||
color: joinScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
CheckboxListTile(
|
||||
value: addPublicChannel,
|
||||
onChanged: (value) {
|
||||
setSheetState(() {
|
||||
setDialogState(() {
|
||||
addPublicChannel = value ?? true;
|
||||
});
|
||||
},
|
||||
title: Text(context.l10n.community_addPublicChannel),
|
||||
subtitle: Text(context.l10n.community_addPublicChannelHint),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
completer.complete(false);
|
||||
Navigator.pop(sheetContext);
|
||||
},
|
||||
child: Text(context.l10n.common_cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
completer.complete(true);
|
||||
Navigator.pop(sheetContext);
|
||||
},
|
||||
child: Text(context.l10n.community_join),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: Text(context.l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: Text(context.l10n.community_join),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// If sheet was dismissed without a button press, treat as cancel
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
|
||||
final result = await completer.future;
|
||||
|
||||
if (result && context.mounted) {
|
||||
if (result == true && context.mounted) {
|
||||
await _joinCommunity(context, community, addPublicChannel);
|
||||
} else if (context.mounted) {
|
||||
// User cancelled - go back
|
||||
@@ -385,7 +231,7 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.community_joined(community.name)),
|
||||
backgroundColor: MeshPalette.signal,
|
||||
backgroundColor: Colors.green,
|
||||
);
|
||||
|
||||
// Return to previous screen
|
||||
|
||||
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||
import 'package:meshcore_open/models/companion_radio_stats.dart';
|
||||
import 'package:meshcore_open/l10n/l10n.dart';
|
||||
import 'package:meshcore_open/theme/mesh_theme.dart';
|
||||
import 'package:meshcore_open/widgets/mesh_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CompanionRadioStatsScreen extends StatefulWidget {
|
||||
@@ -51,25 +49,6 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _tile(String text, IconData icon, Color color) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: MeshTheme.mono(fontSize: 13, color: scheme.onSurface),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -106,105 +85,44 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
|
||||
valueListenable: connector.radioStatsNotifier,
|
||||
builder: (context, stats, _) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (stats != null) ...[
|
||||
const SectionHeader(
|
||||
'Signal',
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
Text(
|
||||
l10n.radioStats_noiseFloor(stats.noiseFloorDbm),
|
||||
style: tt.titleMedium,
|
||||
),
|
||||
MeshCard(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_tile(
|
||||
l10n.radioStats_noiseFloor(stats.noiseFloorDbm),
|
||||
Icons.noise_aware,
|
||||
scheme.onSurfaceVariant,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_tile(
|
||||
l10n.radioStats_lastRssi(stats.lastRssiDbm),
|
||||
Icons.wifi_tethering,
|
||||
scheme.onSurfaceVariant,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_tile(
|
||||
l10n.radioStats_lastSnr(
|
||||
stats.lastSnrDb.toStringAsFixed(1),
|
||||
),
|
||||
Icons.signal_cellular_alt,
|
||||
MeshTheme.snrColor(stats.lastSnrDb, blocked: false),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)),
|
||||
Text(
|
||||
l10n.radioStats_lastSnr(
|
||||
stats.lastSnrDb.toStringAsFixed(1),
|
||||
),
|
||||
),
|
||||
const SectionHeader(
|
||||
'Airtime',
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
),
|
||||
MeshCard(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_tile(
|
||||
l10n.radioStats_txAir(stats.txAirSecs),
|
||||
Icons.upload,
|
||||
MeshPalette.blue,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_tile(
|
||||
l10n.radioStats_rxAir(stats.rxAirSecs),
|
||||
Icons.download,
|
||||
MeshPalette.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 80),
|
||||
Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Text(
|
||||
l10n.radioStats_waiting,
|
||||
style: TextStyle(color: scheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
],
|
||||
SectionHeader(
|
||||
l10n.radioStats_chartCaption,
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: CustomPaint(
|
||||
painter: _NoiseChartPainter(
|
||||
samples: List<double>.from(_noiseHistory),
|
||||
colorScheme: scheme,
|
||||
textTheme: tt,
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
Text(l10n.radioStats_txAir(stats.txAirSecs)),
|
||||
Text(l10n.radioStats_rxAir(stats.rxAirSecs)),
|
||||
const SizedBox(height: 16),
|
||||
] else
|
||||
Text(l10n.radioStats_waiting),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: CustomPaint(
|
||||
painter: _NoiseChartPainter(
|
||||
samples: List<double>.from(_noiseHistory),
|
||||
colorScheme: scheme,
|
||||
textTheme: tt,
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.radioStats_chartCaption,
|
||||
style: tt.bodySmall?.copyWith(
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -292,10 +210,10 @@ class _NoiseChartPainter extends CustomPainter {
|
||||
}
|
||||
final span = maxV - minV;
|
||||
|
||||
for (var i = 0; i <= 4; i++) {
|
||||
final v = maxV - span * i / 4;
|
||||
for (var i = 0; i <= 2; i++) {
|
||||
final v = maxV - span * i / 2;
|
||||
final tp = _yAxisLabel(v);
|
||||
final y = chart.top + (chart.height * i / 4) - tp.height / 2;
|
||||
final y = chart.top + (chart.height * i / 2) - tp.height / 2;
|
||||
tp.paint(canvas, Offset(4, y));
|
||||
}
|
||||
|
||||
|
||||
+334
-486
File diff suppressed because it is too large
Load Diff
+128
-210
@@ -7,14 +7,11 @@ import 'package:provider/provider.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../l10n/contact_localization.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../utils/contact_search.dart';
|
||||
import '../utils/platform_info.dart';
|
||||
import '../widgets/app_bar.dart';
|
||||
import '../widgets/list_filter_widget.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
enum DiscoverySortOption { lastSeen, name, type }
|
||||
@@ -49,34 +46,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
: contact.lastSeen;
|
||||
}
|
||||
|
||||
/// Node-type avatar color per design language.
|
||||
Color _avatarColor(int type) {
|
||||
switch (type) {
|
||||
case advTypeRepeater:
|
||||
return MeshPalette.warn;
|
||||
case advTypeRoom:
|
||||
return MeshPalette.magenta;
|
||||
case advTypeSensor:
|
||||
return const Color(0xFF4ACCC4); // teal
|
||||
default:
|
||||
return MeshPalette.blue;
|
||||
}
|
||||
}
|
||||
|
||||
/// Node-type avatar icon; null = show initials for chat nodes.
|
||||
IconData? _avatarIcon(int type) {
|
||||
switch (type) {
|
||||
case advTypeRepeater:
|
||||
return Icons.cell_tower;
|
||||
case advTypeRoom:
|
||||
return Icons.meeting_room;
|
||||
case advTypeSensor:
|
||||
return Icons.sensors;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -102,10 +71,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Icon(Icons.delete, color: Colors.red),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.l10n.discoveredContacts_deleteContactAll),
|
||||
],
|
||||
@@ -123,185 +89,103 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
children: [
|
||||
_buildFilters(filteredAndSorted, connector),
|
||||
Expanded(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
child: discoveredContacts.isEmpty
|
||||
? Center(
|
||||
key: const ValueKey('empty_all'),
|
||||
child: Text(l10n.contacts_noContacts),
|
||||
)
|
||||
: filteredAndSorted.isEmpty
|
||||
? Center(
|
||||
key: const ValueKey('empty_filtered'),
|
||||
child: Text(l10n.discoveredContacts_noMatching),
|
||||
)
|
||||
: ListView.builder(
|
||||
key: const ValueKey('list'),
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
itemCount: filteredAndSorted.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = filteredAndSorted[index];
|
||||
final tile = _buildDiscoveryTile(
|
||||
context,
|
||||
contact,
|
||||
connector,
|
||||
index,
|
||||
);
|
||||
if (PlatformInfo.isDesktop) {
|
||||
return GestureDetector(
|
||||
onSecondaryTapUp: (_) =>
|
||||
_showContactContextMenu(contact, connector),
|
||||
child: tile,
|
||||
);
|
||||
}
|
||||
return tile;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDiscoveryTile(
|
||||
BuildContext context,
|
||||
Contact contact,
|
||||
MeshCoreConnector connector,
|
||||
int index,
|
||||
) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final isChat = contact.type == advTypeChat;
|
||||
|
||||
return ListEntrance(
|
||||
index: index,
|
||||
child: MeshCard(
|
||||
onTap: () async {
|
||||
try {
|
||||
final imported = await connector.importDiscoveredContact(contact);
|
||||
if (!context.mounted) return;
|
||||
if (!imported) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.contacts_contactImportFailed),
|
||||
);
|
||||
return;
|
||||
}
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.discoveredContacts_contactAdded),
|
||||
action: SnackBarAction(
|
||||
label: context.l10n.common_undo,
|
||||
onPressed: () => connector.removeContact(contact),
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
if (!context.mounted) return;
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.contacts_contactImportFailed),
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () => _showContactContextMenu(contact, connector),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
AvatarCircle(
|
||||
name: contact.name,
|
||||
size: 42,
|
||||
color: isChat ? null : _avatarColor(contact.type),
|
||||
icon: _avatarIcon(contact.type),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Name + type chip
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
child: discoveredContacts.isEmpty
|
||||
? Center(child: Text(l10n.contacts_noContacts))
|
||||
: filteredAndSorted.isEmpty
|
||||
? Center(child: Text(l10n.discoveredContacts_noMatching))
|
||||
: ListView.builder(
|
||||
itemCount: filteredAndSorted.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = filteredAndSorted[index];
|
||||
final tile = ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: _getTypeColor(contact.type),
|
||||
child: Icon(
|
||||
_getTypeIcon(contact.type),
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
contact.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatusChip(
|
||||
label: contact.typeLabel(context.l10n).toUpperCase(),
|
||||
color: _avatarColor(contact.type),
|
||||
icon: _avatarIcon(contact.type),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
// Short pub key
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
subtitle: Text(
|
||||
contact.shortPubKeyHex,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
// Clamp text scaling in trailing section to prevent overflow while
|
||||
// maintaining accessibility. Primary content (title/subtitle) scales normally.
|
||||
trailing: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
MediaQuery.textScalerOf(
|
||||
context,
|
||||
).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
_formatLastSeen(
|
||||
context,
|
||||
_resolveLastSeen(contact),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contact.hasLocation)
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 14,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
if (contact.rawPacket != null)
|
||||
const SizedBox(width: 2),
|
||||
if (contact.rawPacket != null)
|
||||
Icon(
|
||||
Icons.cell_tower,
|
||||
size: 14,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (contact.hasLocation) ...[
|
||||
const SizedBox(width: 6),
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 13,
|
||||
color: scheme.onSurfaceVariant.withValues(
|
||||
alpha: 0.55,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (contact.rawPacket != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.cell_tower,
|
||||
size: 13,
|
||||
color: scheme.onSurfaceVariant.withValues(
|
||||
alpha: 0.55,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
onTap: () {
|
||||
connector.importDiscoveredContact(contact);
|
||||
},
|
||||
onLongPress: () =>
|
||||
_showContactContextMenu(contact, connector),
|
||||
);
|
||||
if (PlatformInfo.isDesktop) {
|
||||
return GestureDetector(
|
||||
onSecondaryTapUp: (_) =>
|
||||
_showContactContextMenu(contact, connector),
|
||||
child: tile,
|
||||
);
|
||||
}
|
||||
return tile;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Last seen time
|
||||
MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
MediaQuery.textScalerOf(context).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
_formatLastSeen(context, _resolveLastSeen(contact)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.right,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -310,17 +194,19 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
Contact contact,
|
||||
MeshCoreConnector connector,
|
||||
) async {
|
||||
final action = await showMeshSheet<String>(
|
||||
context,
|
||||
final action = await showModalBottomSheet<String>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (sheetContext) {
|
||||
final l10n = context.l10n;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHeader(
|
||||
title: contact.name,
|
||||
subtitle: contact.typeLabel(l10n),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_sharp),
|
||||
title: Text(l10n.discoveredContacts_addContact),
|
||||
onTap: () => Navigator.of(sheetContext).pop('import_contact'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
@@ -332,7 +218,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
title: Text(l10n.discoveredContacts_deleteContact),
|
||||
onTap: () => Navigator.of(sheetContext).pop('delete_contact'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -342,6 +227,9 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
if (!mounted || action == null) return;
|
||||
|
||||
switch (action) {
|
||||
case 'import_contact':
|
||||
connector.importDiscoveredContact(contact);
|
||||
break;
|
||||
case 'copy_contact':
|
||||
if (contact.rawPacket == null) return;
|
||||
final hexString = pubKeyToHex(contact.rawPacket!);
|
||||
@@ -541,6 +429,36 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getTypeIcon(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Icons.chat;
|
||||
case advTypeRepeater:
|
||||
return Icons.cell_tower;
|
||||
case advTypeRoom:
|
||||
return Icons.group;
|
||||
case advTypeSensor:
|
||||
return Icons.sensors;
|
||||
default:
|
||||
return Icons.device_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getTypeColor(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Colors.blue;
|
||||
case advTypeRepeater:
|
||||
return Colors.orange;
|
||||
case advTypeRoom:
|
||||
return Colors.purple;
|
||||
case advTypeSensor:
|
||||
return Colors.green;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatLastSeen(BuildContext context, DateTime lastSeen) {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(lastSeen);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+126
-166
@@ -10,9 +10,6 @@ import '../services/app_settings_service.dart';
|
||||
import '../services/map_tile_cache_service.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../widgets/themed_map_tile_layer.dart';
|
||||
|
||||
class MapCacheScreen extends StatefulWidget {
|
||||
const MapCacheScreen({super.key});
|
||||
@@ -79,34 +76,27 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
||||
return Positioned(
|
||||
top: 12,
|
||||
left: 12,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: MeshPalette.bg1.withValues(alpha: 0.90),
|
||||
borderRadius: BorderRadius.circular(MeshRadii.md),
|
||||
border: Border.all(color: MeshPalette.line2),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.md),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: context.l10n.map_zoomIn,
|
||||
onPressed: () => _zoomMapBy(1),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
tooltip: context.l10n.map_zoomOut,
|
||||
onPressed: () => _zoomMapBy(-1),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location),
|
||||
tooltip: context.l10n.map_centerMap,
|
||||
onPressed: _resetMapView,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: 'Zoom in',
|
||||
onPressed: () => _zoomMapBy(1),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
tooltip: 'Zoom out',
|
||||
onPressed: () => _zoomMapBy(-1),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location),
|
||||
tooltip: 'Center map',
|
||||
onPressed: _resetMapView,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -291,7 +281,6 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
||||
final tileCache = context.read<MapTileCacheService>();
|
||||
final selectedBounds = _selectedBounds;
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
|
||||
final progressValue = _estimatedTiles == 0
|
||||
? 0.0
|
||||
@@ -329,7 +318,13 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
||||
),
|
||||
),
|
||||
children: [
|
||||
ThemedMapTileLayer(tileCache: tileCache),
|
||||
TileLayer(
|
||||
urlTemplate: kMapTileUrlTemplate,
|
||||
tileProvider: tileCache.tileProvider,
|
||||
userAgentPackageName:
|
||||
MapTileCacheService.userAgentPackageName,
|
||||
maxZoom: 19,
|
||||
),
|
||||
if (selectedBounds != null)
|
||||
PolygonLayer(
|
||||
polygons: [
|
||||
@@ -347,25 +342,14 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
||||
Positioned(
|
||||
top: 12,
|
||||
right: 12,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: MeshPalette.bg1.withValues(alpha: 0.93),
|
||||
borderRadius: BorderRadius.circular(MeshRadii.md),
|
||||
border: Border.all(color: MeshPalette.line2),
|
||||
),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
selectedBounds == null
|
||||
? l10n.mapCache_noAreaSelected
|
||||
: _formatBounds(selectedBounds, l10n),
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: MeshPalette.ink2,
|
||||
),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -375,133 +359,109 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
||||
),
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainerLow,
|
||||
border: Border(top: BorderSide(color: scheme.outlineVariant)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SectionHeader(
|
||||
l10n.mapCache_cacheArea,
|
||||
padding: const EdgeInsets.fromLTRB(0, 12, 0, 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
l10n.mapCache_cacheArea,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.crop_free),
|
||||
label: Text(l10n.mapCache_useCurrentView),
|
||||
onPressed: _isDownloading
|
||||
? null
|
||||
: _setBoundsFromView,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.crop_free),
|
||||
label: Text(l10n.mapCache_useCurrentView),
|
||||
onPressed: _isDownloading ? null : _setBoundsFromView,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
TextButton(
|
||||
onPressed: _isDownloading || selectedBounds == null
|
||||
? null
|
||||
: _clearBounds,
|
||||
child: Text(l10n.common_clear),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SectionHeader(
|
||||
l10n.mapCache_zoomRange,
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
|
||||
),
|
||||
RangeSlider(
|
||||
values: RangeValues(
|
||||
_minZoom.toDouble(),
|
||||
_maxZoom.toDouble(),
|
||||
),
|
||||
min: 3,
|
||||
max: 18,
|
||||
divisions: 15,
|
||||
labels: RangeLabels('$_minZoom', '$_maxZoom'),
|
||||
onChanged: _isDownloading
|
||||
? null
|
||||
: (values) {
|
||||
setState(() {
|
||||
_minZoom = values.start.round();
|
||||
_maxZoom = values.end.round();
|
||||
});
|
||||
},
|
||||
onChangeEnd: _isDownloading
|
||||
? null
|
||||
: (_) {
|
||||
_saveZoomRange();
|
||||
},
|
||||
),
|
||||
Text(
|
||||
l10n.mapCache_estimatedTiles(_estimatedTiles),
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (_isDownloading) ...[
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: progressValue,
|
||||
color: MeshPalette.blue,
|
||||
backgroundColor: scheme.surfaceContainerHighest,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.mapCache_downloadedTiles(
|
||||
_completedTiles,
|
||||
_estimatedTiles,
|
||||
),
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
TextButton(
|
||||
onPressed: _isDownloading || selectedBounds == null
|
||||
? null
|
||||
: _clearBounds,
|
||||
child: Text(l10n.common_clear),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.download),
|
||||
label: Text(l10n.mapCache_downloadTilesButton),
|
||||
onPressed: _isDownloading || selectedBounds == null
|
||||
? null
|
||||
: _startDownload,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: MeshPalette.alert,
|
||||
side: const BorderSide(
|
||||
color: MeshPalette.alertLine,
|
||||
),
|
||||
),
|
||||
onPressed: _isDownloading ? null : _clearCache,
|
||||
child: Text(l10n.mapCache_clearCacheButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.mapCache_zoomRange,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
if (_failedTiles > 0 && !_isDownloading)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
l10n.mapCache_failedDownloads(_failedTiles),
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
color: MeshPalette.alert,
|
||||
),
|
||||
),
|
||||
RangeSlider(
|
||||
values: RangeValues(
|
||||
_minZoom.toDouble(),
|
||||
_maxZoom.toDouble(),
|
||||
),
|
||||
min: 3,
|
||||
max: 18,
|
||||
divisions: 15,
|
||||
labels: RangeLabels('$_minZoom', '$_maxZoom'),
|
||||
onChanged: _isDownloading
|
||||
? null
|
||||
: (values) {
|
||||
setState(() {
|
||||
_minZoom = values.start.round();
|
||||
_maxZoom = values.end.round();
|
||||
});
|
||||
},
|
||||
onChangeEnd: _isDownloading
|
||||
? null
|
||||
: (_) {
|
||||
_saveZoomRange();
|
||||
},
|
||||
),
|
||||
Text(l10n.mapCache_estimatedTiles(_estimatedTiles)),
|
||||
if (_isDownloading) ...[
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(value: progressValue),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.mapCache_downloadedTiles(
|
||||
_completedTiles,
|
||||
_estimatedTiles,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.download),
|
||||
label: Text(l10n.mapCache_downloadTilesButton),
|
||||
onPressed: _isDownloading || selectedBounds == null
|
||||
? null
|
||||
: _startDownload,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
OutlinedButton(
|
||||
onPressed: _isDownloading ? null : _clearCache,
|
||||
child: Text(l10n.mapCache_clearCacheButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_failedTiles > 0 && !_isDownloading)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
l10n.mapCache_failedDownloads(_failedTiles),
|
||||
style: TextStyle(color: Colors.orange[700]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+780
-2175
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,8 @@ import '../models/path_selection.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../widgets/routing_sheet.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../widgets/snr_indicator.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
class NeighborsScreen extends StatefulWidget {
|
||||
@@ -169,7 +167,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.neighbors_receivedData),
|
||||
backgroundColor: Theme.of(context).colorScheme.tertiary,
|
||||
backgroundColor: Colors.green,
|
||||
);
|
||||
_statusTimeout?.cancel();
|
||||
if (!mounted) return;
|
||||
@@ -229,7 +227,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.neighbors_requestTimedOut),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
_recordStatusResult(false);
|
||||
});
|
||||
@@ -243,7 +241,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -281,9 +279,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
children: [
|
||||
Text(
|
||||
l10n.neighbors_repeatersNeighbors,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
repeater.name,
|
||||
@@ -291,18 +287,75 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
PopupMenuButton<String>(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onSelected: (mode) async {
|
||||
if (mode == 'flood') {
|
||||
await connector.setPathOverride(repeater, pathLen: -1);
|
||||
} else {
|
||||
await connector.setPathOverride(repeater, pathLen: null);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () =>
|
||||
ContactRoutingSheet.show(context, contact: repeater),
|
||||
PathManagementDialog.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
@@ -322,16 +375,23 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _loadNeighbors,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (!_isLoaded &&
|
||||
!_hasData &&
|
||||
(_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
|
||||
EmptyState(icon: Icons.wifi_find, title: l10n.neighbors_noData),
|
||||
Center(
|
||||
child: Text(
|
||||
l10n.neighbors_noData,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
if (_isLoaded ||
|
||||
_hasData &&
|
||||
!(_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
|
||||
_buildNeighborsList(connector),
|
||||
_buildNeighborsInfoCard(
|
||||
"${l10n.repeater_neighbors} - $_neighborCount",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -339,97 +399,81 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNeighborsList(MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
'${l10n.repeater_neighbors} — $_neighborCount',
|
||||
padding: const EdgeInsets.fromLTRB(4, 8, 4, 10),
|
||||
),
|
||||
for (var i = 0; i < _parsedNeighbors!.length; i++)
|
||||
ListEntrance(
|
||||
index: i,
|
||||
child: _buildNeighborRow(_parsedNeighbors![i], connector.currentSf),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNeighborRow(Map<String, dynamic> data, int? spreadingFactor) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final Contact? contact = data['contact'] as Contact?;
|
||||
final double snr = data['snr'] as double;
|
||||
final int lastHeardSeconds = data['lastHeard'] as int;
|
||||
|
||||
final name = contact != null
|
||||
? contact.name
|
||||
: l10n.neighbors_unknownContact(
|
||||
'<${pubKeyToHex(data['publicKey'] as Uint8List)}>',
|
||||
);
|
||||
|
||||
final snrColor = MeshTheme.snrColor(snr, blocked: false);
|
||||
final heardLabel = l10n.neighbors_heardAgo(
|
||||
fmtDuration(lastHeardSeconds + 0.0),
|
||||
);
|
||||
|
||||
return MeshCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
AvatarCircle(
|
||||
name: name,
|
||||
size: 40,
|
||||
color: contact != null ? MeshPalette.warn : scheme.onSurfaceVariant,
|
||||
icon: contact != null ? Icons.cell_tower : Icons.device_unknown,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
Widget _buildNeighborsInfoCard(String title) {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
),
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
heardLabel,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SignalBars(snr: snr, height: 16),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${snr.toStringAsFixed(1)} dB',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: snrColor,
|
||||
const Divider(),
|
||||
for (final entry in _parsedNeighbors!.asMap().entries)
|
||||
_buildInfoRow(
|
||||
entry.value['contact'] != null
|
||||
? entry.value['contact'].name
|
||||
: context.l10n.neighbors_unknownContact(
|
||||
"<${pubKeyToHex(entry.value['publicKey'])}>",
|
||||
),
|
||||
context.l10n.neighbors_heardAgo(
|
||||
fmtDuration(entry.value['lastHeard'] + 0.0),
|
||||
),
|
||||
entry.value['snr'],
|
||||
connector.currentSf!,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(
|
||||
String label,
|
||||
String value,
|
||||
double snr,
|
||||
int spreadingFactor,
|
||||
) {
|
||||
final snrUi = snrUiFromSNR(snr, spreadingFactor);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
label,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(value),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(snrUi.icon, color: snrUi.color, size: 18.0),
|
||||
Text(
|
||||
snrUi.text,
|
||||
style: TextStyle(fontSize: 10, color: snrUi.color),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
+223
-965
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/debug_frame_viewer.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../widgets/routing_sheet.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
class RepeaterCliScreen extends StatefulWidget {
|
||||
@@ -35,6 +34,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
RepeaterCommandService? _commandService;
|
||||
|
||||
// Common commands for quick access
|
||||
late final List<Map<String, String>> _quickCommands = [
|
||||
{'labelKey': 'advertise', 'command': 'advert'},
|
||||
{'labelKey': 'getName', 'command': 'get name'},
|
||||
@@ -67,8 +67,12 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
|
||||
void _setupMessageListener() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
// Listen for incoming text messages from the repeater
|
||||
_frameSubscription = connector.receivedFrames.listen((frame) {
|
||||
if (frame.isEmpty) return;
|
||||
|
||||
// Check if it's a text message response
|
||||
if (frame[0] == respCodeContactMsgRecv ||
|
||||
frame[0] == respCodeContactMsgRecvV3) {
|
||||
_handleTextMessageResponse(frame);
|
||||
@@ -98,7 +102,12 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
final parsed = parseContactMessageText(frame);
|
||||
if (parsed == null) return;
|
||||
if (!_matchesRepeaterPrefix(parsed.senderPrefix)) return;
|
||||
|
||||
// Notify command service of response (for retry handling)
|
||||
_commandService?.handleResponse(widget.repeater, parsed.text);
|
||||
|
||||
// Note: The command service will handle the response via the Future
|
||||
// We don't need to add it to history here anymore as _sendCommand will do it
|
||||
}
|
||||
|
||||
bool _matchesRepeaterPrefix(Uint8List prefix) {
|
||||
@@ -122,6 +131,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
});
|
||||
});
|
||||
|
||||
// Show debug info if requested
|
||||
if (showDebug && mounted) {
|
||||
final frame = buildSendCliCommandFrame(
|
||||
widget.repeater.publicKey,
|
||||
@@ -134,6 +144,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// Send CLI command to repeater with retry
|
||||
try {
|
||||
if (_commandService != null) {
|
||||
final connector = Provider.of<MeshCoreConnector>(
|
||||
@@ -146,6 +157,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
command,
|
||||
retries: 1,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_commandHistory.add({
|
||||
@@ -172,6 +184,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
_historyIndex = -1;
|
||||
_commandFocusNode.requestFocus();
|
||||
|
||||
// Auto-scroll to bottom
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
@@ -226,6 +239,161 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(l10n.repeater_cliTitle),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onSelected: (mode) async {
|
||||
if (mode == 'flood') {
|
||||
await connector.setPathOverride(repeater, pathLen: -1);
|
||||
} else {
|
||||
await connector.setPathOverride(repeater, pathLen: null);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () =>
|
||||
PathManagementDialog.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.bug_report),
|
||||
tooltip: l10n.repeater_debugNextCommand,
|
||||
onPressed: () {
|
||||
// Set a flag or just send next command with debug
|
||||
if (_commandController.text.trim().isNotEmpty) {
|
||||
_sendCommand(showDebug: true);
|
||||
} else {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(l10n.repeater_enterCommandFirst),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline),
|
||||
tooltip: l10n.repeater_commandHelp,
|
||||
onPressed: () => _showCommandHelp(context),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear_all),
|
||||
tooltip: l10n.repeater_clearHistory,
|
||||
onPressed: _commandHistory.isEmpty ? null : _clearHistory,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildQuickCommandsBar(),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: _commandHistory.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildCommandHistory(),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_buildCommandInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickCommandsBar() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: _quickCommands.map((cmd) {
|
||||
final label = _quickCommandLabel(cmd['labelKey']!);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ActionChip(
|
||||
label: Text(label),
|
||||
onPressed: () => _useQuickCommand(cmd['command']!),
|
||||
avatar: const Icon(Icons.play_arrow, size: 16),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _quickCommandLabel(String key) {
|
||||
final l10n = context.l10n;
|
||||
switch (key) {
|
||||
@@ -252,234 +420,22 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: MeshPalette.bg,
|
||||
appBar: AppBar(
|
||||
backgroundColor: MeshPalette.bg1,
|
||||
title: Text(l10n.repeater_cliTitle),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onPressed: () =>
|
||||
ContactRoutingSheet.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline),
|
||||
tooltip: l10n.repeater_commandHelp,
|
||||
onPressed: () => _showCommandHelp(context),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear_all),
|
||||
tooltip: l10n.repeater_clearHistory,
|
||||
onPressed: _commandHistory.isEmpty ? null : _clearHistory,
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: (value) {
|
||||
if (value == 'debug') {
|
||||
if (_commandController.text.trim().isNotEmpty) {
|
||||
_sendCommand(showDebug: true);
|
||||
} else {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(l10n.repeater_enterCommandFirst),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'debug',
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.bug_report),
|
||||
const SizedBox(width: 8),
|
||||
Text(l10n.repeater_debugNextCommand),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// Quick commands bar
|
||||
Container(
|
||||
color: MeshPalette.bg1,
|
||||
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: _quickCommands.map((cmd) {
|
||||
final label = _quickCommandLabel(cmd['labelKey']!);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: ActionChip(
|
||||
label: Text(
|
||||
label,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: MeshPalette.blue,
|
||||
),
|
||||
),
|
||||
backgroundColor: MeshPalette.blueBg,
|
||||
side: const BorderSide(color: MeshPalette.blueLine),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () => _useQuickCommand(cmd['command']!),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(height: 1, color: MeshPalette.line),
|
||||
|
||||
// Output area
|
||||
Expanded(
|
||||
child: _commandHistory.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildCommandHistory(),
|
||||
),
|
||||
|
||||
Divider(height: 1, color: MeshPalette.line),
|
||||
|
||||
// Command input
|
||||
Container(
|
||||
color: MeshPalette.bg1,
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_upward,
|
||||
size: 18,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
tooltip: l10n.repeater_previousCommand,
|
||||
onPressed: () => _navigateHistory(true),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_downward,
|
||||
size: 18,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
tooltip: l10n.repeater_nextCommand,
|
||||
onPressed: () => _navigateHistory(false),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _commandController,
|
||||
focusNode: _commandFocusNode,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 13,
|
||||
color: MeshPalette.ink,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.repeater_enterCommandHint,
|
||||
hintStyle: MeshTheme.mono(
|
||||
fontSize: 13,
|
||||
color: MeshPalette.ink4,
|
||||
),
|
||||
prefixText: '> ',
|
||||
prefixStyle: MeshTheme.mono(
|
||||
fontSize: 13,
|
||||
color: MeshPalette.blue,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: MeshPalette.bg2,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
borderSide: const BorderSide(
|
||||
color: MeshPalette.line2,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
borderSide: const BorderSide(
|
||||
color: MeshPalette.line2,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
borderSide: const BorderSide(
|
||||
color: MeshPalette.blue,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: (_) => _sendCommand(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Material(
|
||||
color: MeshPalette.blue.withValues(alpha: 0.15),
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(color: MeshPalette.blueLine),
|
||||
),
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
_sendCommand();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Icon(
|
||||
Icons.send,
|
||||
size: 18,
|
||||
color: MeshPalette.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
final l10n = context.l10n;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.terminal, size: 48, color: MeshPalette.ink4),
|
||||
const SizedBox(height: 12),
|
||||
Icon(Icons.terminal, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.repeater_noCommandsSent,
|
||||
style: MeshTheme.mono(fontSize: 13, color: MeshPalette.ink3),
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.repeater_typeCommandOrUseQuick,
|
||||
style: const TextStyle(fontSize: 12, color: MeshPalette.ink4),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -489,37 +445,49 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
Widget _buildCommandHistory() {
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _commandHistory.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = _commandHistory[index];
|
||||
final isCommand = entry['type'] == 'command';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Gutter prefix
|
||||
SizedBox(
|
||||
width: 20,
|
||||
child: Text(
|
||||
isCommand ? '>' : ' ',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isCommand ? MeshPalette.blue : MeshPalette.ink3,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: isCommand
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Icon(
|
||||
isCommand ? Icons.chevron_right : Icons.arrow_back,
|
||||
size: 16,
|
||||
color: isCommand
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer
|
||||
: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
entry['text']!,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12.5,
|
||||
color: isCommand ? MeshPalette.blue : MeshPalette.ink,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
entry['text']!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
color: isCommand
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -529,6 +497,54 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommandInput() {
|
||||
final l10n = context.l10n;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_upward, size: 20),
|
||||
tooltip: l10n.repeater_previousCommand,
|
||||
onPressed: () => _navigateHistory(true),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_downward, size: 20),
|
||||
tooltip: l10n.repeater_nextCommand,
|
||||
onPressed: () => _navigateHistory(false),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _commandController,
|
||||
focusNode: _commandFocusNode,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.repeater_enterCommandHint,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
prefixText: '> ',
|
||||
),
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: (_) => _sendCommand(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton.filled(
|
||||
icon: const Icon(Icons.send),
|
||||
onPressed: _sendCommand,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _applyHelpCommand(String command) {
|
||||
_commandController.text = command;
|
||||
_commandController.selection = TextSelection.fromPosition(
|
||||
@@ -1149,20 +1165,16 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
List<_CommandHelpEntry> commands, {
|
||||
String? note,
|
||||
}) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
if (note != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
note,
|
||||
style: TextStyle(fontSize: 11, color: scheme.onSurfaceVariant),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(note, style: const TextStyle(fontSize: 12)),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
...commands.map((entry) => _buildHelpCommandCard(context, entry)),
|
||||
@@ -1171,35 +1183,39 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
}
|
||||
|
||||
Widget _buildHelpCommandCard(BuildContext context, _CommandHelpEntry entry) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Card(
|
||||
elevation: 0,
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
color: scheme.surfaceContainerHighest,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.sm),
|
||||
side: BorderSide(color: scheme.outlineVariant),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.sm),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => _applyHelpCommand(entry.command),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entry.command,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: MeshPalette.blue,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
entry.description,
|
||||
style: TextStyle(fontSize: 12, color: scheme.onSurfaceVariant),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1212,5 +1228,6 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
class _CommandHelpEntry {
|
||||
final String command;
|
||||
final String description;
|
||||
|
||||
const _CommandHelpEntry({required this.command, required this.description});
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../l10n/contact_localization.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import 'repeater_status_screen.dart';
|
||||
import 'repeater_cli_screen.dart';
|
||||
import 'repeater_settings_screen.dart';
|
||||
@@ -29,157 +26,175 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final settingsService = context.watch<AppSettingsService>();
|
||||
final chemistry = settingsService.batteryChemistryForRepeater(
|
||||
repeater.publicKeyHex,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
repeater.type == advTypeRepeater
|
||||
? (isAdmin ? l10n.repeater_management : l10n.repeater_guest)
|
||||
: (isAdmin ? l10n.room_management : l10n.room_guest),
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isAdmin)
|
||||
Text(
|
||||
repeater.type == advTypeRepeater
|
||||
? l10n.repeater_management
|
||||
: l10n.room_management,
|
||||
),
|
||||
if (!isAdmin)
|
||||
Text(
|
||||
repeater.type == advTypeRepeater
|
||||
? l10n.repeater_guest
|
||||
: l10n.room_guest,
|
||||
),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
centerTitle: true,
|
||||
centerTitle: false,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// ── Identity card ─────────────────────────────────────────────
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 20, 16, 4),
|
||||
child: MeshCard(
|
||||
margin: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
// Repeater info card
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
AvatarCircle(
|
||||
name: repeater.name,
|
||||
size: 52,
|
||||
color: MeshPalette.warn,
|
||||
icon: Icons.cell_tower,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
repeater.name,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
repeater.shortPubKeyHex,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
repeater.pathLabel(l10n),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: scheme.onSurfaceVariant),
|
||||
),
|
||||
if (repeater.hasLocation) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 12,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${repeater.latitude?.toStringAsFixed(4)}, '
|
||||
'${repeater.longitude?.toStringAsFixed(4)}',
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 10,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor: Colors.orange,
|
||||
child: const Icon(
|
||||
Icons.cell_tower,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
StatusChip(
|
||||
label: isAdmin ? 'ADMIN' : 'GUEST',
|
||||
color: isAdmin
|
||||
? MeshPalette.blue
|
||||
: scheme.onSurfaceVariant,
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
repeater.shortPubKeyHex,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
repeater.pathLabel(context.l10n),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
if (repeater.hasLocation) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Battery chemistry (admin only) ─────────────────────────────
|
||||
if (isAdmin) ...[
|
||||
SectionHeader(l10n.appSettings_batteryChemistry),
|
||||
MeshCard(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
padding: const EdgeInsets.fromLTRB(14, 10, 14, 14),
|
||||
child: DropdownButtonFormField<String>(
|
||||
initialValue: chemistry,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.battery_full, size: 18),
|
||||
labelText: l10n.appSettings_batteryChemistry,
|
||||
const SizedBox(height: 24),
|
||||
if (isAdmin)
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.battery_full),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.appSettings_batteryChemistry,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: chemistry,
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
isDense: true,
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
settingsService.setBatteryChemistryForRepeater(
|
||||
repeater.publicKeyHex,
|
||||
value,
|
||||
);
|
||||
},
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
settingsService.setBatteryChemistryForRepeater(
|
||||
repeater.publicKeyHex,
|
||||
value,
|
||||
);
|
||||
},
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// ── Tools ──────────────────────────────────────────────────────
|
||||
SectionHeader(
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
isAdmin
|
||||
? l10n.repeater_managementTools
|
||||
: l10n.repeater_guestTools,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
|
||||
_HubActionTile(
|
||||
index: 0,
|
||||
const SizedBox(height: 16),
|
||||
// Status button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.analytics,
|
||||
title: l10n.repeater_status,
|
||||
subtitle: l10n.repeater_statusSubtitle,
|
||||
accentColor: MeshPalette.blue,
|
||||
color: Colors.blue,
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
@@ -191,15 +206,15 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
_HubActionTile(
|
||||
index: 1,
|
||||
const SizedBox(height: 16),
|
||||
// Telemetry button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.bar_chart_sharp,
|
||||
title: l10n.repeater_telemetry,
|
||||
subtitle: l10n.repeater_telemetrySubtitle,
|
||||
accentColor: MeshPalette.magenta,
|
||||
color: Colors.teal,
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
@@ -208,34 +223,16 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
_HubActionTile(
|
||||
index: 2,
|
||||
icon: Icons.group,
|
||||
title: l10n.repeater_neighbors,
|
||||
subtitle: l10n.repeater_neighborsSubtitle,
|
||||
accentColor: MeshPalette.signal,
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
NeighborsScreen(repeater: repeater, password: password),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
if (isAdmin) ...[
|
||||
_HubActionTile(
|
||||
index: 3,
|
||||
if (isAdmin) const SizedBox(height: 12),
|
||||
// CLI button
|
||||
if (isAdmin)
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.terminal,
|
||||
title: l10n.repeater_cli,
|
||||
subtitle: l10n.repeater_cliSubtitle,
|
||||
accentColor: MeshPalette.warn,
|
||||
color: Colors.green,
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
@@ -247,14 +244,34 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
_HubActionTile(
|
||||
index: 4,
|
||||
const SizedBox(height: 12),
|
||||
// Neighbors button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.group,
|
||||
title: l10n.repeater_neighbors,
|
||||
subtitle: l10n.repeater_neighborsSubtitle,
|
||||
color: Colors.orange,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
NeighborsScreen(repeater: repeater, password: password),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isAdmin) const SizedBox(height: 12),
|
||||
// Settings button
|
||||
if (isAdmin)
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.settings,
|
||||
title: l10n.repeater_settings,
|
||||
subtitle: l10n.repeater_settingsSubtitle,
|
||||
accentColor: MeshPalette.alert,
|
||||
color: Colors.deepOrange,
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
@@ -266,76 +283,60 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HubActionTile extends StatelessWidget {
|
||||
final int index;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final Color accentColor;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _HubActionTile({
|
||||
required this.index,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.accentColor,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ListEntrance(
|
||||
index: index,
|
||||
child: MeshCard(
|
||||
Widget _buildManagementCard(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: accentColor.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(MeshRadii.md),
|
||||
border: Border.all(color: accentColor.withValues(alpha: 0.3)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 32),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(icon, size: 22, color: accentColor),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
color: scheme.onSurfaceVariant,
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
|
||||
],
|
||||
Icon(Icons.chevron_right, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -11,10 +10,8 @@ import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../utils/battery_utils.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../widgets/routing_sheet.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
|
||||
class RepeaterStatusScreen extends StatefulWidget {
|
||||
@@ -67,6 +64,8 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
_commandService = RepeaterCommandService(connector);
|
||||
_setupMessageListener();
|
||||
// Defer until after the first frame so any notifyListeners() triggered
|
||||
// during preparePathForContactSend doesn't fire mid-build.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _loadStatus();
|
||||
});
|
||||
@@ -82,8 +81,12 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
|
||||
void _setupMessageListener() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
// Listen for incoming text messages from the repeater
|
||||
_frameSubscription = connector.receivedFrames.listen((frame) {
|
||||
if (frame.isEmpty) return;
|
||||
|
||||
// Check if it's a text message response
|
||||
if (frame[0] == pushCodeStatusResponse) {
|
||||
_handleStatusResponse(frame);
|
||||
} else if (frame[0] == respCodeContactMsgRecv ||
|
||||
@@ -115,7 +118,11 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
final parsed = parseContactMessageText(frame);
|
||||
if (parsed == null) return;
|
||||
if (!_matchesRepeaterPrefix(parsed.senderPrefix)) return;
|
||||
|
||||
// Notify command service of response (for retry handling)
|
||||
_commandService?.handleResponse(widget.repeater, parsed.text);
|
||||
|
||||
// Parse status responses
|
||||
_parseStatusResponse(parsed.text);
|
||||
_recordStatusResult(true);
|
||||
}
|
||||
@@ -124,6 +131,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
if (frame.length < 8) return;
|
||||
final prefix = frame.sublist(2, 8);
|
||||
if (!_matchesRepeaterPrefix(prefix)) return;
|
||||
|
||||
if (frame.length < _statusResponseBytes) return;
|
||||
|
||||
final data = ByteData.sublistView(
|
||||
@@ -246,9 +254,14 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_dupFlood = _asInt(data['dup_flood']);
|
||||
_dupDirect = _asInt(data['dup_direct']);
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (_) {
|
||||
// Ignore parse failures for non-JSON responses.
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _loadStatus() async {
|
||||
@@ -289,7 +302,9 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
var messageBytes = frame.length >= _statusResponseBytes
|
||||
? frame.length
|
||||
: _statusResponseBytes;
|
||||
if (messageBytes < maxFrameSize) messageBytes = maxFrameSize;
|
||||
if (messageBytes < maxFrameSize) {
|
||||
messageBytes = maxFrameSize;
|
||||
}
|
||||
final timeoutMs = connector.calculateTimeout(
|
||||
pathLength: pathLengthValue,
|
||||
messageBytes: messageBytes,
|
||||
@@ -297,21 +312,26 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_statusTimeout?.cancel();
|
||||
_statusTimeout = Timer(Duration(milliseconds: timeoutMs), () {
|
||||
if (!mounted) return;
|
||||
setState(() => _isLoading = false);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.repeater_statusRequestTimeout),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
_recordStatusResult(false);
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.repeater_errorLoadingStatus(e.toString())),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
_recordStatusResult(false);
|
||||
@@ -327,6 +347,268 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_pendingStatusSelection = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(l10n.repeater_statusTitle),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onSelected: (mode) async {
|
||||
if (mode == 'flood') {
|
||||
await connector.setPathOverride(repeater, pathLen: -1);
|
||||
} else {
|
||||
await connector.setPathOverride(repeater, pathLen: null);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () =>
|
||||
PathManagementDialog.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: _isLoading ? null : _loadStatus,
|
||||
tooltip: l10n.repeater_refresh,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _loadStatus,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_buildSystemInfoCard(),
|
||||
const SizedBox(height: 16),
|
||||
_buildRadioStatsCard(),
|
||||
const SizedBox(height: 16),
|
||||
_buildPacketStatsCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSystemInfoCard() {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_systemInformation,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
_buildInfoRow(l10n.repeater_battery, _batteryText()),
|
||||
_buildInfoRow(l10n.repeater_clockAtLogin, _clockText()),
|
||||
_buildInfoRow(l10n.repeater_uptime, _formatDuration(_uptimeSecs)),
|
||||
_buildInfoRow(l10n.repeater_queueLength, _formatValue(_queueLen)),
|
||||
_buildInfoRow(l10n.repeater_debugFlags, _formatValue(_debugFlags)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioStatsCard() {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.radio,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_radioStatistics,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
_buildInfoRow(
|
||||
l10n.repeater_lastRssi,
|
||||
_formatValue(_lastRssi, suffix: ' dB'),
|
||||
),
|
||||
_buildInfoRow(l10n.repeater_lastSnr, _formatSnr(_lastSnr)),
|
||||
_buildInfoRow(
|
||||
l10n.repeater_noiseFloor,
|
||||
_formatValue(_noiseFloor, suffix: ' dB'),
|
||||
),
|
||||
_buildInfoRow(l10n.repeater_txAirtime, _formatDuration(_txAirSecs)),
|
||||
_buildInfoRow(l10n.repeater_rxAirtime, _formatDuration(_rxAirSecs)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPacketStatsCard() {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.analytics,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_packetStatistics,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
_buildInfoRow(l10n.repeater_sent, _packetTxText()),
|
||||
_buildInfoRow(l10n.repeater_received, _packetRxText()),
|
||||
_buildInfoRow(l10n.repeater_duplicates, _duplicateText()),
|
||||
_buildInfoRow(l10n.repeater_chanUtil, _chanUtilText()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 130,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int? _asInt(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
@@ -433,221 +715,4 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
if (snr == null) return '—';
|
||||
return snr.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.repeater_statusTitle),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onPressed: () =>
|
||||
ContactRoutingSheet.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: _isLoading ? null : _loadStatus,
|
||||
tooltip: l10n.repeater_refresh,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _loadStatus,
|
||||
child: _isLoading && _batteryMv == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _buildBody(l10n, repeater.name),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(dynamic l10n, String name) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
children: [
|
||||
// ── System ─────────────────────────────────────────────────────────
|
||||
SectionHeader(l10n.repeater_systemInformation),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: _buildStatGrid([
|
||||
_StatItem(
|
||||
icon: Icons.battery_std,
|
||||
label: l10n.repeater_battery,
|
||||
value: _batteryText(),
|
||||
color: _batteryColor(),
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.timer_outlined,
|
||||
label: l10n.repeater_uptime,
|
||||
value: _formatDuration(_uptimeSecs),
|
||||
color: MeshPalette.blue,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.schedule,
|
||||
label: l10n.repeater_clockAtLogin,
|
||||
value: _clockText(),
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.inbox,
|
||||
label: l10n.repeater_queueLength,
|
||||
value: _formatValue(_queueLen),
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.bug_report_outlined,
|
||||
label: l10n.repeater_debugFlags,
|
||||
value: _formatValue(_debugFlags),
|
||||
color: _debugFlags != null && _debugFlags! > 0
|
||||
? MeshPalette.warn
|
||||
: scheme.onSurfaceVariant,
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
||||
// ── Radio ──────────────────────────────────────────────────────────
|
||||
SectionHeader(l10n.repeater_radioStatistics),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: _buildStatGrid([
|
||||
_StatItem(
|
||||
icon: Icons.signal_cellular_alt,
|
||||
label: l10n.repeater_lastRssi,
|
||||
value: _formatValue(_lastRssi, suffix: ' dB'),
|
||||
color: MeshPalette.blue,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.waves,
|
||||
label: l10n.repeater_lastSnr,
|
||||
value: _formatSnr(_lastSnr),
|
||||
color: MeshTheme.snrColor(_lastSnr, blocked: false),
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.noise_control_off,
|
||||
label: l10n.repeater_noiseFloor,
|
||||
value: _formatValue(_noiseFloor, suffix: ' dB'),
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.upload,
|
||||
label: l10n.repeater_txAirtime,
|
||||
value: _formatDuration(_txAirSecs),
|
||||
color: MeshPalette.warn,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.download,
|
||||
label: l10n.repeater_rxAirtime,
|
||||
value: _formatDuration(_rxAirSecs),
|
||||
color: MeshPalette.signal,
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
||||
// ── Packets ────────────────────────────────────────────────────────
|
||||
SectionHeader(l10n.repeater_packetStatistics),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: _buildStatGrid([
|
||||
_StatItem(
|
||||
icon: Icons.send,
|
||||
label: l10n.repeater_sent,
|
||||
value: _packetTxText(),
|
||||
color: MeshPalette.blue,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.call_received,
|
||||
label: l10n.repeater_received,
|
||||
value: _packetRxText(),
|
||||
color: MeshPalette.signal,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.content_copy,
|
||||
label: l10n.repeater_duplicates,
|
||||
value: _duplicateText(),
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
_StatItem(
|
||||
icon: Icons.percent,
|
||||
label: l10n.repeater_chanUtil,
|
||||
value: _chanUtilText(),
|
||||
color: _chanUtil != null && _chanUtil! > 80
|
||||
? MeshPalette.alert
|
||||
: _chanUtil != null && _chanUtil! > 50
|
||||
? MeshPalette.warn
|
||||
: MeshPalette.signal,
|
||||
),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color _batteryColor() {
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final batteryMv =
|
||||
connector.getRepeaterBatteryMillivolts(widget.repeater.publicKeyHex) ??
|
||||
_batteryMv;
|
||||
if (batteryMv == null)
|
||||
return Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
final percent = estimateBatteryPercentFromMillivolts(
|
||||
batteryMv,
|
||||
_batteryChemistry(),
|
||||
);
|
||||
if (percent < 20) return MeshPalette.alert;
|
||||
if (percent < 40) return MeshPalette.warn;
|
||||
return MeshPalette.signal;
|
||||
}
|
||||
|
||||
Widget _buildStatGrid(List<_StatItem> items) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 2.2,
|
||||
children: items
|
||||
.map(
|
||||
(item) => StatTile(
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
color: item.color,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatItem {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
final Color color;
|
||||
|
||||
const _StatItem({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.color,
|
||||
});
|
||||
}
|
||||
|
||||
+156
-221
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../utils/platform_info.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -8,14 +7,11 @@ import 'package:provider/provider.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../services/linux_ble_error_classifier.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/device_tile.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import 'channels_screen.dart';
|
||||
import 'contacts_screen.dart';
|
||||
import 'tcp_screen.dart';
|
||||
import 'usb_screen.dart';
|
||||
|
||||
@@ -29,7 +25,6 @@ class ScannerScreen extends StatefulWidget {
|
||||
|
||||
class _ScannerScreenState extends State<ScannerScreen> {
|
||||
bool _changedNavigation = false;
|
||||
String? _connectingDeviceId;
|
||||
late final MeshCoreConnector _connector;
|
||||
late final VoidCallback _connectionListener;
|
||||
BluetoothAdapterState _bluetoothState = BluetoothAdapterState.unknown;
|
||||
@@ -51,7 +46,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
_changedNavigation = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => const ChannelsScreen()),
|
||||
MaterialPageRoute(builder: (context) => const ContactsScreen()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -106,32 +101,6 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
title: AdaptiveAppBarTitle(context.l10n.scanner_title),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [
|
||||
if (PlatformInfo.supportsUsbSerial)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.usb),
|
||||
tooltip: context.l10n.connectionChoiceUsbLabel,
|
||||
onPressed: () {
|
||||
appLogger.info(
|
||||
'USB selected, opening UsbScreen',
|
||||
tag: 'ScannerScreen',
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => const UsbScreen()));
|
||||
},
|
||||
),
|
||||
if (!PlatformInfo.isWeb)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.lan),
|
||||
tooltip: context.l10n.connectionChoiceTcpLabel,
|
||||
onPressed: () {
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => const TcpScreen()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
@@ -139,21 +108,12 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
builder: (context, connector, child) {
|
||||
return Column(
|
||||
children: [
|
||||
// Bluetooth off warning — slides in/out with AnimatedSize
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: _bluetoothState == BluetoothAdapterState.off
|
||||
? _BluetoothOffBanner(
|
||||
onEnable: PlatformInfo.isAndroid
|
||||
? () => FlutterBluePlus.turnOn()
|
||||
: null,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
// Bluetooth off warning
|
||||
if (_bluetoothState == BluetoothAdapterState.off)
|
||||
_bluetoothOffWarning(context),
|
||||
|
||||
// Connection status header
|
||||
_ConnectionStatusHeader(connector: connector),
|
||||
// Status bar
|
||||
_buildStatusBar(context, connector),
|
||||
|
||||
// Device list
|
||||
Expanded(child: _buildDeviceList(context, connector)),
|
||||
@@ -162,43 +122,84 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: Consumer<MeshCoreConnector>(
|
||||
bottomNavigationBar: Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, child) {
|
||||
final isScanning =
|
||||
connector.state == MeshCoreConnectionState.scanning;
|
||||
final isBluetoothOff = _bluetoothState == BluetoothAdapterState.off;
|
||||
final usbSupported = PlatformInfo.supportsUsbSerial;
|
||||
final tcpSupported = !PlatformInfo.isWeb;
|
||||
|
||||
return FloatingActionButton.extended(
|
||||
heroTag: 'scanner_ble_action',
|
||||
onPressed: isBluetoothOff
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.lightImpact();
|
||||
_toggleScan(connector);
|
||||
},
|
||||
icon: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
transitionBuilder: (child, anim) =>
|
||||
ScaleTransition(scale: anim, child: child),
|
||||
child: isScanning
|
||||
? SizedBox(
|
||||
key: const ValueKey('scanning'),
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.bluetooth_searching,
|
||||
key: ValueKey('idle'),
|
||||
return SafeArea(
|
||||
top: false,
|
||||
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (usbSupported)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
appLogger.info(
|
||||
'USB selected, opening UsbScreen',
|
||||
tag: 'ScannerScreen',
|
||||
);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const UsbScreen()),
|
||||
);
|
||||
},
|
||||
heroTag: 'scanner_usb_action',
|
||||
icon: const Icon(Icons.usb),
|
||||
label: Text(context.l10n.connectionChoiceUsbLabel),
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
isScanning
|
||||
? context.l10n.scanner_stop
|
||||
: context.l10n.scanner_scan,
|
||||
if (usbSupported) const SizedBox(width: 12),
|
||||
if (tcpSupported)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const TcpScreen()),
|
||||
);
|
||||
},
|
||||
heroTag: 'scanner_tcp_action',
|
||||
icon: const Icon(Icons.lan),
|
||||
label: Text(context.l10n.connectionChoiceTcpLabel),
|
||||
),
|
||||
if (tcpSupported) const SizedBox(width: 12),
|
||||
FloatingActionButton.extended(
|
||||
heroTag: 'scanner_ble_action',
|
||||
onPressed: isBluetoothOff
|
||||
? null
|
||||
: () {
|
||||
if (isScanning) {
|
||||
connector.stopScan();
|
||||
} else {
|
||||
unawaited(
|
||||
connector.startScan().catchError((e) {
|
||||
appLogger.warn(
|
||||
'startScan error: $e',
|
||||
tag: 'ScannerScreen',
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: isScanning
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.bluetooth_searching),
|
||||
label: Text(
|
||||
isScanning
|
||||
? context.l10n.scanner_stop
|
||||
: context.l10n.scanner_scan,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -206,70 +207,79 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleScan(MeshCoreConnector connector) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
// flutter_blue_plus has no web backend, so a BLE scan silently no-ops in
|
||||
// the browser. Tell the user instead of leaving them staring at a button.
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.scanner_bluetoothWebUnsupported),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (connector.state == MeshCoreConnectionState.scanning) {
|
||||
connector.stopScan();
|
||||
} else {
|
||||
unawaited(
|
||||
connector.startScan().catchError((e) {
|
||||
appLogger.warn('startScan error: $e', tag: 'ScannerScreen');
|
||||
}),
|
||||
);
|
||||
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
|
||||
String statusText;
|
||||
Color statusColor;
|
||||
|
||||
final l10n = context.l10n;
|
||||
switch (connector.state) {
|
||||
case MeshCoreConnectionState.scanning:
|
||||
statusText = l10n.scanner_scanning;
|
||||
statusColor = Colors.blue;
|
||||
break;
|
||||
case MeshCoreConnectionState.connecting:
|
||||
statusText = l10n.scanner_connecting;
|
||||
statusColor = Colors.orange;
|
||||
break;
|
||||
case MeshCoreConnectionState.connected:
|
||||
statusText = l10n.scanner_connectedTo(connector.deviceDisplayName);
|
||||
statusColor = Colors.green;
|
||||
break;
|
||||
case MeshCoreConnectionState.disconnecting:
|
||||
statusText = l10n.scanner_disconnecting;
|
||||
statusColor = Colors.orange;
|
||||
break;
|
||||
case MeshCoreConnectionState.disconnected:
|
||||
statusText = l10n.scanner_notConnected;
|
||||
statusColor = Colors.grey;
|
||||
break;
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
color: statusColor.withValues(alpha: 0.1),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.circle, size: 12, color: statusColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
statusText,
|
||||
style: TextStyle(color: statusColor, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceList(BuildContext context, MeshCoreConnector connector) {
|
||||
if (connector.scanResults.isEmpty) {
|
||||
final isBluetoothOff = _bluetoothState == BluetoothAdapterState.off;
|
||||
final isScanning = connector.state == MeshCoreConnectionState.scanning;
|
||||
return EmptyState(
|
||||
icon: isBluetoothOff ? Icons.bluetooth_disabled : Icons.bluetooth,
|
||||
title: isBluetoothOff
|
||||
? context.l10n.scanner_bluetoothOff
|
||||
: isScanning
|
||||
? context.l10n.scanner_searchingDevices
|
||||
: context.l10n.scanner_tapToScan,
|
||||
subtitle: isBluetoothOff
|
||||
? context.l10n.scanner_bluetoothOffMessage
|
||||
: null,
|
||||
action: (isBluetoothOff || isScanning)
|
||||
? null
|
||||
: FilledButton.icon(
|
||||
onPressed: () {
|
||||
HapticFeedback.lightImpact();
|
||||
_toggleScan(connector);
|
||||
},
|
||||
icon: const Icon(Icons.bluetooth_searching),
|
||||
label: Text(context.l10n.scanner_scan),
|
||||
),
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.bluetooth, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
connector.state == MeshCoreConnectionState.scanning
|
||||
? context.l10n.scanner_searchingDevices
|
||||
: context.l10n.scanner_tapToScan,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final isConnecting = connector.state == MeshCoreConnectionState.connecting;
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 96),
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: connector.scanResults.length,
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemBuilder: (context, index) {
|
||||
final result = connector.scanResults[index];
|
||||
final deviceId = result.device.remoteId.toString();
|
||||
return ListEntrance(
|
||||
index: index,
|
||||
child: DeviceTile(
|
||||
scanResult: result,
|
||||
isConnecting: isConnecting && _connectingDeviceId == deviceId,
|
||||
onTap: isConnecting
|
||||
? null
|
||||
: () => _connectToDevice(context, connector, result),
|
||||
),
|
||||
return DeviceTile(
|
||||
scanResult: result,
|
||||
onTap: () => _connectToDevice(context, connector, result),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -283,9 +293,6 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
final name = result.device.platformName.isNotEmpty
|
||||
? result.device.platformName
|
||||
: result.advertisementData.advName;
|
||||
setState(() {
|
||||
_connectingDeviceId = result.device.remoteId.toString();
|
||||
});
|
||||
try {
|
||||
await connector.connect(
|
||||
result.device,
|
||||
@@ -314,15 +321,9 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.scanner_connectionFailed(e.toString())),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_connectingDeviceId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,113 +412,47 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
||||
);
|
||||
return pin;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Private sub-widgets ────────────────────────────────────────────────────
|
||||
|
||||
/// Bluetooth-off warning banner — styled as an alert MeshCard.
|
||||
class _BluetoothOffBanner extends StatelessWidget {
|
||||
final VoidCallback? onEnable;
|
||||
|
||||
const _BluetoothOffBanner({this.onEnable});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return MeshCard(
|
||||
color: scheme.error.withValues(alpha: 0.08),
|
||||
borderColor: scheme.error.withValues(alpha: 0.35),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
Widget _bluetoothOffWarning(BuildContext context) {
|
||||
final errorColor = Theme.of(context).colorScheme.error;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
color: errorColor.withValues(alpha: 0.15),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.bluetooth_disabled, size: 20, color: scheme.error),
|
||||
const SizedBox(width: 10),
|
||||
Icon(Icons.bluetooth_disabled, size: 24, color: errorColor),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.scanner_bluetoothOff,
|
||||
style: TextStyle(
|
||||
color: scheme.error,
|
||||
color: errorColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13.5,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.l10n.scanner_bluetoothOffMessage,
|
||||
style: TextStyle(
|
||||
color: scheme.error.withValues(alpha: 0.8),
|
||||
color: errorColor.withValues(alpha: 0.85),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (onEnable != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
if (PlatformInfo.isAndroid)
|
||||
TextButton(
|
||||
onPressed: onEnable,
|
||||
onPressed: () => FlutterBluePlus.turnOn(),
|
||||
child: Text(context.l10n.scanner_enableBluetooth),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection status header with AnimatedSwitcher between states.
|
||||
class _ConnectionStatusHeader extends StatelessWidget {
|
||||
final MeshCoreConnector connector;
|
||||
|
||||
const _ConnectionStatusHeader({required this.connector});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
final (String label, Color color, bool pulse) = switch (connector.state) {
|
||||
MeshCoreConnectionState.scanning => (
|
||||
l10n.scanner_scanning,
|
||||
MeshPalette.blue,
|
||||
true,
|
||||
),
|
||||
MeshCoreConnectionState.connecting => (
|
||||
l10n.scanner_connecting,
|
||||
MeshPalette.warn,
|
||||
true,
|
||||
),
|
||||
MeshCoreConnectionState.connected => (
|
||||
l10n.scanner_connectedTo(connector.deviceDisplayName),
|
||||
MeshPalette.signal,
|
||||
false,
|
||||
),
|
||||
MeshCoreConnectionState.disconnecting => (
|
||||
l10n.scanner_disconnecting,
|
||||
MeshPalette.warn,
|
||||
true,
|
||||
),
|
||||
MeshCoreConnectionState.disconnected => (
|
||||
l10n.scanner_notConnected,
|
||||
scheme.onSurfaceVariant,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Align(
|
||||
key: ValueKey(connector.state),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: StatusChip(label: label, color: color, pulse: pulse),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+446
-653
File diff suppressed because it is too large
Load Diff
+81
-112
@@ -1,18 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../utils/platform_info.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import 'channels_screen.dart';
|
||||
import 'contacts_screen.dart';
|
||||
import 'usb_screen.dart';
|
||||
|
||||
class TcpScreen extends StatefulWidget {
|
||||
@@ -27,7 +24,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
late final TextEditingController _portController;
|
||||
late final MeshCoreConnector _connector;
|
||||
late final VoidCallback _connectionListener;
|
||||
bool _navigatedToChannels = false;
|
||||
bool _navigatedToContacts = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -45,20 +42,20 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
_connectionListener = () {
|
||||
if (!mounted) return;
|
||||
if (_connector.state == MeshCoreConnectionState.disconnected) {
|
||||
_navigatedToChannels = false;
|
||||
_navigatedToContacts = false;
|
||||
}
|
||||
if (_connector.state == MeshCoreConnectionState.connected &&
|
||||
_connector.isTcpTransportConnected &&
|
||||
!_navigatedToChannels) {
|
||||
!_navigatedToContacts) {
|
||||
context.read<AppSettingsService>().setTcpServerAddress(
|
||||
_hostController.text,
|
||||
);
|
||||
context.read<AppSettingsService>().setTcpServerPort(
|
||||
int.tryParse(_portController.text) ?? 0,
|
||||
);
|
||||
_navigatedToChannels = true;
|
||||
_navigatedToContacts = true;
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
|
||||
MaterialPageRoute(builder: (_) => const ContactsScreen()),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -70,7 +67,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
_hostController.dispose();
|
||||
_portController.dispose();
|
||||
_connector.removeListener(_connectionListener);
|
||||
if (!_navigatedToChannels &&
|
||||
if (!_navigatedToContacts &&
|
||||
_connector.activeTransport == MeshCoreTransportType.tcp &&
|
||||
_connector.state != MeshCoreConnectionState.disconnected) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -98,32 +95,13 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
final isConnecting =
|
||||
connector.state == MeshCoreConnectionState.connecting &&
|
||||
connector.activeTransport == MeshCoreTransportType.tcp;
|
||||
// Connect is only available from a fully disconnected state —
|
||||
// scanning, connecting, or an active session must settle first.
|
||||
final isButtonDisabled =
|
||||
connector.state != MeshCoreConnectionState.disconnected;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
isConnecting ||
|
||||
connector.state == MeshCoreConnectionState.scanning;
|
||||
return Column(
|
||||
children: [
|
||||
// Status header
|
||||
_buildStatusBar(context, connector),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Align(
|
||||
key: ValueKey(connector.state),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _buildStatusChip(context, connector),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Transport switcher
|
||||
_buildTransportLinks(context),
|
||||
|
||||
// Connection form
|
||||
const SectionHeader('TCP / IP'),
|
||||
MeshCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@@ -133,6 +111,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.tcpHostLabel,
|
||||
hintText: context.l10n.tcpHostHint,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
enabled: !isConnecting,
|
||||
keyboardType: TextInputType.url,
|
||||
@@ -143,6 +122,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.tcpPortLabel,
|
||||
hintText: context.l10n.tcpPortHint,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
enabled: !isConnecting,
|
||||
keyboardType: TextInputType.number,
|
||||
@@ -150,12 +130,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
key: const Key('tcp_connect_button'),
|
||||
onPressed: isButtonDisabled
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.lightImpact();
|
||||
_connectTcp();
|
||||
},
|
||||
onPressed: isButtonDisabled ? null : _connectTcp,
|
||||
icon: isConnecting
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
@@ -174,100 +149,94 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Last used endpoint
|
||||
if (connector.activeTcpEndpoint != null &&
|
||||
connector.isTcpTransportConnected) ...[
|
||||
const SectionHeader('CONNECTED TO'),
|
||||
MeshCard(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.lan,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
connector.activeTcpEndpoint!,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (PlatformInfo.supportsUsbSerial)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const UsbScreen()),
|
||||
);
|
||||
},
|
||||
heroTag: 'tcp_usb_action',
|
||||
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
icon: const Icon(Icons.usb),
|
||||
label: Text(context.l10n.connectionChoiceUsbLabel),
|
||||
),
|
||||
if (PlatformInfo.supportsUsbSerial) const SizedBox(width: 12),
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
heroTag: 'tcp_ble_action',
|
||||
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
icon: const Icon(Icons.bluetooth),
|
||||
label: Text(context.l10n.connectionChoiceBluetoothLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
String statusText;
|
||||
Color statusColor;
|
||||
|
||||
if (connector.isTcpTransportConnected) {
|
||||
return StatusChip(
|
||||
label: l10n.scanner_connectedTo(connector.activeTcpEndpoint ?? 'TCP'),
|
||||
color: MeshPalette.signal,
|
||||
statusText = l10n.scanner_connectedTo(
|
||||
connector.activeTcpEndpoint ?? 'TCP',
|
||||
);
|
||||
statusColor = Colors.green;
|
||||
} else if (connector.state == MeshCoreConnectionState.connecting &&
|
||||
connector.activeTransport == MeshCoreTransportType.tcp) {
|
||||
return StatusChip(
|
||||
label: l10n.tcpStatus_connectingTo(
|
||||
'${_hostController.text}:${_portController.text}',
|
||||
),
|
||||
color: MeshPalette.warn,
|
||||
pulse: true,
|
||||
statusText = l10n.tcpStatus_connectingTo(
|
||||
'${_hostController.text}:${_portController.text}',
|
||||
);
|
||||
statusColor = Colors.orange;
|
||||
} else if (connector.state == MeshCoreConnectionState.disconnecting &&
|
||||
connector.activeTransport == MeshCoreTransportType.tcp) {
|
||||
return StatusChip(
|
||||
label: l10n.scanner_disconnecting,
|
||||
color: MeshPalette.warn,
|
||||
pulse: true,
|
||||
);
|
||||
statusText = l10n.scanner_disconnecting;
|
||||
statusColor = Colors.orange;
|
||||
} else {
|
||||
return StatusChip(
|
||||
label: l10n.tcpStatus_notConnected,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
);
|
||||
statusText = l10n.tcpStatus_notConnected;
|
||||
statusColor = Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTransportLinks(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
color: statusColor.withValues(alpha: 0.1),
|
||||
child: Row(
|
||||
children: [
|
||||
if (PlatformInfo.supportsUsbSerial)
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const UsbScreen()),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.usb),
|
||||
label: Text(context.l10n.connectionChoiceUsbLabel),
|
||||
Icon(Icons.circle, size: 12, color: statusColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
icon: const Icon(Icons.bluetooth),
|
||||
label: Text(context.l10n.connectionChoiceBluetoothLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -305,7 +274,7 @@ class _TcpScreenState extends State<TcpScreen> {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(message),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+139
-626
@@ -1,26 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/path_selection.dart';
|
||||
import '../models/app_settings.dart';
|
||||
import '../storage/prefs_manager.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../widgets/routing_sheet.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../helpers/cayenne_lpp.dart';
|
||||
import '../utils/battery_utils.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import '../widgets/sync_progress_overlay.dart';
|
||||
import '../widgets/telemetry_location_map.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
|
||||
class TelemetryScreen extends StatefulWidget {
|
||||
final Contact contact;
|
||||
@@ -32,13 +26,6 @@ class TelemetryScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
static const int _autoRefreshDefaultIntervalSeconds = 20;
|
||||
static const int _autoRefreshDefaultQuantity = 10;
|
||||
static const int _autoRefreshMinIntervalSeconds = 10;
|
||||
static const int _autoRefreshMaxIntervalSeconds = 300;
|
||||
static const int _autoRefreshMinQuantity = 1;
|
||||
static const int _autoRefreshMaxQuantity = 10;
|
||||
|
||||
int _tagData = 0;
|
||||
|
||||
bool _isLoading = false;
|
||||
@@ -49,17 +36,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
RepeaterCommandService? _commandService;
|
||||
PathSelection? _pendingStatusSelection;
|
||||
List<Map<String, dynamic>>? _parsedTelemetry;
|
||||
final TextEditingController _autoRefreshIntervalController =
|
||||
TextEditingController(text: '$_autoRefreshDefaultIntervalSeconds');
|
||||
final TextEditingController _autoRefreshQuantityController =
|
||||
TextEditingController(text: '$_autoRefreshDefaultQuantity');
|
||||
Timer? _autoRefreshTimer;
|
||||
bool _isAutoRefreshEnabled = false;
|
||||
bool _activeTelemetryRequestIsAutoRefresh = false;
|
||||
bool _autoRefreshLastAttemptFailed = false;
|
||||
int _autoRefreshCurrentAttempt = 0;
|
||||
int _autoRefreshTotalAttempts = 0;
|
||||
int _autoRefreshIntervalSeconds = _autoRefreshDefaultIntervalSeconds;
|
||||
|
||||
int _tripTime = 0;
|
||||
|
||||
@@ -86,7 +62,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
super.initState();
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
_commandService = RepeaterCommandService(connector);
|
||||
_loadAutoRefreshSettings();
|
||||
_setupMessageListener();
|
||||
_loadTelemetry();
|
||||
_hasData = false;
|
||||
@@ -106,26 +81,17 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
_tagData = reader.readUInt32LE();
|
||||
_tripTime = reader.readUInt32LE();
|
||||
_statusTimeout?.cancel();
|
||||
final isAutoRefreshRequest = _activeTelemetryRequestIsAutoRefresh;
|
||||
_statusTimeout = Timer(Duration(milliseconds: _tripTime), () {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isLoaded = false;
|
||||
if (isAutoRefreshRequest && _isAutoRefreshEnabled) {
|
||||
_autoRefreshLastAttemptFailed = true;
|
||||
}
|
||||
});
|
||||
if (!isAutoRefreshRequest) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_requestTimeout),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
);
|
||||
}
|
||||
if (isAutoRefreshRequest && _isAutoRefreshEnabled) {
|
||||
_scheduleNextAutoRefreshAttempt();
|
||||
}
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_requestTimeout),
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
_recordTelemetryResult(false);
|
||||
});
|
||||
}
|
||||
@@ -167,21 +133,15 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
);
|
||||
}
|
||||
if (!mounted) return;
|
||||
final isAutoRefreshRequest = _activeTelemetryRequestIsAutoRefresh;
|
||||
setState(() {
|
||||
_parsedTelemetry = parsedTelemetry;
|
||||
if (isAutoRefreshRequest) {
|
||||
_autoRefreshLastAttemptFailed = false;
|
||||
}
|
||||
_activeTelemetryRequestIsAutoRefresh = false;
|
||||
});
|
||||
|
||||
if (!isAutoRefreshRequest) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_receivedData),
|
||||
);
|
||||
}
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_receivedData),
|
||||
backgroundColor: Colors.green,
|
||||
);
|
||||
_statusTimeout?.cancel();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@@ -189,18 +149,14 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
_isLoaded = true;
|
||||
_hasData = true;
|
||||
});
|
||||
if (isAutoRefreshRequest) {
|
||||
_scheduleNextAutoRefreshAttempt();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadTelemetry({bool isAutoRefresh = false}) async {
|
||||
Future<void> _loadTelemetry() async {
|
||||
if (_commandService == null) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_isLoaded = false;
|
||||
_activeTelemetryRequestIsAutoRefresh = isAutoRefresh;
|
||||
});
|
||||
try {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
@@ -212,7 +168,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
if (widget.contact.type != advTypeChat) {
|
||||
frame = buildSendBinaryReq(
|
||||
widget.contact.publicKey,
|
||||
payload: buildTelemetryBinaryPayload(),
|
||||
payload: Uint8List.fromList([reqTypeGetTelemetry]),
|
||||
);
|
||||
} else {
|
||||
frame = buildSendTelemetryReq(widget.contact.publicKey);
|
||||
@@ -223,76 +179,17 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isLoaded = false;
|
||||
if (isAutoRefresh) {
|
||||
_autoRefreshLastAttemptFailed = true;
|
||||
}
|
||||
_activeTelemetryRequestIsAutoRefresh = false;
|
||||
});
|
||||
if (isAutoRefresh) {
|
||||
_scheduleNextAutoRefreshAttempt();
|
||||
}
|
||||
|
||||
if (!isAutoRefresh) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
);
|
||||
}
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _loadAutoRefreshSettings() {
|
||||
final prefs = PrefsManager.instance;
|
||||
final contactKey = widget.contact.publicKeyHex;
|
||||
final interval =
|
||||
(prefs.getInt(_autoRefreshIntervalKey(contactKey)) ??
|
||||
_autoRefreshDefaultIntervalSeconds)
|
||||
.clamp(
|
||||
_autoRefreshMinIntervalSeconds,
|
||||
_autoRefreshMaxIntervalSeconds,
|
||||
)
|
||||
.toInt();
|
||||
final quantity =
|
||||
(prefs.getInt(_autoRefreshQuantityKey(contactKey)) ??
|
||||
_autoRefreshDefaultQuantity)
|
||||
.clamp(_autoRefreshMinQuantity, _autoRefreshMaxQuantity)
|
||||
.toInt();
|
||||
|
||||
_autoRefreshIntervalSeconds = interval;
|
||||
_autoRefreshIntervalController.text = interval.toString();
|
||||
_autoRefreshQuantityController.text = quantity.toString();
|
||||
}
|
||||
|
||||
Future<void> _saveAutoRefreshSettings() async {
|
||||
final contactKey = widget.contact.publicKeyHex;
|
||||
final interval = _clampControllerValue(
|
||||
controller: _autoRefreshIntervalController,
|
||||
min: _autoRefreshMinIntervalSeconds,
|
||||
max: _autoRefreshMaxIntervalSeconds,
|
||||
fallback: _autoRefreshIntervalSeconds,
|
||||
);
|
||||
final quantity = _clampControllerValue(
|
||||
controller: _autoRefreshQuantityController,
|
||||
min: _autoRefreshMinQuantity,
|
||||
max: _autoRefreshMaxQuantity,
|
||||
fallback: _autoRefreshDefaultQuantity,
|
||||
);
|
||||
|
||||
final prefs = PrefsManager.instance;
|
||||
await prefs.setInt(_autoRefreshIntervalKey(contactKey), interval);
|
||||
await prefs.setInt(_autoRefreshQuantityKey(contactKey), quantity);
|
||||
}
|
||||
|
||||
String _autoRefreshIntervalKey(String contactKey) {
|
||||
return 'telemetry_auto_refresh_interval_$contactKey';
|
||||
}
|
||||
|
||||
String _autoRefreshQuantityKey(String contactKey) {
|
||||
return 'telemetry_auto_refresh_quantity_$contactKey';
|
||||
}
|
||||
|
||||
void _recordTelemetryResult(bool success) {
|
||||
final selection = _pendingStatusSelection;
|
||||
if (selection == null) return;
|
||||
@@ -308,28 +205,19 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_saveAutoRefreshSettings());
|
||||
_frameSubscription?.cancel();
|
||||
_commandService?.dispose();
|
||||
_statusTimeout?.cancel();
|
||||
_autoRefreshTimer?.cancel();
|
||||
_autoRefreshIntervalController.dispose();
|
||||
_autoRefreshQuantityController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final settings = context.watch<AppSettingsService>().settings;
|
||||
final isImperialUnits = settings.unitSystem == UnitSystem.imperial;
|
||||
final contact = connector.contacts.firstWhere(
|
||||
(c) => c.publicKeyHex == widget.contact.publicKeyHex,
|
||||
orElse: () => widget.contact,
|
||||
);
|
||||
final isFloodMode = contact.pathOverride == -1;
|
||||
final isFloodMode = widget.contact.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -351,13 +239,71 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
bottom: const SyncProgressAppBarBottom(),
|
||||
actions: [
|
||||
IconButton(
|
||||
PopupMenuButton<String>(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onSelected: (mode) async {
|
||||
if (mode == 'flood') {
|
||||
await connector.setPathOverride(widget.contact, pathLen: -1);
|
||||
} else {
|
||||
await connector.setPathOverride(widget.contact, pathLen: null);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () =>
|
||||
ContactRoutingSheet.show(context, contact: widget.contact),
|
||||
PathManagementDialog.show(context, contact: widget.contact),
|
||||
),
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
@@ -367,9 +313,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: (_isLoading || _isAutoRefreshEnabled)
|
||||
? null
|
||||
: () => _loadTelemetry(),
|
||||
onPressed: _isLoading ? null : _loadTelemetry,
|
||||
tooltip: l10n.repeater_refresh,
|
||||
),
|
||||
],
|
||||
@@ -377,8 +321,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () =>
|
||||
_isAutoRefreshEnabled ? Future.value() : _loadTelemetry(),
|
||||
onRefresh: _loadTelemetry,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
@@ -388,10 +331,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
Center(
|
||||
child: Text(
|
||||
l10n.telemetry_noData,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
style: const TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
if ((_isLoaded || _hasData) &&
|
||||
@@ -404,7 +344,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
entry['channel'],
|
||||
isImperialUnits,
|
||||
),
|
||||
_buildAutoRefreshCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -417,505 +356,86 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
String title,
|
||||
int channel,
|
||||
bool isImperialUnits,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(title, padding: const EdgeInsets.fromLTRB(16, 16, 16, 8)),
|
||||
MeshCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final entry in channelData.entries)
|
||||
_buildTelemetryField(entry, channel, isImperialUnits),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTelemetryField(
|
||||
MapEntry<String, dynamic> entry,
|
||||
int channel,
|
||||
bool isImperialUnits,
|
||||
) {
|
||||
if (entry.key == 'gps') {
|
||||
return _buildGpsInfo(entry.value);
|
||||
}
|
||||
|
||||
final display = _formatTelemetryField(
|
||||
entry.key,
|
||||
entry.value,
|
||||
channel,
|
||||
isImperialUnits,
|
||||
);
|
||||
return _buildInfoRow(display.label, display.value);
|
||||
}
|
||||
|
||||
_TelemetryFieldDisplay _formatTelemetryField(
|
||||
String key,
|
||||
dynamic value,
|
||||
int channel,
|
||||
bool isImperialUnits,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
final text = _telemetryValueText(value);
|
||||
|
||||
switch (key) {
|
||||
case 'digitalInput':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_digitalInputLabel, text);
|
||||
case 'digitalOutput':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_digitalOutputLabel, text);
|
||||
case 'analogInput':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_analogInputLabel,
|
||||
l10n.telemetry_analogValue(text),
|
||||
);
|
||||
case 'analogOutput':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_analogOutputLabel,
|
||||
l10n.telemetry_analogValue(text),
|
||||
);
|
||||
case 'generic':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_genericLabel, text);
|
||||
case 'luminosity':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_luminosityLabel,
|
||||
l10n.telemetry_luminosityValue(text),
|
||||
);
|
||||
case 'presence':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_presenceLabel, text);
|
||||
case 'temperature':
|
||||
return _TelemetryFieldDisplay(
|
||||
channel == 1
|
||||
? l10n.telemetry_mcuTemperatureLabel
|
||||
: l10n.telemetry_temperatureLabel,
|
||||
_temperatureText(value, isImperialUnits),
|
||||
);
|
||||
case 'humidity':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_humidityLabel, text);
|
||||
case 'accelerometer':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_accelerometerLabel,
|
||||
_telemetryAxisText(value),
|
||||
);
|
||||
case 'pressure':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_pressureLabel,
|
||||
l10n.telemetry_pressureValue(text),
|
||||
);
|
||||
case 'altitude':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_altitudeLabel,
|
||||
l10n.telemetry_altitudeValue(text),
|
||||
);
|
||||
case 'voltage':
|
||||
return _TelemetryFieldDisplay(
|
||||
channel == 1
|
||||
? l10n.telemetry_batteryLabel
|
||||
: l10n.telemetry_voltageLabel,
|
||||
channel == 1
|
||||
? _batteryText(value)
|
||||
: l10n.telemetry_voltageValue(text),
|
||||
);
|
||||
case 'current':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_currentLabel,
|
||||
l10n.telemetry_currentValue(text),
|
||||
);
|
||||
case 'frequency':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_frequencyLabel,
|
||||
l10n.telemetry_frequencyValue(text),
|
||||
);
|
||||
case 'percentage':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_percentageLabel,
|
||||
l10n.telemetry_percentageValue(text),
|
||||
);
|
||||
case 'concentration':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_concentrationLabel,
|
||||
l10n.telemetry_concentrationValue(text),
|
||||
);
|
||||
case 'power':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_powerLabel,
|
||||
l10n.telemetry_powerValue(text),
|
||||
);
|
||||
case 'distance':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_distanceLabel,
|
||||
l10n.telemetry_distanceValue(text),
|
||||
);
|
||||
case 'energy':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_energyLabel,
|
||||
l10n.telemetry_energyValue(text),
|
||||
);
|
||||
case 'direction':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_directionLabel,
|
||||
l10n.telemetry_directionValue(text),
|
||||
);
|
||||
case 'time':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_timeLabel,
|
||||
_telemetryTimeText(value),
|
||||
);
|
||||
case 'gyrometer':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_gyrometerLabel,
|
||||
_telemetryAxisText(value),
|
||||
);
|
||||
case 'colour':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_colourLabel,
|
||||
_telemetryColorText(value),
|
||||
);
|
||||
case 'switch':
|
||||
return _TelemetryFieldDisplay(l10n.telemetry_switchLabel, text);
|
||||
case 'polyline':
|
||||
return _TelemetryFieldDisplay(
|
||||
l10n.telemetry_polylineLabel,
|
||||
_telemetryMapText(value),
|
||||
);
|
||||
default:
|
||||
return _TelemetryFieldDisplay(key, text);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAutoRefreshCard() {
|
||||
final l10n = context.l10n;
|
||||
final counterText = _autoRefreshCounterText();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
l10n.common_autoRefresh,
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
),
|
||||
MeshCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildAutoRefreshNumberField(
|
||||
controller: _autoRefreshIntervalController,
|
||||
label: l10n.common_interval,
|
||||
min: _autoRefreshMinIntervalSeconds,
|
||||
max: _autoRefreshMaxIntervalSeconds,
|
||||
fallback: _autoRefreshIntervalSeconds,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildAutoRefreshNumberField(
|
||||
controller: _autoRefreshQuantityController,
|
||||
label: l10n.telemetry_autoFetchQuantity,
|
||||
min: _autoRefreshMinQuantity,
|
||||
max: _autoRefreshMaxQuantity,
|
||||
fallback: _autoRefreshDefaultQuantity,
|
||||
),
|
||||
if (counterText != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
counterText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _autoRefreshLastAttemptFailed
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
fontWeight: FontWeight.w600,
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: _isLoading && !_isAutoRefreshEnabled
|
||||
? null
|
||||
: _toggleAutoRefresh,
|
||||
child: _isAutoRefreshEnabled
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
height: 20,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Center(child: Text(l10n.common_disable)),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(l10n.common_enable),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
for (final entry in channelData.entries)
|
||||
if (entry.key == 'voltage' && channel == 1)
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_batteryLabel,
|
||||
_batteryText(entry.value),
|
||||
)
|
||||
else if (entry.key == 'voltage')
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_voltageLabel,
|
||||
l10n.telemetry_voltageValue(entry.value.toString()),
|
||||
)
|
||||
else if (entry.key == 'temperature' && channel == 1)
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_mcuTemperatureLabel,
|
||||
_temperatureText(entry.value, isImperialUnits),
|
||||
)
|
||||
else if (entry.key == 'temperature')
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_temperatureLabel,
|
||||
_temperatureText(entry.value, isImperialUnits),
|
||||
)
|
||||
else if (entry.key == 'current' && channel == 1)
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_currentLabel,
|
||||
l10n.telemetry_currentValue(entry.value.toString()),
|
||||
)
|
||||
else
|
||||
_buildInfoRow(entry.key, entry.value.toString()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutoRefreshNumberField({
|
||||
required TextEditingController controller,
|
||||
required String label,
|
||||
required int min,
|
||||
required int max,
|
||||
required int fallback,
|
||||
}) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
enabled: !_isAutoRefreshEnabled,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
border: const OutlineInputBorder(),
|
||||
isDense: true,
|
||||
),
|
||||
onEditingComplete: () {
|
||||
_clampControllerValue(
|
||||
controller: controller,
|
||||
min: min,
|
||||
max: max,
|
||||
fallback: fallback,
|
||||
);
|
||||
unawaited(_saveAutoRefreshSettings());
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onSubmitted: (_) => unawaited(_saveAutoRefreshSettings()),
|
||||
onTapOutside: (_) {
|
||||
unawaited(_saveAutoRefreshSettings());
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String? _autoRefreshCounterText() {
|
||||
if (!_isAutoRefreshEnabled && _autoRefreshCurrentAttempt == 0) return null;
|
||||
final counter = '$_autoRefreshCurrentAttempt/$_autoRefreshTotalAttempts';
|
||||
if (_autoRefreshLastAttemptFailed) {
|
||||
return '${context.l10n.telemetry_error}: $counter';
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void _toggleAutoRefresh() {
|
||||
if (_isAutoRefreshEnabled) {
|
||||
_stopAutoRefresh();
|
||||
return;
|
||||
}
|
||||
_startAutoRefresh();
|
||||
}
|
||||
|
||||
void _startAutoRefresh() {
|
||||
final interval = _clampControllerValue(
|
||||
controller: _autoRefreshIntervalController,
|
||||
min: _autoRefreshMinIntervalSeconds,
|
||||
max: _autoRefreshMaxIntervalSeconds,
|
||||
fallback: _autoRefreshIntervalSeconds,
|
||||
);
|
||||
final quantity = _clampControllerValue(
|
||||
controller: _autoRefreshQuantityController,
|
||||
min: _autoRefreshMinQuantity,
|
||||
max: _autoRefreshMaxQuantity,
|
||||
fallback: _autoRefreshDefaultQuantity,
|
||||
);
|
||||
unawaited(_saveAutoRefreshSettings());
|
||||
|
||||
setState(() {
|
||||
_isAutoRefreshEnabled = true;
|
||||
_autoRefreshIntervalSeconds = interval;
|
||||
_autoRefreshTotalAttempts = quantity;
|
||||
_autoRefreshCurrentAttempt = 0;
|
||||
_autoRefreshLastAttemptFailed = false;
|
||||
});
|
||||
_runAutoRefreshAttempt();
|
||||
}
|
||||
|
||||
void _stopAutoRefresh() {
|
||||
_autoRefreshTimer?.cancel();
|
||||
_autoRefreshTimer = null;
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isAutoRefreshEnabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _runAutoRefreshAttempt() async {
|
||||
if (!_isAutoRefreshEnabled || !mounted) return;
|
||||
if (_autoRefreshCurrentAttempt >= _autoRefreshTotalAttempts) {
|
||||
_stopAutoRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_autoRefreshCurrentAttempt += 1;
|
||||
});
|
||||
await _loadTelemetry(isAutoRefresh: true);
|
||||
}
|
||||
|
||||
void _scheduleNextAutoRefreshAttempt() {
|
||||
if (!_isAutoRefreshEnabled || !mounted) return;
|
||||
_autoRefreshTimer?.cancel();
|
||||
if (_autoRefreshCurrentAttempt >= _autoRefreshTotalAttempts) {
|
||||
_stopAutoRefresh();
|
||||
return;
|
||||
}
|
||||
// Start the interval only after the current request has finished: after a
|
||||
// telemetry response, timeout, or send error. This keeps slow replies from
|
||||
// shortening the intended pause between requests.
|
||||
_autoRefreshTimer = Timer(
|
||||
Duration(seconds: _autoRefreshIntervalSeconds),
|
||||
_runAutoRefreshAttempt,
|
||||
);
|
||||
}
|
||||
|
||||
int _clampControllerValue({
|
||||
required TextEditingController controller,
|
||||
required int min,
|
||||
required int max,
|
||||
required int fallback,
|
||||
}) {
|
||||
final parsed = int.tryParse(controller.text);
|
||||
final value = (parsed ?? fallback).clamp(min, max).toInt();
|
||||
controller.text = value.toString();
|
||||
controller.selection = TextSelection.collapsed(
|
||||
offset: controller.text.length,
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
Widget _buildGpsInfo(dynamic value) {
|
||||
final latitude = _readGpsValue(value, 'latitude');
|
||||
final longitude = _readGpsValue(value, 'longitude');
|
||||
final altitude = _readGpsValue(value, 'altitude');
|
||||
final isValidPosition = _isValidGpsPosition(latitude, longitude);
|
||||
final gpsText = isValidPosition
|
||||
? [
|
||||
latitude!.toStringAsFixed(5),
|
||||
longitude!.toStringAsFixed(5),
|
||||
if (altitude != null) '${altitude.toStringAsFixed(1)} m',
|
||||
].join(', ')
|
||||
: value.toString();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(context.l10n.telemetry_gpsLabel, gpsText),
|
||||
if (isValidPosition)
|
||||
TelemetryLocationMap(
|
||||
// The map renders only after bounds validation, keeping malformed
|
||||
// Cayenne payloads from creating an invalid FlutterMap center.
|
||||
latitude: latitude!,
|
||||
longitude: longitude!,
|
||||
label: widget.contact.name,
|
||||
contactType: widget.contact.type,
|
||||
contactPublicKeyHex: widget.contact.publicKeyHex,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
double? _readGpsValue(dynamic value, String key) {
|
||||
if (value is! Map) return null;
|
||||
final rawValue = value[key];
|
||||
if (rawValue is num) return rawValue.toDouble();
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isValidGpsPosition(double? latitude, double? longitude) {
|
||||
if (latitude == null || longitude == null) return false;
|
||||
const double epsilon = 1e-6;
|
||||
return (latitude.abs() > epsilon || longitude.abs() > epsilon) &&
|
||||
latitude >= -90.0 &&
|
||||
latitude <= 90.0 &&
|
||||
longitude >= -180.0 &&
|
||||
longitude <= 180.0;
|
||||
}
|
||||
|
||||
String _telemetryValueText(dynamic value) {
|
||||
if (value == null) return context.l10n.common_notAvailable;
|
||||
if (value is double) {
|
||||
return value.toStringAsFixed(value.truncateToDouble() == value ? 0 : 2);
|
||||
}
|
||||
if (value is num) {
|
||||
return value.toString();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
String _telemetryAxisText(dynamic value) {
|
||||
if (value is! Map) return _telemetryValueText(value);
|
||||
final x = _telemetryValueText(value['x']);
|
||||
final y = _telemetryValueText(value['y']);
|
||||
final z = _telemetryValueText(value['z']);
|
||||
return 'X: $x, Y: $y, Z: $z';
|
||||
}
|
||||
|
||||
String _telemetryColorText(dynamic value) {
|
||||
if (value is! Map) return _telemetryValueText(value);
|
||||
final red = _telemetryValueText(value['red']);
|
||||
final green = _telemetryValueText(value['green']);
|
||||
final blue = _telemetryValueText(value['blue']);
|
||||
return 'R: $red, G: $green, B: $blue';
|
||||
}
|
||||
|
||||
String _telemetryMapText(dynamic value) {
|
||||
if (value is! Map) return _telemetryValueText(value);
|
||||
return value.entries
|
||||
.map((entry) => '${entry.key}: ${entry.value}')
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
String _telemetryTimeText(dynamic value) {
|
||||
if (value is! num || value <= 0) return _telemetryValueText(value);
|
||||
final dateTime = DateTime.fromMillisecondsSinceEpoch(
|
||||
value.toInt() * 1000,
|
||||
isUtc: true,
|
||||
).toLocal();
|
||||
final localizations = MaterialLocalizations.of(context);
|
||||
final time = localizations.formatTimeOfDay(
|
||||
TimeOfDay.fromDateTime(dateTime),
|
||||
);
|
||||
return '${localizations.formatFullDate(dateTime)} $time';
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
SizedBox(
|
||||
width: 130,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: scheme.onSurfaceVariant,
|
||||
fontSize: 13,
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
value,
|
||||
style: MeshTheme.mono(fontSize: 13, color: scheme.onSurface),
|
||||
textAlign: TextAlign.end,
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -963,10 +483,3 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
return '${tempC.toStringAsFixed(1)}°C';
|
||||
}
|
||||
}
|
||||
|
||||
class _TelemetryFieldDisplay {
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
const _TelemetryFieldDisplay(this.label, this.value);
|
||||
}
|
||||
|
||||
+149
-157
@@ -6,15 +6,13 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/platform_info.dart';
|
||||
import '../utils/usb_port_labels.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/mesh_ui.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import 'channels_screen.dart';
|
||||
import 'contacts_screen.dart';
|
||||
import 'scanner_screen.dart';
|
||||
import 'tcp_screen.dart';
|
||||
|
||||
class UsbScreen extends StatefulWidget {
|
||||
@@ -27,7 +25,7 @@ class UsbScreen extends StatefulWidget {
|
||||
class _UsbScreenState extends State<UsbScreen> {
|
||||
final List<String> _ports = <String>[];
|
||||
bool _isLoadingPorts = true;
|
||||
bool _navigatedToChannels = false;
|
||||
bool _navigatedToContacts = false;
|
||||
bool _didScheduleInitialLoad = false;
|
||||
Timer? _hotPlugTimer;
|
||||
late final MeshCoreConnector _connector;
|
||||
@@ -43,14 +41,14 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
_connectionListener = () {
|
||||
if (!mounted) return;
|
||||
if (_connector.state == MeshCoreConnectionState.disconnected) {
|
||||
_navigatedToChannels = false;
|
||||
_navigatedToContacts = false;
|
||||
}
|
||||
if (_connector.state == MeshCoreConnectionState.connected &&
|
||||
_connector.isUsbTransportConnected &&
|
||||
!_navigatedToChannels) {
|
||||
_navigatedToChannels = true;
|
||||
!_navigatedToContacts) {
|
||||
_navigatedToContacts = true;
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
|
||||
MaterialPageRoute(builder: (_) => const ContactsScreen()),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -74,7 +72,7 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
_hotPlugTimer?.cancel();
|
||||
_hotPlugTimer = null;
|
||||
_connector.removeListener(_connectionListener);
|
||||
if (!_navigatedToChannels &&
|
||||
if (!_navigatedToContacts &&
|
||||
_connector.activeTransport == MeshCoreTransportType.usb &&
|
||||
_connector.state != MeshCoreConnectionState.disconnected) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -100,124 +98,138 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
child: Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Status header
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Align(
|
||||
key: ValueKey('${connector.state}_$_isLoadingPorts'),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _buildStatusChip(context, connector),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Transport switcher
|
||||
_buildTransportLinks(context),
|
||||
|
||||
// Port list
|
||||
_buildStatusBar(context, connector),
|
||||
Expanded(child: _buildPortList(context, connector)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: _supportsHotPlug
|
||||
? null
|
||||
: SafeArea(
|
||||
top: false,
|
||||
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
bottomNavigationBar: Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, child) {
|
||||
final isLoading = _isLoadingPorts;
|
||||
final showBle = true;
|
||||
final showTcp = !PlatformInfo.isWeb;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FloatingActionButton.extended(
|
||||
onPressed: _isLoadingPorts ? null : _loadPorts,
|
||||
heroTag: 'usb_refresh_action',
|
||||
icon: _isLoadingPorts
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.usb),
|
||||
label: Text(context.l10n.scanner_scan),
|
||||
),
|
||||
if (showTcp)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const TcpScreen()),
|
||||
);
|
||||
},
|
||||
heroTag: 'usb_tcp_action',
|
||||
extendedPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
icon: const Icon(Icons.lan),
|
||||
label: Text(context.l10n.connectionChoiceTcpLabel),
|
||||
),
|
||||
if (showTcp && showBle) const SizedBox(width: 12),
|
||||
if (showBle)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const ScannerScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
heroTag: 'usb_ble_action',
|
||||
extendedPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
icon: const Icon(Icons.bluetooth),
|
||||
label: Text(context.l10n.connectionChoiceBluetoothLabel),
|
||||
),
|
||||
if ((showTcp || showBle) && !_supportsHotPlug)
|
||||
const SizedBox(width: 12),
|
||||
if (!_supportsHotPlug)
|
||||
FloatingActionButton.extended(
|
||||
onPressed: isLoading ? null : _loadPorts,
|
||||
heroTag: 'usb_refresh_action',
|
||||
extendedPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
icon: isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.usb),
|
||||
label: Text(context.l10n.scanner_scan),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
String statusText;
|
||||
Color statusColor;
|
||||
|
||||
if (_isLoadingPorts) {
|
||||
return StatusChip(
|
||||
label: l10n.usbStatus_searching,
|
||||
color: scheme.primary,
|
||||
pulse: true,
|
||||
);
|
||||
statusText = l10n.usbStatus_searching;
|
||||
statusColor = Colors.blue;
|
||||
} else if (connector.isUsbTransportConnected) {
|
||||
switch (connector.state) {
|
||||
case MeshCoreConnectionState.connected:
|
||||
return StatusChip(
|
||||
label: l10n.scanner_connectedTo(
|
||||
connector.activeUsbPortDisplayLabel ?? 'USB',
|
||||
),
|
||||
color: MeshPalette.signal,
|
||||
statusText = l10n.scanner_connectedTo(
|
||||
connector.activeUsbPortDisplayLabel ?? 'USB',
|
||||
);
|
||||
statusColor = Colors.green;
|
||||
case MeshCoreConnectionState.disconnecting:
|
||||
return StatusChip(
|
||||
label: l10n.scanner_disconnecting,
|
||||
color: MeshPalette.warn,
|
||||
pulse: true,
|
||||
);
|
||||
statusText = l10n.scanner_disconnecting;
|
||||
statusColor = Colors.orange;
|
||||
default:
|
||||
return StatusChip(
|
||||
label: l10n.usbStatus_notConnected,
|
||||
color: scheme.onSurfaceVariant,
|
||||
);
|
||||
statusText = l10n.usbStatus_notConnected;
|
||||
statusColor = Colors.grey;
|
||||
}
|
||||
} else if (connector.state == MeshCoreConnectionState.connecting &&
|
||||
connector.activeTransport == MeshCoreTransportType.usb) {
|
||||
return StatusChip(
|
||||
label: l10n.usbStatus_connecting,
|
||||
color: MeshPalette.warn,
|
||||
pulse: true,
|
||||
);
|
||||
statusText = l10n.usbStatus_connecting;
|
||||
statusColor = Colors.orange;
|
||||
} else {
|
||||
return StatusChip(
|
||||
label: l10n.usbStatus_notConnected,
|
||||
color: scheme.onSurfaceVariant,
|
||||
);
|
||||
statusText = l10n.usbStatus_notConnected;
|
||||
statusColor = Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTransportLinks(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
color: statusColor.withValues(alpha: 0.1),
|
||||
child: Row(
|
||||
children: [
|
||||
if (!PlatformInfo.isWeb)
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const TcpScreen()),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.lan),
|
||||
label: Text(context.l10n.connectionChoiceTcpLabel),
|
||||
Icon(Icons.circle, size: 12, color: statusColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
icon: const Icon(Icons.bluetooth),
|
||||
label: Text(context.l10n.connectionChoiceBluetoothLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -228,20 +240,46 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
final l10n = context.l10n;
|
||||
|
||||
if (_isLoadingPorts) {
|
||||
return EmptyState(icon: Icons.usb, title: l10n.usbStatus_searching);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.usb, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.usbStatus_searching,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_ports.isEmpty) {
|
||||
return EmptyState(icon: Icons.usb, title: l10n.usbScreenEmptyState);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.usb, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.usbScreenEmptyState,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final isConnecting =
|
||||
connector.state == MeshCoreConnectionState.connecting &&
|
||||
connector.activeTransport == MeshCoreTransportType.usb;
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: _ports.length,
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemBuilder: (context, index) {
|
||||
final port = _ports[index];
|
||||
final displayName = friendlyUsbPortName(port);
|
||||
@@ -249,50 +287,18 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
final showRawName =
|
||||
rawName != displayName && !rawName.startsWith('web:');
|
||||
|
||||
return ListEntrance(
|
||||
index: index,
|
||||
child: MeshCard(
|
||||
padding: EdgeInsets.zero,
|
||||
child: ListTile(
|
||||
onTap: isConnecting
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.selectionClick();
|
||||
_connectPort(port);
|
||||
},
|
||||
leading: AvatarCircle(
|
||||
name: displayName,
|
||||
size: 40,
|
||||
icon: Icons.usb,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
title: Text(
|
||||
displayName,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: showRawName
|
||||
? Text(
|
||||
rawName,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: null,
|
||||
trailing: Icon(
|
||||
Icons.chevron_right,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.usb),
|
||||
title: Text(
|
||||
displayName,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: showRawName ? Text(rawName) : null,
|
||||
trailing: ElevatedButton(
|
||||
onPressed: isConnecting ? null : () => _connectPort(port),
|
||||
child: Text(l10n.common_connect),
|
||||
),
|
||||
onTap: isConnecting ? null : () => _connectPort(port),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -378,27 +384,13 @@ class _UsbScreenState extends State<UsbScreen> {
|
||||
|
||||
void _showError(Object error) {
|
||||
if (!mounted) return;
|
||||
// Cancelling the browser's serial port picker is a normal user action, not
|
||||
// an error — don't show a scary red toast (and never leak the raw
|
||||
// DOMException text).
|
||||
if (_isUserCancelledPortPicker(error)) return;
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(_friendlyErrorMessage(error)),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isUserCancelledPortPicker(Object error) {
|
||||
if (error is StateError &&
|
||||
error.message.contains('No USB serial device selected')) {
|
||||
return true;
|
||||
}
|
||||
final text = error.toString();
|
||||
return text.contains('No port selected by the user') ||
|
||||
text.contains("Failed to execute 'requestPort'");
|
||||
}
|
||||
|
||||
String _friendlyErrorMessage(Object error) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
|
||||
@@ -235,12 +235,6 @@ class AppSettingsService extends ChangeNotifier {
|
||||
await updateSettings(_settings.copyWith(translationEnabled: value));
|
||||
}
|
||||
|
||||
Future<void> setAutoTranslateIncomingMessages(bool value) async {
|
||||
await updateSettings(
|
||||
_settings.copyWith(autoTranslateIncomingMessages: value),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setTranslationTargetLanguageCode(String? value) async {
|
||||
await updateSettings(
|
||||
_settings.copyWith(translationTargetLanguageCode: value),
|
||||
|
||||
@@ -39,12 +39,7 @@ class RetryServiceConfig {
|
||||
final void Function(Message) updateMessage;
|
||||
final Function(Contact)? clearContactPath;
|
||||
final Function(Contact, Uint8List, int)? setContactPath;
|
||||
final int Function(
|
||||
int pathLength,
|
||||
int messageBytes, {
|
||||
String? contactKey,
|
||||
int? deviceTimeoutMs,
|
||||
})?
|
||||
final int Function(int pathLength, int messageBytes, {String? contactKey})?
|
||||
calculateTimeout;
|
||||
final Uint8List? Function()? getSelfPublicKey;
|
||||
final String Function(Contact, String)? prepareContactOutboundText;
|
||||
@@ -79,12 +74,6 @@ class RetryServiceConfig {
|
||||
|
||||
class MessageRetryService extends ChangeNotifier {
|
||||
static const int maxAckHistorySize = 100;
|
||||
|
||||
/// Global cap on concurrent in-flight messages across ALL contacts.
|
||||
/// The firmware's expected_ack_table is a single 8-entry circular buffer
|
||||
/// shared globally; cap at 6 to leave two slots of headroom.
|
||||
static const int _maxGlobalInFlight = 6;
|
||||
|
||||
int _maxRetries = 5;
|
||||
int get maxRetries => _maxRetries;
|
||||
|
||||
@@ -181,9 +170,8 @@ class MessageRetryService extends ChangeNotifier {
|
||||
|
||||
_config?.addMessage(contact.publicKeyHex, message);
|
||||
|
||||
// Queue per contact — one message in-flight per contact at a time, and
|
||||
// bounded globally by _maxGlobalInFlight across all contacts so we never
|
||||
// overflow the firmware's 8-entry global expected_ack_table.
|
||||
// Queue per contact — only one message in-flight at a time to avoid
|
||||
// overflowing the firmware's 8-entry expected_ack_table.
|
||||
final contactKey = contact.publicKeyHex;
|
||||
_sendQueue[contactKey] ??= [];
|
||||
_sendQueue[contactKey]!.add(messageId);
|
||||
@@ -196,11 +184,6 @@ class MessageRetryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void _sendNextForContact(String contactKey) {
|
||||
// Enforce the global in-flight cap before starting a new send.
|
||||
// The firmware's expected_ack_table is a single 8-entry circular buffer
|
||||
// shared across all contacts; exceeding it silently evicts an older slot.
|
||||
if (_activeMessages.length >= _maxGlobalInFlight) return;
|
||||
|
||||
final queue = _sendQueue[contactKey];
|
||||
if (queue == null) return;
|
||||
|
||||
@@ -228,16 +211,7 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_resolvedMessages.contains(messageId)) return;
|
||||
_resolvedMessages.add(messageId);
|
||||
_activeMessages.remove(messageId);
|
||||
// Pump this contact's queue first, then any other contacts that are waiting.
|
||||
_sendNextForContact(contactKey);
|
||||
for (final key in _sendQueue.keys) {
|
||||
if (key == contactKey) continue;
|
||||
if (_activeMessages.length >= _maxGlobalInFlight) break;
|
||||
final queue = _sendQueue[key];
|
||||
if (queue != null && queue.isNotEmpty) {
|
||||
_sendNextForContact(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathSelection? _selectPathForAttempt(Message message, Contact contact) {
|
||||
@@ -378,10 +352,6 @@ class MessageRetryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
bool updateMessageFromSent(int ackHash, int timeoutMs) {
|
||||
// Firmware sets expected_ack = 0 for CLI/command sends (TXT_TYPE_CLI_DATA).
|
||||
// No ACK will ever be issued for these, so arming a retry timer is wrong.
|
||||
if (ackHash == 0) return false;
|
||||
|
||||
final config = _config;
|
||||
if (config == null) return false;
|
||||
|
||||
@@ -434,18 +404,13 @@ class MessageRetryService extends ChangeNotifier {
|
||||
|
||||
// Calculate timeout: prefer ML prediction, then device-provided, then physics fallback
|
||||
final pathLengthValue = message.pathLength ?? contact.pathLength;
|
||||
final outboundTextForTimeout =
|
||||
config.prepareContactOutboundText?.call(contact, message.text) ??
|
||||
message.text;
|
||||
final messageBytesForTimeout = utf8.encode(outboundTextForTimeout).length;
|
||||
|
||||
int actualTimeout = timeoutMs;
|
||||
if (config.calculateTimeout != null) {
|
||||
actualTimeout = config.calculateTimeout!(
|
||||
pathLengthValue,
|
||||
messageBytesForTimeout,
|
||||
message.text.length,
|
||||
contactKey: contact.publicKeyHex,
|
||||
deviceTimeoutMs: timeoutMs > 0 ? timeoutMs : null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -484,11 +449,6 @@ class MessageRetryService extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
void untrack(String messageId) {
|
||||
_timeoutTimers[messageId]?.cancel();
|
||||
_cleanupMessage(messageId);
|
||||
}
|
||||
|
||||
void _cleanupMessage(String messageId) {
|
||||
_moveAckHashesToHistory(messageId);
|
||||
_ackHashToMessageId.removeWhere(
|
||||
@@ -652,6 +612,7 @@ class MessageRetryService extends ChangeNotifier {
|
||||
for (final expectedHash in expectedHashes) {
|
||||
if (expectedHash == ackHash) {
|
||||
matchedMessageId = messageId;
|
||||
matchedAttemptIndex = expectedHashes.indexOf(expectedHash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -703,16 +664,10 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (config?.onDeliveryObserved != null &&
|
||||
tripTimeMs > 0 &&
|
||||
message.pathLength != null) {
|
||||
final outboundTextForObserved =
|
||||
config!.prepareContactOutboundText?.call(contact, message.text) ??
|
||||
message.text;
|
||||
final messageBytesForObserved = utf8
|
||||
.encode(outboundTextForObserved)
|
||||
.length;
|
||||
config.onDeliveryObserved!(
|
||||
config!.onDeliveryObserved!(
|
||||
contact.publicKeyHex,
|
||||
message.pathLength!,
|
||||
messageBytesForObserved,
|
||||
message.text.length,
|
||||
tripTimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,36 +114,6 @@ class NotificationService {
|
||||
return _isInitialized;
|
||||
}
|
||||
|
||||
// Cached "are we allowed to post notifications" result. Null = not yet
|
||||
// determined. Avoids calling _notifications.show() when it would only throw
|
||||
// "You must request notifications permissions first" (every web build, and
|
||||
// Android 13+ before the user grants the permission).
|
||||
bool? _canNotify;
|
||||
|
||||
Future<bool> _ensureCanNotify() async {
|
||||
if (!await _ensureInitialized()) return false;
|
||||
final cached = _canNotify;
|
||||
if (cached != null) return cached;
|
||||
|
||||
// flutter_local_notifications has no web backend, so show() always throws.
|
||||
// Skip silently instead of logging an error per incoming message.
|
||||
if (kIsWeb) return _canNotify = false;
|
||||
|
||||
// On Android 13+ notifications require an explicit grant; reflect the real
|
||||
// OS state so we don't spam failed show() calls when denied.
|
||||
final androidPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>();
|
||||
if (androidPlugin != null) {
|
||||
final enabled = await androidPlugin.areNotificationsEnabled();
|
||||
return _canNotify = enabled ?? false;
|
||||
}
|
||||
|
||||
// iOS/macOS request permission during initialize(); desktop has no gate.
|
||||
return _canNotify = true;
|
||||
}
|
||||
|
||||
Future<bool> requestPermissions() async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
@@ -156,8 +126,7 @@ class NotificationService {
|
||||
>();
|
||||
if (androidPlugin != null) {
|
||||
final granted = await androidPlugin.requestNotificationsPermission();
|
||||
_canNotify = granted ?? false;
|
||||
return _canNotify!;
|
||||
return granted ?? false;
|
||||
}
|
||||
|
||||
// iOS permissions are requested during initialization
|
||||
@@ -171,8 +140,7 @@ class NotificationService {
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
_canNotify = granted ?? false;
|
||||
return _canNotify!;
|
||||
return granted ?? false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -197,7 +165,7 @@ class NotificationService {
|
||||
String? contactId,
|
||||
int? badgeCount,
|
||||
}) async {
|
||||
if (!await _ensureCanNotify()) return;
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'messages',
|
||||
@@ -247,7 +215,7 @@ class NotificationService {
|
||||
required String contactType,
|
||||
String? contactId,
|
||||
}) async {
|
||||
if (!await _ensureCanNotify()) return;
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'adverts',
|
||||
@@ -280,7 +248,7 @@ class NotificationService {
|
||||
await _notifications.show(
|
||||
id: contactId != null
|
||||
? 'advert:$contactId'.hashCode
|
||||
: DateTime.now().millisecondsSinceEpoch & 0x7FFFFFFF,
|
||||
: DateTime.now().millisecondsSinceEpoch,
|
||||
title: _l10n.notification_newTypeDiscovered(contactType),
|
||||
body: contactName,
|
||||
notificationDetails: notificationDetails,
|
||||
@@ -297,7 +265,7 @@ class NotificationService {
|
||||
int? channelIndex,
|
||||
int? badgeCount,
|
||||
}) async {
|
||||
if (!await _ensureCanNotify()) return;
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'channel_messages',
|
||||
@@ -336,9 +304,7 @@ class NotificationService {
|
||||
|
||||
try {
|
||||
await _notifications.show(
|
||||
id:
|
||||
channelIndex?.hashCode ??
|
||||
DateTime.now().millisecondsSinceEpoch & 0x7FFFFFFF,
|
||||
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
title: channelName,
|
||||
body: body,
|
||||
notificationDetails: notificationDetails,
|
||||
@@ -577,7 +543,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
Future<void> _showBatchSummary(List<_PendingNotification> batch) async {
|
||||
if (!await _ensureCanNotify()) return;
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
// Group by type
|
||||
final messages = batch
|
||||
|
||||
@@ -134,12 +134,10 @@ class PathHistoryService extends ChangeNotifier {
|
||||
newWeight = (currentWeight + successIncrement).clamp(0.0, maxWeight);
|
||||
} else {
|
||||
newWeight = currentWeight - failureDecrement;
|
||||
if (newWeight <= 0 && failureCount >= 3) {
|
||||
if (newWeight <= 0) {
|
||||
removePathRecord(contactPubKeyHex, selection.pathBytes);
|
||||
return;
|
||||
}
|
||||
// Keep the record with a small floor weight until we have enough evidence
|
||||
newWeight = newWeight.clamp(0.1, maxWeight);
|
||||
}
|
||||
|
||||
_addPathRecord(
|
||||
|
||||
@@ -63,15 +63,12 @@ class TimeoutPredictionService extends ChangeNotifier {
|
||||
required int tripTimeMs,
|
||||
int secondsSinceLastRx = 0,
|
||||
}) {
|
||||
final isFlood = pathLength < 0;
|
||||
final observation = DeliveryObservation(
|
||||
contactKey: contactKey,
|
||||
// Clamp to 0 for flood so the hop-count slope is learned from direct paths
|
||||
// only; isFlood carries the flood signal as a separate feature.
|
||||
pathLength: isFlood ? 0 : pathLength,
|
||||
pathLength: pathLength,
|
||||
messageBytes: messageBytes,
|
||||
secondsSinceLastRx: secondsSinceLastRx,
|
||||
isFlood: isFlood,
|
||||
isFlood: pathLength < 0,
|
||||
deliveryMs: tripTimeMs,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
@@ -79,12 +76,11 @@ class TimeoutPredictionService extends ChangeNotifier {
|
||||
_observations.add(observation);
|
||||
if (_observations.length > maxObservations) {
|
||||
_observations.removeAt(0);
|
||||
_rebuildContactStats();
|
||||
} else {
|
||||
_contactStats.putIfAbsent(contactKey, () => _ContactStats());
|
||||
_contactStats[contactKey]!.add(tripTimeMs.toDouble());
|
||||
}
|
||||
|
||||
_contactStats.putIfAbsent(contactKey, () => _ContactStats());
|
||||
_contactStats[contactKey]!.add(tripTimeMs.toDouble());
|
||||
|
||||
_observationsSinceLastTrain++;
|
||||
if (_observationsSinceLastTrain >= _retrainInterval &&
|
||||
_observations.length >= minObservations) {
|
||||
@@ -112,14 +108,11 @@ class TimeoutPredictionService extends ChangeNotifier {
|
||||
try {
|
||||
if (_activeFeatures.isEmpty) return null;
|
||||
|
||||
final flood = pathLength < 0;
|
||||
final allFeatures = {
|
||||
// Clamp to 0 for flood — mirrors recordObservation so training and
|
||||
// prediction see the same pathLength values; isFlood carries the signal.
|
||||
'pathLength': flood ? 0.0 : pathLength.toDouble(),
|
||||
'pathLength': pathLength.toDouble(),
|
||||
'messageBytes': messageBytes.toDouble(),
|
||||
'secSinceRx': secondsSinceLastRx.toDouble(),
|
||||
'isFlood': flood ? 1.0 : 0.0,
|
||||
'isFlood': pathLength < 0 ? 1.0 : 0.0,
|
||||
};
|
||||
final row = _activeFeatures.map((f) => allFeatures[f]!).toList();
|
||||
|
||||
@@ -171,9 +164,7 @@ class TimeoutPredictionService extends ChangeNotifier {
|
||||
// (ml_algo's OLS produces all-zero coefficients for singular matrices)
|
||||
final allNames = ['pathLength', 'messageBytes', 'secSinceRx', 'isFlood'];
|
||||
final allExtractors = <double Function(DeliveryObservation)>[
|
||||
// pathLength is already clamped to >=0 in recordObservation, but guard
|
||||
// here as well for any observations loaded from older persisted data.
|
||||
(o) => o.pathLength < 0 ? 0.0 : o.pathLength.toDouble(),
|
||||
(o) => o.pathLength.toDouble(),
|
||||
(o) => o.messageBytes.toDouble(),
|
||||
(o) => o.secondsSinceLastRx.toDouble(),
|
||||
(o) => o.isFlood ? 1.0 : 0.0,
|
||||
@@ -224,9 +215,6 @@ class TimeoutPredictionService extends ChangeNotifier {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_persistTimer?.isActive == true) {
|
||||
_storage?.saveDeliveryObservations(_observations);
|
||||
}
|
||||
_persistTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:llamadart/llamadart.dart';
|
||||
import 'package:flutter_langdetect/flutter_langdetect.dart';
|
||||
|
||||
import '../models/app_settings.dart';
|
||||
import '../models/translation_support.dart';
|
||||
@@ -42,12 +41,8 @@ class TranslationService extends ChangeNotifier {
|
||||
TranslationService(
|
||||
this._appSettingsService, {
|
||||
TranslationFileStore? fileStore,
|
||||
}) : _fileStore = fileStore ?? TranslationFileStore() {
|
||||
// Initialize langdetect once at service construction.
|
||||
_langDetectInit = initLangDetect();
|
||||
}
|
||||
}) : _fileStore = fileStore ?? TranslationFileStore();
|
||||
|
||||
bool _disposed = false;
|
||||
bool _isBusy = false;
|
||||
bool _isDownloading = false;
|
||||
bool _cancelDownloadRequested = false;
|
||||
@@ -56,7 +51,6 @@ class TranslationService extends ChangeNotifier {
|
||||
LlamaEngine? _engine;
|
||||
String? _loadedModelPath;
|
||||
String? _failedModelPath;
|
||||
Future<void>? _langDetectInit;
|
||||
int _downloadedBytes = 0;
|
||||
int? _downloadTotalBytes;
|
||||
String? _downloadFileName;
|
||||
@@ -90,22 +84,7 @@ class TranslationService extends ChangeNotifier {
|
||||
'en';
|
||||
}
|
||||
|
||||
bool shouldAutoTranslateIncoming({
|
||||
required String text,
|
||||
required bool isCli,
|
||||
required bool isOutgoing,
|
||||
}) {
|
||||
if (!_settings.autoTranslateIncomingMessages) {
|
||||
return false;
|
||||
}
|
||||
return canTranslateIncoming(
|
||||
text: text,
|
||||
isCli: isCli,
|
||||
isOutgoing: isOutgoing,
|
||||
);
|
||||
}
|
||||
|
||||
bool canTranslateIncoming({
|
||||
bool shouldTranslateIncoming({
|
||||
required String text,
|
||||
required bool isCli,
|
||||
required bool isOutgoing,
|
||||
@@ -216,7 +195,7 @@ class TranslationService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
_downloadTotalBytes = totalSize;
|
||||
_notify();
|
||||
notifyListeners();
|
||||
|
||||
DownloadedModelFile downloaded;
|
||||
if (supportsRange &&
|
||||
@@ -269,7 +248,7 @@ class TranslationService extends ChangeNotifier {
|
||||
throw StateError('Model download failed: HTTP ${response.statusCode}');
|
||||
}
|
||||
_downloadTotalBytes ??= response.contentLength;
|
||||
_notify();
|
||||
notifyListeners();
|
||||
final trackedStream = _trackDownloadProgress(response.stream);
|
||||
return await _fileStore.writeModelBytes(
|
||||
fileName: fileName,
|
||||
@@ -314,7 +293,7 @@ class TranslationService extends ChangeNotifier {
|
||||
throw const TranslationDownloadCancelled();
|
||||
}
|
||||
_downloadFileName = 'Merging chunks...';
|
||||
_notify();
|
||||
notifyListeners();
|
||||
combineReached = true;
|
||||
return await _fileStore.combineChunks(
|
||||
fileName: fileName,
|
||||
@@ -362,7 +341,7 @@ class TranslationService extends ChangeNotifier {
|
||||
}
|
||||
_cancelDownloadRequested = true;
|
||||
_lastError = 'Download stopped.';
|
||||
_notify();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> removeModel(TranslationModelRecord model) async {
|
||||
@@ -389,9 +368,7 @@ class TranslationService extends ChangeNotifier {
|
||||
if (targetLanguageCode == null || !_isPlainTextEligible(text)) {
|
||||
return null;
|
||||
}
|
||||
final detectedLanguageCode = await detectLanguage(
|
||||
_stripReplyInfoForDetection(text),
|
||||
);
|
||||
final detectedLanguageCode = await detectLanguage(text);
|
||||
if (detectedLanguageCode != null &&
|
||||
detectedLanguageCode == targetLanguageCode) {
|
||||
return const TranslationResult(
|
||||
@@ -432,9 +409,7 @@ class TranslationService extends ChangeNotifier {
|
||||
if (targetLanguageCode == null || !_isPlainTextEligible(text)) {
|
||||
return null;
|
||||
}
|
||||
final detectedLanguageCode = await detectLanguage(
|
||||
_stripReplyInfoForDetection(text),
|
||||
);
|
||||
final detectedLanguageCode = await detectLanguage(text);
|
||||
if (detectedLanguageCode != null &&
|
||||
detectedLanguageCode == targetLanguageCode) {
|
||||
return const TranslationResult(
|
||||
@@ -461,26 +436,7 @@ class TranslationService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<String?> detectLanguage(String text) async {
|
||||
try {
|
||||
// Ensure the detector is initialized (constructor starts init).
|
||||
await (_langDetectInit ??= initLangDetect());
|
||||
final code = detect(text);
|
||||
if (code.isEmpty) return null;
|
||||
return code;
|
||||
} catch (error) {
|
||||
_lastError = error.toString();
|
||||
appLogger.warn('Language detection failed: $error');
|
||||
_notify();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String _stripReplyInfoForDetection(String text) {
|
||||
final match = RegExp(
|
||||
r'@\[([^\]]+)\]\s+(.+)$',
|
||||
dotAll: true,
|
||||
).firstMatch(text);
|
||||
return match?.group(2) ?? text;
|
||||
return _heuristicLanguageCode(text);
|
||||
}
|
||||
|
||||
Future<String?> _translateText({
|
||||
@@ -539,7 +495,7 @@ class TranslationService extends ChangeNotifier {
|
||||
} catch (error) {
|
||||
_lastError = error.toString();
|
||||
appLogger.warn('Translation request failed: $error');
|
||||
_notify();
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -562,6 +518,72 @@ class TranslationService extends ChangeNotifier {
|
||||
trimmed.startsWith('r:'));
|
||||
}
|
||||
|
||||
String? _heuristicLanguageCode(String text) {
|
||||
final trimmed = text.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (RegExp(r'[ぁ-んァ-ン]').hasMatch(text)) {
|
||||
return 'ja';
|
||||
}
|
||||
if (RegExp(r'[가-힣]').hasMatch(text)) {
|
||||
return 'ko';
|
||||
}
|
||||
if (RegExp(r'[\u4e00-\u9fff]').hasMatch(text)) {
|
||||
return 'zh';
|
||||
}
|
||||
|
||||
final lower = trimmed.toLowerCase();
|
||||
final patterns = <String, String>{
|
||||
'uk': r'\b(привіт|дякую|будь|ласка|як|де|не|так|це|є|най|ще|може|для)\b',
|
||||
'ru':
|
||||
r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b',
|
||||
'bg': r'\b(ще|няма|благодаря|моля|това|какво|тук|ние|вие|не|със|за)\b',
|
||||
'de':
|
||||
r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b',
|
||||
'en':
|
||||
r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b',
|
||||
'es':
|
||||
r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b',
|
||||
'fr':
|
||||
r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b',
|
||||
'it':
|
||||
r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b',
|
||||
'pt':
|
||||
r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b',
|
||||
'nl':
|
||||
r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b',
|
||||
'sv':
|
||||
r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b',
|
||||
'pl':
|
||||
r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b',
|
||||
'sk': r'\b(je|na|so|že|do|od|za|si|to|ten|tá|tí|ako|má|nie|som|sa)\b',
|
||||
'sl': r'\b(in|je|na|se|da|za|od|ne|to|ta|so|kako|bo|sem|si)\b',
|
||||
'hu':
|
||||
r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b',
|
||||
};
|
||||
|
||||
final scores = <String, int>{};
|
||||
for (final entry in patterns.entries) {
|
||||
scores[entry.key] = RegExp(
|
||||
entry.value,
|
||||
caseSensitive: false,
|
||||
).allMatches(lower).length;
|
||||
}
|
||||
|
||||
final sorted = scores.entries.toList()
|
||||
..sort((a, b) => b.value.compareTo(a.value));
|
||||
if (sorted.isEmpty || sorted.first.value == 0) {
|
||||
return null;
|
||||
}
|
||||
if (sorted.length > 1 && sorted.first.value == sorted[1].value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sorted.first.key;
|
||||
}
|
||||
|
||||
String _languageLabel(String code) {
|
||||
for (final option in supportedTranslationLanguages) {
|
||||
if (option.code == code) {
|
||||
@@ -632,10 +654,6 @@ class TranslationService extends ChangeNotifier {
|
||||
final completer = Completer<T>();
|
||||
_setBusy(true);
|
||||
_queue = _queue.then((_) async {
|
||||
if (_disposed) {
|
||||
completer.completeError(StateError('TranslationService disposed.'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
completer.complete(await action());
|
||||
} catch (error, stackTrace) {
|
||||
@@ -653,24 +671,17 @@ class TranslationService extends ChangeNotifier {
|
||||
throw const TranslationDownloadCancelled();
|
||||
}
|
||||
_downloadedBytes += chunk.length;
|
||||
_notify();
|
||||
notifyListeners();
|
||||
yield chunk;
|
||||
}
|
||||
}
|
||||
|
||||
void _notify() {
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setBusy(bool value) {
|
||||
if (_isBusy == value) {
|
||||
return;
|
||||
}
|
||||
_isBusy = value;
|
||||
_notify();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setDownloading(bool value) {
|
||||
@@ -681,12 +692,11 @@ class TranslationService extends ChangeNotifier {
|
||||
_downloadTotalBytes = null;
|
||||
_downloadFileName = null;
|
||||
}
|
||||
_notify();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
final engine = _engine;
|
||||
_engine = null;
|
||||
_loadedModelPath = null;
|
||||
|
||||
@@ -33,14 +33,12 @@ class UsbSerialService {
|
||||
String? _connectedPortLabel;
|
||||
FlSerial? _serial;
|
||||
AppDebugLogService? _debugLogService;
|
||||
Object? _lastError;
|
||||
|
||||
UsbSerialStatus get status => _status;
|
||||
String? get activePortKey => _connectedPortKey;
|
||||
String? get activePortDisplayLabel =>
|
||||
_connectedPortLabel ?? _connectedPortKey;
|
||||
Stream<Uint8List> get frameStream => _frameController.stream;
|
||||
Object? get lastError => _lastError;
|
||||
bool get _useAndroidUsbHost =>
|
||||
!kIsWeb && defaultTargetPlatform == TargetPlatform.android;
|
||||
bool get _useDesktopFlSerial =>
|
||||
@@ -436,7 +434,6 @@ class UsbSerialService {
|
||||
}
|
||||
|
||||
void _addFrameError(Object error, [StackTrace? stackTrace]) {
|
||||
_lastError = error;
|
||||
if (_frameController.isClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,18 +15,6 @@ class UsbSerialService {
|
||||
static const Map<String, String> _knownUsbNames = <String, String>{
|
||||
'2886:1667': 'Seeed Wio Tracker L1',
|
||||
};
|
||||
|
||||
/// USB-to-UART bridge chips whose hardware auto-reset circuit requires DTR
|
||||
/// to be held asserted after open (otherwise the MCU resets). Native-USB-CDC
|
||||
/// boards (nRF52840/Adafruit 0x239A, Espressif native 0x303A, Seeed 0x2886)
|
||||
/// tie DTR to the bootloader/reset line, so asserting it re-enumerates and
|
||||
/// drops the device ("The device has been lost"); they must be left alone.
|
||||
static const Set<int> _uartBridgeVendorIds = <int>{
|
||||
0x10C4, // Silicon Labs CP210x
|
||||
0x1A86, // QinHeng CH340 / CH9102
|
||||
0x0403, // FTDI
|
||||
0x067B, // Prolific PL2303
|
||||
};
|
||||
static final Map<String, String> _deviceNamesByPortKey = <String, String>{};
|
||||
static final Map<String, String> _baseLabelsByPortKey = <String, String>{};
|
||||
static final Map<String, JSObject> _authorizedPortsByKey =
|
||||
@@ -46,14 +34,12 @@ class UsbSerialService {
|
||||
String _requestPortLabel = 'Choose USB Device';
|
||||
String _fallbackDeviceName = 'Web Serial Device';
|
||||
AppDebugLogService? _debugLogService;
|
||||
Object? _lastError;
|
||||
|
||||
UsbSerialStatus get status => _status;
|
||||
String? get activePortKey => _connectedPortKey;
|
||||
String? get activePortDisplayLabel => _connectedPortName ?? _connectedPortKey;
|
||||
Stream<Uint8List> get frameStream => _frameController.stream;
|
||||
bool get isConnected => _status == UsbSerialStatus.connected;
|
||||
Object? get lastError => _lastError;
|
||||
|
||||
JSObject get _navigator => JSObject.fromInteropObject(web.window.navigator);
|
||||
bool get _isSupported => _navigator.has('serial');
|
||||
@@ -88,7 +74,6 @@ class UsbSerialService {
|
||||
}
|
||||
|
||||
_status = UsbSerialStatus.connecting;
|
||||
_lastError = null;
|
||||
_frameDecoder.reset();
|
||||
|
||||
try {
|
||||
@@ -297,30 +282,16 @@ class UsbSerialService {
|
||||
..['flowControl'] = 'none'.toJS;
|
||||
await port.callMethod<JSPromise<JSAny?>>('open'.toJS, options).toDart;
|
||||
|
||||
// Only UART-bridge chips (CP210x/CH340/FTDI/PL2303) need DTR held high to
|
||||
// avoid the auto-reset circuit firing on open. Native-USB-CDC boards
|
||||
// (e.g. nRF52840/Adafruit) tie DTR to the reset line — toggling it there
|
||||
// re-enumerates the device and Web Serial reports "The device has been
|
||||
// lost". Leave their signals untouched.
|
||||
final vendorId = _portInfo(port)?.usbVendorId;
|
||||
final isUartBridge =
|
||||
vendorId != null && _uartBridgeVendorIds.contains(vendorId);
|
||||
_debugLogService?.info(
|
||||
'Open: vendorId=${vendorId == null ? 'unknown' : '0x${vendorId.toRadixString(16)}'} '
|
||||
'uartBridge=$isUartBridge (DTR ${isUartBridge ? 'asserted' : 'left default'})',
|
||||
tag: 'USB Serial',
|
||||
);
|
||||
if (isUartBridge) {
|
||||
try {
|
||||
final signals = JSObject()
|
||||
..['dataTerminalReady'] = true.toJS
|
||||
..['requestToSend'] = false.toJS;
|
||||
await port
|
||||
.callMethod<JSPromise<JSAny?>>('setSignals'.toJS, signals)
|
||||
.toDart;
|
||||
} catch (_) {
|
||||
// setSignals may not be supported on all browsers/devices.
|
||||
}
|
||||
// Prevent ESP32 USB-CDC reset: hold DTR=true, RTS=false after open.
|
||||
try {
|
||||
final signals = JSObject()
|
||||
..['dataTerminalReady'] = true.toJS
|
||||
..['requestToSend'] = false.toJS;
|
||||
await port
|
||||
.callMethod<JSPromise<JSAny?>>('setSignals'.toJS, signals)
|
||||
.toDart;
|
||||
} catch (_) {
|
||||
// setSignals may not be supported on all browsers/devices.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,21 +384,13 @@ class UsbSerialService {
|
||||
} catch (error, stackTrace) {
|
||||
_debugLogService?.error('_pumpReads error: $error', tag: 'USB Serial');
|
||||
if (_status == UsbSerialStatus.connected) {
|
||||
// The transport is dead — reflect that in status immediately so a
|
||||
// concurrent connect handshake fails fast instead of waiting for a
|
||||
// SELF_INFO that can never arrive.
|
||||
_status = UsbSerialStatus.disconnected;
|
||||
_lastError = error;
|
||||
_addFrameError(error, stackTrace);
|
||||
}
|
||||
} finally {
|
||||
_debugLogService?.info('_pumpReads: ended', tag: 'USB Serial');
|
||||
_releaseLock(reader);
|
||||
if (_status == UsbSerialStatus.connected && identical(reader, _reader)) {
|
||||
_status = UsbSerialStatus.disconnected;
|
||||
final closedError = StateError('USB serial connection closed');
|
||||
_lastError = closedError;
|
||||
_addFrameError(closedError);
|
||||
_addFrameError(StateError('USB serial connection closed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,18 @@ class ChannelStore {
|
||||
|
||||
try {
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
return jsonList
|
||||
final channels = jsonList
|
||||
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
||||
.toList();
|
||||
// Deduplicate: keep the last entry per channel index
|
||||
final seen = <int>{};
|
||||
final deduped = <Channel>[];
|
||||
for (final channel in channels.reversed) {
|
||||
if (seen.add(channel.index)) {
|
||||
deduped.add(channel);
|
||||
}
|
||||
}
|
||||
return deduped.reversed.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
+69
-238
@@ -1,113 +1,70 @@
|
||||
import 'package:flutter/cupertino.dart' show CupertinoPageTransitionsBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// MeshCore palette — high-contrast slate surfaces with sky-blue accents.
|
||||
/// MeshCore redesign palette — warm field-journal dark theme with
|
||||
/// phosphor-green signal accents. Mirrors values from the redesign spec.
|
||||
class MeshPalette {
|
||||
MeshPalette._();
|
||||
|
||||
// Surfaces shared with the map overlays and navigation.
|
||||
static const bg = Color(0xFF0B1220);
|
||||
static const bg1 = Color(0xFF0F172A);
|
||||
static const bg2 = Color(0xFF162033);
|
||||
static const bg3 = Color(0xFF1E293B);
|
||||
static const bg4 = Color(0xFF334155);
|
||||
// Surfaces (warm near-black, olive undertone)
|
||||
static const bg = Color(0xFF0F1412);
|
||||
static const bg1 = Color(0xFF161C19);
|
||||
static const bg2 = Color(0xFF1D2521);
|
||||
static const bg3 = Color(0xFF28322D);
|
||||
static const bg4 = Color(0xFF34403A);
|
||||
|
||||
// Lines
|
||||
static const line = Color(0xFF1E293B);
|
||||
static const line2 = Color(0xFF334155);
|
||||
static const line3 = Color(0xFF475569);
|
||||
static const line = Color(0xFF232C28);
|
||||
static const line2 = Color(0xFF34403A);
|
||||
static const line3 = Color(0xFF48564F);
|
||||
|
||||
// Ink
|
||||
static const ink = Color(0xFFF8FAFC);
|
||||
static const ink2 = Color(0xFFCBD5E1);
|
||||
static const ink3 = Color(0xFF94A3B8);
|
||||
static const ink4 = Color(0xFF64748B);
|
||||
static const ink = Color(0xFFEFF3E8);
|
||||
static const ink2 = Color(0xFFBAC4B5);
|
||||
static const ink3 = Color(0xFF7C8B82);
|
||||
static const ink4 = Color(0xFF55635B);
|
||||
|
||||
// Signal-quality green (used only for SNR coloring, not UI chrome)
|
||||
static const signal = Color(0xFF22C55E);
|
||||
static const signalDim = Color(0xFF16A34A);
|
||||
// Signal (phosphor)
|
||||
static const signal = Color(0xFF7BEFA8);
|
||||
static const signalDim = Color(0xFF4DC580);
|
||||
static const signalBg = Color(0x177BEFA8); // ~9% alpha
|
||||
static const signalLine = Color(0x427BEFA8); // ~26%
|
||||
static const signalGlow = Color(0x597BEFA8); // ~35%
|
||||
|
||||
// Warn
|
||||
static const warn = Color(0xFFF59E0B);
|
||||
static const warnDim = Color(0xFFD97706);
|
||||
static const warnBg = Color(0x1FF59E0B);
|
||||
static const warnLine = Color(0x66F59E0B);
|
||||
// Warn (ember)
|
||||
static const warn = Color(0xFFFFA552);
|
||||
static const warnDim = Color(0xFFC27E3C);
|
||||
static const warnBg = Color(0x1CFFA552);
|
||||
static const warnLine = Color(0x4DFFA552);
|
||||
|
||||
// Alert
|
||||
static const alert = Color(0xFFEF4444);
|
||||
static const alertBg = Color(0x1FEF4444);
|
||||
static const alertLine = Color(0x66EF4444);
|
||||
// Alert (coral)
|
||||
static const alert = Color(0xFFFF6A5C);
|
||||
static const alertBg = Color(0x1CFF6A5C);
|
||||
static const alertLine = Color(0x52FF6A5C);
|
||||
|
||||
// Blue — primary map/app accent
|
||||
static const blue = Color(0xFF0EA5E9);
|
||||
static const blueDim = Color(0xFF0284C7);
|
||||
static const blueBg = Color(0x290EA5E9);
|
||||
static const blueLine = Color(0x800EA5E9);
|
||||
// Blue (dusk sky)
|
||||
static const blue = Color(0xFF7FCBF5);
|
||||
static const blueBg = Color(0x1C7FCBF5);
|
||||
static const blueLine = Color(0x477FCBF5);
|
||||
|
||||
// Magenta
|
||||
static const magenta = Color(0xFFDE7FDB);
|
||||
static const magentaBg = Color(0x1CDE7FDB);
|
||||
static const magentaLine = Color(0x47DE7FDB);
|
||||
|
||||
// Me bubble (dusk blue)
|
||||
static const me = Color(0xFF0C4A6E);
|
||||
static const meBorder = Color(0xFF0369A1);
|
||||
static const meInk = Color(0xFFF0F9FF);
|
||||
// Me bubble (mossy)
|
||||
static const me = Color(0xFF1E3527);
|
||||
static const meBorder = Color(0xFF2D5039);
|
||||
static const meInk = Color(0xFFDEF0DC);
|
||||
|
||||
// ── Light variant (used when user explicitly picks light theme)
|
||||
static const lightBg = Color(0xFFF4F6F8);
|
||||
static const lightBg1 = Color(0xFFEAEEF2);
|
||||
static const lightBg2 = Color(0xFFDFE5EA);
|
||||
static const lightLine = Color(0xFFC3CCD4);
|
||||
static const lightInk = Color(0xFF10161B);
|
||||
static const lightInk2 = Color(0xFF3C4853);
|
||||
static const lightInk3 = Color(0xFF69767F);
|
||||
static const lightBlue = Color(0xFF2F6EA8);
|
||||
}
|
||||
|
||||
/// High-contrast semantic colors for UI rendered over variable map tiles.
|
||||
class MapPalette {
|
||||
MapPalette._();
|
||||
|
||||
static const online = Color(0xFF22C55E);
|
||||
static const offline = Color(0xFF6B7280);
|
||||
static const stale = Color(0xFFF59E0B);
|
||||
static const repeater = Color(0xFF2563EB);
|
||||
static const router = Color(0xFF7C3AED);
|
||||
static const batteryLow = Color(0xFFEF4444);
|
||||
static const cluster = Color(0xFFF97316);
|
||||
static const selected = Color(0xFF0EA5E9);
|
||||
static const sensor = Color(0xFF0F766E);
|
||||
static const shared = Color(0xFF0369A1);
|
||||
|
||||
static const panelLight = Color(0xF0FFFFFF);
|
||||
static const panelDark = Color(0xF50B1220);
|
||||
static const textPrimary = Color(0xFFF8FAFC);
|
||||
static const textSecondary = Color(0xFFCBD5E1);
|
||||
static const textMuted = Color(0xFF94A3B8);
|
||||
static const border = Color(0x5264758B);
|
||||
static const markerOutline = Colors.white;
|
||||
static const markerShadow = Color(0xB3000000);
|
||||
}
|
||||
|
||||
/// High-contrast colors for line-of-sight maps and elevation profiles.
|
||||
class LosPalette {
|
||||
LosPalette._();
|
||||
|
||||
static const terrain = Color(0xFFA3E635);
|
||||
static const beam = Color(0xFF38BDF8);
|
||||
static const horizon = Color(0xFFFBBF24);
|
||||
static const blocked = Color(0xFFEF4444);
|
||||
static const marginal = Color(0xFFF59E0B);
|
||||
static const clear = Color(0xFF22C55E);
|
||||
static const selected = Color(0xFF0EA5E9);
|
||||
static const chartBackground = Color(0xFF0B1220);
|
||||
static const panelDark = Color(0xF00F172A);
|
||||
static const panelLight = Color(0xF5FFFFFF);
|
||||
static const text = Color(0xFFF8FAFC);
|
||||
static const textMuted = Color(0xFFCBD5E1);
|
||||
static const border = Color(0x5264758B);
|
||||
static const shadow = Color(0x99000000);
|
||||
static const lightBg = Color(0xFFF5F3EC);
|
||||
static const lightBg1 = Color(0xFFECE9DF);
|
||||
static const lightBg2 = Color(0xFFE2DED2);
|
||||
static const lightLine = Color(0xFFCAC5B4);
|
||||
static const lightInk = Color(0xFF0F1410);
|
||||
static const lightInk2 = Color(0xFF3D463E);
|
||||
static const lightInk3 = Color(0xFF6A756D);
|
||||
static const lightSignal = Color(0xFF1A7A44);
|
||||
}
|
||||
|
||||
/// Named font stacks — Flutter falls back to system fonts when the named
|
||||
@@ -118,7 +75,6 @@ class MeshFonts {
|
||||
static const sans = 'Inter';
|
||||
static const mono = 'JetBrains Mono';
|
||||
static const display = 'Instrument Serif';
|
||||
static const emoji = 'Noto Color Emoji';
|
||||
|
||||
static const List<String> sansFallback = [
|
||||
'system-ui',
|
||||
@@ -140,11 +96,6 @@ class MeshFonts {
|
||||
'Times New Roman',
|
||||
'serif',
|
||||
];
|
||||
static const List<String> emojiFallback = [
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Noto Emoji',
|
||||
];
|
||||
}
|
||||
|
||||
/// Radii used consistently across the app.
|
||||
@@ -164,22 +115,18 @@ class MeshTheme {
|
||||
|
||||
static ThemeData dark() {
|
||||
const scheme = ColorScheme.dark(
|
||||
primary: MeshPalette.blue,
|
||||
onPrimary: Colors.white,
|
||||
primaryContainer: Color(0xFF075985),
|
||||
onPrimaryContainer: Colors.white,
|
||||
secondary: MeshPalette.magenta,
|
||||
onSecondary: Colors.white,
|
||||
secondaryContainer: Color(0xFF331A33),
|
||||
onSecondaryContainer: Colors.white,
|
||||
tertiary: MeshPalette.warn,
|
||||
onTertiary: Color(0xFF0B1220),
|
||||
tertiaryContainer: Color(0xFF78350F),
|
||||
onTertiaryContainer: Colors.white,
|
||||
primary: MeshPalette.signal,
|
||||
onPrimary: Color(0xFF0A1810),
|
||||
primaryContainer: MeshPalette.signalBg,
|
||||
onPrimaryContainer: MeshPalette.signal,
|
||||
secondary: MeshPalette.blue,
|
||||
onSecondary: Color(0xFF0A1520),
|
||||
tertiary: MeshPalette.magenta,
|
||||
onTertiary: Color(0xFF201020),
|
||||
error: MeshPalette.alert,
|
||||
onError: Colors.white,
|
||||
errorContainer: Color(0xFF7F1D1D),
|
||||
onErrorContainer: Colors.white,
|
||||
onError: Color(0xFF1A0A08),
|
||||
errorContainer: MeshPalette.alertBg,
|
||||
onErrorContainer: MeshPalette.alert,
|
||||
surface: MeshPalette.bg,
|
||||
onSurface: MeshPalette.ink,
|
||||
surfaceContainerLowest: MeshPalette.bg,
|
||||
@@ -194,39 +141,33 @@ class MeshTheme {
|
||||
scrim: Colors.black54,
|
||||
inverseSurface: MeshPalette.ink,
|
||||
onInverseSurface: MeshPalette.bg,
|
||||
inversePrimary: MeshPalette.blueDim,
|
||||
inversePrimary: MeshPalette.signalDim,
|
||||
);
|
||||
return _build(scheme, Brightness.dark);
|
||||
}
|
||||
|
||||
static ThemeData light() {
|
||||
const scheme = ColorScheme.light(
|
||||
primary: MeshPalette.lightBlue,
|
||||
primary: MeshPalette.lightSignal,
|
||||
onPrimary: Colors.white,
|
||||
primaryContainer: Color(0xFFD3E4F5),
|
||||
onPrimaryContainer: Color(0xFF12354F),
|
||||
secondary: Color(0xFF8C4A8A),
|
||||
primaryContainer: Color(0xFFD4E8D8),
|
||||
onPrimaryContainer: MeshPalette.lightSignal,
|
||||
secondary: Color(0xFF2F6EA8),
|
||||
onSecondary: Colors.white,
|
||||
secondaryContainer: Color(0xFFEFD6EE),
|
||||
onSecondaryContainer: Color(0xFF3D1A3C),
|
||||
tertiary: Color(0xFF9A5B16),
|
||||
tertiary: Color(0xFF8C4A8A),
|
||||
onTertiary: Colors.white,
|
||||
tertiaryContainer: Color(0xFFF8E3C9),
|
||||
onTertiaryContainer: Color(0xFF4A2A05),
|
||||
error: Color(0xFFB53D2F),
|
||||
onError: Colors.white,
|
||||
errorContainer: Color(0xFFF6D9D4),
|
||||
onErrorContainer: Color(0xFF5C1A12),
|
||||
surface: MeshPalette.lightBg,
|
||||
onSurface: MeshPalette.lightInk,
|
||||
surfaceContainerLowest: MeshPalette.lightBg,
|
||||
surfaceContainerLow: MeshPalette.lightBg1,
|
||||
surfaceContainer: MeshPalette.lightBg1,
|
||||
surfaceContainerHigh: MeshPalette.lightBg2,
|
||||
surfaceContainerHighest: Color(0xFFD2DAE1),
|
||||
surfaceContainerHighest: Color(0xFFD5D0C0),
|
||||
onSurfaceVariant: MeshPalette.lightInk2,
|
||||
outline: MeshPalette.lightLine,
|
||||
outlineVariant: Color(0xFFD8DEE5),
|
||||
outlineVariant: Color(0xFFDBD6C6),
|
||||
);
|
||||
return _build(scheme, Brightness.light);
|
||||
}
|
||||
@@ -386,9 +327,9 @@ class MeshTheme {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: scheme.surface,
|
||||
backgroundColor: scheme.surfaceContainerLow,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
indicatorColor: scheme.primary,
|
||||
indicatorColor: scheme.primary.withValues(alpha: 0.14),
|
||||
indicatorShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.md),
|
||||
),
|
||||
@@ -400,13 +341,13 @@ class MeshTheme {
|
||||
fontSize: 10,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
color: selected ? scheme.onPrimary : scheme.onSurfaceVariant,
|
||||
color: selected ? scheme.primary : scheme.onSurfaceVariant,
|
||||
);
|
||||
}),
|
||||
iconTheme: WidgetStateProperty.resolveWith((states) {
|
||||
final selected = states.contains(WidgetState.selected);
|
||||
return IconThemeData(
|
||||
color: selected ? scheme.onPrimary : scheme.onSurfaceVariant,
|
||||
color: selected ? scheme.primary : scheme.onSurfaceVariant,
|
||||
size: 22,
|
||||
);
|
||||
}),
|
||||
@@ -445,106 +386,6 @@ class MeshTheme {
|
||||
),
|
||||
iconTheme: IconThemeData(color: scheme.onSurfaceVariant, size: 22),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: FadeForwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.linux: FadeForwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: FadeForwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.windows: FadeForwardsPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||
style: SegmentedButton.styleFrom(
|
||||
selectedBackgroundColor: scheme.primary.withValues(alpha: 0.16),
|
||||
selectedForegroundColor: scheme.primary,
|
||||
side: BorderSide(color: scheme.outlineVariant),
|
||||
textStyle: const TextStyle(
|
||||
fontFamily: MeshFonts.sans,
|
||||
fontFamilyFallback: MeshFonts.sansFallback,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor: WidgetStateProperty.resolveWith(
|
||||
(states) => states.contains(WidgetState.selected)
|
||||
? scheme.onPrimary
|
||||
: scheme.onSurfaceVariant,
|
||||
),
|
||||
trackColor: WidgetStateProperty.resolveWith(
|
||||
(states) => states.contains(WidgetState.selected)
|
||||
? scheme.primary
|
||||
: scheme.surfaceContainerHighest,
|
||||
),
|
||||
trackOutlineColor: WidgetStateProperty.resolveWith(
|
||||
(states) => states.contains(WidgetState.selected)
|
||||
? Colors.transparent
|
||||
: scheme.outline,
|
||||
),
|
||||
),
|
||||
sliderTheme: SliderThemeData(
|
||||
activeTrackColor: scheme.primary,
|
||||
inactiveTrackColor: scheme.surfaceContainerHighest,
|
||||
thumbColor: scheme.primary,
|
||||
overlayColor: scheme.primary.withValues(alpha: 0.12),
|
||||
valueIndicatorColor: scheme.surfaceContainerHighest,
|
||||
valueIndicatorTextStyle: TextStyle(
|
||||
fontFamily: MeshFonts.mono,
|
||||
fontFamilyFallback: MeshFonts.monoFallback,
|
||||
color: scheme.onSurface,
|
||||
fontSize: 12,
|
||||
),
|
||||
trackHeight: 3,
|
||||
),
|
||||
tabBarTheme: TabBarThemeData(
|
||||
labelColor: scheme.primary,
|
||||
unselectedLabelColor: scheme.onSurfaceVariant,
|
||||
indicatorColor: scheme.primary,
|
||||
dividerColor: scheme.outlineVariant,
|
||||
labelStyle: const TextStyle(
|
||||
fontFamily: MeshFonts.sans,
|
||||
fontFamilyFallback: MeshFonts.sansFallback,
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontFamily: MeshFonts.sans,
|
||||
fontFamilyFallback: MeshFonts.sansFallback,
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
color: scheme.primary,
|
||||
linearTrackColor: scheme.surfaceContainerHigh,
|
||||
circularTrackColor: Colors.transparent,
|
||||
),
|
||||
tooltipTheme: TooltipThemeData(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(MeshRadii.sm),
|
||||
border: Border.all(color: scheme.outline),
|
||||
),
|
||||
textStyle: TextStyle(color: scheme.onSurface, fontSize: 12),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: scheme.primary,
|
||||
foregroundColor: scheme.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontFamily: MeshFonts.sans,
|
||||
fontFamilyFallback: MeshFonts.sansFallback,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,16 +436,6 @@ class MeshTheme {
|
||||
);
|
||||
}
|
||||
|
||||
/// Color-emoji style with platform fallbacks and stable vertical metrics.
|
||||
static TextStyle emoji({double fontSize = 28}) {
|
||||
return TextStyle(
|
||||
fontFamily: MeshFonts.emoji,
|
||||
fontFamilyFallback: MeshFonts.emojiFallback,
|
||||
fontSize: fontSize,
|
||||
height: 1,
|
||||
);
|
||||
}
|
||||
|
||||
/// Color-code an SNR value for consistency across the app.
|
||||
static Color snrColor(num? snr, {required bool blocked}) {
|
||||
if (blocked) return MeshPalette.alert;
|
||||
|
||||
@@ -31,6 +31,21 @@ Future<bool> showDisconnectDialog(
|
||||
if (confirmed == true) {
|
||||
appLogger.info('Disconnect confirmed from popup', tag: 'Connection');
|
||||
await connector.disconnect();
|
||||
if (context.mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.dialog_disconnectedTitle),
|
||||
content: Text(context.l10n.dialog_disconnectedMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.common_ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
|
||||
/// Mixin that automatically navigates back to scanner when disconnected.
|
||||
/// Use in State classes for screens that require active connection.
|
||||
mixin DisconnectNavigationMixin<T extends StatefulWidget> on State<T> {
|
||||
/// Call this in your Widget build method to enable auto-navigation.
|
||||
/// Returns true if still connected, false if navigation was triggered.
|
||||
bool checkConnectionAndNavigate(MeshCoreConnector connector) {
|
||||
if (!connector.isConnected) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,8 @@ class AppBarTitle extends StatelessWidget {
|
||||
if (showBattery) BatteryIndicator(connector: connector),
|
||||
if (showSnr) SNRIndicator(connector: connector),
|
||||
if (connector.supportsCompanionRadioStats)
|
||||
const RadioStatsIconButton(compact: true),
|
||||
if (connector.isConnected)
|
||||
const RadioStatsIconButton(compact: true),
|
||||
],
|
||||
),
|
||||
trailing ?? const SizedBox.shrink(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
|
||||
class BatteryUi {
|
||||
final IconData icon;
|
||||
@@ -11,19 +10,19 @@ class BatteryUi {
|
||||
|
||||
BatteryUi batteryUiForPercent(int? percent) {
|
||||
if (percent == null) {
|
||||
return const BatteryUi(Icons.battery_unknown, null);
|
||||
return const BatteryUi(Icons.battery_unknown, Colors.grey);
|
||||
}
|
||||
|
||||
final p = percent.clamp(0, 100);
|
||||
|
||||
return switch (p) {
|
||||
<= 5 => const BatteryUi(Icons.battery_alert, MeshPalette.alert),
|
||||
<= 15 => const BatteryUi(Icons.battery_0_bar, MeshPalette.alert),
|
||||
<= 30 => const BatteryUi(Icons.battery_1_bar, MeshPalette.warn),
|
||||
<= 45 => const BatteryUi(Icons.battery_2_bar, MeshPalette.warn),
|
||||
<= 60 => const BatteryUi(Icons.battery_3_bar, null),
|
||||
<= 80 => const BatteryUi(Icons.battery_5_bar, null),
|
||||
_ => const BatteryUi(Icons.battery_full, MeshPalette.signal),
|
||||
<= 5 => const BatteryUi(Icons.battery_alert, Colors.redAccent),
|
||||
<= 15 => const BatteryUi(Icons.battery_0_bar, Colors.redAccent),
|
||||
<= 30 => const BatteryUi(Icons.battery_1_bar, Colors.orange),
|
||||
<= 45 => const BatteryUi(Icons.battery_2_bar, Colors.amber),
|
||||
<= 60 => const BatteryUi(Icons.battery_3_bar, Colors.lightGreen),
|
||||
<= 80 => const BatteryUi(Icons.battery_5_bar, Colors.green),
|
||||
_ => const BatteryUi(Icons.battery_full, Colors.green),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,9 +76,9 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
|
||||
Flexible(
|
||||
child: Text(
|
||||
displayText,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: batteryUi.color,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
||||
@@ -86,7 +86,7 @@ class ByteCountedTextField extends StatelessWidget {
|
||||
final counterColor = ratio > errorThreshold
|
||||
? Theme.of(context).colorScheme.error
|
||||
: ratio > warningThreshold
|
||||
? Theme.of(context).colorScheme.tertiary
|
||||
? Colors.orange
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
|
||||
return Column(
|
||||
@@ -118,9 +118,8 @@ class ByteCountedTextField extends StatelessWidget {
|
||||
textInputAction: textInputAction,
|
||||
onSubmitted: onSubmitted,
|
||||
),
|
||||
Opacity(
|
||||
opacity: showCounter ? 1 : 0,
|
||||
child: Padding(
|
||||
if (showCounter)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4, right: 4),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
@@ -130,7 +129,6 @@ class ByteCountedTextField extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
import 'mesh_ui.dart';
|
||||
import 'signal_ui.dart';
|
||||
|
||||
/// A MeshCard-based row for displaying a scanned BLE device.
|
||||
/// Shows an AvatarCircle (router icon, deterministic hue from device name),
|
||||
/// device name, mono MAC address, mono RSSI dBm, and SignalBars on the right.
|
||||
/// While connecting, shows a small progress ring instead of signal bars.
|
||||
/// A reusable tile widget for displaying a MeshCore device in a list
|
||||
class DeviceTile extends StatelessWidget {
|
||||
final ScanResult scanResult;
|
||||
final VoidCallback? onTap;
|
||||
final bool isConnecting;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const DeviceTile({
|
||||
super.key,
|
||||
required this.scanResult,
|
||||
required this.onTap,
|
||||
this.isConnecting = false,
|
||||
});
|
||||
const DeviceTile({super.key, required this.scanResult, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -30,12 +17,23 @@ class DeviceTile extends StatelessWidget {
|
||||
final name = device.platformName.isNotEmpty
|
||||
? device.platformName
|
||||
: scanResult.advertisementData.advName;
|
||||
final displayName = name.isNotEmpty
|
||||
? name
|
||||
: context.l10n.common_unknownDevice;
|
||||
final mac = device.remoteId.toString();
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return ListTile(
|
||||
leading: _buildSignalIcon(rssi),
|
||||
title: Text(
|
||||
name.isNotEmpty ? name : context.l10n.common_unknownDevice,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(device.remoteId.toString()),
|
||||
trailing: ElevatedButton(
|
||||
onPressed: onTap,
|
||||
child: Text(context.l10n.common_connect),
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSignalIcon(int rssi) {
|
||||
final tier = rssi >= -60
|
||||
? 0
|
||||
: rssi >= -70
|
||||
@@ -47,70 +45,15 @@ class DeviceTile extends StatelessWidget {
|
||||
: 4;
|
||||
final signalUi = signalUiForStrengthTier(tier);
|
||||
|
||||
return MeshCard(
|
||||
onTap: onTap == null
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.selectionClick();
|
||||
onTap!();
|
||||
},
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
AvatarCircle(name: displayName, size: 42, icon: Icons.router),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
displayName,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: scheme.onSurface,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
mac,
|
||||
style: MeshTheme.mono(
|
||||
fontSize: 11,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (isConnecting)
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: scheme.primary,
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Icon(signalUi.icon, size: 16, color: signalUi.color),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
'$rssi dBm',
|
||||
style: MeshTheme.mono(fontSize: 10, color: signalUi.color),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(signalUi.icon, color: signalUi.color),
|
||||
Text(
|
||||
'$rssi dBm',
|
||||
style: TextStyle(fontSize: 10, color: signalUi.color),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,65 +29,31 @@ class FeatureToggleRow extends StatefulWidget {
|
||||
class _FeatureToggleRow extends State<FeatureToggleRow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
widget.subtitle,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
title: Text(widget.title),
|
||||
subtitle: Text(widget.subtitle),
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Switch(value: widget.value, onChanged: widget.onChanged),
|
||||
if (widget.hasRefreshing) ...[
|
||||
const SizedBox(width: 4),
|
||||
widget.isRefreshing
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1.8,
|
||||
color: scheme.primary,
|
||||
),
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
onPressed: widget.onRefresh,
|
||||
tooltip: widget.refreshTooltip,
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 32,
|
||||
minHeight: 32,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (widget.hasRefreshing)
|
||||
IconButton(
|
||||
icon: widget.isRefreshing
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh, size: 20),
|
||||
onPressed: widget.isRefreshing ? null : widget.onRefresh,
|
||||
tooltip: widget.refreshTooltip,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
|
||||
class EmojiPicker extends StatelessWidget {
|
||||
final Function(String) onEmojiSelected;
|
||||
@@ -258,11 +257,7 @@ class EmojiPicker extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
emoji,
|
||||
style: MeshTheme.emoji(),
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
),
|
||||
style: const TextStyle(fontSize: 28),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -303,12 +298,7 @@ class EmojiPicker extends StatelessWidget {
|
||||
child: Center(
|
||||
child: Text(
|
||||
emojis[index],
|
||||
style: MeshTheme.emoji(),
|
||||
textHeightBehavior:
|
||||
const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
),
|
||||
style: const TextStyle(fontSize: 28),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A centered empty state display with icon, title, and optional subtitle/action.
|
||||
/// Features a tinted icon circle, fade+slide entrance animation, and clear
|
||||
/// typography hierarchy using the MeshCore design system.
|
||||
class EmptyState extends StatefulWidget {
|
||||
class EmptyState extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
@@ -17,97 +15,25 @@ class EmptyState extends StatefulWidget {
|
||||
this.action,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EmptyState> createState() => _EmptyStateState();
|
||||
}
|
||||
|
||||
class _EmptyStateState extends State<EmptyState>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 420),
|
||||
);
|
||||
late final CurvedAnimation _curve = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_curve.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return FadeTransition(
|
||||
opacity: _curve,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 0.06),
|
||||
end: Offset.zero,
|
||||
).animate(_curve),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.primary.withValues(alpha: 0.08),
|
||||
border: Border.all(
|
||||
color: scheme.primary.withValues(alpha: 0.18),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: 36,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
widget.title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: scheme.onSurface,
|
||||
letterSpacing: -0.1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (widget.subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
widget.subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
color: scheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
if (widget.action != null) ...[
|
||||
const SizedBox(height: 28),
|
||||
widget.action!,
|
||||
],
|
||||
],
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(title, style: TextStyle(fontSize: 16, color: Colors.grey[600])),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (action != null) ...[const SizedBox(height: 24), action!],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,10 +180,7 @@ class _GifPickerState extends State<GifPicker> {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.gifPicker_poweredBy,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -200,18 +197,11 @@ class _GifPickerState extends State<GifPicker> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Icon(Icons.error_outline, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
@@ -229,18 +219,11 @@ class _GifPickerState extends State<GifPicker> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_off,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Icon(Icons.search_off, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.gifPicker_noGifsFound,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../theme/mesh_theme.dart';
|
||||
|
||||
class JumpToBottomButton extends StatelessWidget {
|
||||
final ChatScrollController scrollController;
|
||||
@@ -10,7 +8,6 @@ class JumpToBottomButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: scrollController.showJumpToBottom,
|
||||
builder: (context, show, _) {
|
||||
@@ -18,33 +15,9 @@ class JumpToBottomButton extends StatelessWidget {
|
||||
return Positioned(
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: scrollController.jumpToBottom,
|
||||
borderRadius: BorderRadius.circular(MeshRadii.pill),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.surfaceContainerHigh.withValues(alpha: 0.92),
|
||||
border: Border.all(color: scheme.outlineVariant, width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.18),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: 22,
|
||||
color: scheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: scrollController.jumpToBottom,
|
||||
child: const Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user