Merge branch 'dev' into dev

This commit is contained in:
PacoX
2026-05-28 08:51:23 +02:00
committed by GitHub
60 changed files with 1450 additions and 325 deletions
+37 -4
View File
@@ -11,6 +11,7 @@ import '../services/app_settings_service.dart';
import '../services/notification_service.dart';
import '../services/translation_service.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/sync_progress_overlay.dart';
import '../helpers/snack_bar_builder.dart';
import 'map_cache_screen.dart';
@@ -23,6 +24,7 @@ class AppSettingsScreen extends StatelessWidget {
appBar: AppBar(
title: AdaptiveAppBarTitle(context.l10n.appSettings_title),
centerTitle: true,
bottom: const SyncProgressAppBarBottom(),
),
body: SafeArea(
top: false,
@@ -559,6 +561,7 @@ class AppSettingsScreen extends StatelessWidget {
TranslationService translationService,
) {
final settings = settingsService.settings;
final translationEnabled = settings.translationEnabled;
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -579,11 +582,41 @@ class AppSettingsScreen extends StatelessWidget {
),
const Divider(height: 1),
SwitchListTile(
secondary: const Icon(Icons.outgoing_mail),
title: Text(context.l10n.translation_composerTitle),
subtitle: Text(context.l10n.translation_composerSubtitle),
secondary: Icon(
Icons.auto_awesome_outlined,
color: translationEnabled ? null : Colors.grey,
),
title: Text(
context.l10n.translation_autoIncomingTitle,
style: TextStyle(color: translationEnabled ? null : Colors.grey),
),
subtitle: Text(
context.l10n.translation_autoIncomingSubtitle,
style: TextStyle(color: translationEnabled ? null : Colors.grey),
),
value: settings.autoTranslateIncomingMessages,
onChanged: translationEnabled
? settingsService.setAutoTranslateIncomingMessages
: null,
),
const Divider(height: 1),
SwitchListTile(
secondary: Icon(
Icons.outgoing_mail,
color: translationEnabled ? null : Colors.grey,
),
title: Text(
context.l10n.translation_composerTitle,
style: TextStyle(color: translationEnabled ? null : Colors.grey),
),
subtitle: Text(
context.l10n.translation_composerSubtitle,
style: TextStyle(color: translationEnabled ? null : Colors.grey),
),
value: settings.composerTranslationEnabled,
onChanged: settingsService.setComposerTranslationEnabled,
onChanged: translationEnabled
? settingsService.setComposerTranslationEnabled
: null,
),
const Divider(height: 1),
ListTile(
+97 -4
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math' as math;
@@ -8,6 +9,8 @@ import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../models/community.dart';
import '../storage/community_store.dart';
import '../utils/platform_info.dart';
import '../helpers/chat_scroll_controller.dart';
import '../connector/meshcore_protocol.dart';
@@ -33,6 +36,7 @@ import '../widgets/gif_picker.dart';
import '../widgets/message_translation_button.dart';
import '../widgets/message_status_icon.dart';
import '../widgets/radio_stats_entry.dart';
import '../widgets/sync_progress_overlay.dart';
import '../widgets/translated_message_content.dart';
import '../widgets/unread_divider.dart';
import 'channel_message_path_screen.dart';
@@ -57,8 +61,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
final ChatScrollController _scrollController = ChatScrollController();
final FocusNode _textFieldFocusNode = FocusNode();
ChannelMessage? _replyingToMessage;
final CommunityStore _communityStore = CommunityStore();
final CommunityPskIndex _communityIndex = CommunityPskIndex();
final Map<String, GlobalKey> _messageKeys = {};
bool _isLoadingOlder = false;
bool _communitiesLoaded = false;
MeshCoreConnector? _connector;
DateTime? _lastChannelSendAt;
@@ -82,6 +89,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
final idx = widget.channel.index;
final unread = widget.initialUnreadCount;
final messages = connector.getChannelMessages(widget.channel);
_loadCommunities();
ChannelMessage? anchor;
if (unread > 0) {
anchor = _findOldestUnreadChannelAnchor(messages, unread);
@@ -108,6 +116,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
});
}
// TODO: Reload communities when returning from another screen
Future<void> _loadCommunities() async {
final connector = context.read<MeshCoreConnector>();
_communityStore.setPublicKeyHex = connector.selfPublicKeyHex;
final communities = await _communityStore.loadCommunities();
if (mounted) {
setState(() {
_communityIndex.initialize(communities);
_communitiesLoaded = true;
});
}
}
ChannelMessage? _findOldestUnreadChannelAnchor(
List<ChannelMessage> messages,
int unreadCount,
@@ -194,16 +215,63 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
);
}
Widget _channelIcon(Channel channel) {
// Determine icon based on channel type
final ChannelType channelType = Channel.getChannelType(
channel,
_communityIndex,
);
final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
IconData icon;
switch (channelType) {
case ChannelType.communityPublic:
icon = Icons.groups;
case ChannelType.communityHashtag:
icon = Icons.tag;
case ChannelType.public:
icon = Icons.public;
case ChannelType.hashtag:
icon = Icons.tag;
case ChannelType.private:
icon = Icons.lock;
}
return Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: _communitiesLoaded
? Icon(icon, size: 20)
: SizedBox.square(dimension: 20),
),
if (isCommunityChannel)
Positioned(
right: 0,
bottom: 0,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Colors.purple,
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).cardColor,
width: 2,
),
),
child: const Icon(Icons.people, size: 8, color: Colors.white),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Icon(
widget.channel.isPublicChannel ? Icons.public : Icons.tag,
size: 20,
),
_channelIcon(widget.channel),
const SizedBox(width: 8),
Expanded(
child: Column(
@@ -237,6 +305,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
],
),
centerTitle: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
const RadioStatsIconButton(),
PopupMenuButton<String>(
@@ -1327,6 +1396,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
}
void _showMessageActions(ChannelMessage message) {
final translationService = context.read<TranslationService>();
final canTranslateMessage =
translationService.canTranslateIncoming(
text: message.text,
isCli: false,
isOutgoing: message.isOutgoing,
) &&
(message.translatedText?.trim().isEmpty ?? true);
showModalBottomSheet(
context: context,
builder: (sheetContext) => SafeArea(
@@ -1368,6 +1446,21 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_copyMessageText(message.text);
},
),
if (canTranslateMessage)
ListTile(
leading: const Icon(Icons.translate),
title: Text(context.l10n.translation_translateMessage),
onTap: () {
Navigator.pop(sheetContext);
unawaited(
context.read<MeshCoreConnector>().translateChannelMessage(
widget.channel.index,
message,
manualTranslation: true,
),
);
},
),
if (!message.isOutgoing)
ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
+39 -58
View File
@@ -23,6 +23,7 @@ import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart';
import '../widgets/qr_code_display.dart';
import '../widgets/quick_switch_bar.dart';
import '../widgets/sync_progress_overlay.dart';
import '../widgets/unread_badge.dart';
import '../helpers/snack_bar_builder.dart';
import 'channel_chat_screen.dart';
@@ -44,11 +45,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
with DisconnectNavigationMixin {
final TextEditingController _searchController = TextEditingController();
final CommunityStore _communityStore = CommunityStore();
Timer? _searchDebounce;
final CommunityPskIndex _communityIndex = CommunityPskIndex();
List<Community> _communities = [];
// Cache of PSK hex -> Community for quick lookup
final Map<String, Community> _pskToCommunity = {};
Timer? _searchDebounce;
ChannelMessageStore get _channelMessageStore => ChannelMessageStore();
@@ -71,37 +70,11 @@ class _ChannelsScreenState extends State<ChannelsScreen>
if (mounted) {
setState(() {
_communities = communities;
_buildPskCommunityMap();
_communityIndex.initialize(communities);
});
}
}
void _buildPskCommunityMap() {
_pskToCommunity.clear();
for (final community in _communities) {
// Map the community public channel PSK
final publicPsk = community.deriveCommunityPublicPsk();
_pskToCommunity[Channel.formatPskHex(publicPsk)] = community;
// Map all known hashtag channel PSKs
for (final hashtag in community.hashtagChannels) {
final hashtagPsk = community.deriveCommunityHashtagPsk(hashtag);
_pskToCommunity[Channel.formatPskHex(hashtagPsk)] = community;
}
}
}
/// Returns the community this channel belongs to, or null if not a community channel
Community? _getCommunityForChannel(Channel channel) {
return _pskToCommunity[channel.pskHex];
}
/// Returns true if this is the community's public channel
bool _isCommunityPublicChannel(Channel channel, Community community) {
final publicPsk = community.deriveCommunityPublicPsk();
return channel.pskHex == Channel.formatPskHex(publicPsk);
}
@override
void dispose() {
_searchDebounce?.cancel();
@@ -131,6 +104,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
title: AppBarTitle(context.l10n.channels_title),
centerTitle: true,
automaticallyImplyLeading: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
PopupMenuButton(
itemBuilder: (context) => [
@@ -180,12 +154,17 @@ class _ChannelsScreenState extends State<ChannelsScreen>
await context.read<MeshCoreConnector>().getChannels(force: true);
},
child: () {
if (connector.isLoadingChannels) {
final channels = connector.channels;
final waitingForFirstChannel =
connector.isLoadingChannels && channels.isEmpty;
// Only block the list while the first channel is actively loading.
// If the initial sync aborts, show cached/partial channels instead
// of trapping the user behind an idle spinner.
if (waitingForFirstChannel) {
return const Center(child: CircularProgressIndicator());
}
final channels = connector.channels;
if (channels.isEmpty) {
return ListView(
children: [
@@ -359,6 +338,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
selectedIndex: 1,
onDestinationSelected: (index) =>
_handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
),
),
),
@@ -374,37 +355,37 @@ class _ChannelsScreenState extends State<ChannelsScreen>
int? dragIndex,
}) {
final unreadCount = connector.getUnreadCountForChannel(channel);
final community = _getCommunityForChannel(channel);
final isCommunityChannel = community != null;
final isCommunityPublic =
isCommunityChannel && _isCommunityPublicChannel(channel, community);
// Determine icon and colors based on channel type
IconData icon;
Color iconColor;
Color bgColor;
if (isCommunityChannel) {
// Community channel styling
iconColor = Colors.purple;
bgColor = Colors.purple.withValues(alpha: 0.2);
if (isCommunityPublic) {
final ChannelType channelType = Channel.getChannelType(
channel,
_communityIndex,
);
final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
switch (channelType) {
case ChannelType.communityPublic:
icon = Icons.groups;
} else {
iconColor = Colors.purple;
bgColor = Colors.purple.withValues(alpha: 0.2);
case ChannelType.communityHashtag:
icon = Icons.tag;
}
} else if (channel.isPublicChannel) {
icon = Icons.public;
iconColor = Colors.green;
bgColor = Colors.green.withValues(alpha: 0.2);
} else if (channel.name.startsWith('#')) {
icon = Icons.tag;
iconColor = Colors.blue;
bgColor = Colors.blue.withValues(alpha: 0.2);
} else {
icon = Icons.lock;
iconColor = Colors.blue;
bgColor = Colors.blue.withValues(alpha: 0.2);
iconColor = Colors.purple;
bgColor = Colors.purple.withValues(alpha: 0.2);
case ChannelType.public:
icon = Icons.public;
iconColor = Colors.green;
bgColor = Colors.green.withValues(alpha: 0.2);
case ChannelType.hashtag:
icon = Icons.tag;
iconColor = Colors.blue;
bgColor = Colors.blue.withValues(alpha: 0.2);
case ChannelType.private:
icon = Icons.lock;
iconColor = Colors.blue;
bgColor = Colors.blue.withValues(alpha: 0.2);
}
return Card(
+31 -7
View File
@@ -41,6 +41,7 @@ import '../widgets/gif_picker.dart';
import '../widgets/message_translation_button.dart';
import '../widgets/path_selection_dialog.dart';
import '../widgets/radio_stats_entry.dart';
import '../widgets/sync_progress_overlay.dart';
import '../widgets/translated_message_content.dart';
import '../utils/app_logger.dart';
import '../l10n/l10n.dart';
@@ -216,6 +217,7 @@ class _ChatScreenState extends State<ChatScreen> {
},
),
centerTitle: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
Consumer<MeshCoreConnector>(
builder: (context, connector, _) {
@@ -485,6 +487,8 @@ class _ChatScreenState extends State<ChatScreen> {
final message = reversedMessages[messageIndex];
String fourByteHex = '';
if (contact.type == advTypeRoom) {
// Room-server messages carry the original author's 4-byte prefix
// separately from message.text; use it only for resolving the name.
contact = _resolveContactFrom4Bytes(
connector,
message.fourByteRoomContactKey.isEmpty
@@ -509,7 +513,6 @@ class _ChatScreenState extends State<ChatScreen> {
? "${contact.name} [$fourByteHex]"
: contact.name,
sourceId: widget.contact.publicKeyHex,
isRoomServer: resolvedContact.type == advTypeRoom,
textScale: textScale,
onTap: () => _openMessagePath(message, contact),
onLongPress: () => _showMessageActions(message, contact),
@@ -1587,6 +1590,15 @@ class _ChatScreenState extends State<ChatScreen> {
}
void _showMessageActions(Message message, Contact contact) {
final translationService = context.read<TranslationService>();
final canTranslateMessage =
translationService.canTranslateIncoming(
text: message.text,
isCli: message.isCli,
isOutgoing: message.isOutgoing,
) &&
(message.translatedText?.trim().isEmpty ?? true);
showModalBottomSheet(
context: context,
builder: (sheetContext) => SafeArea(
@@ -1620,6 +1632,21 @@ class _ChatScreenState extends State<ChatScreen> {
_copyMessageText(message.text);
},
),
if (canTranslateMessage)
ListTile(
leading: const Icon(Icons.translate),
title: Text(context.l10n.translation_translateMessage),
onTap: () {
Navigator.pop(sheetContext);
unawaited(
context.read<MeshCoreConnector>().translateContactMessage(
widget.contact.publicKeyHex,
message,
manualTranslation: true,
),
);
},
),
if (!message.isOutgoing)
ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
@@ -1731,7 +1758,6 @@ class _ChatScreenState extends State<ChatScreen> {
class _MessageBubble extends StatelessWidget {
final Message message;
final String senderName;
final bool isRoomServer;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final void Function(Message message, String emoji)? onRetryReaction;
@@ -1742,7 +1768,6 @@ class _MessageBubble extends StatelessWidget {
required this.message,
required this.senderName,
required this.sourceId,
required this.isRoomServer,
required this.textScale,
this.onTap,
this.onLongPress,
@@ -1768,10 +1793,9 @@ class _MessageBubble extends StatelessWidget {
: (isOutgoing ? colorScheme.onPrimary : colorScheme.onSurface);
final metaColor = textColor.withValues(alpha: 0.7);
const bodyFontSize = 14.0;
String messageText = message.text;
if (isRoomServer && !isOutgoing) {
messageText = message.text.substring(4.clamp(0, message.text.length));
}
// Do not strip room-server author bytes here: the parser stores them in
// fourByteRoomContactKey, so message.text is safe to render as-is.
final messageText = message.text;
final translatedDisplayText =
message.translatedText != null &&
message.translatedText!.trim().isNotEmpty
+10 -7
View File
@@ -27,6 +27,7 @@ import '../widgets/empty_state.dart';
import '../widgets/quick_switch_bar.dart';
import '../widgets/repeater_login_dialog.dart';
import '../widgets/room_login_dialog.dart';
import '../widgets/sync_progress_overlay.dart';
import '../widgets/unread_badge.dart';
import '../helpers/snack_bar_builder.dart';
import 'channels_screen.dart';
@@ -318,6 +319,7 @@ class _ContactsScreenState extends State<ContactsScreen>
appBar: AppBar(
title: AppBarTitle(context.l10n.contacts_title),
automaticallyImplyLeading: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
PopupMenuButton(
itemBuilder: (context) => [
@@ -430,6 +432,8 @@ class _ContactsScreenState extends State<ContactsScreen>
selectedIndex: 0,
onDestinationSelected: (index) =>
_handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
),
),
),
@@ -604,15 +608,14 @@ class _ContactsScreenState extends State<ContactsScreen>
Widget _buildContactsBody(BuildContext context, MeshCoreConnector connector) {
final viewState = context.watch<UiViewStateService>();
final contacts = connector.contacts;
final shouldShowStartupSpinner =
contacts.isEmpty &&
_groups.isEmpty &&
final waitingForInitialContacts =
connector.isConnected &&
(connector.isLoadingContacts ||
connector.isLoadingChannels ||
connector.selfPublicKey == null);
!connector.hasLoadedContacts &&
!connector.isLoadingContacts;
final waitingForFirstContact =
connector.isLoadingContacts && contacts.isEmpty;
if (shouldShowStartupSpinner) {
if (waitingForInitialContacts || waitingForFirstContact) {
return const Center(child: CircularProgressIndicator());
}
@@ -539,6 +539,12 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
child: QuickSwitchBar(
selectedIndex: 2,
onDestinationSelected: (index) => _handleQuickSwitch(index, context),
contactsUnreadCount: context
.watch<MeshCoreConnector>()
.getTotalContactsUnreadCount(),
channelsUnreadCount: context
.watch<MeshCoreConnector>()
.getTotalChannelsUnreadCount(),
),
),
);
+4
View File
@@ -24,6 +24,7 @@ import '../services/map_tile_cache_service.dart';
import '../utils/contact_search.dart';
import '../utils/route_transitions.dart';
import '../widgets/quick_switch_bar.dart';
import '../widgets/sync_progress_overlay.dart';
import '../icons/los_icon.dart';
import 'channels_screen.dart';
import 'chat_screen.dart';
@@ -417,6 +418,7 @@ class _MapScreenState extends State<MapScreen> {
title: AppBarTitle(context.l10n.map_title),
centerTitle: true,
automaticallyImplyLeading: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
if (!_isBuildingPathTrace)
IconButton(
@@ -679,6 +681,8 @@ class _MapScreenState extends State<MapScreen> {
selectedIndex: 2,
onDestinationSelected: (index) =>
_handleQuickSwitch(index, context),
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
),
),
floatingActionButton: FloatingActionButton(
+2 -2
View File
@@ -11,7 +11,7 @@ import '../utils/app_logger.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/device_tile.dart';
import '../helpers/snack_bar_builder.dart';
import 'contacts_screen.dart';
import 'channels_screen.dart';
import 'tcp_screen.dart';
import 'usb_screen.dart';
@@ -46,7 +46,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
_changedNavigation = true;
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const ContactsScreen()),
MaterialPageRoute(builder: (context) => const ChannelsScreen()),
);
}
}
+2
View File
@@ -16,6 +16,7 @@ import 'app_settings_screen.dart';
import 'app_debug_log_screen.dart';
import 'ble_debug_log_screen.dart';
import '../widgets/radio_stats_entry.dart';
import '../widgets/sync_progress_overlay.dart';
/// Convert device coding-rate value (1-4 on some firmware, 5-8 on others)
/// to the UI enum range (always 5-8).
@@ -67,6 +68,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
indicators: false,
subtitle: false,
),
bottom: const SyncProgressAppBarBottom(),
),
body: SafeArea(
top: false,
+7 -7
View File
@@ -9,7 +9,7 @@ import '../services/app_settings_service.dart';
import '../utils/platform_info.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../helpers/snack_bar_builder.dart';
import 'contacts_screen.dart';
import 'channels_screen.dart';
import 'usb_screen.dart';
class TcpScreen extends StatefulWidget {
@@ -24,7 +24,7 @@ class _TcpScreenState extends State<TcpScreen> {
late final TextEditingController _portController;
late final MeshCoreConnector _connector;
late final VoidCallback _connectionListener;
bool _navigatedToContacts = false;
bool _navigatedToChannels = false;
@override
void initState() {
@@ -42,20 +42,20 @@ class _TcpScreenState extends State<TcpScreen> {
_connectionListener = () {
if (!mounted) return;
if (_connector.state == MeshCoreConnectionState.disconnected) {
_navigatedToContacts = false;
_navigatedToChannels = false;
}
if (_connector.state == MeshCoreConnectionState.connected &&
_connector.isTcpTransportConnected &&
!_navigatedToContacts) {
!_navigatedToChannels) {
context.read<AppSettingsService>().setTcpServerAddress(
_hostController.text,
);
context.read<AppSettingsService>().setTcpServerPort(
int.tryParse(_portController.text) ?? 0,
);
_navigatedToContacts = true;
_navigatedToChannels = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const ContactsScreen()),
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
);
}
};
@@ -67,7 +67,7 @@ class _TcpScreenState extends State<TcpScreen> {
_hostController.dispose();
_portController.dispose();
_connector.removeListener(_connectionListener);
if (!_navigatedToContacts &&
if (!_navigatedToChannels &&
_connector.activeTransport == MeshCoreTransportType.tcp &&
_connector.state != MeshCoreConnectionState.disconnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
+2
View File
@@ -15,6 +15,7 @@ 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';
class TelemetryScreen extends StatefulWidget {
final Contact contact;
@@ -239,6 +240,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
],
),
centerTitle: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
+7 -7
View File
@@ -11,7 +11,7 @@ import '../utils/platform_info.dart';
import '../utils/usb_port_labels.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../helpers/snack_bar_builder.dart';
import 'contacts_screen.dart';
import 'channels_screen.dart';
import 'scanner_screen.dart';
import 'tcp_screen.dart';
@@ -25,7 +25,7 @@ class UsbScreen extends StatefulWidget {
class _UsbScreenState extends State<UsbScreen> {
final List<String> _ports = <String>[];
bool _isLoadingPorts = true;
bool _navigatedToContacts = false;
bool _navigatedToChannels = false;
bool _didScheduleInitialLoad = false;
Timer? _hotPlugTimer;
late final MeshCoreConnector _connector;
@@ -41,14 +41,14 @@ class _UsbScreenState extends State<UsbScreen> {
_connectionListener = () {
if (!mounted) return;
if (_connector.state == MeshCoreConnectionState.disconnected) {
_navigatedToContacts = false;
_navigatedToChannels = false;
}
if (_connector.state == MeshCoreConnectionState.connected &&
_connector.isUsbTransportConnected &&
!_navigatedToContacts) {
_navigatedToContacts = true;
!_navigatedToChannels) {
_navigatedToChannels = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const ContactsScreen()),
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
);
}
};
@@ -72,7 +72,7 @@ class _UsbScreenState extends State<UsbScreen> {
_hotPlugTimer?.cancel();
_hotPlugTimer = null;
_connector.removeListener(_connectionListener);
if (!_navigatedToContacts &&
if (!_navigatedToChannels &&
_connector.activeTransport == MeshCoreTransportType.usb &&
_connector.state != MeshCoreConnectionState.disconnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {