feat: Enhance companion connection features and UI updates

- Added functionality to load and restore the last companion's scope on app startup.
- Implemented caching mechanisms for contacts, channels, and messages related to the current companion.
- Updated UI to reflect connection status, including disabling message input when disconnected.
- Introduced new dialog prompts to inform users when they need to connect to a companion for accessing features.
- Refactored navigation logic to improve user experience when disconnected, directing users to the scanner screen.
- Added localization strings for new companion connection prompts in multiple languages.
This commit is contained in:
Winston Lowe
2026-05-10 16:02:58 -07:00
parent 209fee48ca
commit d6ed8c5f13
45 changed files with 320 additions and 113 deletions
+54 -17
View File
@@ -19,7 +19,6 @@ import '../services/ui_view_state_service.dart';
import '../utils/contact_search.dart';
import '../storage/contact_group_store.dart';
import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart';
import '../utils/emoji_utils.dart';
import '../utils/route_transitions.dart';
import '../widgets/list_filter_widget.dart';
@@ -34,6 +33,7 @@ import 'chat_screen.dart';
import 'discovery_screen.dart';
import 'map_screen.dart';
import 'repeater_hub_screen.dart';
import 'scanner_screen.dart';
import 'settings_screen.dart';
enum RoomLoginDestination { chat, management }
@@ -49,8 +49,7 @@ class ContactsScreen extends StatefulWidget {
State<ContactsScreen> createState() => _ContactsScreenState();
}
class _ContactsScreenState extends State<ContactsScreen>
with DisconnectNavigationMixin {
class _ContactsScreenState extends State<ContactsScreen> {
final TextEditingController _searchController = TextEditingController();
final ContactGroupStore _groupStore = ContactGroupStore();
MeshCoreConnector? _scopeSyncConnector;
@@ -306,11 +305,6 @@ class _ContactsScreenState extends State<ContactsScreen>
Widget build(BuildContext context) {
final connector = context.watch<MeshCoreConnector>();
// Auto-navigate back to scanner if disconnected
if (!checkConnectionAndNavigate(connector)) {
return const SizedBox.shrink();
}
final allowBack = !connector.isConnected;
return PopScope(
canPop: allowBack,
@@ -378,16 +372,33 @@ class _ContactsScreenState extends State<ContactsScreen>
),
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.logout, color: Colors.red),
const SizedBox(width: 8),
Text(context.l10n.common_disconnect),
],
if (connector.isConnected)
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.logout, color: Colors.red),
const SizedBox(width: 8),
Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context, connector),
)
else
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.bluetooth_searching),
const SizedBox(width: 8),
Text(context.l10n.common_connect),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScannerScreen(),
),
),
),
onTap: () => _disconnect(context, connector),
),
PopupMenuItem(
child: Row(
children: [
@@ -967,6 +978,11 @@ class _ContactsScreenState extends State<ContactsScreen>
}
void _showRepeaterLogin(BuildContext context, Contact repeater) {
final connector = context.read<MeshCoreConnector>();
if (!connector.isConnected) {
_showCompanionRequiredDialog(context);
return;
}
showDialog(
context: context,
builder: (context) => RepeaterLoginDialog(
@@ -993,6 +1009,11 @@ class _ContactsScreenState extends State<ContactsScreen>
Contact room,
RoomLoginDestination destination,
) {
final connector = context.read<MeshCoreConnector>();
if (!connector.isConnected) {
_showCompanionRequiredDialog(context);
return;
}
showDialog(
context: context,
builder: (context) => RoomLoginDialog(
@@ -1021,6 +1042,22 @@ class _ContactsScreenState extends State<ContactsScreen>
);
}
void _showCompanionRequiredDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.l10n.scanner_notConnected),
content: Text(context.l10n.contact_connectCompanion),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(context.l10n.common_ok),
),
],
),
);
}
void _confirmDeleteGroup(BuildContext context, ContactGroup group) {
if (!_hasGroupStoreScope(context.read<MeshCoreConnector>())) {
_showGroupsUnavailableMessage(context);