2025-12-30 19:27:25 -07:00
|
|
|
import 'dart:async';
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
import 'package:flutter/material.dart';
|
2026-01-26 12:19:45 -08:00
|
|
|
import 'package:flutter/services.dart';
|
2026-02-08 11:32:36 -08:00
|
|
|
import 'package:meshcore_open/screens/path_trace_map.dart';
|
2026-03-14 09:33:37 -07:00
|
|
|
import 'package:meshcore_open/services/notification_service.dart';
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
import 'package:meshcore_open/utils/app_logger.dart';
|
2026-03-20 01:54:31 -07:00
|
|
|
import 'package:meshcore_open/utils/platform_info.dart';
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
import 'package:meshcore_open/widgets/app_bar.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
|
|
|
|
import '../connector/meshcore_connector.dart';
|
2026-01-11 17:13:50 -07:00
|
|
|
import '../l10n/l10n.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import '../connector/meshcore_protocol.dart';
|
|
|
|
|
import '../models/contact.dart';
|
|
|
|
|
import '../models/contact_group.dart';
|
2026-03-15 00:34:09 +01:00
|
|
|
import '../services/ui_view_state_service.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import '../utils/contact_search.dart';
|
2026-03-15 00:34:09 +01:00
|
|
|
import '../storage/contact_group_store.dart';
|
2025-12-30 19:27:25 -07:00
|
|
|
import '../utils/dialog_utils.dart';
|
|
|
|
|
import '../utils/disconnect_navigation_mixin.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import '../utils/emoji_utils.dart';
|
|
|
|
|
import '../utils/route_transitions.dart';
|
2026-01-02 14:22:39 -07:00
|
|
|
import '../widgets/list_filter_widget.dart';
|
2025-12-30 19:27:25 -07:00
|
|
|
import '../widgets/empty_state.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import '../widgets/quick_switch_bar.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import '../widgets/repeater_login_dialog.dart';
|
2026-01-07 23:28:49 -08:00
|
|
|
import '../widgets/room_login_dialog.dart';
|
2025-12-26 13:33:03 -07:00
|
|
|
import '../widgets/unread_badge.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import 'channels_screen.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import 'chat_screen.dart';
|
2026-02-28 19:11:11 -08:00
|
|
|
import 'discovery_screen.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import 'map_screen.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
import 'repeater_hub_screen.dart';
|
2025-12-27 15:32:32 -07:00
|
|
|
import 'settings_screen.dart';
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2026-02-04 08:32:35 -08:00
|
|
|
enum RoomLoginDestination { chat, management }
|
2026-01-19 18:29:53 -07:00
|
|
|
|
2026-02-04 08:32:35 -08:00
|
|
|
enum ContactOperationType { import, export, zeroHopShare }
|
2026-02-01 16:57:17 -07:00
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
class ContactsScreen extends StatefulWidget {
|
2025-12-27 15:32:32 -07:00
|
|
|
final bool hideBackButton;
|
|
|
|
|
|
2026-01-18 21:21:33 -08:00
|
|
|
const ContactsScreen({super.key, this.hideBackButton = false});
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<ContactsScreen> createState() => _ContactsScreenState();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 19:27:25 -07:00
|
|
|
class _ContactsScreenState extends State<ContactsScreen>
|
|
|
|
|
with DisconnectNavigationMixin {
|
2025-12-26 11:42:02 -07:00
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
|
final ContactGroupStore _groupStore = ContactGroupStore();
|
2026-03-15 00:34:09 +01:00
|
|
|
MeshCoreConnector? _scopeSyncConnector;
|
2025-12-26 11:42:02 -07:00
|
|
|
List<ContactGroup> _groups = [];
|
2026-03-15 00:34:09 +01:00
|
|
|
String _loadedGroupScopeKeyHex = '';
|
2025-12-30 19:27:25 -07:00
|
|
|
Timer? _searchDebounce;
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2026-02-01 16:57:17 -07:00
|
|
|
final Set<ContactOperationType> _pendingOperations = {};
|
2026-01-31 15:00:33 -08:00
|
|
|
|
2026-01-26 12:19:45 -08:00
|
|
|
StreamSubscription<Uint8List>? _frameSubscription;
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
2026-03-15 00:34:09 +01:00
|
|
|
_searchController.text = context
|
|
|
|
|
.read<UiViewStateService>()
|
|
|
|
|
.contactsSearchText;
|
2025-12-26 11:42:02 -07:00
|
|
|
_loadGroups();
|
2026-01-26 12:19:45 -08:00
|
|
|
_setupFrameListener();
|
2026-03-14 09:33:37 -07:00
|
|
|
_clearAdvertNotifications();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _clearAdvertNotifications() {
|
|
|
|
|
final connector = context.read<MeshCoreConnector>();
|
|
|
|
|
final contactIds = connector.contacts.map((c) => c.publicKeyHex).toList();
|
|
|
|
|
NotificationService().clearAdvertNotifications(contactIds);
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
@override
|
|
|
|
|
void didChangeDependencies() {
|
|
|
|
|
super.didChangeDependencies();
|
|
|
|
|
final connector = context.read<MeshCoreConnector>();
|
|
|
|
|
if (!identical(_scopeSyncConnector, connector)) {
|
|
|
|
|
_scopeSyncConnector?.removeListener(_handleConnectorScopeChange);
|
|
|
|
|
_scopeSyncConnector = connector;
|
|
|
|
|
_scopeSyncConnector?.addListener(_handleConnectorScopeChange);
|
|
|
|
|
}
|
|
|
|
|
_handleConnectorScopeChange();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
@override
|
|
|
|
|
void dispose() {
|
2025-12-30 19:27:25 -07:00
|
|
|
_searchDebounce?.cancel();
|
2025-12-26 11:42:02 -07:00
|
|
|
_searchController.dispose();
|
2026-01-26 12:19:45 -08:00
|
|
|
_frameSubscription?.cancel();
|
2026-03-15 00:34:09 +01:00
|
|
|
_scopeSyncConnector?.removeListener(_handleConnectorScopeChange);
|
2025-12-26 11:42:02 -07:00
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
void _handleConnectorScopeChange() {
|
|
|
|
|
final connector = _scopeSyncConnector;
|
|
|
|
|
if (connector == null) return;
|
|
|
|
|
_syncGroupScopeIfNeeded(connector);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
Future<void> _loadGroups() async {
|
2026-03-15 00:34:09 +01:00
|
|
|
final selfPublicKeyHex = context.read<MeshCoreConnector>().selfPublicKeyHex;
|
|
|
|
|
if (selfPublicKeyHex.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_groupStore.setPublicKeyHex = selfPublicKeyHex;
|
2025-12-26 11:42:02 -07:00
|
|
|
final groups = await _groupStore.loadGroups();
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
setState(() {
|
2026-03-15 00:34:09 +01:00
|
|
|
_loadedGroupScopeKeyHex = selfPublicKeyHex;
|
2025-12-26 11:42:02 -07:00
|
|
|
_groups = groups;
|
2026-03-15 00:34:09 +01:00
|
|
|
_ensureValidSelectedGroup();
|
2025-12-26 11:42:02 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _saveGroups() async {
|
2026-03-15 00:34:09 +01:00
|
|
|
final selfPublicKeyHex = context.read<MeshCoreConnector>().selfPublicKeyHex;
|
|
|
|
|
if (selfPublicKeyHex.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_groupStore.setPublicKeyHex = selfPublicKeyHex;
|
2025-12-26 11:42:02 -07:00
|
|
|
await _groupStore.saveGroups(_groups);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
bool _hasGroupStoreScope(MeshCoreConnector connector) {
|
|
|
|
|
return connector.selfPublicKeyHex.isNotEmpty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _syncGroupScopeIfNeeded(MeshCoreConnector connector) {
|
|
|
|
|
final selfPublicKeyHex = connector.selfPublicKeyHex;
|
|
|
|
|
if (selfPublicKeyHex.isEmpty ||
|
|
|
|
|
selfPublicKeyHex == _loadedGroupScopeKeyHex) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_loadGroups();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _collapseContactsSearch(UiViewStateService viewState) {
|
|
|
|
|
_searchDebounce?.cancel();
|
|
|
|
|
_searchDebounce = null;
|
|
|
|
|
_searchController.clear();
|
|
|
|
|
viewState.setContactsSearchText('');
|
|
|
|
|
viewState.setContactsSearchExpanded(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _showGroupsUnavailableMessage(BuildContext context) {
|
|
|
|
|
ScaffoldMessenger.of(
|
|
|
|
|
context,
|
|
|
|
|
).showSnackBar(SnackBar(content: Text(context.l10n.common_loading)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 12:19:45 -08:00
|
|
|
void _setupFrameListener() {
|
|
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
|
|
|
// Listen for incoming text messages from the repeater
|
|
|
|
|
_frameSubscription = connector.receivedFrames.listen((frame) {
|
|
|
|
|
if (frame.isEmpty) return;
|
|
|
|
|
final frameBuffer = BufferReader(frame);
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
try {
|
|
|
|
|
final code = frameBuffer.readUInt8();
|
2026-01-26 12:19:45 -08:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
if (code == respCodeExportContact) {
|
|
|
|
|
final advertPacket = frameBuffer.readRemainingBytes();
|
|
|
|
|
// Validate packet has expected minimum size (98+ bytes per protocol)
|
|
|
|
|
if (advertPacket.length < 98) {
|
|
|
|
|
if (mounted) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_invalidAdvertFormat),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
_pendingOperations.remove(ContactOperationType.export);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final hexString = pubKeyToHex(advertPacket);
|
|
|
|
|
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code == respCodeOk) {
|
|
|
|
|
// Show a snackbar indicating success
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
|
|
|
|
|
if (_pendingOperations.contains(ContactOperationType.import)) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(content: Text(context.l10n.contacts_contactImported)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
2026-02-01 16:57:17 -07:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
2026-02-04 08:32:35 -08:00
|
|
|
SnackBar(
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
content: Text(context.l10n.contacts_zeroHopContactAdvertSent),
|
2026-02-04 08:32:35 -08:00
|
|
|
),
|
2026-02-01 16:57:17 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
if (_pendingOperations.contains(ContactOperationType.export)) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_contactAdvertCopied),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-31 15:00:33 -08:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
_pendingOperations.clear();
|
2026-01-31 15:00:33 -08:00
|
|
|
}
|
2026-02-01 16:57:17 -07:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
if (code == respCodeErr) {
|
|
|
|
|
// Show a snackbar indicating failure
|
|
|
|
|
if (!mounted) return;
|
2026-01-27 18:43:59 -08:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
if (_pendingOperations.contains(ContactOperationType.import)) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_contactImportFailed),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-02-01 16:57:17 -07:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_zeroHopContactAdvertFailed),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (_pendingOperations.contains(ContactOperationType.export)) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_contactAdvertCopyFailed),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-31 15:00:33 -08:00
|
|
|
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
_pendingOperations.clear();
|
2026-01-31 15:00:33 -08:00
|
|
|
}
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
} catch (e) {
|
|
|
|
|
appLogger.error(
|
|
|
|
|
'Error processing received frame: $e',
|
|
|
|
|
tag: 'ContactsScreen',
|
|
|
|
|
);
|
2026-01-27 18:43:59 -08:00
|
|
|
}
|
2026-01-26 12:19:45 -08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 11:56:42 -08:00
|
|
|
Future<void> _contactExport(Uint8List pubKey) async {
|
|
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
|
|
|
final exportContactFrame = buildExportContactFrame(pubKey);
|
2026-02-01 16:57:17 -07:00
|
|
|
_pendingOperations.add(ContactOperationType.export);
|
2026-02-21 18:31:51 -05:00
|
|
|
await connector.sendFrame(exportContactFrame, expectsGenericAck: true);
|
2026-01-26 11:56:42 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 15:00:33 -08:00
|
|
|
Future<void> _contactZeroHop(Uint8List pubKey) async {
|
|
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
|
|
|
final exportContactZeroHopFrame = buildZeroHopContact(pubKey);
|
2026-02-01 16:57:17 -07:00
|
|
|
_pendingOperations.add(ContactOperationType.zeroHopShare);
|
2026-02-21 18:31:51 -05:00
|
|
|
await connector.sendFrame(
|
|
|
|
|
exportContactZeroHopFrame,
|
|
|
|
|
expectsGenericAck: true,
|
|
|
|
|
);
|
2026-01-31 15:00:33 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 16:11:21 -08:00
|
|
|
Future<void> _contactImport() async {
|
2026-01-31 15:00:33 -08:00
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
2026-01-26 16:11:21 -08:00
|
|
|
final clipboardData = await Clipboard.getData('text/plain');
|
|
|
|
|
if (clipboardData == null || clipboardData.text == null) {
|
2026-02-04 08:32:35 -08:00
|
|
|
if (mounted) {
|
2026-01-31 15:00:33 -08:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-26 16:11:21 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final text = clipboardData.text!.trim();
|
|
|
|
|
if (!text.startsWith('meshcore://')) {
|
2026-02-04 08:32:35 -08:00
|
|
|
if (mounted) {
|
2026-01-31 15:00:33 -08:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-26 16:11:21 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final hexString = text.substring('meshcore://'.length);
|
|
|
|
|
try {
|
feat(localization): update contact settings translations for multiple languages
- Translated contact settings and related strings in Slovenian, Swedish, Ukrainian, Chinese, Dutch, Polish, Portuguese, Russian, and Slovak.
- Added new strings for discovered contacts actions such as adding, copying, and deleting contacts.
- Enhanced the DiscoveryContact model to include a rawPacket field for better data handling.
- Updated the contacts screen to support new actions in the context menu for discovered contacts.
- Improved the contact discovery store to handle the serialization of the new rawPacket field.
2026-03-01 10:13:17 -08:00
|
|
|
final bytes = hex2Uint8List(hexString);
|
|
|
|
|
final importContactFrame = buildImportContactFrame(bytes);
|
2026-02-01 16:57:17 -07:00
|
|
|
_pendingOperations.add(ContactOperationType.import);
|
2026-03-02 10:23:14 -08:00
|
|
|
connector.importContact(importContactFrame);
|
2026-01-26 16:11:21 -08:00
|
|
|
} catch (e) {
|
2026-02-04 08:32:35 -08:00
|
|
|
if (mounted) {
|
2026-01-31 15:00:33 -08:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-26 16:11:21 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-12-27 15:32:32 -07:00
|
|
|
final connector = context.watch<MeshCoreConnector>();
|
|
|
|
|
|
2025-12-30 19:27:25 -07:00
|
|
|
// Auto-navigate back to scanner if disconnected
|
|
|
|
|
if (!checkConnectionAndNavigate(connector)) {
|
|
|
|
|
return const SizedBox.shrink();
|
2025-12-27 15:32:32 -07:00
|
|
|
}
|
|
|
|
|
|
2025-12-30 19:27:25 -07:00
|
|
|
final allowBack = !connector.isConnected;
|
2025-12-29 20:01:16 -07:00
|
|
|
return PopScope(
|
|
|
|
|
canPop: allowBack,
|
|
|
|
|
child: Scaffold(
|
|
|
|
|
appBar: AppBar(
|
Add a signal readout for the nearest repeater. With improvements to app bar and other UI polish. (#200)
* Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
* Fix trace route bytes generation logic in Contact model
* Ignore advertisements from self in MeshCoreConnector
* Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen
* Add SNRIndicator to AppBar and refactor BatteryIndicator layout
* Enhance path management dialog to display direct repeaters with color coding based on signal strength
* Remove unused import from SNR indicator widget
* Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor packet handling to skip only the RSSI byte for improved reliability
* Add SNR indicator localization and update UI references for nearby repeaters
* Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout
* Throw an exception for unsupported LPP types in CayenneLpp class
* Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
* Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog
* Prevent notifications for chat and sensor adverts without a valid path
* Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
* Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
- app_localizations_bg.dart
- app_localizations_de.dart
- app_localizations_en.dart
- app_localizations_es.dart
- app_localizations_fr.dart
- app_localizations_it.dart
- app_localizations_nl.dart
- app_localizations_pl.dart
- app_localizations_pt.dart
- app_localizations_ru.dart
- app_localizations_sk.dart
- app_localizations_sl.dart
- app_localizations_sv.dart
- app_localizations_uk.dart
- app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
* Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy
* Fix typo in variable name for second direct repeater in path management dialog
* Refactor ranking calculation for direct repeaters and update path handling in channel message screens
* Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages
* Fix AppBarTitle horizontal overflow with long titles (#187)
* Initial plan
* Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
* Refactor AppBarTitle widget to simplify Text widget initialization
* Add "Show All Paths" feature to chat path management
- Implemented localization for "Show All Paths" in multiple languages (DE, EN, ES, FR, IT, NL, PL, PT, RU, SK, SL, SV, UK, ZH).
- Updated path management dialog to include a toggle for showing all paths.
- Refactored path history display logic to conditionally show paths based on the toggle state.
- Cleaned up unused print statements and improved code readability in path tracing and chat screens.
* Refactor FeatureToggleRow visibility in chat and path management dialogs based on repeaters list
* Remove unused import of 'dart:ffi' in path_trace_map.dart
* Refactor repeater management logic and update UI state handling in chat and path management dialogs
* Refactor RX data handling and improve repeater management logic in chat and path management dialogs
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-20 20:27:38 -08:00
|
|
|
title: AppBarTitle(context.l10n.contacts_title),
|
2026-01-02 14:22:39 -07:00
|
|
|
automaticallyImplyLeading: false,
|
2025-12-29 20:01:16 -07:00
|
|
|
actions: [
|
2026-02-04 08:32:35 -08:00
|
|
|
PopupMenuButton(
|
|
|
|
|
itemBuilder: (context) => [
|
|
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Icon(Icons.connect_without_contact),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Text(context.l10n.contacts_zeroHopAdvert),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
onTap: () => {
|
2026-01-26 11:56:42 -08:00
|
|
|
connector.sendSelfAdvert(flood: false),
|
2026-02-04 08:32:35 -08:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.settings_advertisementSent),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-01-26 11:56:42 -08:00
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Icon(Icons.cell_tower),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Text(context.l10n.contacts_floodAdvert),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
onTap: () => {
|
2026-01-26 11:56:42 -08:00
|
|
|
connector.sendSelfAdvert(flood: true),
|
2026-02-04 08:32:35 -08:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.settings_advertisementSent),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-01-26 11:56:42 -08:00
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Icon(Icons.copy),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Text(context.l10n.contacts_copyAdvertToClipboard),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
onTap: () => _contactExport(Uint8List.fromList([])),
|
2026-01-26 16:11:21 -08:00
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Icon(Icons.paste),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Text(context.l10n.contacts_addContactFromClipboard),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
onTap: () => _contactImport(),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
icon: const Icon(Icons.connect_without_contact),
|
2026-01-26 11:56:42 -08:00
|
|
|
),
|
|
|
|
|
PopupMenuButton(
|
|
|
|
|
itemBuilder: (context) => [
|
|
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
2026-01-28 11:04:34 -08:00
|
|
|
const Icon(Icons.logout, color: Colors.red),
|
2026-01-26 11:56:42 -08:00
|
|
|
const SizedBox(width: 8),
|
2026-01-28 11:04:34 -08:00
|
|
|
Text(context.l10n.common_disconnect),
|
2026-01-26 11:56:42 -08:00
|
|
|
],
|
|
|
|
|
),
|
2026-01-28 11:04:34 -08:00
|
|
|
onTap: () => _disconnect(context, connector),
|
2026-01-26 11:56:42 -08:00
|
|
|
),
|
2026-02-28 19:11:11 -08:00
|
|
|
PopupMenuItem(
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Icon(Icons.person_add_rounded),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Text("Discovered Contacts"),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
onTap: () => Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (context) => const DiscoveryScreen(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-01-28 11:04:34 -08:00
|
|
|
PopupMenuItem(
|
2026-01-26 16:11:21 -08:00
|
|
|
child: Row(
|
|
|
|
|
children: [
|
2026-01-28 11:04:34 -08:00
|
|
|
const Icon(Icons.settings),
|
2026-01-26 16:11:21 -08:00
|
|
|
const SizedBox(width: 8),
|
2026-01-28 11:04:34 -08:00
|
|
|
Text(context.l10n.settings_title),
|
2026-01-26 16:11:21 -08:00
|
|
|
],
|
|
|
|
|
),
|
2026-01-28 11:04:34 -08:00
|
|
|
onTap: () => Navigator.push(
|
|
|
|
|
context,
|
2026-02-04 08:32:35 -08:00
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (context) => const SettingsScreen(),
|
|
|
|
|
),
|
2026-01-28 11:04:34 -08:00
|
|
|
),
|
|
|
|
|
),
|
2026-01-26 11:56:42 -08:00
|
|
|
],
|
|
|
|
|
icon: const Icon(Icons.more_vert),
|
2025-12-27 15:32:32 -07:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-12-29 20:01:16 -07:00
|
|
|
body: _buildContactsBody(context, connector),
|
|
|
|
|
bottomNavigationBar: SafeArea(
|
|
|
|
|
top: false,
|
|
|
|
|
child: QuickSwitchBar(
|
|
|
|
|
selectedIndex: 0,
|
2026-01-18 21:21:33 -08:00
|
|
|
onDestinationSelected: (index) =>
|
|
|
|
|
_handleQuickSwitch(index, context),
|
2025-12-27 15:32:32 -07:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _disconnect(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
MeshCoreConnector connector,
|
|
|
|
|
) async {
|
2025-12-30 19:27:25 -07:00
|
|
|
await showDisconnectDialog(context, connector);
|
2025-12-27 15:32:32 -07:00
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
ContactGroup? _selectedGroupForName(String selectedGroupName) {
|
|
|
|
|
if (selectedGroupName == contactsAllGroupsValue) return null;
|
|
|
|
|
for (final group in _groups) {
|
|
|
|
|
if (group.name == selectedGroupName) return group;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _ensureValidSelectedGroup() {
|
|
|
|
|
final viewState = context.read<UiViewStateService>();
|
|
|
|
|
if (viewState.contactsSelectedGroupName == contactsAllGroupsValue) return;
|
|
|
|
|
final exists = _groups.any(
|
|
|
|
|
(group) => group.name == viewState.contactsSelectedGroupName,
|
|
|
|
|
);
|
|
|
|
|
if (!exists) {
|
|
|
|
|
viewState.setContactsSelectedGroupName(contactsAllGroupsValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 17:59:48 -07:00
|
|
|
void _closeDropdownAndRun(BuildContext popupContext, VoidCallback action) {
|
|
|
|
|
final route = ModalRoute.of(popupContext);
|
|
|
|
|
if (route != null && route.isCurrent) {
|
|
|
|
|
Navigator.of(popupContext).pop();
|
|
|
|
|
}
|
2026-03-15 00:34:09 +01:00
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
action();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildFilterButton(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
UiViewStateService viewState,
|
|
|
|
|
) {
|
2026-01-02 14:22:39 -07:00
|
|
|
return ContactsFilterMenu(
|
2026-03-15 00:34:09 +01:00
|
|
|
sortOption: viewState.contactsSortOption,
|
|
|
|
|
typeFilter: viewState.contactsTypeFilter,
|
|
|
|
|
showUnreadOnly: viewState.contactsShowUnreadOnly,
|
2026-01-02 14:22:39 -07:00
|
|
|
onSortChanged: (value) {
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.setContactsSortOption(value);
|
2026-01-02 14:22:39 -07:00
|
|
|
},
|
|
|
|
|
onTypeFilterChanged: (value) {
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.setContactsTypeFilter(value);
|
2026-01-02 14:22:39 -07:00
|
|
|
},
|
|
|
|
|
onUnreadOnlyChanged: (value) {
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.setContactsShowUnreadOnly(value);
|
2026-01-02 14:22:39 -07:00
|
|
|
},
|
2026-03-15 00:34:09 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildGroupButton(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
MeshCoreConnector connector,
|
|
|
|
|
UiViewStateService viewState,
|
|
|
|
|
List<Contact> contacts,
|
|
|
|
|
List<ContactGroup> sortedGroups,
|
|
|
|
|
) {
|
|
|
|
|
final canManageGroups = _hasGroupStoreScope(connector);
|
|
|
|
|
final selectedGroupName =
|
|
|
|
|
_selectedGroupForName(viewState.contactsSelectedGroupName)?.name ??
|
|
|
|
|
context.l10n.listFilter_all;
|
|
|
|
|
final double menuWidth = (MediaQuery.sizeOf(context).width - 16).clamp(
|
|
|
|
|
0.0,
|
|
|
|
|
double.infinity,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return PopupMenuButton<String>(
|
|
|
|
|
position: PopupMenuPosition.under,
|
|
|
|
|
constraints: BoxConstraints.tightFor(width: menuWidth),
|
|
|
|
|
onSelected: (String value) {
|
|
|
|
|
viewState.setContactsSelectedGroupName(value);
|
|
|
|
|
},
|
|
|
|
|
itemBuilder: (menuContext) => [
|
|
|
|
|
PopupMenuItem<String>(
|
|
|
|
|
value: contactsAllGroupsValue,
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
Text(menuContext.l10n.listFilter_all),
|
|
|
|
|
IconButton(
|
|
|
|
|
tooltip: menuContext.l10n.contacts_newGroup,
|
|
|
|
|
icon: const Icon(Icons.group_add, size: 20),
|
|
|
|
|
onPressed: canManageGroups
|
|
|
|
|
? () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _showGroupEditor(this.context, contacts),
|
|
|
|
|
)
|
|
|
|
|
: () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _showGroupsUnavailableMessage(this.context),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
...sortedGroups.map((group) {
|
|
|
|
|
return PopupMenuItem<String>(
|
|
|
|
|
value: group.name,
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Text(group.name, overflow: TextOverflow.ellipsis),
|
|
|
|
|
),
|
|
|
|
|
IconButton(
|
|
|
|
|
tooltip: menuContext.l10n.contacts_editGroup,
|
|
|
|
|
icon: const Icon(Icons.edit, size: 20),
|
|
|
|
|
onPressed: canManageGroups
|
|
|
|
|
? () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _showGroupEditor(
|
|
|
|
|
this.context,
|
|
|
|
|
contacts,
|
|
|
|
|
group: group,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
: () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _showGroupsUnavailableMessage(this.context),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
IconButton(
|
|
|
|
|
tooltip: menuContext.l10n.contacts_deleteGroup,
|
|
|
|
|
icon: const Icon(Icons.delete, size: 20, color: Colors.red),
|
|
|
|
|
onPressed: canManageGroups
|
|
|
|
|
? () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _confirmDeleteGroup(this.context, group),
|
|
|
|
|
)
|
|
|
|
|
: () => _closeDropdownAndRun(
|
|
|
|
|
menuContext,
|
|
|
|
|
() => _showGroupsUnavailableMessage(this.context),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
height: 48,
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Text(selectedGroupName, overflow: TextOverflow.ellipsis),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
const Icon(Icons.arrow_drop_down),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-01-02 14:22:39 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-27 15:32:32 -07:00
|
|
|
Widget _buildContactsBody(BuildContext context, MeshCoreConnector connector) {
|
2026-03-15 00:34:09 +01:00
|
|
|
final viewState = context.watch<UiViewStateService>();
|
2025-12-27 15:32:32 -07:00
|
|
|
final contacts = connector.contacts;
|
2026-03-02 02:34:46 -05:00
|
|
|
final shouldShowStartupSpinner =
|
|
|
|
|
contacts.isEmpty &&
|
|
|
|
|
_groups.isEmpty &&
|
|
|
|
|
connector.isConnected &&
|
|
|
|
|
(connector.isLoadingContacts ||
|
|
|
|
|
connector.isLoadingChannels ||
|
|
|
|
|
connector.selfPublicKey == null);
|
|
|
|
|
|
|
|
|
|
if (shouldShowStartupSpinner) {
|
2025-12-27 15:32:32 -07:00
|
|
|
return const Center(child: CircularProgressIndicator());
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
|
2025-12-27 15:32:32 -07:00
|
|
|
if (contacts.isEmpty && _groups.isEmpty) {
|
2026-01-11 17:13:50 -07:00
|
|
|
return EmptyState(
|
2025-12-30 19:27:25 -07:00
|
|
|
icon: Icons.people_outline,
|
2026-01-11 17:13:50 -07:00
|
|
|
title: context.l10n.contacts_noContacts,
|
|
|
|
|
subtitle: context.l10n.contacts_contactsWillAppear,
|
2025-12-27 15:32:32 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
final filteredAndSorted = _filterAndSortContacts(
|
|
|
|
|
contacts,
|
|
|
|
|
connector,
|
|
|
|
|
viewState,
|
|
|
|
|
);
|
2025-12-27 15:32:32 -07:00
|
|
|
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
String hintText = "";
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
switch (viewState.contactsTypeFilter) {
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
case ContactTypeFilter.all:
|
|
|
|
|
hintText = context.l10n.contacts_searchContacts(
|
|
|
|
|
filteredAndSorted.length,
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
|
|
|
|
? " ${context.l10n.contacts_unread}"
|
|
|
|
|
: "",
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case ContactTypeFilter.users:
|
|
|
|
|
hintText = context.l10n.contacts_searchUsers(
|
|
|
|
|
filteredAndSorted.length,
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
|
|
|
|
? " ${context.l10n.contacts_unread}"
|
|
|
|
|
: "",
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case ContactTypeFilter.repeaters:
|
|
|
|
|
hintText = context.l10n.contacts_searchRepeaters(
|
|
|
|
|
filteredAndSorted.length,
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
|
|
|
|
? " ${context.l10n.contacts_unread}"
|
|
|
|
|
: "",
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case ContactTypeFilter.rooms:
|
|
|
|
|
hintText = context.l10n.contacts_searchRoomServers(
|
|
|
|
|
filteredAndSorted.length,
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
|
|
|
|
? " ${context.l10n.contacts_unread}"
|
|
|
|
|
: "",
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case ContactTypeFilter.favorites:
|
|
|
|
|
hintText = context.l10n.contacts_searchFavorites(
|
|
|
|
|
filteredAndSorted.length,
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
|
|
|
|
? " ${context.l10n.contacts_unread}"
|
|
|
|
|
: "",
|
Add localized search functionality for contacts (#244)
- Introduced new localization keys for searching contacts, users, favorites, repeaters, and room servers in multiple languages.
- Updated localization files for Italian, Bulgarian, German, English, Spanish, French, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Enhanced the contacts screen to dynamically display search hints based on the selected contact type filter.
- Modified the map screen to utilize the new search functionality for contacts without a number.
2026-02-26 22:53:52 -08:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
final groupsByName = <String, ContactGroup>{};
|
|
|
|
|
for (final group in _groups) {
|
|
|
|
|
groupsByName.putIfAbsent(group.name, () => group);
|
|
|
|
|
}
|
|
|
|
|
final sortedGroups = groupsByName.values.toList()
|
|
|
|
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
|
|
|
|
|
|
|
|
|
final screenWidth = MediaQuery.sizeOf(context).width;
|
|
|
|
|
final searchExpandedWidth = (screenWidth * 0.52).clamp(
|
|
|
|
|
97.0,
|
|
|
|
|
double.infinity,
|
|
|
|
|
); // allow expansion up to 52% of screen width, but not less than the collapsed width
|
|
|
|
|
final searchCollapsedWidth = (screenWidth * 0.22).clamp(
|
|
|
|
|
97.0,
|
|
|
|
|
120.0,
|
|
|
|
|
); //two 48px icon buttons + 1px divider
|
|
|
|
|
|
2025-12-27 15:32:32 -07:00
|
|
|
return Column(
|
|
|
|
|
children: [
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
2026-03-15 00:34:09 +01:00
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: _buildGroupButton(
|
|
|
|
|
context,
|
|
|
|
|
connector,
|
|
|
|
|
viewState,
|
|
|
|
|
contacts,
|
|
|
|
|
sortedGroups,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
2026-03-15 00:34:09 +01:00
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
AnimatedContainer(
|
|
|
|
|
duration: const Duration(milliseconds: 220),
|
|
|
|
|
curve: Curves.easeOutCubic,
|
|
|
|
|
width: viewState.contactsSearchExpanded
|
|
|
|
|
? searchExpandedWidth
|
|
|
|
|
: searchCollapsedWidth,
|
|
|
|
|
height: 48,
|
|
|
|
|
child: DecoratedBox(
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
border: Border.all(
|
|
|
|
|
color: Theme.of(context).colorScheme.outline,
|
|
|
|
|
),
|
|
|
|
|
borderRadius: BorderRadius.circular(12),
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: viewState.contactsSearchExpanded
|
|
|
|
|
? TextField(
|
|
|
|
|
controller: _searchController,
|
|
|
|
|
autofocus: true,
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
hintText: hintText,
|
|
|
|
|
border: InputBorder.none,
|
|
|
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
|
|
|
horizontal: 12,
|
|
|
|
|
vertical: 10,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
_searchDebounce?.cancel();
|
|
|
|
|
_searchDebounce = Timer(
|
|
|
|
|
const Duration(milliseconds: 300),
|
|
|
|
|
() {
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
context
|
|
|
|
|
.read<UiViewStateService>()
|
|
|
|
|
.setContactsSearchText(value);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
: const SizedBox.shrink(),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 48,
|
|
|
|
|
height: 48,
|
|
|
|
|
child: IconButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
if (viewState.contactsSearchExpanded) {
|
|
|
|
|
_collapseContactsSearch(viewState);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
viewState.setContactsSearchExpanded(true);
|
|
|
|
|
},
|
|
|
|
|
icon: Icon(
|
|
|
|
|
viewState.contactsSearchExpanded
|
|
|
|
|
? Icons.close
|
|
|
|
|
: Icons.search,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Container(
|
|
|
|
|
width: 1,
|
|
|
|
|
height: 24,
|
|
|
|
|
color: Theme.of(context).colorScheme.outlineVariant,
|
|
|
|
|
),
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 48,
|
|
|
|
|
height: 48,
|
|
|
|
|
child: _buildFilterButton(context, viewState),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-01-18 21:21:33 -08:00
|
|
|
),
|
2026-03-15 00:34:09 +01:00
|
|
|
],
|
2025-12-27 15:32:32 -07:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
2026-03-15 00:34:09 +01:00
|
|
|
child: filteredAndSorted.isEmpty
|
2025-12-27 15:32:32 -07:00
|
|
|
? Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Icon(Icons.search_off, size: 64, color: Colors.grey[400]),
|
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
|
Text(
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.contactsShowUnreadOnly
|
2026-01-11 17:13:50 -07:00
|
|
|
? context.l10n.contacts_noUnreadContacts
|
|
|
|
|
: context.l10n.contacts_noContactsFound,
|
2025-12-27 15:32:32 -07:00
|
|
|
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
2025-12-27 15:32:32 -07:00
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
: RefreshIndicator(
|
|
|
|
|
onRefresh: () => connector.getContacts(),
|
|
|
|
|
child: ListView.builder(
|
2026-03-15 00:34:09 +01:00
|
|
|
itemCount: filteredAndSorted.length,
|
2025-12-27 15:32:32 -07:00
|
|
|
itemBuilder: (context, index) {
|
2026-03-15 00:34:09 +01:00
|
|
|
final contact = filteredAndSorted[index];
|
2026-01-18 21:21:33 -08:00
|
|
|
final unreadCount = connector.getUnreadCountForContact(
|
|
|
|
|
contact,
|
|
|
|
|
);
|
2025-12-27 15:32:32 -07:00
|
|
|
return _ContactTile(
|
|
|
|
|
contact: contact,
|
|
|
|
|
lastSeen: _resolveLastSeen(contact),
|
|
|
|
|
unreadCount: unreadCount,
|
2026-02-24 23:56:30 +01:00
|
|
|
isFavorite: contact.isFavorite,
|
2025-12-27 15:32:32 -07:00
|
|
|
onTap: () => _openChat(context, contact),
|
2026-01-18 21:21:33 -08:00
|
|
|
onLongPress: () =>
|
|
|
|
|
_showContactOptions(context, connector, contact),
|
2025-12-27 15:32:32 -07:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
2025-12-26 11:42:02 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 21:21:33 -08:00
|
|
|
List<Contact> _filterAndSortContacts(
|
|
|
|
|
List<Contact> contacts,
|
|
|
|
|
MeshCoreConnector connector,
|
2026-03-15 00:34:09 +01:00
|
|
|
UiViewStateService viewState,
|
2026-01-18 21:21:33 -08:00
|
|
|
) {
|
2025-12-26 11:42:02 -07:00
|
|
|
var filtered = contacts.where((contact) {
|
2026-03-15 00:34:09 +01:00
|
|
|
if (viewState.contactsSearchText.isEmpty) return true;
|
|
|
|
|
return matchesContactQuery(contact, viewState.contactsSearchText);
|
2025-12-26 11:42:02 -07:00
|
|
|
}).toList();
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
final selectedGroup = _selectedGroupForName(
|
|
|
|
|
viewState.contactsSelectedGroupName,
|
|
|
|
|
);
|
|
|
|
|
if (selectedGroup != null) {
|
|
|
|
|
final memberKeys = selectedGroup.memberKeys.toSet();
|
|
|
|
|
filtered = filtered
|
|
|
|
|
.where((contact) => memberKeys.contains(contact.publicKeyHex))
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 00:17:18 -07:00
|
|
|
// Filter out own node from the list
|
|
|
|
|
if (connector.selfPublicKey != null) {
|
|
|
|
|
final selfPubKeyHex = pubKeyToHex(connector.selfPublicKey!);
|
|
|
|
|
filtered = filtered.where((contact) {
|
|
|
|
|
return contact.publicKeyHex != selfPubKeyHex;
|
|
|
|
|
}).toList();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
if (viewState.contactsTypeFilter != ContactTypeFilter.all) {
|
|
|
|
|
filtered = filtered
|
|
|
|
|
.where(
|
|
|
|
|
(contact) =>
|
|
|
|
|
_matchesTypeFilter(contact, viewState.contactsTypeFilter),
|
|
|
|
|
)
|
|
|
|
|
.toList();
|
2026-01-02 14:22:39 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
if (viewState.contactsShowUnreadOnly) {
|
2025-12-26 13:33:03 -07:00
|
|
|
filtered = filtered.where((contact) {
|
|
|
|
|
return connector.getUnreadCountForContact(contact) > 0;
|
|
|
|
|
}).toList();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
switch (viewState.contactsSortOption) {
|
2025-12-26 11:42:02 -07:00
|
|
|
case ContactSortOption.lastSeen:
|
2026-01-18 21:21:33 -08:00
|
|
|
filtered.sort(
|
|
|
|
|
(a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)),
|
|
|
|
|
);
|
2025-12-26 11:42:02 -07:00
|
|
|
break;
|
|
|
|
|
case ContactSortOption.recentMessages:
|
|
|
|
|
filtered.sort((a, b) {
|
|
|
|
|
final aMessages = connector.getMessages(a);
|
|
|
|
|
final bMessages = connector.getMessages(b);
|
2026-01-18 21:21:33 -08:00
|
|
|
final aLastMsg = aMessages.isEmpty
|
|
|
|
|
? DateTime(1970)
|
|
|
|
|
: aMessages.last.timestamp;
|
|
|
|
|
final bLastMsg = bMessages.isEmpty
|
|
|
|
|
? DateTime(1970)
|
|
|
|
|
: bMessages.last.timestamp;
|
2025-12-26 11:42:02 -07:00
|
|
|
return bLastMsg.compareTo(aLastMsg);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case ContactSortOption.name:
|
2026-01-18 21:21:33 -08:00
|
|
|
filtered.sort(
|
|
|
|
|
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
|
|
|
|
);
|
2025-12-26 11:42:02 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filtered;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:34:09 +01:00
|
|
|
bool _matchesTypeFilter(Contact contact, ContactTypeFilter typeFilter) {
|
|
|
|
|
switch (typeFilter) {
|
2026-01-02 14:22:39 -07:00
|
|
|
case ContactTypeFilter.all:
|
|
|
|
|
return true;
|
2026-02-24 23:56:30 +01:00
|
|
|
case ContactTypeFilter.favorites:
|
|
|
|
|
return contact.isFavorite;
|
2026-01-02 14:22:39 -07:00
|
|
|
case ContactTypeFilter.users:
|
|
|
|
|
return contact.type == advTypeChat;
|
|
|
|
|
case ContactTypeFilter.repeaters:
|
|
|
|
|
return contact.type == advTypeRepeater;
|
|
|
|
|
case ContactTypeFilter.rooms:
|
|
|
|
|
return contact.type == advTypeRoom;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-27 15:32:32 -07:00
|
|
|
DateTime _resolveLastSeen(Contact contact) {
|
|
|
|
|
if (contact.type != advTypeChat) return contact.lastSeen;
|
|
|
|
|
return contact.lastMessageAt.isAfter(contact.lastSeen)
|
|
|
|
|
? contact.lastMessageAt
|
|
|
|
|
: contact.lastSeen;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
void _openChat(BuildContext context, Contact contact) {
|
|
|
|
|
// Check if this is a repeater
|
|
|
|
|
if (contact.type == advTypeRepeater) {
|
|
|
|
|
_showRepeaterLogin(context, contact);
|
2026-01-07 23:28:49 -08:00
|
|
|
} else if (contact.type == advTypeRoom) {
|
2026-01-19 18:29:53 -07:00
|
|
|
_showRoomLogin(context, contact, RoomLoginDestination.chat);
|
2025-12-26 11:42:02 -07:00
|
|
|
} else {
|
2025-12-26 13:33:03 -07:00
|
|
|
context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex);
|
2025-12-26 11:42:02 -07:00
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(builder: (context) => ChatScreen(contact: contact)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-27 15:32:32 -07:00
|
|
|
void _handleQuickSwitch(int index, BuildContext context) {
|
|
|
|
|
if (index == 0) return;
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 1:
|
|
|
|
|
Navigator.pushReplacement(
|
|
|
|
|
context,
|
2026-01-18 21:21:33 -08:00
|
|
|
buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)),
|
2025-12-27 15:32:32 -07:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
Navigator.pushReplacement(
|
|
|
|
|
context,
|
2026-01-18 21:21:33 -08:00
|
|
|
buildQuickSwitchRoute(const MapScreen(hideBackButton: true)),
|
2025-12-27 15:32:32 -07:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
void _showRepeaterLogin(BuildContext context, Contact repeater) {
|
|
|
|
|
showDialog(
|
|
|
|
|
context: context,
|
|
|
|
|
builder: (context) => RepeaterLoginDialog(
|
|
|
|
|
repeater: repeater,
|
|
|
|
|
onLogin: (password) {
|
|
|
|
|
// Navigate to repeater hub screen after successful login
|
|
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
2026-01-18 21:21:33 -08:00
|
|
|
builder: (context) =>
|
|
|
|
|
RepeaterHubScreen(repeater: repeater, password: password),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-19 18:29:53 -07:00
|
|
|
void _showRoomLogin(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
Contact room,
|
|
|
|
|
RoomLoginDestination destination,
|
|
|
|
|
) {
|
2026-01-07 23:28:49 -08:00
|
|
|
showDialog(
|
|
|
|
|
context: context,
|
|
|
|
|
builder: (context) => RoomLoginDialog(
|
|
|
|
|
room: room,
|
|
|
|
|
onLogin: (password) {
|
|
|
|
|
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
|
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
2026-02-04 08:32:35 -08:00
|
|
|
builder: (context) =>
|
|
|
|
|
destination == RoomLoginDestination.management
|
2026-01-18 21:21:33 -08:00
|
|
|
? RepeaterHubScreen(repeater: room, password: password)
|
|
|
|
|
: ChatScreen(contact: room),
|
2026-01-07 23:28:49 -08:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:42:02 -07:00
|
|
|
void _confirmDeleteGroup(BuildContext context, ContactGroup group) {
|
2026-03-15 00:34:09 +01:00
|
|
|
if (!_hasGroupStoreScope(context.read<MeshCoreConnector>())) {
|
|
|
|
|
_showGroupsUnavailableMessage(context);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
showDialog(
|
|
|
|
|
context: context,
|
|
|
|
|
builder: (dialogContext) => AlertDialog(
|
2026-01-11 17:13:50 -07:00
|
|
|
title: Text(context.l10n.contacts_deleteGroup),
|
|
|
|
|
content: Text(context.l10n.contacts_deleteGroupConfirm(group.name)),
|
2025-12-26 11:42:02 -07:00
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () => Navigator.pop(dialogContext),
|
2026-01-11 17:13:50 -07:00
|
|
|
child: Text(context.l10n.common_cancel),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () async {
|
|
|
|
|
Navigator.pop(dialogContext);
|
|
|
|
|
setState(() {
|
|
|
|
|
_groups.removeWhere((g) => g.name == group.name);
|
2026-03-15 00:34:09 +01:00
|
|
|
_ensureValidSelectedGroup();
|
2025-12-26 11:42:02 -07:00
|
|
|
});
|
|
|
|
|
await _saveGroups();
|
|
|
|
|
},
|
2026-01-18 21:21:33 -08:00
|
|
|
child: Text(
|
|
|
|
|
context.l10n.common_delete,
|
|
|
|
|
style: const TextStyle(color: Colors.red),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _showGroupEditor(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
List<Contact> contacts, {
|
|
|
|
|
ContactGroup? group,
|
|
|
|
|
}) {
|
2026-03-15 00:34:09 +01:00
|
|
|
if (!_hasGroupStoreScope(context.read<MeshCoreConnector>())) {
|
|
|
|
|
_showGroupsUnavailableMessage(context);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
final isEditing = group != null;
|
|
|
|
|
final nameController = TextEditingController(text: group?.name ?? '');
|
|
|
|
|
final selectedKeys = <String>{...group?.memberKeys ?? []};
|
|
|
|
|
String filterQuery = '';
|
|
|
|
|
final sortedContacts = List<Contact>.from(contacts)
|
|
|
|
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
|
|
|
|
|
|
|
|
|
showDialog(
|
|
|
|
|
context: context,
|
|
|
|
|
builder: (dialogContext) => StatefulBuilder(
|
|
|
|
|
builder: (builderContext, setDialogState) {
|
|
|
|
|
final filteredContacts = filterQuery.isEmpty
|
|
|
|
|
? sortedContacts
|
|
|
|
|
: sortedContacts
|
2026-01-18 21:21:33 -08:00
|
|
|
.where(
|
|
|
|
|
(contact) => matchesContactQuery(contact, filterQuery),
|
|
|
|
|
)
|
|
|
|
|
.toList();
|
2025-12-26 11:42:02 -07:00
|
|
|
return AlertDialog(
|
2026-01-18 21:21:33 -08:00
|
|
|
title: Text(
|
|
|
|
|
isEditing
|
|
|
|
|
? context.l10n.contacts_editGroup
|
|
|
|
|
: context.l10n.contacts_newGroup,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
content: SizedBox(
|
|
|
|
|
width: double.maxFinite,
|
2026-03-15 00:34:09 +01:00
|
|
|
child: ConstrainedBox(
|
|
|
|
|
constraints: BoxConstraints(
|
|
|
|
|
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
|
|
|
|
),
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
TextField(
|
|
|
|
|
controller: nameController,
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
labelText: context.l10n.contacts_groupName,
|
|
|
|
|
border: const OutlineInputBorder(),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
2026-03-15 00:34:09 +01:00
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
TextField(
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
hintText: context.l10n.contacts_filterContacts,
|
|
|
|
|
prefixIcon: const Icon(Icons.search),
|
|
|
|
|
border: const OutlineInputBorder(),
|
|
|
|
|
isDense: true,
|
|
|
|
|
),
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
setDialogState(() {
|
|
|
|
|
filterQuery = value.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
},
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
2026-03-15 00:34:09 +01:00
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: filteredContacts.isEmpty
|
|
|
|
|
? Center(
|
|
|
|
|
child: Text(
|
|
|
|
|
context.l10n.contacts_noContactsMatchFilter,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
: ListView.builder(
|
|
|
|
|
itemCount: filteredContacts.length,
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
final contact = filteredContacts[index];
|
|
|
|
|
final isSelected = selectedKeys.contains(
|
|
|
|
|
contact.publicKeyHex,
|
|
|
|
|
);
|
|
|
|
|
return CheckboxListTile(
|
|
|
|
|
value: isSelected,
|
|
|
|
|
title: Text(contact.name),
|
|
|
|
|
subtitle: Text(contact.typeLabel),
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
setDialogState(() {
|
|
|
|
|
if (value == true) {
|
|
|
|
|
selectedKeys.add(contact.publicKeyHex);
|
|
|
|
|
} else {
|
|
|
|
|
selectedKeys.remove(
|
|
|
|
|
contact.publicKeyHex,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-01-18 21:21:33 -08:00
|
|
|
),
|
2026-03-15 00:34:09 +01:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () => Navigator.pop(dialogContext),
|
2026-01-11 17:13:50 -07:00
|
|
|
child: Text(context.l10n.common_cancel),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () async {
|
|
|
|
|
final name = nameController.text.trim();
|
|
|
|
|
if (name.isEmpty) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
2026-01-18 21:21:33 -08:00
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_groupNameRequired),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-15 00:34:09 +01:00
|
|
|
if (name.toLowerCase() ==
|
|
|
|
|
contactsAllGroupsValue.toLowerCase()) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
content: Text(context.l10n.contacts_groupNameReserved),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
final exists = _groups.any((g) {
|
2025-12-31 22:19:48 -07:00
|
|
|
if (isEditing && g.name == group.name) return false;
|
2025-12-26 11:42:02 -07:00
|
|
|
return g.name.toLowerCase() == name.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
if (exists) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
2026-01-18 21:21:33 -08:00
|
|
|
SnackBar(
|
|
|
|
|
content: Text(
|
|
|
|
|
context.l10n.contacts_groupAlreadyExists(name),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setState(() {
|
2026-03-15 00:34:09 +01:00
|
|
|
final viewState = context.read<UiViewStateService>();
|
2025-12-26 11:42:02 -07:00
|
|
|
if (isEditing) {
|
2026-01-18 21:21:33 -08:00
|
|
|
final index = _groups.indexWhere(
|
|
|
|
|
(g) => g.name == group.name,
|
|
|
|
|
);
|
2025-12-26 11:42:02 -07:00
|
|
|
if (index != -1) {
|
2026-03-15 00:34:09 +01:00
|
|
|
final wasSelected =
|
|
|
|
|
viewState.contactsSelectedGroupName == group.name;
|
2025-12-26 11:42:02 -07:00
|
|
|
_groups[index] = ContactGroup(
|
|
|
|
|
name: name,
|
|
|
|
|
memberKeys: selectedKeys.toList(),
|
|
|
|
|
);
|
2026-03-15 00:34:09 +01:00
|
|
|
if (wasSelected) {
|
|
|
|
|
viewState.setContactsSelectedGroupName(name);
|
|
|
|
|
}
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-01-18 21:21:33 -08:00
|
|
|
_groups.add(
|
|
|
|
|
ContactGroup(
|
|
|
|
|
name: name,
|
|
|
|
|
memberKeys: selectedKeys.toList(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-03-15 00:34:09 +01:00
|
|
|
viewState.setContactsSelectedGroupName(name);
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
2026-03-15 00:34:09 +01:00
|
|
|
_ensureValidSelectedGroup();
|
2025-12-26 11:42:02 -07:00
|
|
|
});
|
|
|
|
|
await _saveGroups();
|
|
|
|
|
if (dialogContext.mounted) {
|
|
|
|
|
Navigator.pop(dialogContext);
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-01-18 21:21:33 -08:00
|
|
|
child: Text(
|
|
|
|
|
isEditing
|
|
|
|
|
? context.l10n.common_save
|
|
|
|
|
: context.l10n.common_create,
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _showContactOptions(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
MeshCoreConnector connector,
|
|
|
|
|
Contact contact,
|
|
|
|
|
) {
|
|
|
|
|
final isRepeater = contact.type == advTypeRepeater;
|
2026-01-07 23:28:49 -08:00
|
|
|
final isRoom = contact.type == advTypeRoom;
|
2026-02-24 23:56:30 +01:00
|
|
|
final isFavorite = contact.isFavorite;
|
2025-12-26 11:42:02 -07:00
|
|
|
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
2026-01-11 17:13:50 -07:00
|
|
|
builder: (sheetContext) => SafeArea(
|
2025-12-26 11:42:02 -07:00
|
|
|
child: Column(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
2026-01-22 23:42:10 -08:00
|
|
|
if (isRepeater) ...[
|
|
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.radar, color: Colors.green),
|
2026-03-24 17:45:54 -07:00
|
|
|
title: Text(context.l10n.contacts_ping),
|
2026-01-25 10:55:42 -08:00
|
|
|
onTap: () {
|
2026-03-23 19:26:05 -07:00
|
|
|
final hw = context
|
|
|
|
|
.read<MeshCoreConnector>()
|
|
|
|
|
.pathHashByteWidth;
|
2026-02-08 11:32:36 -08:00
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (context) => PathTraceMapScreen(
|
2026-03-24 17:45:54 -07:00
|
|
|
title: context.l10n.contacts_repeaterPing,
|
|
|
|
|
path: Uint8List.fromList([contact.publicKey.first]),
|
2026-03-14 17:51:24 -07:00
|
|
|
targetContact: contact,
|
2026-03-23 19:24:27 -07:00
|
|
|
pathHashByteWidth: hw,
|
2026-02-08 11:32:36 -08:00
|
|
|
),
|
|
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
);
|
|
|
|
|
},
|
2026-01-22 23:42:10 -08:00
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
ListTile(
|
2026-01-19 18:29:53 -07:00
|
|
|
leading: const Icon(Icons.cell_tower, color: Colors.orange),
|
2026-01-11 17:13:50 -07:00
|
|
|
title: Text(context.l10n.contacts_manageRepeater),
|
2025-12-26 11:42:02 -07:00
|
|
|
onTap: () {
|
2026-01-11 17:13:50 -07:00
|
|
|
Navigator.pop(sheetContext);
|
2025-12-26 11:42:02 -07:00
|
|
|
_showRepeaterLogin(context, contact);
|
|
|
|
|
},
|
2026-02-04 08:32:35 -08:00
|
|
|
),
|
|
|
|
|
] else if (isRoom) ...[
|
2026-01-22 23:42:10 -08:00
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.radar, color: Colors.green),
|
2026-03-24 17:45:54 -07:00
|
|
|
title: Text(context.l10n.contacts_pathTrace),
|
2026-01-25 10:55:42 -08:00
|
|
|
onTap: () {
|
2026-03-23 19:26:05 -07:00
|
|
|
final hw = context
|
|
|
|
|
.read<MeshCoreConnector>()
|
|
|
|
|
.pathHashByteWidth;
|
2026-02-08 11:32:36 -08:00
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (context) => PathTraceMapScreen(
|
2026-03-14 17:51:24 -07:00
|
|
|
title: contact.pathBytesForDisplay.isNotEmpty
|
2026-02-04 08:32:35 -08:00
|
|
|
? context.l10n.contacts_roomPathTrace
|
|
|
|
|
: context.l10n.contacts_roomPing,
|
2026-03-24 17:45:54 -07:00
|
|
|
path: contact.pathBytesForDisplay.isNotEmpty
|
|
|
|
|
? contact.pathBytesForDisplay
|
|
|
|
|
: Uint8List.fromList([contact.publicKey.first]),
|
2026-03-14 17:51:24 -07:00
|
|
|
flipPathAround: contact.pathBytesForDisplay.isNotEmpty,
|
|
|
|
|
targetContact: contact,
|
2026-03-23 19:24:27 -07:00
|
|
|
pathHashByteWidth: hw,
|
2026-02-08 11:32:36 -08:00
|
|
|
),
|
|
|
|
|
),
|
2026-02-04 08:32:35 -08:00
|
|
|
);
|
|
|
|
|
},
|
2026-01-22 23:42:10 -08:00
|
|
|
),
|
2026-01-07 23:28:49 -08:00
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.room, color: Colors.blue),
|
2026-01-11 17:13:50 -07:00
|
|
|
title: Text(context.l10n.contacts_roomLogin),
|
2026-01-07 23:28:49 -08:00
|
|
|
onTap: () {
|
2026-01-11 17:13:50 -07:00
|
|
|
Navigator.pop(sheetContext);
|
2026-01-19 18:29:53 -07:00
|
|
|
_showRoomLogin(context, contact, RoomLoginDestination.chat);
|
2026-01-07 23:28:49 -08:00
|
|
|
},
|
2026-01-18 21:21:33 -08:00
|
|
|
),
|
|
|
|
|
ListTile(
|
2026-02-04 08:32:35 -08:00
|
|
|
leading: const Icon(
|
|
|
|
|
Icons.room_preferences,
|
|
|
|
|
color: Colors.orange,
|
|
|
|
|
),
|
2026-01-18 21:21:33 -08:00
|
|
|
title: Text(context.l10n.room_management),
|
|
|
|
|
onTap: () {
|
|
|
|
|
Navigator.pop(sheetContext);
|
2026-02-04 08:32:35 -08:00
|
|
|
_showRoomLogin(
|
|
|
|
|
context,
|
|
|
|
|
contact,
|
|
|
|
|
RoomLoginDestination.management,
|
|
|
|
|
);
|
2026-01-18 21:21:33 -08:00
|
|
|
},
|
|
|
|
|
),
|
2026-01-25 10:55:42 -08:00
|
|
|
] else ...[
|
2026-02-04 08:32:35 -08:00
|
|
|
if (contact.pathLength > 0)
|
|
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.radar, color: Colors.green),
|
|
|
|
|
title: Text(context.l10n.contacts_chatTraceRoute),
|
|
|
|
|
onTap: () {
|
2026-03-23 19:26:05 -07:00
|
|
|
final hw = context
|
|
|
|
|
.read<MeshCoreConnector>()
|
|
|
|
|
.pathHashByteWidth;
|
2026-02-08 11:32:36 -08:00
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (context) => PathTraceMapScreen(
|
2026-02-04 08:32:35 -08:00
|
|
|
title: context.l10n.contacts_pathTraceTo(
|
|
|
|
|
contact.name,
|
|
|
|
|
),
|
2026-03-14 17:51:24 -07:00
|
|
|
path: contact.pathBytesForDisplay,
|
|
|
|
|
flipPathAround: true,
|
2026-03-06 15:02:37 -07:00
|
|
|
targetContact: contact,
|
2026-03-23 19:24:27 -07:00
|
|
|
pathHashByteWidth: hw,
|
2026-02-08 11:32:36 -08:00
|
|
|
),
|
|
|
|
|
),
|
2026-01-25 10:55:42 -08:00
|
|
|
);
|
2026-02-04 08:32:35 -08:00
|
|
|
},
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.chat),
|
2026-01-11 17:13:50 -07:00
|
|
|
title: Text(context.l10n.contacts_openChat),
|
2025-12-26 11:42:02 -07:00
|
|
|
onTap: () {
|
2026-01-11 17:13:50 -07:00
|
|
|
Navigator.pop(sheetContext);
|
2025-12-26 11:42:02 -07:00
|
|
|
_openChat(context, contact);
|
|
|
|
|
},
|
|
|
|
|
),
|
2026-01-27 18:43:59 -08:00
|
|
|
],
|
2026-02-24 23:56:30 +01:00
|
|
|
ListTile(
|
|
|
|
|
leading: Icon(
|
|
|
|
|
isFavorite ? Icons.star : Icons.star_border,
|
|
|
|
|
color: Colors.amber[700],
|
|
|
|
|
),
|
|
|
|
|
title: Text(
|
|
|
|
|
isFavorite
|
2026-02-24 20:07:15 -07:00
|
|
|
? context.l10n.listFilter_removeFromFavorites
|
|
|
|
|
: context.l10n.listFilter_addToFavorites,
|
2026-02-24 23:56:30 +01:00
|
|
|
),
|
|
|
|
|
onTap: () async {
|
|
|
|
|
Navigator.pop(sheetContext);
|
2026-03-19 22:49:16 -07:00
|
|
|
await connector.setContactFlags(
|
|
|
|
|
contact,
|
|
|
|
|
isFavorite: !isFavorite,
|
|
|
|
|
);
|
2026-02-24 23:56:30 +01:00
|
|
|
},
|
|
|
|
|
),
|
2026-01-31 15:00:33 -08:00
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.copy),
|
|
|
|
|
title: Text(context.l10n.contacts_ShareContact),
|
|
|
|
|
onTap: () {
|
|
|
|
|
Navigator.pop(sheetContext);
|
|
|
|
|
_contactExport(contact.publicKey);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.connect_without_contact),
|
|
|
|
|
title: Text(context.l10n.contacts_ShareContactZeroHop),
|
2026-02-04 08:32:35 -08:00
|
|
|
onTap: () {
|
2026-01-31 15:00:33 -08:00
|
|
|
Navigator.pop(sheetContext);
|
|
|
|
|
_contactZeroHop(contact.publicKey);
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.delete, color: Colors.red),
|
2026-01-18 21:21:33 -08:00
|
|
|
title: Text(
|
|
|
|
|
context.l10n.contacts_deleteContact,
|
|
|
|
|
style: const TextStyle(color: Colors.red),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
onTap: () {
|
2026-01-11 17:13:50 -07:00
|
|
|
Navigator.pop(sheetContext);
|
2025-12-26 11:42:02 -07:00
|
|
|
_confirmDelete(context, connector, contact);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _confirmDelete(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
MeshCoreConnector connector,
|
|
|
|
|
Contact contact,
|
|
|
|
|
) {
|
|
|
|
|
showDialog(
|
|
|
|
|
context: context,
|
2026-01-11 17:13:50 -07:00
|
|
|
builder: (dialogContext) => AlertDialog(
|
|
|
|
|
title: Text(context.l10n.contacts_deleteContact),
|
|
|
|
|
content: Text(context.l10n.contacts_removeConfirm(contact.name)),
|
2025-12-26 11:42:02 -07:00
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
2026-01-11 17:13:50 -07:00
|
|
|
onPressed: () => Navigator.pop(dialogContext),
|
|
|
|
|
child: Text(context.l10n.common_cancel),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
2026-01-11 17:13:50 -07:00
|
|
|
Navigator.pop(dialogContext);
|
2025-12-26 11:42:02 -07:00
|
|
|
connector.removeContact(contact);
|
|
|
|
|
},
|
2026-01-18 21:21:33 -08:00
|
|
|
child: Text(
|
|
|
|
|
context.l10n.common_delete,
|
|
|
|
|
style: const TextStyle(color: Colors.red),
|
|
|
|
|
),
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _ContactTile extends StatelessWidget {
|
|
|
|
|
final Contact contact;
|
2025-12-27 15:32:32 -07:00
|
|
|
final DateTime lastSeen;
|
2025-12-26 13:33:03 -07:00
|
|
|
final int unreadCount;
|
2026-02-24 23:56:30 +01:00
|
|
|
final bool isFavorite;
|
2025-12-26 11:42:02 -07:00
|
|
|
final VoidCallback onTap;
|
|
|
|
|
final VoidCallback onLongPress;
|
|
|
|
|
|
|
|
|
|
const _ContactTile({
|
|
|
|
|
required this.contact,
|
2025-12-27 15:32:32 -07:00
|
|
|
required this.lastSeen,
|
2025-12-26 13:33:03 -07:00
|
|
|
required this.unreadCount,
|
2026-02-24 23:56:30 +01:00
|
|
|
required this.isFavorite,
|
2025-12-26 11:42:02 -07:00
|
|
|
required this.onTap,
|
|
|
|
|
required this.onLongPress,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-03-20 01:54:31 -07:00
|
|
|
return GestureDetector(
|
|
|
|
|
onSecondaryTapUp: PlatformInfo.isDesktop ? (_) => onLongPress() : null,
|
|
|
|
|
child: ListTile(
|
|
|
|
|
leading: CircleAvatar(
|
|
|
|
|
backgroundColor: _getTypeColor(contact.type),
|
|
|
|
|
child: _buildContactAvatar(contact),
|
2026-01-24 00:17:18 -07:00
|
|
|
),
|
2026-03-20 01:54:31 -07:00
|
|
|
title: Text(contact.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
|
|
|
subtitle: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
contact.pathLabel,
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
),
|
|
|
|
|
Text(
|
|
|
|
|
contact.shortPubKeyHex,
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
style: const TextStyle(fontSize: 12),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
// 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: [
|
|
|
|
|
if (unreadCount > 0) ...[
|
|
|
|
|
UnreadBadge(count: unreadCount),
|
|
|
|
|
const SizedBox(height: 4),
|
2026-02-21 01:08:23 -05:00
|
|
|
],
|
2026-03-20 01:54:31 -07:00
|
|
|
Text(
|
|
|
|
|
_formatLastSeen(context, lastSeen),
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
textAlign: TextAlign.right,
|
|
|
|
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
|
|
|
|
),
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
if (isFavorite)
|
|
|
|
|
Icon(Icons.star, size: 14, color: Colors.amber[700]),
|
|
|
|
|
if (isFavorite && contact.hasLocation)
|
|
|
|
|
const SizedBox(width: 2),
|
|
|
|
|
if (contact.hasLocation)
|
|
|
|
|
Icon(
|
|
|
|
|
Icons.location_on,
|
|
|
|
|
size: 14,
|
|
|
|
|
color: Colors.grey[400],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2026-02-21 01:08:23 -05:00
|
|
|
),
|
2026-01-24 00:17:18 -07:00
|
|
|
),
|
2026-03-20 01:54:31 -07:00
|
|
|
onTap: onTap,
|
|
|
|
|
onLongPress: onLongPress,
|
2025-12-26 11:42:02 -07:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildContactAvatar(Contact contact) {
|
|
|
|
|
final emoji = firstEmoji(contact.name);
|
|
|
|
|
if (emoji != null) {
|
2026-01-18 21:21:33 -08:00
|
|
|
return Text(emoji, style: const TextStyle(fontSize: 18));
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
|
|
|
|
return Icon(_getTypeIcon(contact.type), color: Colors.white, size: 20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 17:13:50 -07:00
|
|
|
String _formatLastSeen(BuildContext context, DateTime lastSeen) {
|
2025-12-26 11:42:02 -07:00
|
|
|
final now = DateTime.now();
|
|
|
|
|
final diff = now.difference(lastSeen);
|
|
|
|
|
|
2026-01-19 18:29:53 -07:00
|
|
|
if (diff.isNegative || diff.inMinutes < 5) {
|
2026-01-18 21:21:33 -08:00
|
|
|
return context.l10n.contacts_lastSeenNow;
|
2026-01-19 18:29:53 -07:00
|
|
|
}
|
|
|
|
|
if (diff.inMinutes < 60) {
|
2026-01-18 21:21:33 -08:00
|
|
|
return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes);
|
2026-01-19 18:29:53 -07:00
|
|
|
}
|
2025-12-27 15:32:32 -07:00
|
|
|
if (diff.inHours < 24) {
|
|
|
|
|
final hours = diff.inHours;
|
2026-01-18 21:21:33 -08:00
|
|
|
return hours == 1
|
|
|
|
|
? context.l10n.contacts_lastSeenHourAgo
|
|
|
|
|
: context.l10n.contacts_lastSeenHoursAgo(hours);
|
2025-12-27 15:32:32 -07:00
|
|
|
}
|
|
|
|
|
final days = diff.inDays;
|
2026-01-18 21:21:33 -08:00
|
|
|
return days == 1
|
|
|
|
|
? context.l10n.contacts_lastSeenDayAgo
|
|
|
|
|
: context.l10n.contacts_lastSeenDaysAgo(days);
|
2025-12-26 11:42:02 -07:00
|
|
|
}
|
|
|
|
|
}
|