fix: remove companion remote connection history

This commit is contained in:
edde746
2026-02-26 01:08:46 +01:00
parent acf91107ad
commit 659b59d450
23 changed files with 30 additions and 725 deletions
+20 -219
View File
@@ -5,11 +5,8 @@ import 'package:flutter/services.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:provider/provider.dart';
import '../../focus/focusable_button.dart';
import '../../i18n/strings.g.dart';
import '../../providers/companion_remote_provider.dart';
import '../../utils/formatters.dart';
import '../../models/companion_remote/recent_remote_session.dart';
import '../../utils/app_logger.dart';
class PairingScreen extends StatefulWidget {
@@ -25,8 +22,6 @@ class _PairingScreenState extends State<PairingScreen> {
final _pinController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isConnecting = false;
String? _connectingSessionId;
bool _isDiscovering = false;
String? _errorMessage;
int _selectedTab = 0;
@@ -36,15 +31,9 @@ class _PairingScreenState extends State<PairingScreen> {
bool get _isMobile => Platform.isAndroid || Platform.isIOS;
// Tab indices shift when scan tab is present
int get _scanTabIndex => _isMobile ? 1 : -1;
int get _manualTabIndex => _isMobile ? 2 : 1;
@override
void initState() {
super.initState();
_loadRecentSessions();
}
// Tab indices: mobile gets Scan (0) + Manual (1), desktop gets Manual (0)
int get _scanTabIndex => _isMobile ? 0 : -1;
int get _manualTabIndex => _isMobile ? 1 : 0;
@override
void dispose() {
@@ -55,51 +44,6 @@ class _PairingScreenState extends State<PairingScreen> {
super.dispose();
}
Future<void> _loadRecentSessions() async {
setState(() {
_isDiscovering = true;
_errorMessage = null;
});
try {
await context.read<CompanionRemoteProvider>().loadRecentSessions();
if (!mounted) return;
setState(() {
_isDiscovering = false;
});
} catch (e) {
appLogger.e('Failed to load recent sessions', error: e);
if (!mounted) return;
setState(() {
_isDiscovering = false;
_errorMessage = t.companionRemote.pairing.failedToLoadRecent(error: e.toString());
});
}
}
Future<void> _connectToRecentSession(RecentRemoteSession session) async {
setState(() {
_isConnecting = true;
_connectingSessionId = session.sessionId;
_errorMessage = null;
});
try {
await context.read<CompanionRemoteProvider>().connectToRecentSession(session);
if (mounted) {
Navigator.of(context).pop();
}
} catch (e) {
appLogger.e('Failed to connect to recent session', error: e);
setState(() {
_isConnecting = false;
_connectingSessionId = null;
_errorMessage = _parseErrorMessage(e.toString());
});
}
}
Future<void> _connect() async {
if (!_formKey.currentState!.validate()) {
return;
@@ -209,39 +153,30 @@ class _PairingScreenState extends State<PairingScreen> {
return Scaffold(
appBar: AppBar(
title: Text(t.companionRemote.connectToDevice),
actions: [
if (_selectedTab == 0)
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _isDiscovering ? null : _loadRecentSessions,
tooltip: t.common.refresh,
),
],
),
body: Column(
children: [
SegmentedButton<int>(
segments: [
ButtonSegment(value: 0, label: Text(t.companionRemote.pairing.recent), icon: const Icon(Icons.history)),
if (_isMobile)
if (_isMobile)
SegmentedButton<int>(
segments: [
ButtonSegment(
value: _scanTabIndex,
label: Text(t.companionRemote.pairing.scan),
icon: const Icon(Icons.qr_code_scanner),
),
ButtonSegment(
value: _manualTabIndex,
label: Text(t.companionRemote.pairing.manual),
icon: const Icon(Icons.keyboard),
),
],
selected: {_selectedTab},
onSelectionChanged: (Set<int> selection) {
setState(() {
_selectedTab = selection.first;
});
},
),
ButtonSegment(
value: _manualTabIndex,
label: Text(t.companionRemote.pairing.manual),
icon: const Icon(Icons.keyboard),
),
],
selected: {_selectedTab},
onSelectionChanged: (Set<int> selection) {
setState(() {
_selectedTab = selection.first;
});
},
),
Expanded(child: _buildTabContent()),
],
),
@@ -249,8 +184,7 @@ class _PairingScreenState extends State<PairingScreen> {
}
Widget _buildTabContent() {
if (_selectedTab == 0) return _buildDiscoveryTab();
if (_selectedTab == _scanTabIndex) return _buildScanTab();
if (_selectedTab == _scanTabIndex && _isMobile) return _buildScanTab();
return _buildManualEntryTab();
}
@@ -337,139 +271,6 @@ class _PairingScreenState extends State<PairingScreen> {
);
}
Widget _buildDiscoveryTab() {
return Consumer<CompanionRemoteProvider>(
builder: (context, provider, child) {
final sessions = provider.recentSessions;
return SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Icon(Icons.history, size: 64, color: Colors.blue),
const SizedBox(height: 24),
Text(
t.companionRemote.pairing.recentConnections,
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
t.companionRemote.pairing.quickReconnect,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
if (_isDiscovering) ...[
const Center(child: CircularProgressIndicator()),
const SizedBox(height: 16),
Text(t.common.loading, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center),
] else if (sessions.isEmpty) ...[
Card(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
Icon(Icons.devices_other, size: 48, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 16),
Text(
t.companionRemote.pairing.noRecentConnections,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
t.companionRemote.pairing.connectUsingManual,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
],
),
),
),
] else ...[
...sessions.map((session) {
final isThisConnecting = _isConnecting && _connectingSessionId == session.sessionId;
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: const Icon(Icons.computer, size: 40),
title: Text(session.deviceName),
subtitle: Text(
'${session.platform}\n'
'Session: ${session.sessionId}\n'
'Last used: ${_formatDate(session.lastConnected)}',
),
isThreeLine: true,
trailing: isThisConnecting
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.arrow_forward),
onTap: _isConnecting ? null : () => _connectToRecentSession(session),
onLongPress: () => _showRemoveSessionDialog(session),
),
);
}),
],
if (_errorMessage != null) ...[
const SizedBox(height: 16),
Card(
color: Theme.of(context).colorScheme.errorContainer,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer),
const SizedBox(width: 12),
Expanded(
child: Text(
_errorMessage!,
style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer),
),
),
],
),
),
),
],
],
),
);
},
);
}
String _formatDate(DateTime date) {
return formatRelativeTime(date);
}
Future<void> _showRemoveSessionDialog(RecentRemoteSession session) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(t.companionRemote.pairing.removeRecentConnection),
content: Text(t.companionRemote.pairing.removeConfirm(name: session.deviceName)),
actions: [
FocusableButton(
autofocus: true,
onPressed: () => Navigator.pop(context, false),
child: TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(t.common.cancel),
),
),
FocusableButton(
onPressed: () => Navigator.pop(context, true),
child: TextButton(onPressed: () => Navigator.pop(context, true), child: Text(t.common.remove)),
),
],
),
);
if (confirmed == true && mounted) {
await context.read<CompanionRemoteProvider>().removeRecentSession(session.sessionId);
}
}
Widget _buildManualEntryTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(24.0),