mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-26 04:12:57 +10:00
Added GPS enable and interval settings
This commit is contained in:
+224
-122
@@ -38,10 +38,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settings_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
appBar: AppBar(title: Text(l10n.settings_title), centerTitle: true),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Consumer<MeshCoreConnector>(
|
||||
@@ -68,7 +65,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceInfoCard(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildDeviceInfoCard(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Padding(
|
||||
@@ -83,21 +83,38 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow(l10n.settings_infoName, connector.deviceDisplayName),
|
||||
_buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel),
|
||||
_buildInfoRow(l10n.settings_infoStatus, connector.isConnected ? l10n.common_connected : l10n.common_disconnected),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoStatus,
|
||||
connector.isConnected
|
||||
? l10n.common_connected
|
||||
: l10n.common_disconnected,
|
||||
),
|
||||
_buildBatteryInfoRow(context, connector),
|
||||
if (connector.selfName != null)
|
||||
_buildInfoRow(l10n.settings_nodeName, connector.selfName!),
|
||||
if (connector.selfPublicKey != null)
|
||||
_buildInfoRow(l10n.settings_infoPublicKey, '${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...'),
|
||||
_buildInfoRow(l10n.settings_infoContactsCount, '${connector.contacts.length}'),
|
||||
_buildInfoRow(l10n.settings_infoChannelCount, '${connector.channels.length}'),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoPublicKey,
|
||||
'${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoContactsCount,
|
||||
'${connector.contacts.length}',
|
||||
),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoChannelCount,
|
||||
'${connector.channels.length}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBatteryInfoRow(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildBatteryInfoRow(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
final percent = connector.batteryPercent;
|
||||
final millivolts = connector.batteryMillivolts;
|
||||
@@ -167,7 +184,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNodeSettingsCard(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildNodeSettingsCard(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Column(
|
||||
@@ -298,7 +318,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const BleDebugLogScreen()),
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BleDebugLogScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -311,7 +333,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AppDebugLogScreen()),
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AppDebugLogScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -334,20 +358,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
leading,
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (leading != null) ...[leading, const SizedBox(width: 8)],
|
||||
Text(label, style: TextStyle(color: Colors.grey[600])),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: valueColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: valueColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -413,75 +431,152 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final l10n = context.l10n;
|
||||
final latController = TextEditingController();
|
||||
final lonController = TextEditingController();
|
||||
final intervalController = TextEditingController();
|
||||
intervalController.text = "900";
|
||||
latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? '';
|
||||
lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? '';
|
||||
bool isGPSEnabled = false;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.settings_location),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: latController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_latitude,
|
||||
border: const OutlineInputBorder(),
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text(l10n.settings_location),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isGPSEnabled) ...[
|
||||
TextField(
|
||||
controller: latController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_latitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: lonController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_longitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
CheckboxListTile(
|
||||
value: isGPSEnabled,
|
||||
enabled: true,
|
||||
onChanged: (v) =>
|
||||
setDialogState(() => isGPSEnabled = v ?? false),
|
||||
//controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(
|
||||
l10n.settings_locationGPSEnable,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
subtitle: Text(
|
||||
l10n.settings_locationGPSEnableSubtitle,
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
if (isGPSEnabled) ...{
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: intervalController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_locationIntervalSec,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: false,
|
||||
signed: false,
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: lonController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_longitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
if (isGPSEnabled) {
|
||||
final intervalText = intervalController.text.trim();
|
||||
if (intervalText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final interval = int.tryParse(intervalText);
|
||||
if (interval == null || interval < 60) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.settings_locationIntervalInvalid),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await connector.setCustomVar("gps:1");
|
||||
await connector.setCustomVar("gps_interval:$interval");
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
} else {
|
||||
final latText = latController.text.trim();
|
||||
final lonText = lonController.text.trim();
|
||||
if (latText.isEmpty && lonText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLat = connector.selfLatitude;
|
||||
final currentLon = connector.selfLongitude;
|
||||
final lat = latText.isNotEmpty
|
||||
? double.tryParse(latText)
|
||||
: currentLat;
|
||||
final lon = lonText.isNotEmpty
|
||||
? double.tryParse(lonText)
|
||||
: currentLon;
|
||||
if (lat == null || lon == null) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.settings_locationBothRequired),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationInvalid)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await connector.setCustomVar("gps:0");
|
||||
await connector.setNodeLocation(lat: lat, lon: lon);
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
final latText = latController.text.trim();
|
||||
final lonText = lonController.text.trim();
|
||||
if (latText.isEmpty && lonText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLat = connector.selfLatitude;
|
||||
final currentLon = connector.selfLongitude;
|
||||
final lat = latText.isNotEmpty ? double.tryParse(latText) : currentLat;
|
||||
final lon = lonText.isNotEmpty ? double.tryParse(lonText) : currentLon;
|
||||
if (lat == null || lon == null) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationBothRequired)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationInvalid)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await connector.setNodeLocation(lat: lat, lon: lon);
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
},
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -530,17 +625,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
void _sendAdvert(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
connector.sendSelfAdvert(flood: true);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_advertisementSent)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent)));
|
||||
}
|
||||
|
||||
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
connector.syncTime();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_timeSynchronized)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_timeSynchronized)));
|
||||
}
|
||||
|
||||
void _confirmReboot(BuildContext context, MeshCoreConnector connector) {
|
||||
@@ -560,7 +655,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Navigator.pop(context);
|
||||
connector.rebootDevice();
|
||||
},
|
||||
child: Text(l10n.common_reboot, style: const TextStyle(color: Colors.orange)),
|
||||
child: Text(
|
||||
l10n.common_reboot,
|
||||
style: const TextStyle(color: Colors.orange),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -572,7 +670,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: l10n.appTitle,
|
||||
applicationVersion: _appVersion.isEmpty ? l10n.common_loading : _appVersion,
|
||||
applicationVersion: _appVersion.isEmpty
|
||||
? l10n.common_loading
|
||||
: _appVersion,
|
||||
applicationLegalese: l10n.settings_aboutLegalese,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
@@ -604,7 +704,8 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
|
||||
// Populate with current settings if available
|
||||
if (widget.connector.currentFreqHz != null) {
|
||||
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0).toStringAsFixed(3);
|
||||
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0)
|
||||
.toStringAsFixed(3);
|
||||
} else {
|
||||
_frequencyController.text = '915.0';
|
||||
}
|
||||
@@ -670,26 +771,31 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
final txPower = int.tryParse(_txPowerController.text);
|
||||
|
||||
if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_frequencyInvalid)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_frequencyInvalid)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (txPower == null || txPower < 0 || txPower > 22) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_txPowerInvalid)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_txPowerInvalid)));
|
||||
return;
|
||||
}
|
||||
|
||||
final freqHz = (freqMHz * 1000).round();
|
||||
final bwHz = _bandwidth.hz;
|
||||
final sf = _spreadingFactor.value;
|
||||
final cr = _toDeviceCodingRate(_codingRate.value, widget.connector.currentCr);
|
||||
final cr = _toDeviceCodingRate(
|
||||
_codingRate.value,
|
||||
widget.connector.currentCr,
|
||||
);
|
||||
|
||||
try {
|
||||
await widget.connector.sendFrame(buildSetRadioParamsFrame(freqHz, bwHz, sf, cr));
|
||||
await widget.connector.sendFrame(
|
||||
buildSetRadioParamsFrame(freqHz, bwHz, sf, cr),
|
||||
);
|
||||
await widget.connector.sendFrame(buildSetRadioTxPowerFrame(txPower));
|
||||
await widget.connector.refreshDeviceInfo();
|
||||
|
||||
@@ -727,7 +833,10 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.settings_presets, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
l10n.settings_presets,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
@@ -762,7 +871,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
helperText: l10n.settings_frequencyHelper,
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<LoRaBandwidth>(
|
||||
@@ -772,10 +883,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaBandwidth.values
|
||||
.map((bw) => DropdownMenuItem(
|
||||
value: bw,
|
||||
child: Text(bw.label),
|
||||
))
|
||||
.map(
|
||||
(bw) => DropdownMenuItem(value: bw, child: Text(bw.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _bandwidth = value);
|
||||
@@ -789,10 +899,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaSpreadingFactor.values
|
||||
.map((sf) => DropdownMenuItem(
|
||||
value: sf,
|
||||
child: Text(sf.label),
|
||||
))
|
||||
.map(
|
||||
(sf) => DropdownMenuItem(value: sf, child: Text(sf.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _spreadingFactor = value);
|
||||
@@ -806,10 +915,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaCodingRate.values
|
||||
.map((cr) => DropdownMenuItem(
|
||||
value: cr,
|
||||
child: Text(cr.label),
|
||||
))
|
||||
.map(
|
||||
(cr) => DropdownMenuItem(value: cr, child: Text(cr.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _codingRate = value);
|
||||
@@ -833,10 +941,7 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _saveSettings,
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
FilledButton(onPressed: _saveSettings, child: Text(l10n.common_save)),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -850,9 +955,6 @@ class _PresetChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ActionChip(
|
||||
label: Text(label),
|
||||
onPressed: onTap,
|
||||
);
|
||||
return ActionChip(label: Text(label), onPressed: onTap);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user