mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-13 09:20:29 +00:00
Implement meshcore-open route reload
This commit is contained in:
@@ -23,6 +23,7 @@ import '../l10n/app_localizations.dart';
|
||||
import '../widgets/permission_request_dialog.dart';
|
||||
import '../widgets/connection_dialog.dart';
|
||||
import '../utils/battery_display_helper.dart';
|
||||
import '../services/developer_mode_service.dart';
|
||||
|
||||
enum _HomeTab { messages, contacts, sensors, map }
|
||||
|
||||
@@ -55,6 +56,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
int _currentIndex = 0;
|
||||
bool _isMapFullscreen = false;
|
||||
bool _showRxTxIndicators = true;
|
||||
bool _isDeveloperModeEnabled = false;
|
||||
bool _isMapEnabled = true;
|
||||
bool _isContactsEnabled = true;
|
||||
bool _isSensorsEnabled = false;
|
||||
@@ -90,6 +92,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
// Initialize synchronously so first build always has a valid controller.
|
||||
_initTabController();
|
||||
_loadRxTxPreference();
|
||||
_loadDeveloperModePreference();
|
||||
|
||||
// Show permission dialog after the first frame if needed
|
||||
if (widget.shouldShowPermissionDialog) {
|
||||
@@ -228,6 +231,14 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadDeveloperModePreference() async {
|
||||
final isEnabled = await DeveloperModeService.isEnabled();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isDeveloperModeEnabled = isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
@@ -598,56 +609,67 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.radar),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Spectrum Scan'),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SpectrumScanScreen(),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.settings),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
// Capture context-dependent objects before async gap
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () async {
|
||||
if (!mounted) return;
|
||||
await navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SettingsScreen(
|
||||
onThemeChanged: widget.onThemeChanged,
|
||||
onLocaleChanged: widget.onLocaleChanged,
|
||||
currentTheme: widget.currentTheme,
|
||||
currentLocale: widget.currentLocale,
|
||||
itemBuilder: (context) {
|
||||
final items = <PopupMenuEntry<void>>[];
|
||||
|
||||
if (_isDeveloperModeEnabled) {
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.radar),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Spectrum Scan'),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const SpectrumScanScreen(),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.settings),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () async {
|
||||
if (!mounted) return;
|
||||
await navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SettingsScreen(
|
||||
onThemeChanged: widget.onThemeChanged,
|
||||
onLocaleChanged: widget.onLocaleChanged,
|
||||
currentTheme: widget.currentTheme,
|
||||
currentLocale: widget.currentLocale,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
// Reload preference when returning from settings
|
||||
_loadRxTxPreference();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
_loadRxTxPreference();
|
||||
_loadDeveloperModePreference();
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return items;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -21,6 +21,7 @@ import '../services/voice_bitrate_preferences.dart';
|
||||
import '../services/image_preferences.dart';
|
||||
import '../services/route_hash_preferences.dart';
|
||||
import '../services/image_codec_service.dart';
|
||||
import '../services/developer_mode_service.dart';
|
||||
import '../utils/sample_data_generator.dart';
|
||||
import '../utils/image_message_parser.dart';
|
||||
import '../utils/voice_message_parser.dart';
|
||||
@@ -70,6 +71,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
bool _fastLocationUpdatesEnabled = false;
|
||||
double _fastLocationMovementThresholdMeters = 10.0;
|
||||
int _fastLocationActiveCadenceSeconds = 10;
|
||||
bool _isDeveloperModeEnabled = false;
|
||||
int _versionTapCount = 0;
|
||||
final ImagePicker _imagePicker = ImagePicker();
|
||||
final LocationTrackingService _locationService = LocationTrackingService();
|
||||
|
||||
@@ -85,6 +88,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_loadRouteHashSizePreference();
|
||||
_loadImagePreferences();
|
||||
_loadFastLocationSettings();
|
||||
_loadDeveloperMode();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -114,6 +118,48 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadDeveloperMode() async {
|
||||
final isEnabled = await DeveloperModeService.isEnabled();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isDeveloperModeEnabled = isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleVersionTap() async {
|
||||
if (_isDeveloperModeEnabled) {
|
||||
await DeveloperModeService.setEnabled(false);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isDeveloperModeEnabled = false;
|
||||
_versionTapCount = 0;
|
||||
});
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Developer mode disabled')));
|
||||
return;
|
||||
}
|
||||
|
||||
final nextTapCount = _versionTapCount + 1;
|
||||
if (nextTapCount >= 3) {
|
||||
await DeveloperModeService.setEnabled(true);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isDeveloperModeEnabled = true;
|
||||
_versionTapCount = 0;
|
||||
});
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Developer mode enabled')));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_versionTapCount = nextTapCount;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveRxTxPreference(bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('show_rx_tx_indicators', value);
|
||||
@@ -929,6 +975,32 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: _showRouteHashSizeDialog,
|
||||
),
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.swap_horiz),
|
||||
title: const Text('Auto route rotation'),
|
||||
subtitle: const Text(
|
||||
'Rotate between best known direct paths and flood mode for room/contact sends',
|
||||
),
|
||||
value: appProvider.autoRouteRotationEnabled,
|
||||
onChanged: (value) async {
|
||||
await appProvider.toggleAutoRouteRotationEnabled(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.route),
|
||||
title: const Text('Clear path on max retry'),
|
||||
subtitle: const Text(
|
||||
'Clear the route only after all retries and final router fallback fail',
|
||||
),
|
||||
value: appProvider.clearPathOnMaxRetry,
|
||||
onChanged: (value) async {
|
||||
await appProvider.toggleClearPathOnMaxRetry(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete_sweep, color: Colors.red),
|
||||
title: const Text(
|
||||
@@ -1197,6 +1269,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
? '${_packageInfo!.version} (${_packageInfo!.buildNumber})'
|
||||
: 'Loading...',
|
||||
),
|
||||
onTap: _handleVersionTap,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.badge),
|
||||
|
||||
Reference in New Issue
Block a user