refactor(tcp): promote MeshCoreTcpConnector, fix translations, harden UI

- Replace thin MeshCoreTcpManager facade with a proper MeshCoreTcpConnector
  that owns TcpTransportService and the frame subscription, mirroring
  MeshCoreUsbManager. The connector no longer holds a raw TcpTransportService
  or a _tcpFrameSubscription field.
- Remove hardcoded default host IP from TcpScreen (keep port 5000 hint).
- Disable connect button during scanning state, not just connecting state.
- Fix tcpPortLabel mistranslated as nautical "port/harbor" in de, it, pt,
  nl, sv, sk, sl, zh; fix corrupted Slovak tcpPortHint ("5 000" → "5000").
- Remove unused tcpStatus_connecting string from all 15 locale arb files
  and all generated app_localizations_*.dart files.
- Add extendedPadding to TCP screen FABs to match USB screen.
- Add Key to connect button; update tests to use byKey and assert
  onPressed == null when button is disabled during scanning.
This commit is contained in:
Zach
2026-03-13 10:58:52 -07:00
parent 1ad5db27ca
commit db935a7454
35 changed files with 91 additions and 123 deletions
+17 -22
View File
@@ -116,8 +116,7 @@ class MeshCoreConnector extends ChangeNotifier {
bool _manualDisconnect = false;
final MeshCoreUsbManager _usbManager = MeshCoreUsbManager();
StreamSubscription<Uint8List>? _usbFrameSubscription;
final MeshCoreTcpManager _tcpManager = MeshCoreTcpManager();
StreamSubscription<Uint8List>? _tcpFrameSubscription;
final MeshCoreTcpConnector _tcpConnector = MeshCoreTcpConnector();
MeshCoreTransportType _activeTransport = MeshCoreTransportType.bluetooth;
final List<ScanResult> _scanResults = [];
@@ -257,7 +256,7 @@ class MeshCoreConnector extends ChangeNotifier {
bool get isUsbTransportConnected =>
_state == MeshCoreConnectionState.connected &&
_activeTransport == MeshCoreTransportType.usb;
String? get activeTcpEndpoint => _tcpManager.activeEndpoint;
String? get activeTcpEndpoint => _tcpConnector.activeEndpoint;
bool get isTcpTransportConnected =>
_state == MeshCoreConnectionState.connected &&
_activeTransport == MeshCoreTransportType.tcp;
@@ -666,7 +665,7 @@ class MeshCoreConnector extends ChangeNotifier {
_appDebugLogService = appDebugLogService;
_backgroundService = backgroundService;
_usbManager.setDebugLogService(_appDebugLogService);
_tcpManager.setDebugLogService(_appDebugLogService);
_tcpConnector.setDebugLogService(_appDebugLogService);
// Initialize notification service
_notificationService.initialize();
@@ -1002,22 +1001,21 @@ class MeshCoreConnector extends ChangeNotifier {
await disconnect(manual: false);
return;
}
if (_tcpManager.isConnected) {
await _tcpManager.disconnect();
if (_tcpConnector.isConnected) {
await _tcpConnector.disconnect();
}
}
await _tcpFrameSubscription?.cancel();
_tcpFrameSubscription = null;
await _tcpManager.connect(host: host, port: port);
await _tcpConnector.cancelFrameSubscription();
await _tcpConnector.connect(host: host, port: port);
final isTcpConnectCancelled =
_activeTransport != MeshCoreTransportType.tcp ||
_state != MeshCoreConnectionState.connecting ||
!_tcpManager.isConnected;
!_tcpConnector.isConnected;
if (isTcpConnectCancelled) {
await handleTcpConnectAbort(
message:
'connectTcp aborted before handshake: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}',
'connectTcp aborted before handshake: state=$_state transport=$_activeTransport connected=${_tcpConnector.isConnected}',
);
return;
}
@@ -1027,16 +1025,16 @@ class MeshCoreConnector extends ChangeNotifier {
final isTcpConnectCancelledAfterDelay =
_activeTransport != MeshCoreTransportType.tcp ||
_state != MeshCoreConnectionState.connecting ||
!_tcpManager.isConnected;
!_tcpConnector.isConnected;
if (isTcpConnectCancelledAfterDelay) {
await handleTcpConnectAbort(
message:
'connectTcp aborted after connect delay: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}',
'connectTcp aborted after connect delay: state=$_state transport=$_activeTransport connected=${_tcpConnector.isConnected}',
);
return;
}
_tcpFrameSubscription = _tcpManager.frameStream.listen(
_handleFrame,
_tcpConnector.listenFrames(
onFrame: _handleFrame,
onError: (error, stackTrace) {
_appDebugLogService?.error('TCP transport error: $error', tag: 'TCP');
unawaited(disconnect(manual: false));
@@ -1073,7 +1071,7 @@ class MeshCoreConnector extends ChangeNotifier {
manualDisconnect: _manualDisconnect,
state: _state,
activeTransport: _activeTransport,
tcpManagerConnected: _tcpManager.isConnected,
tcpManagerConnected: _tcpConnector.isConnected,
);
if (tcpConnectCancelledBeforeHandshake) {
_appDebugLogService?.info(
@@ -1445,9 +1443,7 @@ class MeshCoreConnector extends ChangeNotifier {
await _usbFrameSubscription?.cancel();
_usbFrameSubscription = null;
await _usbManager.disconnect();
await _tcpFrameSubscription?.cancel();
_tcpFrameSubscription = null;
await _tcpManager.disconnect();
await _tcpConnector.disconnect();
await _notifySubscription?.cancel();
_notifySubscription = null;
@@ -1530,7 +1526,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (_activeTransport == MeshCoreTransportType.usb) {
await _usbManager.write(data);
} else if (_activeTransport == MeshCoreTransportType.tcp) {
await _tcpManager.write(data);
await _tcpConnector.write(data);
} else {
if (_rxCharacteristic == null) {
throw Exception("MeshCore RX characteristic not available");
@@ -4484,14 +4480,13 @@ class MeshCoreConnector extends ChangeNotifier {
_scanSubscription?.cancel();
_connectionSubscription?.cancel();
_usbFrameSubscription?.cancel();
_tcpFrameSubscription?.cancel();
_notifySubscription?.cancel();
_notifyListenersTimer?.cancel();
_reconnectTimer?.cancel();
_batteryPollTimer?.cancel();
_receivedFramesController.close();
_usbManager.dispose();
_tcpManager.dispose();
_tcpConnector.dispose();
// Flush pending unread writes before disposal
_unreadStore.flush();
+40 -4
View File
@@ -1,34 +1,70 @@
import 'dart:async';
import 'dart:typed_data';
import '../services/app_debug_log_service.dart';
import '../services/tcp_transport_service.dart';
class MeshCoreTcpManager {
/// Manages TCP transport for MeshCore devices.
///
/// Owns the [TcpTransportService] and TCP-specific connection state.
/// The main [MeshCoreConnector] delegates all TCP operations here.
class MeshCoreTcpConnector {
final TcpTransportService _service = TcpTransportService();
AppDebugLogService? _debugLog;
StreamSubscription<Uint8List>? _frameSubscription;
// --- Getters ---
String? get activeEndpoint => _service.activeEndpoint;
bool get isConnected => _service.isConnected;
Stream<Uint8List> get frameStream => _service.frameStream;
// --- Configuration ---
void setDebugLogService(AppDebugLogService? service) {
_debugLog = service;
_service.setDebugLogService(service);
}
// --- Connection lifecycle ---
Future<void> connect({required String host, required int port}) async {
_debugLog?.info('TcpManager.connect endpoint=$host:$port', tag: 'TCP');
_debugLog?.info('TcpConnector.connect endpoint=$host:$port', tag: 'TCP');
await _frameSubscription?.cancel();
_frameSubscription = null;
await _service.connect(host: host, port: port);
_debugLog?.info(
'TcpConnector.connect done, endpoint=${_service.activeEndpoint}',
tag: 'TCP',
);
}
StreamSubscription<Uint8List> listenFrames({
required void Function(Uint8List) onFrame,
required void Function(Object, StackTrace?) onError,
required void Function() onDone,
}) {
_frameSubscription = _service.frameStream.listen(
onFrame,
onError: onError,
onDone: onDone,
);
return _frameSubscription!;
}
Future<void> cancelFrameSubscription() async {
await _frameSubscription?.cancel();
_frameSubscription = null;
}
Future<void> disconnect() async {
_debugLog?.info('TcpManager.disconnect', tag: 'TCP');
if (!_service.isConnected && _frameSubscription == null) return;
_debugLog?.info('TcpConnector.disconnect', tag: 'TCP');
await _frameSubscription?.cancel();
_frameSubscription = null;
await _service.disconnect();
}
Future<void> write(Uint8List data) => _service.write(data);
void dispose() {
_frameSubscription?.cancel();
_service.dispose();
}
}
-1
View File
@@ -1881,7 +1881,6 @@
"tcpPortLabel": "Пристанище",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Въведете крайната точка и свържете се.",
"tcpStatus_connecting": "Свързване към TCP крайния пункт...",
"tcpStatus_connectingTo": "Свързване към {endpoint}...",
"tcpErrorHostRequired": "Необходим е IP адрес.",
"tcpErrorPortInvalid": "Портът трябва да бъде между 1 и 65535.",
+1 -2
View File
@@ -1906,10 +1906,9 @@
"connectionChoiceTcpLabel": "TCP",
"tcpHostHint": "192.168.40.10",
"tcpScreenTitle": "Verbinden über TCP",
"tcpPortLabel": "Hafen",
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Geben Sie den Endpunkt ein und verbinden Sie sich.",
"tcpStatus_connecting": "Verbindung zum TCP-Endpunkt hergestellt...",
"tcpStatus_connectingTo": "Verbindung zu {endpoint}...",
"tcpErrorHostRequired": "Eine IP-Adresse ist erforderlich.",
"tcpErrorPortInvalid": "Die Portnummer muss zwischen 1 und 65535 liegen.",
-1
View File
@@ -56,7 +56,6 @@
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Enter endpoint and connect",
"tcpStatus_connecting": "Connecting to TCP endpoint...",
"tcpStatus_connectingTo": "Connecting to {endpoint}...",
"@tcpStatus_connectingTo": {
"placeholders": {
-1
View File
@@ -1909,7 +1909,6 @@
"tcpPortLabel": "Puerto",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Ingrese la dirección final y conecte.",
"tcpStatus_connecting": "Conectándose al punto final TCP...",
"tcpStatus_connectingTo": "Conectándose a {endpoint}...",
"tcpErrorHostRequired": "Se requiere la dirección IP.",
"tcpErrorPortInvalid": "El puerto debe estar entre 1 y 65535.",
-1
View File
@@ -1881,7 +1881,6 @@
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Entrez l'adresse de destination et connectez-vous.",
"tcpStatus_connecting": "Connexion au point de terminaison TCP...",
"tcpStatus_connectingTo": "Connexion à {endpoint}...",
"tcpErrorHostRequired": "Une adresse IP est obligatoire.",
"tcpErrorPortInvalid": "La taille du port doit être comprise entre 1 et 65535.",
+1 -2
View File
@@ -1878,10 +1878,9 @@
"tcpHostHint": "192.168.40.10",
"connectionChoiceTcpLabel": "TCP",
"tcpScreenTitle": "Stabilire una connessione tramite TCP",
"tcpPortLabel": "Porto",
"tcpPortLabel": "Porta",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Inserisci l'endpoint e connettiti.",
"tcpStatus_connecting": "Connessione al punto finale TCP...",
"tcpStatus_connectingTo": "Connessione a {endpoint}...",
"tcpErrorHostRequired": "È necessario fornire un indirizzo IP.",
"tcpErrorPortInvalid": "La dimensione della porta deve essere compresa tra 1 e 65535.",
-6
View File
@@ -376,12 +376,6 @@ abstract class AppLocalizations {
/// **'Enter endpoint and connect'**
String get tcpStatus_notConnected;
/// No description provided for @tcpStatus_connecting.
///
/// In en, this message translates to:
/// **'Connecting to TCP endpoint...'**
String get tcpStatus_connecting;
/// No description provided for @tcpStatus_connectingTo.
///
/// In en, this message translates to:
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Въведете крайната точка и свържете се.';
@override
String get tcpStatus_connecting => 'Свързване към TCP крайния пункт...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Свързване към $endpoint...';
+1 -5
View File
@@ -130,7 +130,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Hafen';
String get tcpPortLabel => 'Port';
@override
String get tcpPortHint => '5000';
@@ -139,10 +139,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get tcpStatus_notConnected =>
'Geben Sie den Endpunkt ein und verbinden Sie sich.';
@override
String get tcpStatus_connecting =>
'Verbindung zum TCP-Endpunkt hergestellt...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Verbindung zu $endpoint...';
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Enter endpoint and connect';
@override
String get tcpStatus_connecting => 'Connecting to TCP endpoint...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Connecting to $endpoint...';
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Ingrese la dirección final y conecte.';
@override
String get tcpStatus_connecting => 'Conectándose al punto final TCP...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Conectándose a $endpoint...';
-3
View File
@@ -139,9 +139,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get tcpStatus_notConnected =>
'Entrez l\'adresse de destination et connectez-vous.';
@override
String get tcpStatus_connecting => 'Connexion au point de terminaison TCP...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Connexion à $endpoint...';
+1 -4
View File
@@ -130,7 +130,7 @@ class AppLocalizationsIt extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Porto';
String get tcpPortLabel => 'Porta';
@override
String get tcpPortHint => '5000';
@@ -138,9 +138,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Inserisci l\'endpoint e connettiti.';
@override
String get tcpStatus_connecting => 'Connessione al punto finale TCP...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Connessione a $endpoint...';
+1 -4
View File
@@ -130,7 +130,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Haven';
String get tcpPortLabel => 'Poort';
@override
String get tcpPortHint => '5000';
@@ -138,9 +138,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Voer het eindpunt in en verbind';
@override
String get tcpStatus_connecting => 'Verbinding maken met TCP-eindpunt...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Verbinding maken met $endpoint...';
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Wprowadź adres URL i połącz';
@override
String get tcpStatus_connecting => 'Połączenie z punktem TCP...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Połączenie z $endpoint...';
+1 -5
View File
@@ -130,7 +130,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Porto';
String get tcpPortLabel => 'Porta';
@override
String get tcpPortHint => '5000';
@@ -138,10 +138,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Insira o endereço final e conecte-se.';
@override
String get tcpStatus_connecting =>
'Conectando ao ponto de extremidade TCP...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Conectando a $endpoint...';
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Введите адрес и подключитесь.';
@override
String get tcpStatus_connecting => 'Установление соединения с TCP-портом...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Подключение к $endpoint...';
+2 -5
View File
@@ -130,17 +130,14 @@ class AppLocalizationsSk extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Pri항';
String get tcpPortLabel => 'Port';
@override
String get tcpPortHint => '5 000';
String get tcpPortHint => '5000';
@override
String get tcpStatus_notConnected => 'Zadajte cieľovú adresu a pripojte sa.';
@override
String get tcpStatus_connecting => 'Pripojenie k TCP endpointu...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Pripojenie k $endpoint...';
+1 -4
View File
@@ -130,7 +130,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Pril';
String get tcpPortLabel => 'Vrata';
@override
String get tcpPortHint => '5000';
@@ -138,9 +138,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Vnesite končni naslov in se povežite';
@override
String get tcpStatus_connecting => 'Povezava z TCP koncem...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Povezava z $endpoint...';
+1 -4
View File
@@ -130,7 +130,7 @@ class AppLocalizationsSv extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => 'Hamn';
String get tcpPortLabel => 'Port';
@override
String get tcpPortHint => '5000';
@@ -138,9 +138,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Ange slutpunkt och anslut';
@override
String get tcpStatus_connecting => 'Anslutning till TCP-slutpunkt...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Anslutning till $endpoint...';
-3
View File
@@ -138,9 +138,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get tcpStatus_notConnected => 'Введіть кінцеву точку та підключіться';
@override
String get tcpStatus_connecting => 'Підключення до TCP-кінцевої точки...';
@override
String tcpStatus_connectingTo(String endpoint) {
return 'Підключення до $endpoint...';
+1 -4
View File
@@ -130,7 +130,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get tcpHostHint => '192.168.40.10';
@override
String get tcpPortLabel => '';
String get tcpPortLabel => '端口';
@override
String get tcpPortHint => '5000';
@@ -138,9 +138,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get tcpStatus_notConnected => '输入目标地址,然后连接';
@override
String get tcpStatus_connecting => '连接到 TCP 终点...';
@override
String tcpStatus_connectingTo(String endpoint) {
return '连接到 $endpoint...';
+1 -2
View File
@@ -1878,10 +1878,9 @@
"tcpHostLabel": "IP-adres",
"tcpHostHint": "192.168.40.10",
"connectionChoiceTcpLabel": "TCP",
"tcpPortLabel": "Haven",
"tcpPortLabel": "Poort",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Voer het eindpunt in en verbind",
"tcpStatus_connecting": "Verbinding maken met TCP-eindpunt...",
"tcpStatus_connectingTo": "Verbinding maken met {endpoint}...",
"tcpErrorHostRequired": "Een IP-adres is vereist.",
"tcpErrorPortInvalid": "De poortwaarde moet tussen 1 en 65535 liggen.",
-1
View File
@@ -1881,7 +1881,6 @@
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Wprowadź adres URL i połącz",
"tcpStatus_connecting": "Połączenie z punktem TCP...",
"tcpStatus_connectingTo": "Połączenie z {endpoint}...",
"tcpErrorHostRequired": "Wymagana jest adresa IP.",
"tcpErrorPortInvalid": "Numer portu musi mieścić się w zakresie od 1 do 65535.",
+1 -2
View File
@@ -1878,10 +1878,9 @@
"connectionChoiceTcpLabel": "TCP",
"tcpScreenTitle": "Estabelecer conexão via TCP",
"tcpHostHint": "192.168.40.10",
"tcpPortLabel": "Porto",
"tcpPortLabel": "Porta",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Insira o endereço final e conecte-se.",
"tcpStatus_connecting": "Conectando ao ponto de extremidade TCP...",
"tcpStatus_connectingTo": "Conectando a {endpoint}...",
"tcpErrorHostRequired": "É necessário fornecer um endereço IP.",
"tcpErrorPortInvalid": "O valor do porto deve estar entre 1 e 65535.",
-1
View File
@@ -1121,7 +1121,6 @@
"tcpPortLabel": "Порт",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Введите адрес и подключитесь.",
"tcpStatus_connecting": "Установление соединения с TCP-портом...",
"tcpStatus_connectingTo": "Подключение к {endpoint}...",
"tcpErrorHostRequired": "Необходимо указать IP-адрес.",
"tcpErrorPortInvalid": "Порт должен находиться в диапазоне от 1 до 65535.",
+2 -3
View File
@@ -1878,10 +1878,9 @@
"tcpHostLabel": "IP adresa",
"tcpScreenTitle": "Spojte sa pomocou protokolu TCP",
"connectionChoiceTcpLabel": "TCP",
"tcpPortLabel": "Pri항",
"tcpPortHint": "5 000",
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Zadajte cieľovú adresu a pripojte sa.",
"tcpStatus_connecting": "Pripojenie k TCP endpointu...",
"tcpStatus_connectingTo": "Pripojenie k {endpoint}...",
"tcpErrorHostRequired": "Je potrebné zadať IP adresu.",
"tcpErrorPortInvalid": "Číslo portu musí byť medzi 1 a 65535.",
+1 -2
View File
@@ -1878,10 +1878,9 @@
"tcpHostLabel": "IP naslov",
"tcpHostHint": "192.168.40.10",
"tcpScreenTitle": "Komunicirajte preko protokola TCP",
"tcpPortLabel": "Pril",
"tcpPortLabel": "Vrata",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Vnesite končni naslov in se povežite",
"tcpStatus_connecting": "Povezava z TCP koncem...",
"tcpStatus_connectingTo": "Povezava z {endpoint}...",
"tcpErrorHostRequired": "Potrebna je IP-naslov.",
"tcpErrorPortInvalid": "Port mora biti med 1 in 65535.",
+1 -2
View File
@@ -1878,10 +1878,9 @@
"tcpHostLabel": "IP-adress",
"tcpScreenTitle": "Anslut via TCP",
"connectionChoiceTcpLabel": "TCP",
"tcpPortLabel": "Hamn",
"tcpPortLabel": "Port",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Ange slutpunkt och anslut",
"tcpStatus_connecting": "Anslutning till TCP-slutpunkt...",
"tcpStatus_connectingTo": "Anslutning till {endpoint}...",
"tcpErrorHostRequired": "IP-adress krävs.",
"tcpErrorPortInvalid": "Porten måste vara mellan 1 och 65535.",
-1
View File
@@ -1881,7 +1881,6 @@
"tcpPortLabel": "Порт",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "Введіть кінцеву точку та підключіться",
"tcpStatus_connecting": "Підключення до TCP-кінцевої точки...",
"tcpStatus_connectingTo": "Підключення до {endpoint}...",
"tcpErrorHostRequired": "Необхідно вказати IP-адресу.",
"tcpErrorPortInvalid": "Порт повинен бути в межах від 1 до 65535.",
+1 -2
View File
@@ -1883,10 +1883,9 @@
"tcpHostHint": "192.168.40.10",
"tcpScreenTitle": "通过 TCP 连接",
"connectionChoiceTcpLabel": "TCP",
"tcpPortLabel": "",
"tcpPortLabel": "端口",
"tcpPortHint": "5000",
"tcpStatus_notConnected": "输入目标地址,然后连接",
"tcpStatus_connecting": "连接到 TCP 终点...",
"tcpStatus_connectingTo": "连接到 {endpoint}...",
"tcpErrorHostRequired": "需要提供IP地址。",
"tcpErrorPortInvalid": "端口号必须在 1 到 65535 之间。",
+8 -2
View File
@@ -27,7 +27,7 @@ class _TcpScreenState extends State<TcpScreen> {
@override
void initState() {
super.initState();
_hostController = TextEditingController(text: '192.168.40.10');
_hostController = TextEditingController();
_portController = TextEditingController(text: '5000');
_connector = context.read<MeshCoreConnector>();
@@ -81,6 +81,9 @@ class _TcpScreenState extends State<TcpScreen> {
final isConnecting =
connector.state == MeshCoreConnectionState.connecting &&
connector.activeTransport == MeshCoreTransportType.tcp;
final isButtonDisabled =
isConnecting ||
connector.state == MeshCoreConnectionState.scanning;
return Column(
children: [
_buildStatusBar(context, connector),
@@ -112,7 +115,8 @@ class _TcpScreenState extends State<TcpScreen> {
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: isConnecting ? null : _connectTcp,
key: const Key('tcp_connect_button'),
onPressed: isButtonDisabled ? null : _connectTcp,
icon: isConnecting
? const SizedBox(
width: 18,
@@ -153,6 +157,7 @@ class _TcpScreenState extends State<TcpScreen> {
);
},
heroTag: 'tcp_usb_action',
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
icon: const Icon(Icons.usb),
label: Text(context.l10n.connectionChoiceUsbLabel),
),
@@ -162,6 +167,7 @@ class _TcpScreenState extends State<TcpScreen> {
Navigator.of(context).maybePop();
},
heroTag: 'tcp_ble_action',
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
icon: const Icon(Icons.bluetooth),
label: Text(context.l10n.connectionChoiceBluetoothLabel),
),
+8 -9
View File
@@ -93,7 +93,7 @@ void main() {
final l10n = AppLocalizations.of(context);
await tester.enterText(find.byType(TextField).first, '');
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
await tester.tap(find.byKey(const Key('tcp_connect_button')));
await tester.pumpAndSettle();
expect(find.text(l10n.tcpErrorHostRequired), findsOneWidget);
@@ -101,7 +101,7 @@ void main() {
await tester.enterText(find.byType(TextField).first, '192.168.1.50');
await tester.enterText(find.byType(TextField).at(1), '99999');
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
await tester.tap(find.byKey(const Key('tcp_connect_button')));
await tester.pumpAndSettle();
expect(connector.connectTcpCalls, 0);
@@ -135,7 +135,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 60));
});
testWidgets('TcpScreen allows connect while connector is scanning', (
testWidgets('TcpScreen disables connect button while connector is scanning', (
tester,
) async {
final connector = _FakeMeshCoreConnector()
@@ -150,12 +150,11 @@ void main() {
);
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
await tester.pumpAndSettle();
expect(connector.connectTcpCalls, 1);
expect(connector.lastHost, '192.168.40.10');
expect(connector.lastPort, 5000);
final button = tester.widget<ButtonStyleButton>(
find.byKey(const Key('tcp_connect_button')),
);
expect(button.onPressed, isNull);
expect(connector.connectTcpCalls, 0);
});
testWidgets('TcpScreen narrow width long status text does not overflow', (